chg: replaced ant with gradle
This commit is contained in:
parent
cee0be52e0
commit
5cbeb9f01d
609 changed files with 87626 additions and 638 deletions
31
src/main/java/helma/extensions/ConfigurationException.java
Normal file
31
src/main/java/helma/extensions/ConfigurationException.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.extensions;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ConfigurationException extends RuntimeException {
|
||||
/**
|
||||
* Creates a new ConfigurationException object.
|
||||
*
|
||||
* @param msg ...
|
||||
*/
|
||||
public ConfigurationException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
71
src/main/java/helma/extensions/HelmaExtension.java
Normal file
71
src/main/java/helma/extensions/HelmaExtension.java
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.extensions;
|
||||
|
||||
import helma.framework.core.Application;
|
||||
import helma.main.Server;
|
||||
import helma.scripting.ScriptingEngine;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Helma extensions have to subclass this. The extensions to be loaded are
|
||||
* defined in <code>server.properties</code> by setting <code>extensions =
|
||||
* packagename.classname, packagename.classname</code>.
|
||||
*/
|
||||
public abstract class HelmaExtension {
|
||||
/**
|
||||
* called by the Server at startup time. should check wheter the needed classes
|
||||
* are present and throw a ConfigurationException if not.
|
||||
*/
|
||||
public abstract void init(Server server) throws ConfigurationException;
|
||||
|
||||
/**
|
||||
* called when an Application is started. This should be <b>synchronized</b> when
|
||||
* any self-initialization is performed.
|
||||
*/
|
||||
public abstract void applicationStarted(Application app)
|
||||
throws ConfigurationException;
|
||||
|
||||
/**
|
||||
* called when an Application is stopped.
|
||||
* This should be <b>synchronized</b> when any self-destruction is performed.
|
||||
*/
|
||||
public abstract void applicationStopped(Application app);
|
||||
|
||||
/**
|
||||
* called when an Application's properties are have been updated.
|
||||
* note that this will be called at startup once *before* applicationStarted().
|
||||
*/
|
||||
public abstract void applicationUpdated(Application app);
|
||||
|
||||
/**
|
||||
* called by the ScriptingEngine when it is initizalized. Throws a ConfigurationException
|
||||
* when this type of ScriptingEngine is not supported. New methods and prototypes can be
|
||||
* added to the scripting environment. New global vars should be returned in a HashMap
|
||||
* with pairs of varname and ESObjects. This method should be <b>synchronized</b>, if it
|
||||
* performs any other self-initialization outside the scripting environment.
|
||||
*/
|
||||
public abstract HashMap initScripting(Application app, ScriptingEngine engine)
|
||||
throws ConfigurationException;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public abstract String getName();
|
||||
}
|
116
src/main/java/helma/extensions/demo/DemoExtension.java
Normal file
116
src/main/java/helma/extensions/demo/DemoExtension.java
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.extensions.demo;
|
||||
|
||||
|
||||
import helma.extensions.ConfigurationException;
|
||||
import helma.extensions.HelmaExtension;
|
||||
import helma.framework.core.Application;
|
||||
import helma.main.Server;
|
||||
import helma.scripting.ScriptingEngine;
|
||||
import helma.scripting.rhino.RhinoEngine;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* a demo extension implementation, to activate this add <code>extensions =
|
||||
* helma.extensions.demo.DemoExtensions</code> to your <code>server.properties</code>.
|
||||
* a new global object <code>demo</code> that wraps helma.main.Server
|
||||
* will be added to the scripting environment.
|
||||
*/
|
||||
public class DemoExtension extends HelmaExtension {
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param server ...
|
||||
*
|
||||
* @throws ConfigurationException ...
|
||||
*/
|
||||
public void init(Server server) throws ConfigurationException {
|
||||
try {
|
||||
// just a demo with the server class itself (which is always there, obviously)
|
||||
Class check = Class.forName("helma.main.Server");
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new ConfigurationException("helma-library not present in classpath. make sure helma.jar is included. get it from http://www.helma.org/");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param app ...
|
||||
*
|
||||
* @throws ConfigurationException ...
|
||||
*/
|
||||
public void applicationStarted(Application app) throws ConfigurationException {
|
||||
app.logEvent("DemoExtension init with app " + app.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param app ...
|
||||
*/
|
||||
public void applicationStopped(Application app) {
|
||||
app.logEvent("DemoExtension stopped on app " + app.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param app ...
|
||||
*/
|
||||
public void applicationUpdated(Application app) {
|
||||
app.logEvent("DemoExtension updated on app " + app.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param app ...
|
||||
* @param engine ...
|
||||
*
|
||||
* @return ...
|
||||
*
|
||||
* @throws ConfigurationException ...
|
||||
*/
|
||||
public HashMap initScripting(Application app, ScriptingEngine engine)
|
||||
throws ConfigurationException {
|
||||
if (!(engine instanceof RhinoEngine)) {
|
||||
throw new ConfigurationException("scripting engine " + engine.toString() +
|
||||
" not supported in DemoExtension");
|
||||
}
|
||||
|
||||
app.logEvent("initScripting DemoExtension with " + app.getName() + " and " +
|
||||
engine.toString());
|
||||
|
||||
// initialize prototypes and global vars here
|
||||
HashMap globals = new HashMap();
|
||||
|
||||
globals.put("demo", Server.getServer());
|
||||
|
||||
return globals;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getName() {
|
||||
return "DemoExtension";
|
||||
}
|
||||
}
|
27
src/main/java/helma/framework/AbortException.java
Normal file
27
src/main/java/helma/framework/AbortException.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
/**
|
||||
* AbortException is thrown internally when a response is aborted.
|
||||
* Although this is not an Error, it subclasses java.lang.Error
|
||||
* because it's not meant to be caught by application code (similar to
|
||||
* java.lang.ThreadDeath).
|
||||
*/
|
||||
public class AbortException extends Error {
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
|
||||
/**
|
||||
* This is thrown when a request is made to a stopped
|
||||
* application
|
||||
*/
|
||||
public class ApplicationStoppedException extends RuntimeException {
|
||||
/**
|
||||
* Creates a new ApplicationStoppedException object.
|
||||
*/
|
||||
public ApplicationStoppedException() {
|
||||
super("The application has been stopped");
|
||||
}
|
||||
}
|
153
src/main/java/helma/framework/CookieTrans.java
Normal file
153
src/main/java/helma/framework/CookieTrans.java
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
import java.io.Serializable;
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
/**
|
||||
* Cookie Transmitter. A simple, serializable representation
|
||||
* of an HTTP cookie.
|
||||
*/
|
||||
public final class CookieTrans implements Serializable {
|
||||
String name;
|
||||
String value;
|
||||
String path;
|
||||
String domain;
|
||||
int days = -1;
|
||||
boolean secure;
|
||||
boolean httpOnly;
|
||||
|
||||
CookieTrans(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
void setDays(int days) {
|
||||
this.days = days;
|
||||
}
|
||||
|
||||
void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
void setDomain(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public int getDays() {
|
||||
return days;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public boolean isSecure() {
|
||||
return secure;
|
||||
}
|
||||
|
||||
void isSecure(boolean secure) {
|
||||
this.secure = secure;
|
||||
}
|
||||
|
||||
public boolean isHttpOnly() {
|
||||
return httpOnly;
|
||||
}
|
||||
|
||||
void isHttpOnly(boolean httpOnly) {
|
||||
this.httpOnly = httpOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param defaultPath ...
|
||||
* @param defaultDomain ...
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Cookie getCookie(String defaultPath, String defaultDomain) {
|
||||
Cookie c = new Cookie(name, value);
|
||||
|
||||
// NOTE: If cookie version is set to 1, cookie values will be quoted.
|
||||
// c.setVersion(1);
|
||||
|
||||
if (days > -1) {
|
||||
// Cookie time to live, days -> seconds
|
||||
c.setMaxAge(days * 60 * 60 * 24);
|
||||
}
|
||||
|
||||
if (path != null) {
|
||||
c.setPath(path);
|
||||
} else if (defaultPath != null) {
|
||||
c.setPath(defaultPath);
|
||||
}
|
||||
|
||||
if (domain != null) {
|
||||
c.setDomain(domain);
|
||||
} else if (defaultDomain != null) {
|
||||
c.setDomain(defaultDomain);
|
||||
}
|
||||
|
||||
c.setHttpOnly(httpOnly);
|
||||
c.setSecure(secure);
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
33
src/main/java/helma/framework/FrameworkException.java
Normal file
33
src/main/java/helma/framework/FrameworkException.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
|
||||
/**
|
||||
* The basic exception class used to tell when certain things go
|
||||
* wrong in evaluation of requests.
|
||||
*/
|
||||
public class FrameworkException extends RuntimeException {
|
||||
/**
|
||||
* Creates a new FrameworkException object.
|
||||
*
|
||||
* @param msg ...
|
||||
*/
|
||||
public FrameworkException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
59
src/main/java/helma/framework/FutureResult.java
Normal file
59
src/main/java/helma/framework/FutureResult.java
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2006 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
/**
|
||||
* A handle for an asynchronous request execution. This allows to wait for
|
||||
* request termination, get the result or the exception of the execution.
|
||||
*/
|
||||
public interface FutureResult {
|
||||
/**
|
||||
* Get the result of the execution. If the execution is still active,
|
||||
* or if the invocation threw an exception, this method immediately returns null.
|
||||
* @return the result, or null
|
||||
*/
|
||||
Object getResult();
|
||||
|
||||
/**
|
||||
* Get the exception of the execution, if one was thrown. If the execution
|
||||
* is still active, or if no exception was thrown, this method immediately returns null.
|
||||
* @return the exception, or null
|
||||
*/
|
||||
Exception getException();
|
||||
|
||||
/**
|
||||
* Returns true if the execution is still active, and false if not.
|
||||
* @return true if the execution is still active
|
||||
*/
|
||||
boolean getRunning();
|
||||
|
||||
/**
|
||||
* Wait for execution to terminat, returning the execution result, if one is available.
|
||||
* @return the execution result, or null
|
||||
* @throws InterruptedException if we were interrupted by some other thread
|
||||
*/
|
||||
Object waitForResult() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Wait for a specific ammount of thime for the execution to terminate, returning
|
||||
* the execution result, if one is available.
|
||||
* @param timeout the number of milliseconds to wait
|
||||
* @return the execution result, or null
|
||||
* @throws InterruptedException if we were interrupted by some other thread
|
||||
*/
|
||||
Object waitForResult(long timeout) throws InterruptedException;
|
||||
}
|
52
src/main/java/helma/framework/IPathElement.java
Normal file
52
src/main/java/helma/framework/IPathElement.java
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
|
||||
/**
|
||||
* Interface that objects need to implement to build a Helma URL tree. Apart from methods
|
||||
* to retrieve the identifier and its child and parent elements, this interface defines a method
|
||||
* that determines which prototype to use to add scripts and skins to an object. <p>
|
||||
*
|
||||
* Please note that this interface is still work in progress. You should expect it to get some
|
||||
* additional methods that allow for looping through child elements, for example, or retrieving the
|
||||
* parent element. <p>
|
||||
*
|
||||
*/
|
||||
public interface IPathElement {
|
||||
/**
|
||||
* Return the name to be used to get this element from its parent
|
||||
*/
|
||||
public String getElementName();
|
||||
|
||||
/**
|
||||
* Retrieve a child element of this object by name.
|
||||
*/
|
||||
public IPathElement getChildElement(String name);
|
||||
|
||||
/**
|
||||
* Return the parent element of this object.
|
||||
*/
|
||||
public IPathElement getParentElement();
|
||||
|
||||
/**
|
||||
* Get the name of the prototype to be used for this object. This will
|
||||
* determine which scripts, actions and skins can be called on it
|
||||
* within the Helma scripting and rendering framework.
|
||||
*/
|
||||
public String getPrototype();
|
||||
}
|
42
src/main/java/helma/framework/IRemoteApp.java
Normal file
42
src/main/java/helma/framework/IRemoteApp.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
import java.rmi.*;
|
||||
|
||||
/**
|
||||
* RMI interface for an application. Currently only execute is used and supported.
|
||||
*/
|
||||
public interface IRemoteApp extends Remote {
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param param ...
|
||||
*
|
||||
* @return ...
|
||||
*
|
||||
* @throws RemoteException ...
|
||||
*/
|
||||
public ResponseTrans execute(RequestTrans param) throws RemoteException;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @throws RemoteException ...
|
||||
*/
|
||||
public void ping() throws RemoteException;
|
||||
}
|
43
src/main/java/helma/framework/NotFoundException.java
Normal file
43
src/main/java/helma/framework/NotFoundException.java
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
|
||||
/**
|
||||
* The basic exception class used to tell when certain things go
|
||||
* wrong in evaluation of requests.
|
||||
*/
|
||||
public class NotFoundException extends RuntimeException {
|
||||
/**
|
||||
* Creates a new NotFoundException object.
|
||||
*
|
||||
* @param message ...
|
||||
*/
|
||||
public NotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new NotFoundException object with a cause.
|
||||
*
|
||||
* @param message the message
|
||||
* @param cause the cause
|
||||
*/
|
||||
public NotFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
47
src/main/java/helma/framework/RedirectException.java
Normal file
47
src/main/java/helma/framework/RedirectException.java
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
|
||||
/**
|
||||
* RedirectException is thrown internally when a response is redirected to a
|
||||
* new URL. Although this is not an Error, it subclasses java.lang.Error
|
||||
* because it's not meant to be caught by application code (similar to
|
||||
* java.lang.ThreadDeath).
|
||||
*/
|
||||
public class RedirectException extends Error {
|
||||
String url;
|
||||
|
||||
/**
|
||||
* Creates a new RedirectException object.
|
||||
*
|
||||
* @param url the URL
|
||||
*/
|
||||
public RedirectException(String url) {
|
||||
super("Redirection Request to " + url);
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the URL to redirect to.
|
||||
* @return the URL
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
224
src/main/java/helma/framework/RequestBean.java
Normal file
224
src/main/java/helma/framework/RequestBean.java
Normal file
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class RequestBean implements Serializable {
|
||||
RequestTrans req;
|
||||
|
||||
/**
|
||||
* Creates a new RequestBean object.
|
||||
*
|
||||
* @param req ...
|
||||
*/
|
||||
public RequestBean(RequestTrans req) {
|
||||
this.req = req;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param name ...
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Object get(String name) {
|
||||
return req.get(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the method of the request. This may either be a HTTP method or
|
||||
* one of the Helma pseudo methods defined in RequestTrans.
|
||||
*/
|
||||
public String getMethod() {
|
||||
return req.getMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public boolean isGet() {
|
||||
return req.isGet();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public boolean isPost() {
|
||||
return req.isPost();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Servlet request represented by this RequestTrans instance.
|
||||
* Returns null for internal and XML-RPC requests.
|
||||
*/
|
||||
public HttpServletRequest getServletRequest() {
|
||||
return req.getServletRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to HttpServletRequest.getHeader().
|
||||
* @param name the header name
|
||||
* @return the header value, or null
|
||||
*/
|
||||
public String getHeader(String name) {
|
||||
return req.getHeader(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to HttpServletRequest.getHeaders(), returns header values as string array.
|
||||
* @param name the header name
|
||||
* @return the header values as string array
|
||||
*/
|
||||
public String[] getHeaders(String name) {
|
||||
return req.getHeaders(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to HttpServletRequest.getIntHeader(), fails silently by returning -1.
|
||||
* @param name the header name
|
||||
* @return the header parsed as integer or -1
|
||||
*/
|
||||
public int getIntHeader(String name) {
|
||||
return req.getIntHeader(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to HttpServletRequest.getDateHeader(), fails silently by returning -1.
|
||||
* @param name the header name
|
||||
* @return the date in milliseconds, or -1
|
||||
*/
|
||||
public long getDateHeader(String name) {
|
||||
return req.getDateHeader(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A string representation of this request
|
||||
*/
|
||||
public String toString() {
|
||||
return "[Request]";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the invoked action
|
||||
*/
|
||||
public String getAction() {
|
||||
return req.getAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The req.data map containing request parameters, cookies and
|
||||
* assorted HTTP headers
|
||||
*/
|
||||
public Map getData() {
|
||||
return req.getRequestData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the req.params map containing combined query and post parameters
|
||||
*/
|
||||
public Map getParams() {
|
||||
return req.getParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the req.queryParams map containing parameters parsed from the query string
|
||||
*/
|
||||
public Map getQueryParams() {
|
||||
return req.getQueryParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the req.postParams map containing params parsed from post data
|
||||
*/
|
||||
public Map getPostParams() {
|
||||
return req.getPostParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the req.cookies map containing request cookies
|
||||
*/
|
||||
public Map getCookies() {
|
||||
return req.getCookies();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the time this request has been running, in milliseconds
|
||||
*/
|
||||
public long getRuntime() {
|
||||
return (System.currentTimeMillis() - req.getStartTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the password if using HTTP basic authentication
|
||||
*/
|
||||
public String getPassword() {
|
||||
return req.getPassword();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the request path
|
||||
*/
|
||||
public String getPath() {
|
||||
return req.getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the request URI
|
||||
*/
|
||||
public String getUri() {
|
||||
return req.getUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the username if using HTTP basic authentication
|
||||
*/
|
||||
public String getUsername() {
|
||||
return req.getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* The action handler allows the onRequest() method to set the function object
|
||||
* to be invoked for processing the request, overriding the action resolved
|
||||
* from the request path.
|
||||
* @return the action handler
|
||||
*/
|
||||
public Object getActionHandler() {
|
||||
return req.getActionHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* The action handler allows the onRequest() method to set the function object
|
||||
* to be invoked for processing the request, overriding the action resolved
|
||||
* from the request path.
|
||||
* @param handler the action handler
|
||||
*/
|
||||
public void setActionHandler(Object handler) {
|
||||
req.setActionHandler(handler);
|
||||
}
|
||||
}
|
773
src/main/java/helma/framework/RequestTrans.java
Normal file
773
src/main/java/helma/framework/RequestTrans.java
Normal file
|
@ -0,0 +1,773 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
import helma.util.Base64;
|
||||
import helma.util.SystemMap;
|
||||
import helma.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.Cookie;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* A Transmitter for a request from the servlet client. Objects of this
|
||||
* class are directly exposed to JavaScript as global property req.
|
||||
*/
|
||||
public class RequestTrans implements Serializable {
|
||||
|
||||
static final long serialVersionUID = 5398880083482000580L;
|
||||
|
||||
// HTTP methods
|
||||
public final static String GET = "GET";
|
||||
public final static String POST = "POST";
|
||||
public final static String DELETE = "DELETE";
|
||||
public final static String HEAD = "HEAD";
|
||||
public final static String OPTIONS = "OPTIONS";
|
||||
public final static String PUT = "PUT";
|
||||
public final static String TRACE = "TRACE";
|
||||
// Helma pseudo-methods
|
||||
public final static String XMLRPC = "XMLRPC";
|
||||
public final static String EXTERNAL = "EXTERNAL";
|
||||
public final static String INTERNAL = "INTERNAL";
|
||||
|
||||
// the servlet request and response, may be null
|
||||
final HttpServletRequest request;
|
||||
final HttpServletResponse response;
|
||||
|
||||
// the path info of the request
|
||||
private final String path;
|
||||
|
||||
// the uri of the request
|
||||
private final String uri;
|
||||
|
||||
// the request's session id
|
||||
private String session;
|
||||
|
||||
// the map of form and cookie data
|
||||
private final Map values = new DataComboMap();
|
||||
|
||||
private ParamComboMap params;
|
||||
private ParameterMap queryParams, postParams, cookies;
|
||||
|
||||
// the HTTP request method
|
||||
private String method;
|
||||
|
||||
// timestamp of client-cached version, if present in request
|
||||
private long ifModifiedSince = -1;
|
||||
|
||||
// set of ETags the client sent with If-None-Match header
|
||||
private final Set etags = new HashSet();
|
||||
|
||||
// when was execution started on this request?
|
||||
private final long startTime;
|
||||
|
||||
// the name of the action being invoked
|
||||
private String action;
|
||||
private Object actionHandler = null;
|
||||
private String httpUsername;
|
||||
private String httpPassword;
|
||||
|
||||
static private final Pattern paramPattern = Pattern.compile("\\[(.+?)\\]");
|
||||
|
||||
/**
|
||||
* Create a new Request transmitter with an empty data map.
|
||||
*/
|
||||
public RequestTrans(String method, String path) {
|
||||
this.method = method;
|
||||
this.path = path;
|
||||
this.uri = null;
|
||||
this.request = null;
|
||||
this.response = null;
|
||||
startTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new request transmitter with the given data map.
|
||||
*/
|
||||
public RequestTrans(HttpServletRequest request,
|
||||
HttpServletResponse response, String path) {
|
||||
this.method = request.getMethod();
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.path = path;
|
||||
this.uri = request.getRequestURI();
|
||||
startTime = System.currentTimeMillis();
|
||||
|
||||
// do standard HTTP variables
|
||||
String header = request.getHeader("Host");
|
||||
if (header != null) {
|
||||
values.put("http_host", header.toLowerCase());
|
||||
}
|
||||
|
||||
header = request.getHeader("Referer");
|
||||
if (header != null) {
|
||||
values.put("http_referer", header);
|
||||
}
|
||||
|
||||
try {
|
||||
long ifModifiedSince = request.getDateHeader("If-Modified-Since");
|
||||
if (ifModifiedSince > -1) {
|
||||
setIfModifiedSince(ifModifiedSince);
|
||||
}
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
// not a date header
|
||||
}
|
||||
|
||||
header = request.getHeader("If-None-Match");
|
||||
if (header != null) {
|
||||
setETags(header);
|
||||
}
|
||||
|
||||
header = request.getRemoteAddr();
|
||||
if (header != null) {
|
||||
values.put("http_remotehost", header);
|
||||
}
|
||||
|
||||
header = request.getHeader("User-Agent");
|
||||
if (header != null) {
|
||||
values.put("http_browser", header);
|
||||
}
|
||||
|
||||
header = request.getHeader("Accept-Language");
|
||||
if (header != null) {
|
||||
values.put("http_language", header);
|
||||
}
|
||||
|
||||
header = request.getHeader("authorization");
|
||||
if (header != null) {
|
||||
values.put("authorization", header);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we should try to handle this as XML-RPC request.
|
||||
*
|
||||
* @return true if this might be an XML-RPC request.
|
||||
*/
|
||||
public synchronized boolean checkXmlRpc() {
|
||||
if ("POST".equalsIgnoreCase(method)) {
|
||||
String contentType = request.getContentType();
|
||||
if (contentType == null) {
|
||||
return false;
|
||||
}
|
||||
int semi = contentType.indexOf(";");
|
||||
if (semi > -1) {
|
||||
contentType = contentType.substring(0, semi);
|
||||
}
|
||||
return "text/xml".equalsIgnoreCase(contentType.trim());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this request is in fact handled as XML-RPC request.
|
||||
* This implies that {@link #checkXmlRpc()} returns true and a matching
|
||||
* XML-RPC action was found.
|
||||
*
|
||||
* @return true if this request is handled as XML-RPC request.
|
||||
*/
|
||||
public synchronized boolean isXmlRpc() {
|
||||
return XMLRPC.equals(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cookie
|
||||
* @param name the cookie name
|
||||
* @param cookie the cookie
|
||||
*/
|
||||
public void setCookie(String name, Cookie cookie) {
|
||||
if (cookies == null) {
|
||||
cookies = new ParameterMap();
|
||||
}
|
||||
cookies.put(name, cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a map containing the cookies sent with this request
|
||||
*/
|
||||
public Map getCookies() {
|
||||
if (cookies == null) {
|
||||
cookies = new ParameterMap();
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the combined query and post parameters for this request
|
||||
*/
|
||||
public Map getParams() {
|
||||
if (params == null) {
|
||||
params = new ParamComboMap();
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return get the query parameters for this request
|
||||
*/
|
||||
public Map getQueryParams() {
|
||||
if (queryParams == null) {
|
||||
queryParams = new ParameterMap();
|
||||
}
|
||||
return queryParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return get the post parameters for this request
|
||||
*/
|
||||
public Map getPostParams() {
|
||||
if (postParams == null) {
|
||||
postParams = new ParameterMap();
|
||||
}
|
||||
return postParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the request parameters
|
||||
*/
|
||||
public void setParameters(Map parameters, boolean isPost) {
|
||||
if (isPost) {
|
||||
postParams = new ParameterMap(parameters);
|
||||
} else {
|
||||
queryParams = new ParameterMap(parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a post parameter to the request
|
||||
* @param name the parameter name
|
||||
* @param value the parameter value
|
||||
*/
|
||||
public void addPostParam(String name, Object value) {
|
||||
if (postParams == null) {
|
||||
postParams = new ParameterMap();
|
||||
}
|
||||
Object previous = postParams.getRaw(name);
|
||||
if (previous instanceof Object[]) {
|
||||
Object[] array = (Object[]) previous;
|
||||
Object[] values = new Object[array.length + 1];
|
||||
System.arraycopy(array, 0, values, 0, array.length);
|
||||
values[array.length] = value;
|
||||
postParams.put(name, values);
|
||||
} else if (previous == null) {
|
||||
postParams.put(name, new Object[] {value});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a parameter value in this request transmitter. This
|
||||
* parses foo[bar][baz] as nested objects/maps.
|
||||
*/
|
||||
public void set(String name, Object value) {
|
||||
values.put(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from the requests map by key.
|
||||
*/
|
||||
public Object get(String name) {
|
||||
try {
|
||||
return values.get(name);
|
||||
} catch (Exception x) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data map for this request transmitter.
|
||||
*/
|
||||
public Map getRequestData() {
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Servlet request represented by this RequestTrans instance.
|
||||
* Returns null for internal and XML-RPC requests.
|
||||
*/
|
||||
public HttpServletRequest getServletRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to HttpServletRequest.getHeader().
|
||||
* @param name the header name
|
||||
* @return the header value, or null
|
||||
*/
|
||||
public String getHeader(String name) {
|
||||
return request == null ? null : request.getHeader(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to HttpServletRequest.getHeaders(), returns header values as string array.
|
||||
* @param name the header name
|
||||
* @return the header values as string array
|
||||
*/
|
||||
public String[] getHeaders(String name) {
|
||||
return request == null ?
|
||||
null : StringUtils.collect(request.getHeaders(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to HttpServletRequest.getIntHeader(), fails silently by returning -1.
|
||||
* @param name the header name
|
||||
* @return the header parsed as integer or -1
|
||||
*/
|
||||
public int getIntHeader(String name) {
|
||||
try {
|
||||
return request == null ? -1 : getIntHeader(name);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to HttpServletRequest.getDateHeader(), fails silently by returning -1.
|
||||
* @param name the header name
|
||||
* @return the date in milliseconds, or -1
|
||||
*/
|
||||
public long getDateHeader(String name) {
|
||||
try {
|
||||
return request == null ? -1 : getDateHeader(name);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Servlet response for this request.
|
||||
* Returns null for internal and XML-RPC requests.
|
||||
*/
|
||||
public HttpServletResponse getServletResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hash code is computed from the session id if available. This is used to
|
||||
* detect multiple identic requests.
|
||||
*/
|
||||
public int hashCode() {
|
||||
if (session == null || path == null) {
|
||||
return super.hashCode();
|
||||
} else {
|
||||
return 17 + (37 * session.hashCode()) +
|
||||
(37 * path.hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A request is considered equal to another one if it has the same method,
|
||||
* path, session, request data, and conditional get data. This is used to
|
||||
* evaluate multiple simultanous identical requests only once.
|
||||
*/
|
||||
public boolean equals(Object what) {
|
||||
if (what instanceof RequestTrans) {
|
||||
if (session == null || path == null) {
|
||||
return super.equals(what);
|
||||
} else {
|
||||
RequestTrans other = (RequestTrans) what;
|
||||
return (session.equals(other.session)
|
||||
&& path.equalsIgnoreCase(other.path)
|
||||
&& values.equals(other.values)
|
||||
&& ifModifiedSince == other.ifModifiedSince
|
||||
&& etags.equals(other.etags));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method of the request. This may either be a HTTP method or
|
||||
* one of the Helma pseudo methods defined in this class.
|
||||
*/
|
||||
public synchronized String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the method of this request.
|
||||
*
|
||||
* @param method the method.
|
||||
*/
|
||||
public synchronized void setMethod(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this object represents a HTTP GET Request.
|
||||
*/
|
||||
public boolean isGet() {
|
||||
return GET.equalsIgnoreCase(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this object represents a HTTP GET Request.
|
||||
*/
|
||||
public boolean isPost() {
|
||||
return POST.equalsIgnoreCase(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request's session id
|
||||
*/
|
||||
public String getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the request's session id
|
||||
*/
|
||||
public void setSession(String session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request's path
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request's path
|
||||
*/
|
||||
public String getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request's action.
|
||||
*/
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the request's action.
|
||||
*/
|
||||
public void setAction(String action) {
|
||||
int suffix = action.lastIndexOf("_action");
|
||||
this.action = suffix > -1 ? action.substring(0, suffix) : action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request's action handler. The action handler allows the
|
||||
* onRequest() method to set the function object to be invoked for processing
|
||||
* the request, overriding the action resolved from the request path.
|
||||
* @return the action handler function
|
||||
*/
|
||||
public Object getActionHandler() {
|
||||
return actionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the request's action handler. The action handler allows the
|
||||
* onRequest() method to set the function object to be invoked for processing
|
||||
* the request, overriding the action resolved from the request path.
|
||||
* @param handler the action handler
|
||||
*/
|
||||
public void setActionHandler(Object handler) {
|
||||
this.actionHandler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time the request was created.
|
||||
*/
|
||||
public long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param since ...
|
||||
*/
|
||||
public void setIfModifiedSince(long since) {
|
||||
ifModifiedSince = since;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public long getIfModifiedSince() {
|
||||
return ifModifiedSince;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param etagHeader ...
|
||||
*/
|
||||
public void setETags(String etagHeader) {
|
||||
if (etagHeader.indexOf(",") > -1) {
|
||||
StringTokenizer st = new StringTokenizer(etagHeader, ", \r\n");
|
||||
while (st.hasMoreTokens())
|
||||
etags.add(st.nextToken());
|
||||
} else {
|
||||
etags.add(etagHeader);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Set getETags() {
|
||||
return etags;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param etag ...
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public boolean hasETag(String etag) {
|
||||
if ((etags == null) || (etag == null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return etags.contains(etag);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getUsername() {
|
||||
if (httpUsername != null) {
|
||||
return httpUsername;
|
||||
}
|
||||
|
||||
String auth = (String) get("authorization");
|
||||
|
||||
if ((auth == null) || "".equals(auth)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
decodeHttpAuth(auth);
|
||||
|
||||
return httpUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getPassword() {
|
||||
if (httpPassword != null) {
|
||||
return httpPassword;
|
||||
}
|
||||
|
||||
String auth = (String) get("authorization");
|
||||
|
||||
if ((auth == null) || "".equals(auth)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
decodeHttpAuth(auth);
|
||||
|
||||
return httpPassword;
|
||||
}
|
||||
|
||||
private void decodeHttpAuth(String auth) {
|
||||
if (auth == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringTokenizer tok;
|
||||
|
||||
if (auth.startsWith("Basic ")) {
|
||||
tok = new StringTokenizer(new String(Base64.decode((auth.substring(6)).toCharArray())),
|
||||
":");
|
||||
} else {
|
||||
tok = new StringTokenizer(new String(Base64.decode(auth.toCharArray())), ":");
|
||||
}
|
||||
|
||||
try {
|
||||
httpUsername = tok.nextToken();
|
||||
} catch (NoSuchElementException e) {
|
||||
httpUsername = null;
|
||||
}
|
||||
|
||||
try {
|
||||
StringBuffer buf = new StringBuffer(tok.nextToken());
|
||||
while (tok.hasMoreTokens()) {
|
||||
buf.append(":");
|
||||
buf.append(tok.nextToken());
|
||||
}
|
||||
httpPassword = buf.toString();
|
||||
} catch (NoSuchElementException e) {
|
||||
httpPassword = null;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return method + ":" + path;
|
||||
}
|
||||
|
||||
class ParameterMap extends SystemMap {
|
||||
|
||||
public ParameterMap() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ParameterMap(Map map) {
|
||||
super((int) (map.size() / 0.75f) + 1);
|
||||
for (Iterator i = map.entrySet().iterator(); i.hasNext(); ) {
|
||||
Map.Entry e = (Map.Entry) i.next();
|
||||
put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public Object put(Object key, Object value) {
|
||||
if (key instanceof String) {
|
||||
String name = (String) key;
|
||||
int bracket = name.indexOf('[');
|
||||
if (bracket > -1 && name.endsWith("]")) {
|
||||
Matcher matcher = paramPattern.matcher(name);
|
||||
String partName = name.substring(0, bracket);
|
||||
return putInternal(partName, matcher, value);
|
||||
}
|
||||
}
|
||||
Object previous = super.get(key);
|
||||
if (previous != null && (previous instanceof Map || value instanceof Map))
|
||||
throw new RuntimeException("Conflicting HTTP Parameters for '" + key + "'");
|
||||
return super.put(key, value);
|
||||
}
|
||||
|
||||
private Object putInternal(String name, Matcher matcher, Object value) {
|
||||
Object previous = super.get(name);
|
||||
if (matcher.find()) {
|
||||
ParameterMap map = null;
|
||||
if (previous instanceof ParameterMap) {
|
||||
map = (ParameterMap) previous;
|
||||
} else if (previous == null) {
|
||||
map = new ParameterMap();
|
||||
super.put(name, map);
|
||||
} else {
|
||||
throw new RuntimeException("Conflicting HTTP Parameters for '" + name + "'");
|
||||
}
|
||||
String partName = matcher.group(1);
|
||||
return map.putInternal(partName, matcher, value);
|
||||
}
|
||||
if (previous != null && (previous instanceof Map || value instanceof Map))
|
||||
throw new RuntimeException("Conflicting HTTP Parameters for '" + name + "'");
|
||||
return super.put(name, value);
|
||||
}
|
||||
|
||||
public Object get(Object key) {
|
||||
if (key instanceof String) {
|
||||
Object value = super.get(key);
|
||||
String name = (String) key;
|
||||
if (name.endsWith("_array") && value == null) {
|
||||
value = super.get(name.substring(0, name.length() - 6));
|
||||
return value instanceof Object[] ? value : null;
|
||||
} else if (name.endsWith("_cookie") && value == null) {
|
||||
value = super.get(name.substring(0, name.length() - 7));
|
||||
return value instanceof Cookie ? value : null;
|
||||
} else if (value instanceof Object[]) {
|
||||
Object[] values = ((Object[]) value);
|
||||
return values.length > 0 ? values[0] : null;
|
||||
} else if (value instanceof Cookie) {
|
||||
Cookie cookie = (Cookie) value;
|
||||
return cookie.getValue();
|
||||
}
|
||||
}
|
||||
return super.get(key);
|
||||
}
|
||||
|
||||
protected Object getRaw(Object key) {
|
||||
return super.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
class DataComboMap extends SystemMap {
|
||||
|
||||
public Object get(Object key) {
|
||||
Object value = super.get(key);
|
||||
if (value != null)
|
||||
return value;
|
||||
if (postParams != null && (value = postParams.get(key)) != null)
|
||||
return value;
|
||||
if (queryParams != null && (value = queryParams.get(key)) != null)
|
||||
return value;
|
||||
if (cookies != null && (value = cookies.get(key)) != null)
|
||||
return value;
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean containsKey(Object key) {
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
public Set entrySet() {
|
||||
Set entries = new HashSet(super.entrySet());
|
||||
if (postParams != null) entries.addAll(postParams.entrySet());
|
||||
if (queryParams != null) entries.addAll(queryParams.entrySet());
|
||||
if (cookies != null) entries.addAll(cookies.entrySet());
|
||||
return entries;
|
||||
}
|
||||
|
||||
public Set keySet() {
|
||||
Set keys = new HashSet(super.keySet());
|
||||
if (postParams != null) keys.addAll(postParams.keySet());
|
||||
if (queryParams != null) keys.addAll(queryParams.keySet());
|
||||
if (cookies != null) keys.addAll(cookies.keySet());
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
class ParamComboMap extends SystemMap {
|
||||
public Object get(Object key) {
|
||||
Object value;
|
||||
if (postParams != null && (value = postParams.get(key)) != null)
|
||||
return value;
|
||||
if (queryParams != null && (value = queryParams.get(key)) != null)
|
||||
return value;
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean containsKey(Object key) {
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
public Set entrySet() {
|
||||
Set entries = new HashSet();
|
||||
if (postParams != null) entries.addAll(postParams.entrySet());
|
||||
if (queryParams != null) entries.addAll(queryParams.entrySet());
|
||||
return entries;
|
||||
}
|
||||
|
||||
public Set keySet() {
|
||||
Set keys = new HashSet();
|
||||
if (postParams != null) keys.addAll(postParams.keySet());
|
||||
if (queryParams != null) keys.addAll(queryParams.keySet());
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
}
|
675
src/main/java/helma/framework/ResponseBean.java
Normal file
675
src/main/java/helma/framework/ResponseBean.java
Normal file
|
@ -0,0 +1,675 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
import helma.objectmodel.db.Transactor;
|
||||
import helma.scripting.ScriptingException;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.Serializable;
|
||||
import java.io.StringWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ResponseBean implements Serializable {
|
||||
ResponseTrans res;
|
||||
|
||||
/**
|
||||
* Creates a new ResponseBean object.
|
||||
*
|
||||
* @param res the wrapped ResponseTrans
|
||||
*/
|
||||
public ResponseBean(ResponseTrans res) {
|
||||
this.res = res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an object to the response buffer by converting it to a string
|
||||
* and then HTML-encoding it.
|
||||
*
|
||||
* @param obj the object to write to the response buffer
|
||||
*/
|
||||
public void encode(Object obj) {
|
||||
res.encode(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an object to the response buffer by converting it to a string
|
||||
* and then XML-encoding it.
|
||||
*
|
||||
* @param obj the object to write to the response buffer
|
||||
*/
|
||||
public void encodeXml(Object obj) {
|
||||
res.encodeXml(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an object to the response buffer by converting it to a string
|
||||
* and then encoding it for form/text area content use.
|
||||
*
|
||||
* @param obj the object to write to the response buffer
|
||||
*/
|
||||
public void encodeForm(Object obj) {
|
||||
res.encodeForm(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an object to the response buffer by converting it to a string
|
||||
* and then HTML-formatting it.
|
||||
*
|
||||
* @param obj the object to write to the response buffer
|
||||
*/
|
||||
public void format(Object obj) {
|
||||
res.format(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the request to a different URL
|
||||
*
|
||||
* @param url the URL to redirect to
|
||||
* @throws RedirectException to immediately terminate the request
|
||||
*/
|
||||
public void redirect(String url) throws RedirectException {
|
||||
res.redirect(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internally forward the request to a different URL
|
||||
*
|
||||
* @param url the URL to forward to
|
||||
* @throws RedirectException to immediately terminate the request
|
||||
*/
|
||||
public void forward(String url) throws RedirectException {
|
||||
res.forward(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately stop processing the current request
|
||||
*
|
||||
* @throws RedirectException to immediately terminate the request
|
||||
*/
|
||||
public void stop() throws RedirectException {
|
||||
res.redirect(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the response object, clearing all content previously written to it
|
||||
*/
|
||||
public void reset() {
|
||||
res.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the response buffer, clearing all content previously written to it
|
||||
*/
|
||||
public void resetBuffer() {
|
||||
res.resetBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ServletResponse instance for this Response.
|
||||
* Returns null for internal and XML-RPC requests.
|
||||
* @return the servlet response
|
||||
*/
|
||||
public HttpServletResponse getServletResponse() {
|
||||
return res.getServletResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a HTTP cookie with the name and value that is discarded when the
|
||||
* HTTP client is closed
|
||||
*
|
||||
* @param key the cookie name
|
||||
* @param value the cookie value
|
||||
*/
|
||||
public void setCookie(String key, String value) {
|
||||
res.setCookie(key, value, -1, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a HTTP cookie with the name and value that is stored by the
|
||||
* HTTP client for the given number of days. A days value of 0 means the
|
||||
* cookie should be immediately discarded.
|
||||
*
|
||||
* @param key the cookie name
|
||||
* @param value the cookie value
|
||||
* @param days number of days the cookie should be stored
|
||||
*/
|
||||
public void setCookie(String key, String value, int days) {
|
||||
res.setCookie(key, value, days, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a HTTP cookie with the name and value that is only applied to
|
||||
* the URLs matching the given path and is stored by the
|
||||
* HTTP client for the given number of days. A days value of 0 means the
|
||||
* cookie should be immediately discarded.
|
||||
*
|
||||
* @param key the cookie name
|
||||
* @param value the cookie value
|
||||
* @param days number of days the cookie should be stored
|
||||
* @param path the URL path to apply the cookie to
|
||||
*/
|
||||
public void setCookie(String key, String value, int days, String path) {
|
||||
res.setCookie(key, value, days, path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a HTTP cookie with the name and value that is only applied to
|
||||
* the URLs matching the given path and is stored by the
|
||||
* HTTP client for the given number of days. A days value of 0 means the
|
||||
* cookie should be immediately discarded.
|
||||
*
|
||||
* @param key the cookie name
|
||||
* @param value the cookie value
|
||||
* @param days number of days the cookie should be stored
|
||||
* @param path the URL path to apply the cookie to
|
||||
* @param domain domain
|
||||
*/
|
||||
public void setCookie(String key, String value, int days, String path, String domain) {
|
||||
res.setCookie(key, value, days, path, domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset a previously set HTTP cookie, causing it to be discarded immedialtely by the
|
||||
* HTTP client.
|
||||
*
|
||||
* @param key the name of the cookie to be discarded
|
||||
*/
|
||||
public void unsetCookie(String key) {
|
||||
res.setCookie(key, "", 0, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly write a string to the response buffer without any transformation.
|
||||
*
|
||||
* @param str the string to write to the response buffer
|
||||
*/
|
||||
public void write(String... str) {
|
||||
if (str == null) return;
|
||||
|
||||
for (String s : str) {
|
||||
res.write(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write string to response buffer and append a platform dependent newline sequence.
|
||||
*
|
||||
* @param str the string to write to the response buffer
|
||||
*/
|
||||
public void writeln(String... str) {
|
||||
if (str == null) return;
|
||||
|
||||
for (String s : str) {
|
||||
res.writeln(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a platform dependent newline sequence to response buffer.
|
||||
*/
|
||||
public void writeln() {
|
||||
res.writeln();
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly write a byte array to the response buffer without any transformation.
|
||||
*
|
||||
* @param bytes the string to write to the response buffer
|
||||
*/
|
||||
public void writeBinary(byte[] bytes) {
|
||||
res.writeBinary(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* add an HTML formatted debug message to the end of the page.
|
||||
*
|
||||
* @param message the message
|
||||
*/
|
||||
public void debug(String... messages) {
|
||||
if (messages == null) {
|
||||
messages = new String[]{null};
|
||||
}
|
||||
|
||||
for (String message : messages) {
|
||||
res.debug(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation for this object
|
||||
*
|
||||
* @return string representation
|
||||
*/
|
||||
public String toString() {
|
||||
return "[Response]";
|
||||
}
|
||||
|
||||
// property-related methods
|
||||
|
||||
/**
|
||||
* Return the current cachability setting for this response
|
||||
*
|
||||
* @return true if the response may be cached by the HTTP client, false otherwise
|
||||
*/
|
||||
public boolean getCache() {
|
||||
return res.isCacheable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set true cachability setting for this response
|
||||
*
|
||||
* @param cache true if the response may be cached by the HTTP client, false otherwise
|
||||
*/
|
||||
public void setCache(boolean cache) {
|
||||
res.setCacheable(cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current charset/encoding name for the response
|
||||
*
|
||||
* @return The charset name
|
||||
*/
|
||||
public String getCharset() {
|
||||
return res.getCharset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the charset/encoding name for the response
|
||||
*
|
||||
* @param charset The charset name
|
||||
*/
|
||||
public void setCharset(String charset) {
|
||||
res.setCharset(charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current content type name for the response
|
||||
*
|
||||
* @return the content type
|
||||
*/
|
||||
public String getContentType() {
|
||||
return res.getContentType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content type for the response
|
||||
*
|
||||
* @param contentType The charset name
|
||||
*/
|
||||
public void setContentType(String contentType) {
|
||||
res.setContentType(contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to HttpServletResponse.addHeader()
|
||||
* @param name the header name
|
||||
* @param value the header value
|
||||
*/
|
||||
public void addHeader(String name, String value) {
|
||||
res.addHeader(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to HttpServletResponse.addDateHeader()
|
||||
* @param name the header name
|
||||
* @param value the header value
|
||||
*/
|
||||
public void addDateHeader(String name, Date value) {
|
||||
res.addDateHeader(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to HttpServletResponse.setHeader()
|
||||
* @param name the header name
|
||||
* @param value the header value
|
||||
*/
|
||||
public void setHeader(String name, String value) {
|
||||
res.setHeader(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to HttpServletResponse.setDateHeader()
|
||||
* @param name the header name
|
||||
* @param value the header value
|
||||
*/
|
||||
public void setDateHeader(String name, Date value) {
|
||||
res.setDateHeader(name, value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the data map for the response
|
||||
*
|
||||
* @return the data object
|
||||
*/
|
||||
public Map getData() {
|
||||
return res.getResponseData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the macro handlers map for the response
|
||||
*
|
||||
* @return the macro handlers map
|
||||
*/
|
||||
public Map getHandlers() {
|
||||
return res.getMacroHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the meta map for the response
|
||||
*
|
||||
* @return the meta map
|
||||
*/
|
||||
public Map getMeta() {
|
||||
return res.getMetaData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current error message for the response, if any
|
||||
*
|
||||
* @return the error message
|
||||
*/
|
||||
public String getError() {
|
||||
return res.getErrorMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the uncaught exception for the response, if any
|
||||
* @return the uncaught exception
|
||||
*/
|
||||
public Throwable getException() {
|
||||
return res.getError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Javascript stack trace of an uncought exception.
|
||||
* @return the script stack trace of any uncaught exception or null.
|
||||
*/
|
||||
public String getScriptStack() {
|
||||
Throwable t = res.getError();
|
||||
if (t instanceof ScriptingException)
|
||||
return ((ScriptingException) t).getScriptStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Java stack trace of an uncaught exception.
|
||||
* @return the java stack trace of an uncaught exception or null.
|
||||
*/
|
||||
public String getJavaStack() {
|
||||
Throwable t = res.getError();
|
||||
if (t == null)
|
||||
return null;
|
||||
else if (t instanceof ScriptingException)
|
||||
return ((ScriptingException) t).getJavaStackTrace();
|
||||
StringWriter w = new StringWriter();
|
||||
t.printStackTrace(new PrintWriter(w));
|
||||
return w.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current message for the response, if set
|
||||
*
|
||||
* @return the message
|
||||
*/
|
||||
public String getMessage() {
|
||||
return res.getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message property for the response
|
||||
*
|
||||
* @param message the message property
|
||||
*/
|
||||
public void setMessage(String message) {
|
||||
res.setMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTTP authentication realm for the response
|
||||
*
|
||||
* @return the HTTP authentication realm
|
||||
*/
|
||||
public String getRealm() {
|
||||
return res.getRealm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HTTP authentication realm for the response
|
||||
*
|
||||
* @param realm the HTTP authentication realm
|
||||
*/
|
||||
public void setRealm(String realm) {
|
||||
res.setRealm(realm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the skin search path for the response
|
||||
*
|
||||
* @param arr an array containing files or nodes containing skins
|
||||
*/
|
||||
public void setSkinpath(Object[] arr) {
|
||||
res.setSkinpath(arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the skin search path for the response
|
||||
*
|
||||
* @return The array of files or nodes used to search for skins
|
||||
*/
|
||||
public Object[] getSkinpath() {
|
||||
return res.getSkinpath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTTP status code for this response
|
||||
*
|
||||
* @return the HTTP status code
|
||||
*/
|
||||
public int getStatus() {
|
||||
return res.getStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HTTP status code for this response
|
||||
*
|
||||
* @param status the HTTP status code
|
||||
*/
|
||||
public void setStatus(int status) {
|
||||
res.setStatus(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last modified date for this response
|
||||
*
|
||||
* @return the last modified date
|
||||
*/
|
||||
public Date getLastModified() {
|
||||
long modified = res.getLastModified();
|
||||
|
||||
if (modified > -1) {
|
||||
return new Date(modified);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last modified date for this response
|
||||
*
|
||||
* @param date the last modified date
|
||||
*/
|
||||
public void setLastModified(Date date) {
|
||||
if (date == null) {
|
||||
res.setLastModified(-1);
|
||||
} else {
|
||||
res.setLastModified(date.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ETag for this response
|
||||
*
|
||||
* @return the HTTP etag
|
||||
*/
|
||||
public String getETag() {
|
||||
return res.getETag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HTTP Etag for this response
|
||||
*
|
||||
* @param etag the HTTP ETag
|
||||
*/
|
||||
public void setETag(String etag) {
|
||||
res.setETag(etag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to this response's dependencies. If no dependency has changed between
|
||||
* requests, an HTTP not-modified response will be generated.
|
||||
*
|
||||
* @param what a string item this response depends on
|
||||
*/
|
||||
public void dependsOn(String what) {
|
||||
res.dependsOn(what);
|
||||
}
|
||||
|
||||
/**
|
||||
* Digest this response's dependencies to conditionally create a HTTP not-modified response
|
||||
*/
|
||||
public void digest() {
|
||||
res.digestDependencies();
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a string buffer on the response object. All further
|
||||
* writes will be redirected to this buffer.
|
||||
*/
|
||||
public void push() {
|
||||
res.pushBuffer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop a string buffer from the response object containing
|
||||
* all the writes since the last pushBuffer
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String pop() {
|
||||
return res.popString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Old version for push() kept for compatibility
|
||||
* @deprecated
|
||||
*/
|
||||
public void pushStringBuffer() {
|
||||
res.pushBuffer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Old version for pop() kept for compatibility
|
||||
* @deprecated
|
||||
* @return ...
|
||||
*/
|
||||
public String popStringBuffer() {
|
||||
return res.popString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a string buffer on the response object. All further
|
||||
* writes will be redirected to this buffer.
|
||||
* @param buffer the string buffer
|
||||
* @return the new stringBuffer
|
||||
*/
|
||||
public StringBuffer pushBuffer(StringBuffer buffer) {
|
||||
return res.pushBuffer(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a string buffer on the response object. All further
|
||||
* writes will be redirected to this buffer.
|
||||
* @return the new stringBuffer
|
||||
*/
|
||||
public StringBuffer pushBuffer() {
|
||||
return res.pushBuffer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops the current response buffer without converting it to a string
|
||||
* @return the stringBuffer
|
||||
*/
|
||||
public StringBuffer popBuffer() {
|
||||
return res.popBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current response buffer as string.
|
||||
*
|
||||
* @return the response buffer as string
|
||||
*/
|
||||
public String getBuffer() {
|
||||
return res.getBuffer().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit changes made during the course of the current transaction
|
||||
* and start a new one
|
||||
*
|
||||
* @throws Exception thrown if commit fails
|
||||
*/
|
||||
public void commit() throws Exception {
|
||||
Transactor tx = Transactor.getInstance();
|
||||
if (tx != null) {
|
||||
String tname = tx.getTransactionName();
|
||||
tx.commit();
|
||||
tx.begin(tname);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the current transaction and start a new one.
|
||||
*
|
||||
* @throws Exception thrown if rollback fails
|
||||
*/
|
||||
public void rollback() throws Exception {
|
||||
Transactor tx = Transactor.getInstance();
|
||||
if (tx != null) {
|
||||
String tname = tx.getTransactionName();
|
||||
tx.abort();
|
||||
tx.begin(tname);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the current database transaction and abort execution.
|
||||
* This has the same effect as calling rollback() and then stop().
|
||||
*
|
||||
* @throws AbortException thrown to exit the the current execution
|
||||
*/
|
||||
public void abort() throws AbortException {
|
||||
throw new AbortException();
|
||||
}
|
||||
|
||||
}
|
1174
src/main/java/helma/framework/ResponseTrans.java
Normal file
1174
src/main/java/helma/framework/ResponseTrans.java
Normal file
File diff suppressed because it is too large
Load diff
31
src/main/java/helma/framework/TimeoutException.java
Normal file
31
src/main/java/helma/framework/TimeoutException.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
|
||||
/**
|
||||
* TimeoutException is thrown by the request evaluator when a request could
|
||||
* not be serviced within the timeout period specified for an application.
|
||||
*/
|
||||
public class TimeoutException extends RuntimeException {
|
||||
/**
|
||||
* Creates a new TimeoutException object.
|
||||
*/
|
||||
public TimeoutException() {
|
||||
super("Request timed out");
|
||||
}
|
||||
}
|
82
src/main/java/helma/framework/UploadStatus.java
Normal file
82
src/main/java/helma/framework/UploadStatus.java
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 2007 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class UploadStatus implements Serializable {
|
||||
|
||||
long current = 0;
|
||||
long total = 0;
|
||||
int itemsRead = 0;
|
||||
String error = null;
|
||||
long lastModified;
|
||||
|
||||
public UploadStatus() {
|
||||
lastModified = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void update(long bytesRead, long contentLength, int itemsRead) {
|
||||
this.current = bytesRead;
|
||||
this.total = contentLength;
|
||||
this.itemsRead = itemsRead;
|
||||
lastModified = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
lastModified = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public long getCurrent() {
|
||||
return current;
|
||||
}
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public int getItemsRead() {
|
||||
return itemsRead;
|
||||
}
|
||||
|
||||
public boolean isDisposable() {
|
||||
// Make upload status disposable if it hasn't been modified for the last
|
||||
// 10 minutes, regardless of whether the upload has finished or not
|
||||
return System.currentTimeMillis() - lastModified > 60000;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buffer = new StringBuffer("{current: ").append(current)
|
||||
.append(", total: ").append(total)
|
||||
.append(", itemsRead: ").append(itemsRead)
|
||||
.append(", error: ");
|
||||
if (error == null) {
|
||||
buffer.append("null");
|
||||
} else {
|
||||
buffer.append("\"");
|
||||
buffer.append(error.replaceAll("\"", "\\\\\""));
|
||||
buffer.append("\"");
|
||||
}
|
||||
return buffer.append("}").toString();
|
||||
}
|
||||
|
||||
}
|
52
src/main/java/helma/framework/core/AppClassLoader.java
Normal file
52
src/main/java/helma/framework/core/AppClassLoader.java
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.core;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
/**
|
||||
* ClassLoader subclass with package accessible addURL method.
|
||||
*/
|
||||
public class AppClassLoader extends URLClassLoader {
|
||||
private final String appname;
|
||||
|
||||
/**
|
||||
* Create a HelmaClassLoader with the given application name and the given URLs
|
||||
*/
|
||||
public AppClassLoader(String appname, URL[] urls) {
|
||||
super(urls, AppClassLoader.class.getClassLoader());
|
||||
this.appname = appname;
|
||||
}
|
||||
|
||||
protected void addURL(URL url) {
|
||||
super.addURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getAppName() {
|
||||
return appname;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "helma.framework.core.AppClassLoader[" + appname + "]";
|
||||
}
|
||||
}
|
2202
src/main/java/helma/framework/core/Application.java
Normal file
2202
src/main/java/helma/framework/core/Application.java
Normal file
File diff suppressed because it is too large
Load diff
839
src/main/java/helma/framework/core/ApplicationBean.java
Normal file
839
src/main/java/helma/framework/core/ApplicationBean.java
Normal file
|
@ -0,0 +1,839 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.core;
|
||||
|
||||
import helma.objectmodel.INode;
|
||||
import helma.objectmodel.db.DbSource;
|
||||
import helma.util.CronJob;
|
||||
import helma.util.SystemMap;
|
||||
import helma.util.WrappedMap;
|
||||
import helma.framework.repository.*;
|
||||
import helma.framework.FutureResult;
|
||||
import helma.main.Server;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Application bean that provides a handle to the scripting environment to
|
||||
* application specific functionality.
|
||||
*/
|
||||
public class ApplicationBean implements Serializable {
|
||||
transient Application app;
|
||||
WrappedMap properties = null;
|
||||
|
||||
/**
|
||||
* Creates a new ApplicationBean object.
|
||||
*
|
||||
* @param app ...
|
||||
*/
|
||||
public ApplicationBean(Application app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the application cache.
|
||||
*/
|
||||
public void clearCache() {
|
||||
app.clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the app's event logger. This is a Log with the
|
||||
* category helma.[appname].event.
|
||||
*
|
||||
* @return the app logger.
|
||||
*/
|
||||
public Log getLogger() {
|
||||
return app.getEventLog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the app logger. This is a commons-logging Log with the
|
||||
* category <code>logname</code>.
|
||||
*
|
||||
* @return a logger for the given log name.
|
||||
*/
|
||||
public Log getLogger(String logname) {
|
||||
return LogFactory.getLog(logname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a INFO message to the app log.
|
||||
*
|
||||
* @param msg the log message
|
||||
*/
|
||||
public void log(Object msg) {
|
||||
getLogger().info(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a INFO message to the log defined by logname.
|
||||
*
|
||||
* @param logname the name (category) of the log
|
||||
* @param msg the log message
|
||||
*/
|
||||
public void log(String logname, Object msg) {
|
||||
getLogger(logname).info(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a DEBUG message to the app log if debug is set to true in
|
||||
* app.properties.
|
||||
*
|
||||
* @param msg the log message
|
||||
*/
|
||||
public void debug(Object msg) {
|
||||
if (app.debug()) {
|
||||
getLogger().debug(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a DEBUG message to the log defined by logname
|
||||
* if debug is set to true in app.properties.
|
||||
*
|
||||
* @param logname the name (category) of the log
|
||||
* @param msg the log message
|
||||
*/
|
||||
public void debug(String logname, Object msg) {
|
||||
if (app.debug()) {
|
||||
getLogger(logname).debug(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the app's repository list.
|
||||
*
|
||||
* @return the an array containing this app's repositories
|
||||
*/
|
||||
public Object[] getRepositories() {
|
||||
return app.getRepositories().toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a repository to the app's repository list. The .zip extension
|
||||
* is automatically added, if the original library path does not
|
||||
* point to an existing file or directory.
|
||||
*
|
||||
* @param obj the repository, relative or absolute path to the library.
|
||||
*/
|
||||
public synchronized void addRepository(Object obj) {
|
||||
Resource current = app.getCurrentCodeResource();
|
||||
Repository parent = current == null ?
|
||||
null : current.getRepository().getRootRepository();
|
||||
Repository rep;
|
||||
if (obj instanceof String) {
|
||||
String path = (String) obj;
|
||||
File file = findResource(null, path);
|
||||
if (file == null) {
|
||||
file = findResource(app.hopHome, path);
|
||||
if (file == null) {
|
||||
throw new RuntimeException("Repository not found: " + path);
|
||||
}
|
||||
}
|
||||
if (file.isDirectory()) {
|
||||
rep = new FileRepository(file);
|
||||
} else if (file.isFile()) {
|
||||
if (file.getName().endsWith(".zip")) {
|
||||
rep = new ZipRepository(file);
|
||||
} else {
|
||||
rep = new SingleFileRepository(file);
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Unsupported file type in addRepository: " + file);
|
||||
}
|
||||
} else if (obj instanceof Repository) {
|
||||
rep = (Repository) obj;
|
||||
} else {
|
||||
throw new RuntimeException("Invalid argument to addRepository: " + obj);
|
||||
}
|
||||
app.addRepository(rep, parent);
|
||||
try {
|
||||
app.typemgr.checkRepository(rep, true);
|
||||
} catch (IOException iox) {
|
||||
getLogger().error("Error checking repository " + rep, iox);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to resolve a repository path. Returns null if no file is found.
|
||||
* @param parent the parent file
|
||||
* @param path the repository path
|
||||
* @return an existing file, or null
|
||||
*/
|
||||
private File findResource(File parent, String path) {
|
||||
File file = new File(parent, path).getAbsoluteFile();
|
||||
if (!file.exists()) {
|
||||
// if file does not exist, try with .zip and .js extensions appended
|
||||
file = new File(parent, path + ".zip").getAbsoluteFile();
|
||||
if (!file.exists()) {
|
||||
file = new File(parent, path + ".js").getAbsoluteFile();
|
||||
}
|
||||
}
|
||||
return file.exists() ? file : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the app's classloader
|
||||
* @return the app's classloader
|
||||
*/
|
||||
public ClassLoader getClassLoader() {
|
||||
return app.getClassLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of currently active sessions
|
||||
* @return the current number of active sessions
|
||||
*/
|
||||
public int countSessions() {
|
||||
return app.countSessions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a session object for the specified session id
|
||||
* @param sessionID the session id
|
||||
* @return the session belonging to the session id, or null
|
||||
*/
|
||||
public SessionBean getSession(String sessionID) {
|
||||
if (sessionID == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Session session = app.getSession(sessionID.trim());
|
||||
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SessionBean(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new session with the given session id
|
||||
* @param sessionID the session id
|
||||
* @return the newly created session
|
||||
*/
|
||||
public SessionBean createSession(String sessionID) {
|
||||
if (sessionID == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Session session = app.createSession(sessionID.trim());
|
||||
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SessionBean(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all active sessions
|
||||
* @return an array of session beans
|
||||
*/
|
||||
public SessionBean[] getSessions() {
|
||||
Map sessions = app.getSessions();
|
||||
SessionBean[] array = new SessionBean[sessions.size()];
|
||||
int i = 0;
|
||||
|
||||
Iterator it = sessions.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
array[i++] = new SessionBean((Session) it.next());
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a user with the given name and password using the
|
||||
* database mapping of the User prototype
|
||||
* @param username the user name
|
||||
* @param password the user password
|
||||
* @return the newly registered user, or null if we failed
|
||||
*/
|
||||
public INode registerUser(String username, String password) {
|
||||
if ((username == null) || (password == null) || "".equals(username.trim()) ||
|
||||
"".equals(password.trim())) {
|
||||
return null;
|
||||
} else {
|
||||
return app.registerUser(username, password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user object with the given name
|
||||
* @param username the user name
|
||||
* @return the user object, or null
|
||||
*/
|
||||
public INode getUser(String username) {
|
||||
if ((username == null) || "".equals(username.trim())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return app.getUserNode(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of currently active registered users
|
||||
* @return an array of user nodes
|
||||
*/
|
||||
public INode[] getActiveUsers() {
|
||||
List activeUsers = app.getActiveUsers();
|
||||
|
||||
return (INode[]) activeUsers.toArray(new INode[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all registered users
|
||||
* @return an array containing all registered users
|
||||
*/
|
||||
public INode[] getRegisteredUsers() {
|
||||
List registeredUsers = app.getRegisteredUsers();
|
||||
|
||||
return (INode[]) registeredUsers.toArray(new INode[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all currently active sessions for a given user node
|
||||
* @param usernode the user node
|
||||
* @return an array of sessions for the given user
|
||||
*/
|
||||
public SessionBean[] getSessionsForUser(INode usernode) {
|
||||
if (usernode == null) {
|
||||
return new SessionBean[0];
|
||||
} else {
|
||||
return getSessionsForUser(usernode.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all currently active sessions for a given user name
|
||||
* @param username the user node
|
||||
* @return an array of sessions for the given user
|
||||
*/
|
||||
public SessionBean[] getSessionsForUser(String username) {
|
||||
if ((username == null) || "".equals(username.trim())) {
|
||||
return new SessionBean[0];
|
||||
}
|
||||
|
||||
List userSessions = app.getSessionsForUsername(username);
|
||||
|
||||
return (SessionBean[]) userSessions.toArray(new SessionBean[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a cron job that will run once a minute
|
||||
* @param functionName the function name
|
||||
*/
|
||||
public void addCronJob(String functionName) {
|
||||
CronJob job = new CronJob(functionName);
|
||||
|
||||
job.setFunction(functionName);
|
||||
app.customCronJobs.put(functionName, job);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a cron job that will run at the specified time intervals
|
||||
*
|
||||
* @param functionName the function name
|
||||
* @param year comma separated list of years, or *
|
||||
* @param month comma separated list of months, or *
|
||||
* @param day comma separated list of days, or *
|
||||
* @param weekday comma separated list of weekdays, or *
|
||||
* @param hour comma separated list of hours, or *
|
||||
* @param minute comma separated list of minutes, or *
|
||||
*/
|
||||
public void addCronJob(String functionName, String year, String month, String day,
|
||||
String weekday, String hour, String minute) {
|
||||
CronJob job = CronJob.newJob(functionName, year, month, day, weekday, hour, minute);
|
||||
|
||||
app.customCronJobs.put(functionName, job);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a previously registered cron job
|
||||
* @param functionName the function name
|
||||
*/
|
||||
public void removeCronJob(String functionName) {
|
||||
app.customCronJobs.remove(functionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an read-only map of the custom cron jobs registered with the app
|
||||
*
|
||||
* @return a map of cron jobs
|
||||
*/
|
||||
public Map getCronJobs() {
|
||||
return new WrappedMap(app.customCronJobs, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of elements in the NodeManager's cache
|
||||
*/
|
||||
public int getCacheusage() {
|
||||
return app.getCacheUsage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the app's data node used to share data between the app's evaluators
|
||||
*
|
||||
* @return the app.data node
|
||||
*/
|
||||
public INode getData() {
|
||||
return app.getCacheNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the app's modules map used to register application modules
|
||||
*
|
||||
* @return the module map
|
||||
*/
|
||||
public Map getModules() {
|
||||
return app.modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute path of the app dir. When using repositories this
|
||||
* equals the first file based repository.
|
||||
*
|
||||
* @return the app dir
|
||||
*/
|
||||
public String getDir() {
|
||||
return app.getAppDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the app name
|
||||
*/
|
||||
public String getName() {
|
||||
return app.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the application start time
|
||||
*/
|
||||
public Date getUpSince() {
|
||||
return new Date(app.starttime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of requests processed by this app
|
||||
*/
|
||||
public long getRequestCount() {
|
||||
return app.getRequestCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of XML-RPC requests processed
|
||||
*/
|
||||
public long getXmlrpcCount() {
|
||||
return app.getXmlrpcCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of errors encountered
|
||||
*/
|
||||
public long getErrorCount() {
|
||||
return app.getErrorCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the wrapped helma.framework.core.Application object
|
||||
*/
|
||||
public Application get__app__() {
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a wrapper around the app's properties
|
||||
*
|
||||
* @return a readonly wrapper around the application's app properties
|
||||
*/
|
||||
public Map getProperties() {
|
||||
if (properties == null) {
|
||||
properties = new WrappedMap(app.getProperties(), true);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a wrapper around the app's db properties
|
||||
*
|
||||
* @return a readonly wrapper around the application's db properties
|
||||
*/
|
||||
public Map getDbProperties() {
|
||||
return new WrappedMap(app.getDbProperties(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a DbSource object for a given name.
|
||||
*/
|
||||
public DbSource getDbSource(String name) {
|
||||
return app.getDbSource(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a wrapper around the app's apps.properties
|
||||
*
|
||||
* @return a readonly wrapper around the application's apps.properties
|
||||
*/
|
||||
public Map getAppsProperties() {
|
||||
Server server = Server.getServer();
|
||||
if (server == null)
|
||||
return new SystemMap();
|
||||
return new WrappedMap(server.getAppsProperties(app.getName()), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of this app's prototypes
|
||||
*
|
||||
* @return an array containing the app's prototypes
|
||||
*/
|
||||
public Prototype[] getPrototypes() {
|
||||
return (Prototype[]) app.getPrototypes().toArray(new Prototype[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a prototype by name.
|
||||
*
|
||||
* @param name the prototype name
|
||||
* @return the prototype
|
||||
*/
|
||||
public Prototype getPrototype(String name) {
|
||||
return app.getPrototypeByName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of currently available threads/request evaluators
|
||||
* @return the currently available threads
|
||||
*/
|
||||
public int getFreeThreads() {
|
||||
return app.countFreeEvaluators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of currently active request threads
|
||||
* @return the number of currently active threads
|
||||
*/
|
||||
public int getActiveThreads() {
|
||||
return app.countActiveEvaluators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximal thread number for this application
|
||||
* @return the maximal number of threads/request evaluators
|
||||
*/
|
||||
public int getMaxThreads() {
|
||||
return app.countEvaluators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximal thread number for this application
|
||||
* @param n the maximal number of threads/request evaluators
|
||||
*/
|
||||
public void setMaxThreads(int n) {
|
||||
// add one to the number to compensate for the internal scheduler.
|
||||
app.setNumberOfEvaluators(n + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a skin for a given object. The skin is found by determining the prototype
|
||||
* to use for the object, then looking up the skin for the prototype.
|
||||
*/
|
||||
public Skin getSkin(String protoname, String skinname, Object[] skinpath) {
|
||||
try {
|
||||
return app.getSkin(protoname, skinname, skinpath);
|
||||
} catch (Exception x) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of skin resources
|
||||
*
|
||||
* @return a map containing the skin resources
|
||||
*/
|
||||
public Map getSkinfiles() {
|
||||
Map skinz = new SystemMap();
|
||||
|
||||
for (Iterator it = app.getPrototypes().iterator(); it.hasNext();) {
|
||||
Prototype p = (Prototype) it.next();
|
||||
|
||||
Object skinmap = p.getScriptableSkinMap();
|
||||
skinz.put(p.getName(), skinmap);
|
||||
skinz.put(p.getLowerCaseName(), skinmap);
|
||||
}
|
||||
|
||||
return skinz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of skin resources including the app-specific skinpath
|
||||
*
|
||||
* @param skinpath an array of directory paths or HopObjects to search for skins
|
||||
* @return a map containing the skin resources
|
||||
*/
|
||||
public Map getSkinfilesInPath(Object[] skinpath) {
|
||||
Map skinz = new SystemMap();
|
||||
|
||||
for (Iterator it = app.getPrototypes().iterator(); it.hasNext();) {
|
||||
Prototype p = (Prototype) it.next();
|
||||
|
||||
Object skinmap = p.getScriptableSkinMap(skinpath);
|
||||
skinz.put(p.getName(), skinmap);
|
||||
skinz.put(p.getLowerCaseName(), skinmap);
|
||||
}
|
||||
|
||||
return skinz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the absolute application directory (appdir property
|
||||
* in apps.properties file)
|
||||
* @return the app directory as absolute path
|
||||
*/
|
||||
public String getAppDir() {
|
||||
return app.getAppDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the absolute server directory
|
||||
* @return the server directory as absolute path
|
||||
*/
|
||||
public String getServerDir() {
|
||||
File f = app.getServerDir();
|
||||
|
||||
if (f == null) {
|
||||
return app.getAppDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
return f.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the app's default charset/encoding.
|
||||
* @return the app's charset
|
||||
*/
|
||||
public String getCharset() {
|
||||
return app.getCharset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path for global macro resolution
|
||||
* @param path an array of global namespaces, or null
|
||||
*/
|
||||
public void setGlobalMacroPath(String[] path) {
|
||||
app.globalMacroPath = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path for global macro resolution
|
||||
* @return an array of global namespaces, or null
|
||||
*/
|
||||
public String[] getGlobalMacroPath() {
|
||||
return app.globalMacroPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a synchronous Helma invocation with a default timeout of 30 seconds.
|
||||
*
|
||||
* @param thisObject the object to invoke the function on,
|
||||
* or null for global invokation
|
||||
* @param function the function or function name to invoke
|
||||
* @param args an array of arguments
|
||||
* @return the value returned by the function
|
||||
* @throws Exception exception thrown by the function
|
||||
*/
|
||||
public Object invoke(Object thisObject, Object function, Object[] args)
|
||||
throws Exception {
|
||||
// default timeout of 30 seconds
|
||||
return invoke(thisObject, function, args, 30000L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a synchronous Helma invocation.
|
||||
*
|
||||
* @param thisObject the object to invoke the function on,
|
||||
* or null for global invokation
|
||||
* @param function the function or function name to invoke
|
||||
* @param args an array of arguments
|
||||
* @param timeout the timeout in milliseconds. After waiting
|
||||
* this long, we will try to interrupt the invocation
|
||||
* @return the value returned by the function
|
||||
* @throws Exception exception thrown by the function
|
||||
*/
|
||||
public Object invoke(Object thisObject, Object function,
|
||||
Object[] args, long timeout)
|
||||
throws Exception {
|
||||
RequestEvaluator reval = app.getEvaluator();
|
||||
try {
|
||||
return reval.invokeInternal(thisObject, function, args, timeout);
|
||||
} finally {
|
||||
app.releaseEvaluator(reval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an asynchronous Helma invocation. This method returns
|
||||
* immedately with an object that allows to track the result of the
|
||||
* function invocation with the following properties:
|
||||
*
|
||||
* <ul>
|
||||
* <li>running - true while the function is running, false afterwards</li>
|
||||
* <li>result - the value returned by the function, if any</li>
|
||||
* <li>exception - the exception thrown by the function, if any</li>
|
||||
* <li>waitForResult() - wait indefinitely until invocation terminates
|
||||
* and return the result</li>
|
||||
* <li>waitForResult(t) - wait for the specified number of milliseconds
|
||||
* for invocation to terminate and return the result</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param thisObject the object to invoke the function on,
|
||||
* or null for global invokation
|
||||
* @param function the function or function name to invoke
|
||||
* @param args an array of arguments
|
||||
* this long, we will try to interrupt the invocation
|
||||
* @return an object with the properties described above
|
||||
*/
|
||||
public FutureResult invokeAsync(Object thisObject,
|
||||
final Object function,
|
||||
final Object[] args) {
|
||||
// default timeout of 15 minutes
|
||||
return new AsyncInvoker(thisObject, function, args, 60000L * 15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an asynchronous Helma invocation. This method returns
|
||||
* immedately with an object that allows to track the result of the
|
||||
* function invocation with the following methods and properties:
|
||||
*
|
||||
* <ul>
|
||||
* <li>running - true while the function is running, false afterwards</li>
|
||||
* <li>result - the value returned by the function, if any</li>
|
||||
* <li>exception - the exception thrown by the function, if any</li>
|
||||
* <li>waitForResult() - wait indefinitely until invocation terminates
|
||||
* and return the result</li>
|
||||
* <li>waitForResult(t) - wait for the specified number of milliseconds
|
||||
* for invocation to terminate and return the result</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param thisObject the object to invoke the function on,
|
||||
* or null for global invokation
|
||||
* @param function the function or function name to invoke
|
||||
* @param args an array of arguments
|
||||
* @param timeout the timeout in milliseconds. After waiting
|
||||
* this long, we will try to interrupt the invocation
|
||||
* @return an object with the properties described above
|
||||
*/
|
||||
public FutureResult invokeAsync(Object thisObject, Object function,
|
||||
Object[] args, long timeout) {
|
||||
return new AsyncInvoker(thisObject, function, args, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string presentation of this AppBean
|
||||
* @return string description of this app bean object
|
||||
*/
|
||||
public String toString() {
|
||||
return "[Application " + app.getName() + "]";
|
||||
}
|
||||
|
||||
class AsyncInvoker extends Thread implements FutureResult {
|
||||
|
||||
private Object thisObject;
|
||||
private Object function;
|
||||
private Object[] args;
|
||||
private long timeout;
|
||||
|
||||
private Object result;
|
||||
private Exception exception;
|
||||
private boolean running = true;
|
||||
|
||||
private AsyncInvoker(Object thisObj, Object func, Object[] args, long timeout) {
|
||||
thisObject = thisObj;
|
||||
function = func;
|
||||
this.args = args;
|
||||
this.timeout = timeout;
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
RequestEvaluator reval = null;
|
||||
try {
|
||||
reval = app.getEvaluator();
|
||||
setResult(reval.invokeInternal(thisObject, function, args, timeout));
|
||||
} catch (Exception x) {
|
||||
setException(x);
|
||||
} finally {
|
||||
running = false;
|
||||
app.releaseEvaluator(reval);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean getRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
private synchronized void setResult(Object obj) {
|
||||
result = obj;
|
||||
running = false;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
public synchronized Object getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public synchronized Object waitForResult() throws InterruptedException {
|
||||
if (!running)
|
||||
return result;
|
||||
wait();
|
||||
return result;
|
||||
}
|
||||
|
||||
public synchronized Object waitForResult(long timeout)
|
||||
throws InterruptedException {
|
||||
if (!running)
|
||||
return result;
|
||||
wait(timeout);
|
||||
return result;
|
||||
}
|
||||
|
||||
private synchronized void setException(Exception x) {
|
||||
exception = x;
|
||||
running = false;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
public synchronized Exception getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new StringBuffer("AsyncInvokeThread{running: ").append(running)
|
||||
.append(", result: ").append(result).append(", exception: ")
|
||||
.append(exception).append("}").toString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
647
src/main/java/helma/framework/core/Prototype.java
Normal file
647
src/main/java/helma/framework/core/Prototype.java
Normal file
|
@ -0,0 +1,647 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.core;
|
||||
|
||||
import helma.objectmodel.db.DbMapping;
|
||||
import helma.util.ResourceProperties;
|
||||
import helma.util.WrappedMap;
|
||||
import helma.framework.repository.Resource;
|
||||
import helma.framework.repository.Repository;
|
||||
import helma.framework.repository.ResourceTracker;
|
||||
import helma.framework.repository.FileResource;
|
||||
import helma.scripting.ScriptingEngine;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The Prototype class represents Script prototypes/type defined in a Helma
|
||||
* application. This class manages a prototypes templates, functions and actions
|
||||
* as well as optional information about the mapping of this type to a
|
||||
* relational database table.
|
||||
*/
|
||||
public final class Prototype {
|
||||
// the app this prototype belongs to
|
||||
Application app;
|
||||
|
||||
// this prototype's name in natural and lower case
|
||||
String name;
|
||||
String lowerCaseName;
|
||||
|
||||
// this prototype's resources
|
||||
Resource[] resources;
|
||||
|
||||
// tells us the checksum of the repositories at the time we last updated them
|
||||
long lastChecksum = -1;
|
||||
|
||||
// the time at which any of the prototype's files were found updated the last time
|
||||
volatile long lastCodeUpdate = 0;
|
||||
|
||||
TreeSet code;
|
||||
TreeSet skins;
|
||||
|
||||
HashMap trackers;
|
||||
|
||||
TreeSet repositories;
|
||||
|
||||
// a map of this prototype's skins as raw strings
|
||||
// used for exposing skins to application (script) code (via app.skinfiles).
|
||||
SkinMap skinMap;
|
||||
|
||||
DbMapping dbmap;
|
||||
|
||||
private Prototype parent;
|
||||
|
||||
ResourceProperties props;
|
||||
|
||||
/**
|
||||
* Creates a new Prototype object.
|
||||
*
|
||||
* @param name the prototype's name
|
||||
* @param repository the first prototype's repository
|
||||
* @param app the application this prototype is a part of
|
||||
* @param typeProps custom type mapping properties
|
||||
*/
|
||||
public Prototype(String name, Repository repository, Application app, Map typeProps) {
|
||||
// app.logEvent ("Constructing Prototype "+app.getName()+"/"+name);
|
||||
this.app = app;
|
||||
this.name = name;
|
||||
repositories = new TreeSet(app.getResourceComparator());
|
||||
if (repository != null) {
|
||||
repositories.add(repository);
|
||||
}
|
||||
lowerCaseName = name.toLowerCase();
|
||||
|
||||
// Create and register type properties file
|
||||
props = new ResourceProperties(app);
|
||||
if (typeProps != null) {
|
||||
props.putAll(typeProps);
|
||||
} else if (repository != null) {
|
||||
props.addResource(repository.getResource("type.properties"));
|
||||
props.addResource(repository.getResource(name + ".properties"));
|
||||
}
|
||||
|
||||
dbmap = new DbMapping(app, name, props);
|
||||
// we don't need to put the DbMapping into proto.updatables, because
|
||||
// dbmappings are checked separately in TypeManager.checkFiles() for
|
||||
// each request
|
||||
|
||||
code = new TreeSet(app.getResourceComparator());
|
||||
skins = new TreeSet(app.getResourceComparator());
|
||||
|
||||
trackers = new HashMap();
|
||||
|
||||
skinMap = new SkinMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the application this prototype is a part of
|
||||
*/
|
||||
public Application getApplication() {
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an repository to the list of repositories
|
||||
* @param repository repository to add
|
||||
* @param update indicates whether to immediately update the prototype with the new code
|
||||
* @throws IOException if reading/updating from the repository fails
|
||||
*/
|
||||
public void addRepository(Repository repository, boolean update) throws IOException {
|
||||
if (!repositories.contains(repository)) {
|
||||
repositories.add(repository);
|
||||
props.addResource(repository.getResource("type.properties"));
|
||||
props.addResource(repository.getResource(name + ".properties"));
|
||||
if (update) {
|
||||
RequestEvaluator eval = app.getCurrentRequestEvaluator();
|
||||
ScriptingEngine engine = eval == null ? null : eval.scriptingEngine;
|
||||
Iterator it = repository.getAllResources().iterator();
|
||||
while (it.hasNext()) {
|
||||
checkResource((Resource) it.next(), engine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a prototype for new or updated resources. After this has been
|
||||
* called the code and skins collections of this prototype should be
|
||||
* up-to-date and the lastCodeUpdate be set if there has been any changes.
|
||||
*/
|
||||
public synchronized void checkForUpdates() {
|
||||
boolean updatedResources = false;
|
||||
|
||||
// check if any resource the prototype knows about has changed or gone
|
||||
for (Iterator i = trackers.values().iterator(); i.hasNext();) {
|
||||
ResourceTracker tracker = (ResourceTracker) i.next();
|
||||
|
||||
try {
|
||||
if (tracker.hasChanged()) {
|
||||
updatedResources = true;
|
||||
// let tracker know we've seen the update
|
||||
tracker.markClean();
|
||||
// if resource has gone remove it
|
||||
if (!tracker.getResource().exists()) {
|
||||
i.remove();
|
||||
String name = tracker.getResource().getName();
|
||||
if (name.endsWith(TypeManager.skinExtension)) {
|
||||
skins.remove(tracker.getResource());
|
||||
} else {
|
||||
code.remove(tracker.getResource());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException iox) {
|
||||
iox.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// next we check if resources have been created or removed
|
||||
Resource[] resources = getResources();
|
||||
|
||||
for (int i = 0; i < resources.length; i++) {
|
||||
updatedResources |= checkResource(resources[i], null);
|
||||
}
|
||||
|
||||
if (updatedResources) {
|
||||
// mark prototype as dirty and the code as updated
|
||||
lastCodeUpdate = System.currentTimeMillis();
|
||||
app.typemgr.setLastCodeUpdate(lastCodeUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkResource(Resource res, ScriptingEngine engine) {
|
||||
String name = res.getName();
|
||||
boolean updated = false;
|
||||
if (!trackers.containsKey(name)) {
|
||||
if (name.endsWith(TypeManager.templateExtension) ||
|
||||
name.endsWith(TypeManager.scriptExtension) ||
|
||||
name.endsWith(TypeManager.actionExtension) ||
|
||||
name.endsWith(TypeManager.skinExtension)) {
|
||||
updated = true;
|
||||
if (name.endsWith(TypeManager.skinExtension)) {
|
||||
skins.add(res);
|
||||
} else {
|
||||
if (engine != null) {
|
||||
engine.injectCodeResource(lowerCaseName, res);
|
||||
}
|
||||
code.add(res);
|
||||
}
|
||||
trackers.put(res.getName(), new ResourceTracker(res));
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of resources in this prototype's repositories. Used
|
||||
* by checkForUpdates() to see whether there is anything new.
|
||||
*/
|
||||
public Resource[] getResources() {
|
||||
long checksum = getRepositoryChecksum();
|
||||
// reload resources if the repositories checksum has changed
|
||||
if (checksum != lastChecksum) {
|
||||
ArrayList list = new ArrayList();
|
||||
Iterator iterator = repositories.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
try {
|
||||
list.addAll(((Repository) iterator.next()).getAllResources());
|
||||
} catch (IOException iox) {
|
||||
iox.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
resources = (Resource[]) list.toArray(new Resource[list.size()]);
|
||||
lastChecksum = checksum;
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of repositories containing code for this prototype.
|
||||
*/
|
||||
public Repository[] getRepositories() {
|
||||
return (Repository[]) repositories.toArray(new Repository[repositories.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a checksum over this prototype's repositories. This tells us
|
||||
* if any resources were added or removed.
|
||||
*/
|
||||
long getRepositoryChecksum() {
|
||||
long checksum = 0;
|
||||
Iterator iterator = repositories.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
try {
|
||||
checksum += ((Repository) iterator.next()).getChecksum();
|
||||
} catch (IOException iox) {
|
||||
iox.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent prototype of this prototype, i.e. the prototype this
|
||||
* prototype inherits from.
|
||||
*/
|
||||
public void setParentPrototype(Prototype parent) {
|
||||
// this is not allowed for the hopobject and global prototypes
|
||||
if ("hopobject".equals(lowerCaseName) || "global".equals(lowerCaseName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent prototype from which we inherit, or null
|
||||
* if we are top of the line.
|
||||
*/
|
||||
public Prototype getParentPrototype() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given prototype is within this prototype's parent chain.
|
||||
*/
|
||||
public final boolean isInstanceOf(String pname) {
|
||||
if (name.equalsIgnoreCase(pname)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (parent != null) {
|
||||
return parent.isInstanceOf(pname);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an object as handler for all our parent prototypes, but only if
|
||||
* a handler by that prototype name isn't registered yet. This is used to
|
||||
* implement direct over indirect prototype precedence and child over parent
|
||||
* precedence.
|
||||
*/
|
||||
public final void registerParents(Map handlers, Object obj) {
|
||||
|
||||
Prototype p = parent;
|
||||
|
||||
while ((p != null) && !"hopobject".equals(p.getLowerCaseName())) {
|
||||
Object old = handlers.put(p.name, obj);
|
||||
// if an object was already registered by this name, put it back in again.
|
||||
if (old != null) {
|
||||
handlers.put(p.name, old);
|
||||
}
|
||||
// same with lower case name
|
||||
old = handlers.put(p.lowerCaseName, obj);
|
||||
if (old != null) {
|
||||
handlers.put(p.lowerCaseName, old);
|
||||
}
|
||||
|
||||
p = p.parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DbMapping associated with this prototype
|
||||
*/
|
||||
public DbMapping getDbMapping() {
|
||||
return dbmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a skin for this prototype. This only works for skins
|
||||
* residing in the prototype directory, not for skins files in
|
||||
* other locations or database stored skins. If parentName and
|
||||
* subName are defined, the skin may be a subskin of another skin.
|
||||
*/
|
||||
public Skin getSkin(Prototype proto, String skinname, String subskin, Object[] skinpath)
|
||||
throws IOException {
|
||||
Resource res = skinMap.getResource(skinname);
|
||||
while (res != null) {
|
||||
Skin skin = Skin.getSkin(res, app);
|
||||
if (subskin == null && skin.hasMainskin()) {
|
||||
return skin;
|
||||
} else if (subskin != null && skin.hasSubskin(subskin)) {
|
||||
return skin.getSubskin(subskin);
|
||||
}
|
||||
String baseskin = skin.getExtends();
|
||||
if (baseskin != null && !baseskin.equalsIgnoreCase(skinname)) {
|
||||
// we need to call SkinManager.getSkin() to fetch overwritten
|
||||
// base skins from skinpath
|
||||
return app.skinmgr.getSkin(proto, baseskin, subskin, skinpath);
|
||||
}
|
||||
res = res.getOverloadedResource();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this prototype's name
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this prototype's name in lower case letters
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getLowerCaseName() {
|
||||
return lowerCaseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last time any script has been re-read for this prototype.
|
||||
*/
|
||||
public long lastCodeUpdate() {
|
||||
return lastCodeUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that some script in this prototype has been
|
||||
* re-read from disk and needs to be re-compiled by
|
||||
* the evaluators.
|
||||
*/
|
||||
public void markUpdated() {
|
||||
lastCodeUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom type properties for this prototype and update the database mapping.
|
||||
* @param map the custom type mapping properties.
|
||||
*/
|
||||
public void setTypeProperties(Map map) {
|
||||
props.clear();
|
||||
props.putAll(map);
|
||||
dbmap.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prototype's aggregated type.properties
|
||||
*
|
||||
* @return type.properties
|
||||
*/
|
||||
public ResourceProperties getTypeProperties() {
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an iterator over this prototype's code resoruces. Synchronized
|
||||
* to not return a collection in a transient state where it is just being
|
||||
* updated by the type manager.
|
||||
*
|
||||
* @return an iterator of this prototype's code resources
|
||||
*/
|
||||
public synchronized Iterator getCodeResources() {
|
||||
// we copy over to a new list, because the underlying set may grow
|
||||
// during compilation through use of app.addRepository()
|
||||
return new ArrayList(code).iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an iterator over this prototype's skin resoruces. Synchronized
|
||||
* to not return a collection in a transient state where it is just being
|
||||
* updated by the type manager.
|
||||
*
|
||||
* @return an iterator over this prototype's skin resources
|
||||
*/
|
||||
public Iterator getSkinResources() {
|
||||
return skins.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representing this prototype.
|
||||
*/
|
||||
public String toString() {
|
||||
return "[Prototype " + app.getName() + "/" + name + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map containing this prototype's skins as strings
|
||||
*
|
||||
* @return a scriptable skin map
|
||||
*/
|
||||
public Map getScriptableSkinMap() {
|
||||
return new ScriptableSkinMap(new SkinMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map containing this prototype's skins as strings, overloaded by the
|
||||
* skins found in the given skinpath.
|
||||
*
|
||||
* @return a scriptable skin map
|
||||
*/
|
||||
public Map getScriptableSkinMap(Object[] skinpath) {
|
||||
return new ScriptableSkinMap(new SkinMap(skinpath));
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of this prototype's skins that acts as a native JavaScript object in
|
||||
* rhino and returns the skins as strings. This is used to expose the skins
|
||||
* to JavaScript in app.skinfiles[prototypeName][skinName].
|
||||
*/
|
||||
class ScriptableSkinMap extends WrappedMap {
|
||||
|
||||
public ScriptableSkinMap(Map wrapped) {
|
||||
super(wrapped);
|
||||
}
|
||||
|
||||
public Object get(Object key) {
|
||||
Resource res = (Resource) super.get(key);
|
||||
|
||||
if (res == null || !res.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return res.getContent();
|
||||
} catch (IOException iox) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Map that dynamically expands to all skins in this prototype.
|
||||
*/
|
||||
class SkinMap extends HashMap {
|
||||
volatile long lastSkinmapLoad = -1;
|
||||
Object[] skinpath;
|
||||
|
||||
SkinMap() {
|
||||
skinpath = null;
|
||||
}
|
||||
|
||||
SkinMap(Object[] path) {
|
||||
skinpath = path;
|
||||
}
|
||||
|
||||
public boolean containsKey(Object key) {
|
||||
checkForUpdates();
|
||||
return super.containsKey(key);
|
||||
}
|
||||
|
||||
public boolean containsValue(Object value) {
|
||||
checkForUpdates();
|
||||
return super.containsValue(value);
|
||||
}
|
||||
|
||||
public Set entrySet() {
|
||||
checkForUpdates();
|
||||
return super.entrySet();
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
checkForUpdates();
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
public Skin getSkin(Object key) throws IOException {
|
||||
Resource res = (Resource) get(key);
|
||||
|
||||
if (res != null) {
|
||||
return Skin.getSkin(res, app);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Resource getResource(Object key) {
|
||||
return (Resource) get(key);
|
||||
}
|
||||
|
||||
public Object get(Object key) {
|
||||
checkForUpdates();
|
||||
return super.get(key);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
checkForUpdates();
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
checkForUpdates();
|
||||
return super.isEmpty();
|
||||
}
|
||||
|
||||
public Set keySet() {
|
||||
checkForUpdates();
|
||||
return super.keySet();
|
||||
}
|
||||
|
||||
public Object put(Object key, Object value) {
|
||||
// checkForUpdates ();
|
||||
return super.put(key, value);
|
||||
}
|
||||
|
||||
public void putAll(Map t) {
|
||||
// checkForUpdates ();
|
||||
super.putAll(t);
|
||||
}
|
||||
|
||||
public Object remove(Object key) {
|
||||
checkForUpdates();
|
||||
return super.remove(key);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
checkForUpdates();
|
||||
return super.size();
|
||||
}
|
||||
|
||||
public Collection values() {
|
||||
checkForUpdates();
|
||||
return super.values();
|
||||
}
|
||||
|
||||
private void checkForUpdates() {
|
||||
if (lastCodeUpdate > lastSkinmapLoad) {
|
||||
if (lastCodeUpdate == 0) {
|
||||
// if prototype resources haven't been checked yet, check them now
|
||||
Prototype.this.checkForUpdates();
|
||||
}
|
||||
loadSkins();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void loadSkins() {
|
||||
if (lastCodeUpdate == lastSkinmapLoad) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.clear();
|
||||
|
||||
// load Skins
|
||||
for (Iterator i = skins.iterator(); i.hasNext();) {
|
||||
Resource res = (Resource) i.next();
|
||||
Resource prev = (Resource) super.put(res.getBaseName(), res);
|
||||
res.setOverloadedResource(prev);
|
||||
}
|
||||
|
||||
// if skinpath is not null, overload/add skins from there
|
||||
if (skinpath != null) {
|
||||
for (int i = skinpath.length - 1; i >= 0; i--) {
|
||||
if ((skinpath[i] != null) && skinpath[i] instanceof String) {
|
||||
loadSkinFiles((String) skinpath[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastSkinmapLoad = lastCodeUpdate;
|
||||
}
|
||||
|
||||
private void loadSkinFiles(String skinDir) {
|
||||
File dir = new File(skinDir, Prototype.this.getName());
|
||||
// if directory does not exist use lower case property name
|
||||
if (!dir.isDirectory()) {
|
||||
dir = new File(skinDir, Prototype.this.getLowerCaseName());
|
||||
if (!dir.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String[] skinNames = dir.list(app.skinmgr);
|
||||
|
||||
if ((skinNames == null) || (skinNames.length == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < skinNames.length; i++) {
|
||||
String name = skinNames[i].substring(0, skinNames[i].length() - 5);
|
||||
File file = new File(dir, skinNames[i]);
|
||||
|
||||
Resource res = new FileResource(file);
|
||||
Resource prev = (Resource) super.put(name, res);
|
||||
res.setOverloadedResource(prev);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[SkinMap " + name + "]";
|
||||
}
|
||||
}
|
||||
}
|
1148
src/main/java/helma/framework/core/RequestEvaluator.java
Normal file
1148
src/main/java/helma/framework/core/RequestEvaluator.java
Normal file
File diff suppressed because it is too large
Load diff
175
src/main/java/helma/framework/core/RequestPath.java
Normal file
175
src/main/java/helma/framework/core/RequestPath.java
Normal file
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.core;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import helma.util.UrlEncoded;
|
||||
|
||||
/**
|
||||
* Represents a URI request path that has been resolved to an object path.
|
||||
* Offers methods to access objects in the path by index and prototype names,
|
||||
* and to render the path as URI again.
|
||||
*/
|
||||
public class RequestPath {
|
||||
|
||||
Application app;
|
||||
|
||||
List objects;
|
||||
List ids;
|
||||
|
||||
Map primaryProtos;
|
||||
Map secondaryProtos;
|
||||
|
||||
/**
|
||||
* Creates a new RequestPath object.
|
||||
*
|
||||
* @param app the application we're running in
|
||||
*/
|
||||
public RequestPath(Application app) {
|
||||
this.app = app;
|
||||
objects = new ArrayList();
|
||||
ids = new ArrayList();
|
||||
primaryProtos = new HashMap();
|
||||
secondaryProtos = new HashMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item to the end of the path.
|
||||
*
|
||||
* @param id the item id representing the path in the URL
|
||||
* @param obj the object to which the id resolves
|
||||
*/
|
||||
public void add(String id, Object obj) {
|
||||
ids.add(id);
|
||||
objects.add(obj);
|
||||
|
||||
Prototype proto = app.getPrototype(obj);
|
||||
|
||||
if (proto != null) {
|
||||
primaryProtos.put(proto.getName(), obj);
|
||||
primaryProtos.put(proto.getLowerCaseName(), obj);
|
||||
proto.registerParents(secondaryProtos, obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of objects in the request path.
|
||||
*/
|
||||
public int size() {
|
||||
return objects.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an object in the path by index.
|
||||
*
|
||||
* @param idx the index of the object in the request path
|
||||
*/
|
||||
public Object get(int idx) {
|
||||
if (idx < 0 || idx >= objects.size()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return objects.get(idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an object in the path by prototype name.
|
||||
*
|
||||
* @param typeName the prototype name of the object in the request path
|
||||
*/
|
||||
public Object getByPrototypeName(String typeName) {
|
||||
// search primary prototypes first
|
||||
Object obj = primaryProtos.get(typeName);
|
||||
|
||||
if (obj != null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
// if that fails, consult secondary prototype map
|
||||
return secondaryProtos.get(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of this path usable for links.
|
||||
*/
|
||||
public String href(String action) throws UnsupportedEncodingException {
|
||||
StringBuffer buffer = new StringBuffer(app.getBaseURI());
|
||||
|
||||
int start = 1;
|
||||
String hrefRootPrototype = app.getHrefRootPrototype();
|
||||
|
||||
if (hrefRootPrototype != null) {
|
||||
Object rootObject = getByPrototypeName(hrefRootPrototype);
|
||||
|
||||
if (rootObject != null) {
|
||||
start = objects.indexOf(rootObject) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=start; i<ids.size(); i++) {
|
||||
buffer.append(UrlEncoded.encode(ids.get(i).toString(), app.charset));
|
||||
buffer.append("/");
|
||||
}
|
||||
|
||||
if (action != null) {
|
||||
buffer.append(UrlEncoded.encode(action, app.charset));
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given object is contained in the request path.
|
||||
* Itreturns the zero-based index position, or -1 if it isn't contained.
|
||||
*
|
||||
* @param obj the element to check
|
||||
* @return the index of the element, or -1 if it isn't contained
|
||||
* @deprecated use {@link #indexOf(Object)} instead.
|
||||
*/
|
||||
public int contains(Object obj) {
|
||||
return objects.indexOf(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given object is contained in the request path.
|
||||
* Itreturns the zero-based index position, or -1 if it isn't contained.
|
||||
*
|
||||
* @param obj the element to check
|
||||
* @return the index of the element, or -1 if it isn't contained
|
||||
*/
|
||||
public int indexOf(Object obj) {
|
||||
return objects.indexOf(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the Request Path
|
||||
*/
|
||||
public String toString() {
|
||||
// If there's just one element we're on the root object.
|
||||
if (ids.size() <= 1)
|
||||
return "/";
|
||||
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
for (int i=1; i<ids.size(); i++) {
|
||||
buffer.append('/');
|
||||
buffer.append(ids.get(i));
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
414
src/main/java/helma/framework/core/Session.java
Normal file
414
src/main/java/helma/framework/core/Session.java
Normal file
|
@ -0,0 +1,414 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.core;
|
||||
|
||||
import helma.objectmodel.*;
|
||||
import helma.objectmodel.db.*;
|
||||
import helma.framework.ResponseTrans;
|
||||
import helma.framework.UploadStatus;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* This represents a session currently using the Hop application.
|
||||
* This includes anybody who happens to request a page from this application.
|
||||
* Depending on whether the user is logged in or not, the session holds a
|
||||
* persistent user node.
|
||||
*/
|
||||
public class Session implements Serializable {
|
||||
|
||||
static final long serialVersionUID = -6149094040363012913L;
|
||||
|
||||
transient protected Application app;
|
||||
protected String sessionId;
|
||||
|
||||
// the unique id (login name) for the user, if logged in
|
||||
protected String uid;
|
||||
|
||||
// the handle to this user's persistent db node, if logged in
|
||||
protected NodeHandle userHandle;
|
||||
|
||||
// the transient cache node that is exposed to javascript
|
||||
// this stays the same across logins and logouts.
|
||||
protected INode cacheNode;
|
||||
|
||||
// timestamps for creation, last request, last modification
|
||||
protected long onSince;
|
||||
protected long lastTouched;
|
||||
protected long lastModified;
|
||||
protected long cacheLastModified;
|
||||
|
||||
// used to remember messages to the user between requests, mainly between redirects.
|
||||
protected String message;
|
||||
protected StringBuffer debugBuffer;
|
||||
|
||||
protected HashMap uploads = null;
|
||||
|
||||
protected transient boolean modifiedInRequest = false;
|
||||
protected transient boolean registered = false;
|
||||
|
||||
/**
|
||||
* Creates a new Session object.
|
||||
*
|
||||
* @param sessionId ...
|
||||
* @param app ...
|
||||
*/
|
||||
public Session(String sessionId, Application app) {
|
||||
this.sessionId = sessionId;
|
||||
this.app = app;
|
||||
this.uid = null;
|
||||
this.userHandle = null;
|
||||
cacheNode = new TransientNode(app, "session");
|
||||
cacheLastModified = cacheNode.lastModified();
|
||||
// HACK - decrease timestamp by 1 to notice modifications
|
||||
// taking place immediately after object creation
|
||||
onSince = System.currentTimeMillis() - 1;
|
||||
lastTouched = lastModified = onSince;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the given user node to this session.
|
||||
*/
|
||||
public void login(INode usernode) {
|
||||
if (usernode == null) {
|
||||
userHandle = null;
|
||||
uid = null;
|
||||
} else {
|
||||
userHandle = ((Node) usernode).getHandle();
|
||||
uid = usernode.getElementName();
|
||||
}
|
||||
|
||||
lastModified = System.currentTimeMillis();
|
||||
modifiedInRequest = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try logging in this session given the userName and password.
|
||||
*
|
||||
* @param userName the user name
|
||||
* @param password the password
|
||||
* @return true if session was logged in.
|
||||
*/
|
||||
public boolean login(String userName, String password) {
|
||||
if (app.loginSession(userName, password, this)) {
|
||||
lastModified = System.currentTimeMillis();
|
||||
modifiedInRequest = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this sessions's user node.
|
||||
*/
|
||||
public void logout() {
|
||||
if (userHandle != null) {
|
||||
try {
|
||||
// Invoke User.onLogout() iff this is a transactor request with a request
|
||||
// evaluator already associated (i.e., if this is called from an app/script).
|
||||
// Otherwise, we assume being called from the scheduler thread, which takes
|
||||
// care of calling User.onLogout().
|
||||
RequestEvaluator reval = app.getCurrentRequestEvaluator();
|
||||
if (reval != null) {
|
||||
Node userNode = userHandle.getNode(app.nmgr.safe);
|
||||
if (userNode != null)
|
||||
reval.invokeDirectFunction(userNode, "onLogout", new Object[] {sessionId});
|
||||
}
|
||||
} catch (Exception x) {
|
||||
// errors should already be logged by request evaluator, but you never know
|
||||
app.logError("Error in onLogout", x);
|
||||
} finally {
|
||||
// do log out
|
||||
userHandle = null;
|
||||
uid = null;
|
||||
lastModified = System.currentTimeMillis();
|
||||
modifiedInRequest = true;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this session is currently associated with a user object.
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public boolean isLoggedIn() {
|
||||
return userHandle != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user handle for this session.
|
||||
*/
|
||||
public void setUserHandle(NodeHandle handle) {
|
||||
this.userHandle = handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Node handle for the current user, if logged in.
|
||||
*/
|
||||
public NodeHandle getUserHandle() {
|
||||
return userHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user Node from this Application's NodeManager.
|
||||
*/
|
||||
public INode getUserNode() {
|
||||
if (userHandle != null) {
|
||||
return userHandle.getNode(app.getWrappedNodeManager());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cache node for this session.
|
||||
*/
|
||||
public void setCacheNode(INode node) {
|
||||
if (node == null) {
|
||||
throw new NullPointerException("cache node is null");
|
||||
}
|
||||
this.cacheNode = node;
|
||||
this.cacheLastModified = cacheNode.lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the transient cache node.
|
||||
*/
|
||||
public INode getCacheNode() {
|
||||
return cacheNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this session's application
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Application getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this session's application
|
||||
*
|
||||
* @param app ...
|
||||
*/
|
||||
public void setApp(Application app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this session's id.
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the beginning of a request to let the session know it's
|
||||
* being used.
|
||||
*/
|
||||
public void touch() {
|
||||
lastTouched = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a request has been handled.
|
||||
*
|
||||
* @param reval the request evaluator that handled the request
|
||||
*/
|
||||
public void commit(RequestEvaluator reval, SessionManager smgr) {
|
||||
if (modifiedInRequest || cacheLastModified != cacheNode.lastModified()) {
|
||||
if (!registered) {
|
||||
smgr.registerSession(this);
|
||||
registered = true;
|
||||
}
|
||||
modifiedInRequest = false;
|
||||
cacheLastModified = cacheNode.lastModified();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time this session was last touched.
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public long lastTouched() {
|
||||
return lastTouched;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time this session was last modified, meaning the last time
|
||||
* its user status changed or its cache node was modified.
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public long lastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last modified time on this session.
|
||||
*
|
||||
* @param l the timestamp
|
||||
*/
|
||||
public void setLastModified(long l) {
|
||||
lastModified = l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the time this session was created.
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public long onSince() {
|
||||
return onSince;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation for this session.
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String toString() {
|
||||
if (uid != null) {
|
||||
return "[Session for user " + uid + "]";
|
||||
} else {
|
||||
return "[Anonymous Session]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the persistent user id of a registered user.
|
||||
* This is usually the user name, or null if the user is not logged in.
|
||||
*/
|
||||
public String getUID() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the persistent user id of a registered user.
|
||||
* @param uid the user name, or null if the user is not logged in.
|
||||
*/
|
||||
public void setUID(String uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the user and debug messages over from a previous response.
|
||||
* This is used for redirects, where messages can't be displayed immediately.
|
||||
* @param res the response to set the messages on
|
||||
*/
|
||||
public synchronized void recoverResponseMessages(ResponseTrans res) {
|
||||
if (message != null || debugBuffer != null) {
|
||||
res.setMessage(message);
|
||||
res.setDebugBuffer(debugBuffer);
|
||||
message = null;
|
||||
debugBuffer = null;
|
||||
modifiedInRequest = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remember the response's user and debug messages for a later response.
|
||||
* This is used for redirects, where messages can't be displayed immediately.
|
||||
* @param res the response to retrieve the messages from
|
||||
*/
|
||||
public synchronized void storeResponseMessages(ResponseTrans res) {
|
||||
message = res.getMessage();
|
||||
debugBuffer = res.getDebugBuffer();
|
||||
if (message != null || debugBuffer != null) {
|
||||
modifiedInRequest = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the message that is to be displayed upon the next
|
||||
* request within this session.
|
||||
*
|
||||
* @return the message, or null if none was set.
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a message to be displayed to this session's user. This
|
||||
* can be used to save a message over to the next request when
|
||||
* the current request can't be used to display a user visible
|
||||
* message.
|
||||
*
|
||||
* @param msg the message
|
||||
*/
|
||||
public void setMessage(String msg) {
|
||||
message = msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the debug buffer that is to be displayed upon the next
|
||||
* request within this session.
|
||||
*
|
||||
* @return the debug buffer, or null if none was set.
|
||||
*/
|
||||
public StringBuffer getDebugBuffer() {
|
||||
return debugBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the debug buffer to be displayed to this session's user. This
|
||||
* can be used to save the debug buffer over to the next request when
|
||||
* the current request can't be used to display a user visible
|
||||
* message.
|
||||
*
|
||||
* @param buffer the buffer
|
||||
*/
|
||||
public void setDebugBuffer(StringBuffer buffer) {
|
||||
debugBuffer = buffer;
|
||||
}
|
||||
|
||||
protected UploadStatus createUpload(String uploadId) {
|
||||
if (uploads == null) {
|
||||
uploads = new HashMap();
|
||||
}
|
||||
UploadStatus status = new UploadStatus();
|
||||
uploads.put(uploadId, status);
|
||||
return status;
|
||||
}
|
||||
|
||||
protected UploadStatus getUpload(String uploadId) {
|
||||
if (uploads == null) {
|
||||
return null;
|
||||
} else {
|
||||
return (UploadStatus) uploads.get(uploadId);
|
||||
}
|
||||
}
|
||||
|
||||
protected void pruneUploads() {
|
||||
if (uploads == null || uploads.isEmpty())
|
||||
return;
|
||||
for (Iterator it = uploads.values().iterator(); it.hasNext();) {
|
||||
UploadStatus status = (UploadStatus) it.next();
|
||||
if (status.isDisposable()) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
220
src/main/java/helma/framework/core/SessionBean.java
Normal file
220
src/main/java/helma/framework/core/SessionBean.java
Normal file
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.core;
|
||||
|
||||
import helma.objectmodel.INode;
|
||||
import helma.framework.UploadStatus;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* The SessionBean wraps a <code>Session</code> object and
|
||||
* exposes it to the scripting framework.
|
||||
*/
|
||||
public class SessionBean implements Serializable {
|
||||
// the wrapped session object
|
||||
Session session;
|
||||
|
||||
/**
|
||||
* Creates a new SessionBean around a Session object.
|
||||
*
|
||||
* @param session ...
|
||||
*/
|
||||
public SessionBean(Session session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String toString() {
|
||||
return session.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to log in a user with the given username/password credentials.
|
||||
* If username and password match, the user node is associated with the session
|
||||
* and bound to the session.user property.
|
||||
*
|
||||
* @param username the username
|
||||
* @param password the password
|
||||
*
|
||||
* @return true if the user exists and the password matches the user's password property.
|
||||
*/
|
||||
public boolean login(String username, String password) {
|
||||
return session.login(username, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly associates the session with a user object without requiring
|
||||
* a username/password pair. This is for applications that use their own
|
||||
* authentication mechanism.
|
||||
*
|
||||
* @param userNode the HopObject node representing the user.
|
||||
*/
|
||||
public void login(INode userNode) {
|
||||
session.login(userNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disassociate this session from any user object it may have been associated with.
|
||||
*/
|
||||
public void logout() {
|
||||
session.logout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Touching the session marks it as active, avoiding session timeout.
|
||||
* Usually, sessions are touched when the user associated with it sends
|
||||
* a request. This method may be used to artificially keep a session alive.
|
||||
*/
|
||||
public void touch() {
|
||||
session.touch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time this session was last touched.
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Date lastActive() {
|
||||
return new Date(session.lastTouched());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time this session was created.
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Date onSince() {
|
||||
return new Date(session.onSince());
|
||||
}
|
||||
|
||||
// property-related methods:
|
||||
|
||||
/**
|
||||
* Get the cache/data node for this session. This object may be used
|
||||
* to store transient per-session data. It is reflected to the scripting
|
||||
* environment as session.data.
|
||||
*/
|
||||
public INode getData() {
|
||||
return session.getCacheNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user object for this session. This method returns null unless
|
||||
* one of the session.login methods was previously invoked.
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public INode getUser() {
|
||||
return session.getUserNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique identifier for a session object (session cookie).
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String get_id() {
|
||||
return session.getSessionId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique identifier for a session object (session cookie).
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getCookie() {
|
||||
return session.getSessionId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time this session was last touched.
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Date getLastActive() {
|
||||
return new Date(session.lastTouched());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a date object representing the time a user's session was started.
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Date getOnSince() {
|
||||
return new Date(session.onSince());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date at which the session was created or a login or
|
||||
* logout was performed the last time.
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Date getLastModified() {
|
||||
return new Date(session.lastModified());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the date at which the session was created or a login or
|
||||
* logout was performed the last time.
|
||||
*
|
||||
* @param date ...
|
||||
*/
|
||||
public void setLastModified(Date date) {
|
||||
if (date != null) {
|
||||
session.setLastModified(date.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the message that is to be displayed upon the next
|
||||
* request within this session.
|
||||
*
|
||||
* @return the message, or null if none was set.
|
||||
*/
|
||||
public String getMessage() {
|
||||
return session.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a message to be displayed to this session's user. This
|
||||
* can be used to save a message over to the next request when
|
||||
* the current request can't be used to display a user visible
|
||||
* message.
|
||||
*
|
||||
* @param msg
|
||||
*/
|
||||
public void setMessage(String msg) {
|
||||
session.message = msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an upload status for the current user session.
|
||||
* @param uploadId the upload id
|
||||
* @return the upload status
|
||||
*/
|
||||
public UploadStatus getUploadStatus(String uploadId) {
|
||||
return session.getUpload(uploadId);
|
||||
}
|
||||
|
||||
}
|
301
src/main/java/helma/framework/core/SessionManager.java
Normal file
301
src/main/java/helma/framework/core/SessionManager.java
Normal file
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
package helma.framework.core;
|
||||
|
||||
import helma.objectmodel.INode;
|
||||
import helma.objectmodel.db.NodeHandle;
|
||||
import helma.objectmodel.db.Transactor;
|
||||
import helma.scripting.ScriptingEngine;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
public class SessionManager {
|
||||
|
||||
protected Hashtable sessions;
|
||||
|
||||
protected Application app;
|
||||
|
||||
public SessionManager() {
|
||||
sessions = new Hashtable();
|
||||
}
|
||||
|
||||
public void init(Application app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
sessions.clear();
|
||||
}
|
||||
|
||||
public Session createSession(String sessionId) {
|
||||
Session session = getSession(sessionId);
|
||||
if (session == null) {
|
||||
session = new Session(sessionId, app);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
public Session getSession(String sessionId) {
|
||||
if (sessionId == null) {
|
||||
return null;
|
||||
}
|
||||
return (Session) sessions.get(sessionId);
|
||||
}
|
||||
|
||||
public void registerSession(Session session) {
|
||||
sessions.put(session.getSessionId(), session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the whole session map. We return a clone of the table to prevent
|
||||
* actual changes from the table itself, which is managed by the application.
|
||||
* It is safe and allowed to manipulate the session objects contained in the table, though.
|
||||
*/
|
||||
public Map getSessions() {
|
||||
return (Map) sessions.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of currenty active sessions.
|
||||
*/
|
||||
public int countSessions() {
|
||||
return sessions.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the session from the sessions-table and logout the user.
|
||||
*/
|
||||
public void discardSession(Session session) {
|
||||
session.logout();
|
||||
sessions.remove(session.getSessionId());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an array of <code>SessionBean</code> objects currently associated with a given
|
||||
* Helma user.
|
||||
*/
|
||||
public List getSessionsForUsername(String username) {
|
||||
ArrayList list = new ArrayList();
|
||||
|
||||
if (username == null) {
|
||||
return list;
|
||||
}
|
||||
|
||||
Enumeration e = sessions.elements();
|
||||
while (e.hasMoreElements()) {
|
||||
Session s = (Session) e.nextElement();
|
||||
|
||||
if (s != null && username.equals(s.getUID())) {
|
||||
// append to list if session is logged in and fits the given username
|
||||
list.add(new SessionBean(s));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of Helma nodes (HopObjects - the database object representing the user,
|
||||
* not the session object) representing currently logged in users.
|
||||
*/
|
||||
public List getActiveUsers() {
|
||||
ArrayList list = new ArrayList();
|
||||
|
||||
for (Enumeration e = sessions.elements(); e.hasMoreElements();) {
|
||||
Session s = (Session) e.nextElement();
|
||||
|
||||
if (s != null && s.isLoggedIn()) {
|
||||
// returns a session if it is logged in and has not been
|
||||
// returned before (so for each logged-in user is only added once)
|
||||
INode node = s.getUserNode();
|
||||
|
||||
// we check again because user may have been logged out between the first check
|
||||
if (node != null && !list.contains(node)) {
|
||||
list.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dump session state to a file.
|
||||
*
|
||||
* @param f the file to write session into, or null to use the default sesssion store.
|
||||
*/
|
||||
public void storeSessionData(File f, ScriptingEngine engine) {
|
||||
if (f == null) {
|
||||
f = new File(app.dbDir, "sessions");
|
||||
}
|
||||
|
||||
try {
|
||||
OutputStream ostream = new BufferedOutputStream(new FileOutputStream(f));
|
||||
ObjectOutputStream p = new ObjectOutputStream(ostream);
|
||||
|
||||
synchronized (sessions) {
|
||||
p.writeInt(sessions.size());
|
||||
|
||||
for (Enumeration e = sessions.elements(); e.hasMoreElements();) {
|
||||
try {
|
||||
engine.serialize(e.nextElement(), p);
|
||||
// p.writeObject(e.nextElement());
|
||||
} catch (NotSerializableException nsx) {
|
||||
// not serializable, skip this session
|
||||
app.logError("Error serializing session.", nsx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.flush();
|
||||
ostream.close();
|
||||
app.logEvent("stored " + sessions.size() + " sessions in file");
|
||||
} catch (Exception e) {
|
||||
app.logError("error storing session data.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* loads the serialized session table from a given file or from dbdir/sessions
|
||||
*/
|
||||
public void loadSessionData(File f, ScriptingEngine engine) {
|
||||
if (f == null) {
|
||||
f = new File(app.dbDir, "sessions");
|
||||
}
|
||||
|
||||
// compute session timeout value
|
||||
int sessionTimeout = 30;
|
||||
|
||||
try {
|
||||
sessionTimeout = Math.max(0,
|
||||
Integer.parseInt(app.getProperty("sessionTimeout",
|
||||
"30")));
|
||||
} catch (Exception ignore) {
|
||||
System.out.println(ignore.toString());
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
Transactor tx = Transactor.getInstance(app.getNodeManager());
|
||||
|
||||
try {
|
||||
tx.begin("sessionloader");
|
||||
// load the stored data:
|
||||
InputStream istream = new BufferedInputStream(new FileInputStream(f));
|
||||
ObjectInputStream p = new ObjectInputStream(istream);
|
||||
int size = p.readInt();
|
||||
int ct = 0;
|
||||
Hashtable newSessions = new Hashtable();
|
||||
|
||||
while (ct < size) {
|
||||
Session session = (Session) engine.deserialize(p);
|
||||
|
||||
if ((now - session.lastTouched()) < (sessionTimeout * 60000)) {
|
||||
session.setApp(app);
|
||||
newSessions.put(session.getSessionId(), session);
|
||||
}
|
||||
|
||||
ct++;
|
||||
}
|
||||
|
||||
p.close();
|
||||
istream.close();
|
||||
sessions = newSessions;
|
||||
app.logEvent("loaded " + newSessions.size() + " sessions from file");
|
||||
tx.commit();
|
||||
} catch (FileNotFoundException fnf) {
|
||||
// suppress error message if session file doesn't exist
|
||||
tx.abort();
|
||||
} catch (Exception e) {
|
||||
app.logError("error loading session data.", e);
|
||||
tx.abort();
|
||||
} finally {
|
||||
tx.closeConnections();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge sessions that have not been used for a certain amount of time.
|
||||
* This is called by run().
|
||||
*
|
||||
* @param lastSessionCleanup the last time sessions were purged
|
||||
* @return the updated lastSessionCleanup value
|
||||
*/
|
||||
protected long cleanupSessions(long lastSessionCleanup) {
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long sessionCleanupInterval = 60000;
|
||||
|
||||
// check if we should clean up user sessions
|
||||
if ((now - lastSessionCleanup) > sessionCleanupInterval) {
|
||||
|
||||
// get session timeout
|
||||
int sessionTimeout = 30;
|
||||
|
||||
try {
|
||||
sessionTimeout = Math.max(0,
|
||||
Integer.parseInt(app.getProperty("sessionTimeout", "30")));
|
||||
} catch (NumberFormatException nfe) {
|
||||
app.logEvent("Invalid sessionTimeout setting: " + app.getProperty("sessionTimeout"));
|
||||
}
|
||||
|
||||
RequestEvaluator thisEvaluator = null;
|
||||
|
||||
try {
|
||||
|
||||
thisEvaluator = app.getEvaluator();
|
||||
|
||||
Session[] sessionArray = (Session[]) sessions.values().toArray(new Session[0]);
|
||||
|
||||
for (int i = 0; i < sessionArray.length; i++) {
|
||||
Session session = sessionArray[i];
|
||||
|
||||
session.pruneUploads();
|
||||
if ((now - session.lastTouched()) > (sessionTimeout * 60000)) {
|
||||
NodeHandle userhandle = session.userHandle;
|
||||
|
||||
if (userhandle != null) {
|
||||
try {
|
||||
Object[] param = {session.getSessionId()};
|
||||
|
||||
thisEvaluator.invokeInternal(userhandle, "onLogout", param);
|
||||
} catch (Exception x) {
|
||||
// errors should already be logged by requestevaluator, but you never know
|
||||
app.logError("Error in onLogout", x);
|
||||
}
|
||||
}
|
||||
|
||||
discardSession(session);
|
||||
}
|
||||
}
|
||||
} catch (Exception cx) {
|
||||
app.logError("Error cleaning up sessions", cx);
|
||||
} finally {
|
||||
if (thisEvaluator != null) {
|
||||
app.releaseEvaluator(thisEvaluator);
|
||||
}
|
||||
}
|
||||
return now;
|
||||
} else {
|
||||
return lastSessionCleanup;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
1199
src/main/java/helma/framework/core/Skin.java
Normal file
1199
src/main/java/helma/framework/core/Skin.java
Normal file
File diff suppressed because it is too large
Load diff
150
src/main/java/helma/framework/core/SkinManager.java
Normal file
150
src/main/java/helma/framework/core/SkinManager.java
Normal file
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.core;
|
||||
|
||||
import helma.objectmodel.INode;
|
||||
import helma.framework.repository.FileResource;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* Manages skins for a Helma application
|
||||
*/
|
||||
public final class SkinManager implements FilenameFilter {
|
||||
Application app;
|
||||
// the skin property name to use in database skin objects
|
||||
final String skinProperty;
|
||||
// the file name extension for skin files
|
||||
final String skinExtension;
|
||||
|
||||
/**
|
||||
* Creates a new SkinManager object.
|
||||
*
|
||||
* @param app ...
|
||||
*/
|
||||
public SkinManager(Application app) {
|
||||
this.app = app;
|
||||
skinProperty = app.getProperty("skinProperty", "skin");
|
||||
skinExtension = ".skin";
|
||||
}
|
||||
|
||||
public Skin getSkin(Prototype prototype, String skinname, Object[] skinpath)
|
||||
throws IOException {
|
||||
if (prototype == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if name contains '#' split name into mainskin and subskin
|
||||
String subskin = null;
|
||||
int hash = skinname.indexOf('#');
|
||||
if (hash > -1) {
|
||||
subskin = skinname.substring(hash + 1);
|
||||
skinname = skinname.substring(0, hash);
|
||||
}
|
||||
return getSkin(prototype, skinname, subskin, skinpath);
|
||||
}
|
||||
|
||||
public Skin getSkin(Prototype prototype, String skinname,
|
||||
String subskin, Object[] skinpath)
|
||||
throws IOException {
|
||||
Prototype proto = prototype;
|
||||
|
||||
// Loop over prototype chain and check skinpath and prototype skin resources
|
||||
while (proto != null) {
|
||||
Skin skin;
|
||||
if (skinpath != null) {
|
||||
for (int i = 0; i < skinpath.length; i++) {
|
||||
skin = getSkinInPath(skinpath[i], proto.getName(), skinname);
|
||||
if (skin != null) {
|
||||
// check if skin skin contains main skin
|
||||
if (subskin == null && skin.hasMainskin()) {
|
||||
return skin;
|
||||
} else if (subskin != null && skin.hasSubskin(subskin)) {
|
||||
return skin.getSubskin(subskin);
|
||||
}
|
||||
String baseskin = skin.getExtends();
|
||||
if (baseskin != null && !baseskin.equals(skinname)) {
|
||||
return getSkin(prototype, baseskin, subskin, skinpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skin for this prototype wasn't found in the skinsets.
|
||||
// the next step is to look if it is defined as skin file in the application directory
|
||||
skin = proto.getSkin(prototype, skinname, subskin, skinpath);
|
||||
if (skin != null) {
|
||||
return skin;
|
||||
}
|
||||
|
||||
// still not found. See if there is a parent prototype which might define the skin.
|
||||
proto = proto.getParentPrototype();
|
||||
}
|
||||
|
||||
// looked every where, nothing to be found
|
||||
return null;
|
||||
}
|
||||
|
||||
private Skin getSkinInPath(Object skinset, String prototype, String skinname) throws IOException {
|
||||
if ((prototype == null) || (skinset == null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// check if the skinset object is a HopObject (db based skin)
|
||||
// or a String (file based skin)
|
||||
if (skinset instanceof INode) {
|
||||
INode n = (INode) ((INode) skinset).getChildElement(prototype);
|
||||
|
||||
if (n != null) {
|
||||
n = (INode) n.getChildElement(skinname);
|
||||
|
||||
if (n != null) {
|
||||
String skin = n.getString(skinProperty);
|
||||
|
||||
if (skin != null) {
|
||||
return new Skin(skin, app);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Skinset is interpreted as directory name from which to
|
||||
// retrieve the skin
|
||||
StringBuffer b = new StringBuffer(skinset.toString());
|
||||
b.append(File.separatorChar).append(prototype).append(File.separatorChar)
|
||||
.append(skinname).append(skinExtension);
|
||||
|
||||
// TODO: check for lower case prototype name for backwards compat
|
||||
|
||||
File f = new File(b.toString());
|
||||
|
||||
if (f.exists() && f.canRead()) {
|
||||
return Skin.getSkin(new FileResource(f), app);
|
||||
}
|
||||
}
|
||||
|
||||
// Inheritance is taken care of in the above getSkin method.
|
||||
// the sequence is prototype.skin-from-db, prototype.skin-from-file, parent.from-db, parent.from-file etc.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements java.io.FilenameFilter.accept()
|
||||
*/
|
||||
public boolean accept(File d, String n) {
|
||||
return n.endsWith(skinExtension);
|
||||
}
|
||||
}
|
311
src/main/java/helma/framework/core/TypeManager.java
Normal file
311
src/main/java/helma/framework/core/TypeManager.java
Normal file
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.core;
|
||||
|
||||
import helma.objectmodel.db.DbMapping;
|
||||
import helma.framework.repository.Resource;
|
||||
import helma.framework.repository.Repository;
|
||||
import helma.util.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The type manager periodically checks the prototype definitions for its
|
||||
* applications and updates the evaluators if anything has changed.
|
||||
*/
|
||||
public final class TypeManager {
|
||||
final static String[] standardTypes = { "User", "Global", "Root", "HopObject" };
|
||||
final static String templateExtension = ".hsp";
|
||||
final static String scriptExtension = ".js";
|
||||
final static String actionExtension = ".hac";
|
||||
final static String skinExtension = ".skin";
|
||||
|
||||
private Application app;
|
||||
// map of prototypes
|
||||
private HashMap prototypes;
|
||||
|
||||
// set of Java archives
|
||||
private HashSet jarfiles;
|
||||
|
||||
// set of directory names to ignore
|
||||
private HashSet ignoreDirs;
|
||||
|
||||
private long lastCheck = 0;
|
||||
private long lastCodeUpdate;
|
||||
private HashMap lastRepoScan;
|
||||
|
||||
// app specific class loader, includes jar files in the app directory
|
||||
private AppClassLoader loader;
|
||||
|
||||
/**
|
||||
* Creates a new TypeManager object.
|
||||
*
|
||||
* @param app ...
|
||||
*
|
||||
* @throws RuntimeException ...
|
||||
*/
|
||||
public TypeManager(Application app, String ignore) {
|
||||
this.app = app;
|
||||
prototypes = new HashMap();
|
||||
jarfiles = new HashSet();
|
||||
ignoreDirs = new HashSet();
|
||||
lastRepoScan = new HashMap();
|
||||
// split ignore dirs list and add to hash set
|
||||
if (ignore != null) {
|
||||
String[] arr = StringUtils.split(ignore, ",");
|
||||
for (int i=0; i<arr.length; i++)
|
||||
ignoreDirs.add(arr[i].trim());
|
||||
}
|
||||
|
||||
URL helmajar = TypeManager.class.getResource("/");
|
||||
|
||||
if (helmajar == null) {
|
||||
// Helma classes are in jar file, get helma.jar URL
|
||||
URL[] urls = ((URLClassLoader) TypeManager.class.getClassLoader()).getURLs();
|
||||
|
||||
for (int i = 0; i < urls.length; i++) {
|
||||
String url = urls[i].toString().toLowerCase();
|
||||
if (url.endsWith("helma.jar")) {
|
||||
helmajar = urls[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (helmajar == null) {
|
||||
// throw new RuntimeException("helma.jar not found in embedding classpath");
|
||||
loader = new AppClassLoader(app.getName(), new URL[0]);
|
||||
} else {
|
||||
loader = new AppClassLoader(app.getName(), new URL[] { helmajar });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run through application's prototype directories and create prototypes, but don't
|
||||
* compile or evaluate any scripts.
|
||||
*/
|
||||
public synchronized void createPrototypes() throws IOException {
|
||||
// create standard prototypes.
|
||||
for (int i = 0; i < standardTypes.length; i++) {
|
||||
createPrototype(standardTypes[i], null, null);
|
||||
}
|
||||
|
||||
// loop through directories and create prototypes
|
||||
checkRepositories();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run through application's prototype directories and check if anything
|
||||
* has been updated.
|
||||
* If so, update prototypes and scripts.
|
||||
*/
|
||||
public synchronized void checkPrototypes() throws IOException {
|
||||
if ((System.currentTimeMillis() - lastCheck) < 1000L) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkRepositories();
|
||||
|
||||
lastCheck = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
protected synchronized void checkRepository(Repository repository, boolean update) throws IOException {
|
||||
Repository[] list = repository.getRepositories();
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
|
||||
// ignore dir name found - compare to shortname (= Prototype name)
|
||||
if (ignoreDirs.contains(list[i].getShortName())) {
|
||||
// jump this repository
|
||||
if (app.debug) {
|
||||
app.logEvent("Repository " + list[i].getName() + " ignored");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (list[i].isScriptRoot()) {
|
||||
// this is an embedded top-level script repository
|
||||
if (app.addRepository(list[i], list[i].getParentRepository())) {
|
||||
// repository is new, check it
|
||||
checkRepository(list[i], update);
|
||||
}
|
||||
} else {
|
||||
// it's an prototype
|
||||
String name = list[i].getShortName();
|
||||
Prototype proto = getPrototype(name);
|
||||
|
||||
// if prototype doesn't exist, create it
|
||||
if (proto == null) {
|
||||
// create new prototype if type name is valid
|
||||
if (isValidTypeName(name))
|
||||
createPrototype(name, list[i], null);
|
||||
} else {
|
||||
proto.addRepository(list[i], update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Iterator resources = repository.getResources();
|
||||
while (resources.hasNext()) {
|
||||
// check for jar files to add to class loader
|
||||
Resource resource = (Resource) resources.next();
|
||||
String name = resource.getName();
|
||||
if (name.endsWith(".jar")) {
|
||||
if (!jarfiles.contains(name)) {
|
||||
jarfiles.add(name);
|
||||
try {
|
||||
loader.addURL(resource.getUrl());
|
||||
} catch (UnsupportedOperationException x) {
|
||||
// not implemented by all kinds of resources
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run through application's prototype sources and check if
|
||||
* there are any prototypes to be created.
|
||||
*/
|
||||
private synchronized void checkRepositories() throws IOException {
|
||||
List list = app.getRepositories();
|
||||
|
||||
// walk through repositories and check if any of them have changed.
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
Repository repository = (Repository) list.get(i);
|
||||
long lastScan = lastRepoScan.containsKey(repository) ?
|
||||
((Long) lastRepoScan.get(repository)).longValue() : 0;
|
||||
if (repository.lastModified() != lastScan) {
|
||||
lastRepoScan.put(repository, new Long(repository.lastModified()));
|
||||
checkRepository(repository, false);
|
||||
}
|
||||
}
|
||||
|
||||
boolean debug = "true".equalsIgnoreCase(app.getProperty("helma.debugTypeManager"));
|
||||
if (debug) {
|
||||
System.err.println("Starting CHECK loop in " + Thread.currentThread());
|
||||
}
|
||||
|
||||
// loop through prototypes and check if type.properties needs updates
|
||||
// it's important that we do this _after_ potentially new prototypes
|
||||
// have been created in the previous loop.
|
||||
for (Iterator i = prototypes.values().iterator(); i.hasNext();) {
|
||||
Prototype proto = (Prototype) i.next();
|
||||
|
||||
if (debug) {
|
||||
System.err.println("CHECK: " + proto.getName() + " in " + Thread.currentThread());
|
||||
}
|
||||
|
||||
// update prototype's type mapping
|
||||
DbMapping dbmap = proto.getDbMapping();
|
||||
|
||||
if ((dbmap != null) && dbmap.needsUpdate()) {
|
||||
// call dbmap.update(). This also checks the
|
||||
// parent prototype for prototypes other than
|
||||
// global and HopObject, which is a bit awkward...
|
||||
// I mean we're the type manager, so this should
|
||||
// be part of our job, right?
|
||||
proto.props.update();
|
||||
dbmap.update();
|
||||
}
|
||||
}
|
||||
if (debug) {
|
||||
System.err.println("Finished CHECK in " + Thread.currentThread());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidTypeName(String str) {
|
||||
if (str == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char[] c = str.toCharArray();
|
||||
|
||||
for (int i = 0; i < c.length; i++)
|
||||
if (!Character.isJavaIdentifierPart(c[i])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last time any resource in this app was modified.
|
||||
* This can be used to find out quickly if any file has changed.
|
||||
*/
|
||||
public long getLastCodeUpdate() {
|
||||
return lastCodeUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last time any resource in this app was modified.
|
||||
*/
|
||||
public void setLastCodeUpdate(long update) {
|
||||
lastCodeUpdate = update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class loader used by this application.
|
||||
*
|
||||
* @return the ClassLoader
|
||||
*/
|
||||
public ClassLoader getClassLoader() {
|
||||
return loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a collection containing the prototypes defined for this type
|
||||
* manager.
|
||||
*
|
||||
* @return a collection containing the prototypes
|
||||
*/
|
||||
public synchronized Collection getPrototypes() {
|
||||
return Collections.unmodifiableCollection(prototypes.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a prototype defined for this application
|
||||
*/
|
||||
public synchronized Prototype getPrototype(String typename) {
|
||||
if (typename == null) {
|
||||
return null;
|
||||
}
|
||||
return (Prototype) prototypes.get(typename.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and register a new Prototype.
|
||||
*
|
||||
* @param typename the name of the prototype
|
||||
* @param repository the first prototype source
|
||||
* @param typeProps custom type mapping properties
|
||||
* @return the newly created prototype
|
||||
*/
|
||||
public synchronized Prototype createPrototype(String typename, Repository repository, Map typeProps) {
|
||||
if ("true".equalsIgnoreCase(app.getProperty("helma.debugTypeManager"))) {
|
||||
System.err.println("CREATE: " + typename + " from " + repository + " in " + Thread.currentThread());
|
||||
// Thread.dumpStack();
|
||||
}
|
||||
Prototype proto = new Prototype(typename, repository, app, typeProps);
|
||||
// put the prototype into our map
|
||||
prototypes.put(proto.getLowerCaseName(), proto);
|
||||
return proto;
|
||||
}
|
||||
|
||||
}
|
83
src/main/java/helma/framework/demo/SimplePathElement.java
Normal file
83
src/main/java/helma/framework/demo/SimplePathElement.java
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.demo;
|
||||
|
||||
import helma.framework.IPathElement;
|
||||
|
||||
/**
|
||||
* This is an example implementation for the helma.framework.IPathElement interface.
|
||||
* It creates any child element which is requested on the fly without ever asking.
|
||||
*/
|
||||
public class SimplePathElement implements IPathElement {
|
||||
String name;
|
||||
String prototype;
|
||||
IPathElement parent;
|
||||
|
||||
/**
|
||||
* Constructor for the root element.
|
||||
*/
|
||||
public SimplePathElement() {
|
||||
name = "root";
|
||||
prototype = "root";
|
||||
parent = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for non-root elements.
|
||||
*/
|
||||
public SimplePathElement(String n, IPathElement p) {
|
||||
name = n;
|
||||
prototype = "hopobject";
|
||||
parent = p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a child element for this object, creating it on the fly.
|
||||
*/
|
||||
public IPathElement getChildElement(String n) {
|
||||
return new SimplePathElement(n, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this object's parent element
|
||||
*/
|
||||
public IPathElement getParentElement() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element name to be used for this object.
|
||||
*/
|
||||
public String getElementName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the scripting prototype to be used for this object.
|
||||
* This will be "root" for the root element and "hopobject for everything else.
|
||||
*/
|
||||
public String getPrototype() {
|
||||
return prototype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this element.
|
||||
*/
|
||||
public String toString() {
|
||||
return "SimplePathElement " + name;
|
||||
}
|
||||
}
|
167
src/main/java/helma/framework/repository/AbstractRepository.java
Normal file
167
src/main/java/helma/framework/repository/AbstractRepository.java
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Provides common methods and fields for the default implementations of the
|
||||
* repository interface
|
||||
*/
|
||||
public abstract class AbstractRepository implements Repository {
|
||||
|
||||
|
||||
/**
|
||||
* Parent repository this repository is contained in.
|
||||
*/
|
||||
Repository parent;
|
||||
|
||||
/**
|
||||
* Holds direct child repositories
|
||||
*/
|
||||
Repository[] repositories;
|
||||
|
||||
/**
|
||||
* Holds direct resources
|
||||
*/
|
||||
HashMap resources;
|
||||
|
||||
/**
|
||||
* Cached name for faster access
|
||||
*/
|
||||
String name;
|
||||
|
||||
/**
|
||||
* Cached short name for faster access
|
||||
*/
|
||||
String shortName;
|
||||
|
||||
/*
|
||||
* empty repository array for convenience
|
||||
*/
|
||||
final static Repository[] emptyRepositories = new Repository[0];
|
||||
|
||||
/**
|
||||
* Called to check the repository's content.
|
||||
*/
|
||||
public abstract void update();
|
||||
|
||||
/**
|
||||
* Called to create a child resource for this repository
|
||||
*/
|
||||
protected abstract Resource createResource(String name);
|
||||
|
||||
/**
|
||||
* Get the full name that identifies this repository globally
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local name that identifies this repository locally within its
|
||||
* parent repository
|
||||
*/
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this repository's logical script root repository.
|
||||
*
|
||||
*@see {isScriptRoot()}
|
||||
*/
|
||||
public Repository getRootRepository() {
|
||||
if (parent == null || isScriptRoot()) {
|
||||
return this;
|
||||
} else {
|
||||
return parent.getRootRepository();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a resource contained in this repository identified by the given local name.
|
||||
* If the name can't be resolved to a resource, a resource object is returned
|
||||
* for which {@link Resource exists()} returns <code>false<code>.
|
||||
*/
|
||||
public synchronized Resource getResource(String name) {
|
||||
update();
|
||||
|
||||
Resource res = (Resource) resources.get(name);
|
||||
// if resource does not exist, create it
|
||||
if (res == null) {
|
||||
res = createResource(name);
|
||||
resources.put(name, res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an iterator over the resources contained in this repository.
|
||||
*/
|
||||
public synchronized Iterator getResources() {
|
||||
update();
|
||||
|
||||
return resources.values().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an iterator over the sub-repositories contained in this repository.
|
||||
*/
|
||||
public synchronized Repository[] getRepositories() {
|
||||
update();
|
||||
|
||||
return repositories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this repository's parent repository.
|
||||
*/
|
||||
public Repository getParentRepository() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a deep list of this repository's resources, including all resources
|
||||
* contained in sub-reposotories.
|
||||
*/
|
||||
public synchronized List getAllResources() throws IOException {
|
||||
update();
|
||||
|
||||
ArrayList allResources = new ArrayList();
|
||||
allResources.addAll(resources.values());
|
||||
|
||||
for (int i = 0; i < repositories.length; i++) {
|
||||
allResources.addAll(repositories[i].getAllResources());
|
||||
}
|
||||
|
||||
return allResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repositories full name as string representation.
|
||||
* @see {getName()}
|
||||
*/
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.repository;
|
||||
|
||||
/**
|
||||
* Abstract resource base class that implents get/setOverloadedResource.
|
||||
*/
|
||||
public abstract class AbstractResource implements Resource {
|
||||
|
||||
protected Resource overloaded = null;
|
||||
|
||||
/**
|
||||
* Method for registering a Resource this Resource is overloading
|
||||
*
|
||||
* @param res the overloaded resource
|
||||
*/
|
||||
public void setOverloadedResource(Resource res) {
|
||||
overloaded = res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Resource this Resource is overloading
|
||||
*
|
||||
* @return the overloaded resource
|
||||
*/
|
||||
public Resource getOverloadedResource() {
|
||||
return overloaded;
|
||||
}
|
||||
}
|
202
src/main/java/helma/framework/repository/FileRepository.java
Normal file
202
src/main/java/helma/framework/repository/FileRepository.java
Normal file
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.repository;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Repository implementation for directories providing file resources
|
||||
*/
|
||||
public class FileRepository extends AbstractRepository {
|
||||
|
||||
// Directory serving sub-repositories and file resources
|
||||
protected File directory;
|
||||
|
||||
protected long lastModified = -1;
|
||||
protected long lastChecksum = 0;
|
||||
protected long lastChecksumTime = 0;
|
||||
|
||||
/**
|
||||
* Defines how long the checksum of the repository will be cached
|
||||
*/
|
||||
final long cacheTime = 1000L;
|
||||
|
||||
/**
|
||||
* Constructs a FileRepository using the given argument
|
||||
* @param initArgs absolute path to the directory
|
||||
*/
|
||||
public FileRepository(String initArgs) {
|
||||
this(new File(initArgs), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a FileRepository using the given directory as top-level
|
||||
* repository
|
||||
* @param dir directory
|
||||
*/
|
||||
public FileRepository(File dir) {
|
||||
this(dir, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a FileRepository using the given directory and top-level
|
||||
* repository
|
||||
* @param dir directory
|
||||
* @param parent the parent repository, or null
|
||||
*/
|
||||
public FileRepository(File dir, Repository parent) {
|
||||
// make sure our directory has an absolute path,
|
||||
// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4117557
|
||||
if (dir.isAbsolute()) {
|
||||
directory = dir;
|
||||
} else {
|
||||
directory = dir.getAbsoluteFile();
|
||||
}
|
||||
if (!directory.exists()) {
|
||||
create();
|
||||
} else if (!directory.isDirectory()) {
|
||||
throw new IllegalArgumentException("File " + directory + " is not a directory");
|
||||
}
|
||||
|
||||
if (parent == null) {
|
||||
name = shortName = directory.getAbsolutePath();
|
||||
} else {
|
||||
this.parent = parent;
|
||||
shortName = directory.getName();
|
||||
name = directory.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean exists() {
|
||||
return directory.exists() && directory.isDirectory();
|
||||
}
|
||||
|
||||
public void create() {
|
||||
if (!directory.exists() || !directory.isDirectory()) {
|
||||
directory.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the repository is to be considered a top-level
|
||||
* repository from a scripting point of view. For example, a zip
|
||||
* file within a file repository is not a root repository from
|
||||
* a physical point of view, but from the scripting point of view it is.
|
||||
*
|
||||
* @return true if the repository is to be considered a top-level script repository
|
||||
*/
|
||||
public boolean isScriptRoot() {
|
||||
return parent == null || parent instanceof MultiFileRepository;
|
||||
}
|
||||
|
||||
public long lastModified() {
|
||||
return directory.lastModified();
|
||||
}
|
||||
|
||||
public synchronized long getChecksum() throws IOException {
|
||||
// delay checksum check if already checked recently
|
||||
if (System.currentTimeMillis() > lastChecksumTime + cacheTime) {
|
||||
|
||||
update();
|
||||
long checksum = lastModified;
|
||||
|
||||
for (int i = 0; i < repositories.length; i++) {
|
||||
checksum += repositories[i].getChecksum();
|
||||
}
|
||||
|
||||
lastChecksum = checksum;
|
||||
lastChecksumTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
return lastChecksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the content cache of the repository
|
||||
* Gets called from within all methods returning sub-repositories or
|
||||
* resources
|
||||
*/
|
||||
public synchronized void update() {
|
||||
if (!directory.exists()) {
|
||||
repositories = emptyRepositories;
|
||||
if (resources == null) {
|
||||
resources = new HashMap();
|
||||
} else {
|
||||
resources.clear();
|
||||
}
|
||||
lastModified = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (directory.lastModified() != lastModified) {
|
||||
lastModified = directory.lastModified();
|
||||
|
||||
File[] list = directory.listFiles();
|
||||
|
||||
ArrayList newRepositories = new ArrayList(list.length);
|
||||
HashMap newResources = new HashMap(list.length);
|
||||
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
if (list[i].isDirectory()) {
|
||||
// a nested directory aka child file repository
|
||||
newRepositories.add(new FileRepository(list[i], this));
|
||||
} else if (list[i].getName().endsWith(".zip")) {
|
||||
// a nested zip repository
|
||||
newRepositories.add(new ZipRepository(list[i], this));
|
||||
} else if (list[i].isFile()) {
|
||||
// a file resource
|
||||
FileResource resource = new FileResource(list[i], this);
|
||||
newResources.put(resource.getShortName(), resource);
|
||||
}
|
||||
}
|
||||
|
||||
repositories = (Repository[])
|
||||
newRepositories.toArray(new Repository[newRepositories.size()]);
|
||||
resources = newResources;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create a child resource for this repository
|
||||
*/
|
||||
protected Resource createResource(String name) {
|
||||
return new FileResource(new File(directory, name), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the repository's directory
|
||||
*/
|
||||
public File getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return 17 + (37 * directory.hashCode());
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof FileRepository &&
|
||||
directory.equals(((FileRepository) obj).directory);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new StringBuffer("FileRepository[").append(name).append("]").toString();
|
||||
}
|
||||
}
|
117
src/main/java/helma/framework/repository/FileResource.java
Normal file
117
src/main/java/helma/framework/repository/FileResource.java
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.repository;
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
|
||||
public class FileResource extends AbstractResource {
|
||||
|
||||
File file;
|
||||
Repository repository;
|
||||
String name;
|
||||
String shortName;
|
||||
String baseName;
|
||||
|
||||
public FileResource(File file) {
|
||||
this(file, null);
|
||||
}
|
||||
|
||||
protected FileResource(File file, Repository repository) {
|
||||
this.file = file;
|
||||
|
||||
this.repository = repository;
|
||||
name = file.getAbsolutePath();
|
||||
shortName = file.getName();
|
||||
// base name is short name with extension cut off
|
||||
int lastDot = shortName.lastIndexOf(".");
|
||||
baseName = (lastDot == -1) ? shortName : shortName.substring(0, lastDot);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public String getBaseName() {
|
||||
return baseName;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
try {
|
||||
return new URL("file:" + file.getAbsolutePath());
|
||||
} catch (MalformedURLException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public long lastModified() {
|
||||
return file.lastModified();
|
||||
}
|
||||
|
||||
public String getContent(String encoding) throws IOException {
|
||||
InputStream in = getInputStream();
|
||||
int size = (int) file.length();
|
||||
byte[] buf = new byte[size];
|
||||
int read = 0;
|
||||
while (read < size) {
|
||||
int r = in.read(buf, read, size - read);
|
||||
if (r == -1)
|
||||
break;
|
||||
read += r;
|
||||
}
|
||||
in.close();
|
||||
return encoding == null ?
|
||||
new String(buf) :
|
||||
new String(buf, encoding);
|
||||
}
|
||||
|
||||
public String getContent() throws IOException {
|
||||
return getContent(null);
|
||||
}
|
||||
|
||||
public long getLength() {
|
||||
return file.length();
|
||||
}
|
||||
|
||||
public boolean exists() {
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
public Repository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return 17 + name.hashCode();
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof FileResource && name.equals(((FileResource)obj).name);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 2005 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.repository;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Repository implementation that provides all of its subdirectories
|
||||
* as top-level FileRepositories
|
||||
*
|
||||
* @author Barbara Ondrisek
|
||||
*/
|
||||
public class MultiFileRepository extends FileRepository {
|
||||
|
||||
/**
|
||||
* Constructs a MultiFileRepository using the given argument
|
||||
* @param initArgs absolute path to the directory
|
||||
*/
|
||||
public MultiFileRepository(String initArgs) {
|
||||
this(new File(initArgs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a MultiFileRepository using the given directory as top-level
|
||||
* repository
|
||||
* @param dir directory
|
||||
*/
|
||||
public MultiFileRepository(File dir) {
|
||||
super(dir, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the content cache of the repository. We override this
|
||||
* to create child repositories that act as top-level script repositories
|
||||
* rather than prototype repositories. Zip files are handled as top-level
|
||||
* script repositories like in FileRepository, while resources are ignored.
|
||||
*/
|
||||
public synchronized void update() {
|
||||
if (!directory.exists()) {
|
||||
repositories = emptyRepositories;
|
||||
if (resources != null)
|
||||
resources.clear();
|
||||
lastModified = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (directory.lastModified() != lastModified) {
|
||||
lastModified = directory.lastModified();
|
||||
|
||||
File[] list = directory.listFiles();
|
||||
|
||||
ArrayList newRepositories = new ArrayList(list.length);
|
||||
HashMap newResources = new HashMap(list.length);
|
||||
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
// create both directories and zip files as top-level repositories,
|
||||
// while resources (files) are ignored.
|
||||
if (list[i].isDirectory()) {
|
||||
// a nested directory aka child file repository
|
||||
newRepositories.add(new FileRepository(list[i], this));
|
||||
} else if (list[i].getName().endsWith(".zip")) {
|
||||
// a nested zip repository
|
||||
newRepositories.add(new ZipRepository(list[i], this));
|
||||
}
|
||||
}
|
||||
|
||||
repositories = (Repository[])
|
||||
newRepositories.toArray(new Repository[newRepositories.size()]);
|
||||
resources = newResources;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get hashcode
|
||||
* @return int
|
||||
*/
|
||||
public int hashCode() {
|
||||
return 37 + (37 * directory.hashCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* equals object
|
||||
* @param obj Object
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof MultiFileRepository &&
|
||||
directory.equals(((MultiFileRepository) obj).directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* get object serialized as string
|
||||
* @return String
|
||||
*/
|
||||
public String toString() {
|
||||
return new StringBuffer("MultiFileRepository[").append(name).append("]").toString();
|
||||
}
|
||||
}
|
136
src/main/java/helma/framework/repository/Repository.java
Normal file
136
src/main/java/helma/framework/repository/Repository.java
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Repository represents an abstract container of resources (e.g. code, skins, ...).
|
||||
* In addition to resources, repositories may contain other repositories, building
|
||||
* a hierarchical structure.
|
||||
*/
|
||||
public interface Repository {
|
||||
|
||||
/**
|
||||
* Checksum of the repository and all its content. Implementations
|
||||
* should make sure
|
||||
*
|
||||
* @return checksum
|
||||
* @throws IOException
|
||||
*/
|
||||
public long getChecksum() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the date the repository was last modified.
|
||||
*
|
||||
* @return last modified date
|
||||
* @throws IOException
|
||||
*/
|
||||
public long lastModified() throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a specific direct resource of the repository
|
||||
*
|
||||
* @param resourceName name of the child resource to return
|
||||
* @return specified child resource
|
||||
*/
|
||||
public Resource getResource(String resourceName);
|
||||
|
||||
/**
|
||||
* Returns all direct resources
|
||||
*
|
||||
* @return direct resources
|
||||
* @throws IOException
|
||||
*/
|
||||
public Iterator getResources() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns all direct and indirect resources
|
||||
*
|
||||
* @return resources recursive
|
||||
* @throws IOException
|
||||
*/
|
||||
public List getAllResources() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns this repository's direct child repositories
|
||||
*
|
||||
* @return direct repositories
|
||||
* @throws IOException
|
||||
*/
|
||||
public Repository[] getRepositories() throws IOException;
|
||||
|
||||
/**
|
||||
* Checks wether the repository actually (or still) exists
|
||||
*
|
||||
* @return true if the repository exists
|
||||
* @throws IOException
|
||||
*/
|
||||
public boolean exists() throws IOException;
|
||||
|
||||
/**
|
||||
* Creates the repository if does not exist yet
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void create() throws IOException;
|
||||
|
||||
/**
|
||||
* Checks wether the repository is to be considered a top-level
|
||||
* repository from a scripting point of view. For example, a zip
|
||||
* file within a file repository is not a root repository from
|
||||
* a physical point of view, but from the scripting point of view it is.
|
||||
*
|
||||
* @return true if the repository is to be considered a top-level script repository
|
||||
*/
|
||||
public boolean isScriptRoot();
|
||||
|
||||
/**
|
||||
* Returns this repository's parent repository.
|
||||
* Returns null if this repository already is the top-level repository
|
||||
*
|
||||
* @return the parent repository
|
||||
*/
|
||||
public Repository getParentRepository();
|
||||
|
||||
/**
|
||||
* Get this repository's logical script root repository.
|
||||
*
|
||||
* @see {isScriptRoot()}
|
||||
* @return top-level repository
|
||||
*/
|
||||
public Repository getRootRepository();
|
||||
|
||||
/**
|
||||
* Returns the name of the repository; this is a full name including all
|
||||
* parent repositories.
|
||||
*
|
||||
* @return full name of the repository
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* Returns the name of the repository.
|
||||
*
|
||||
* @return name of the repository
|
||||
*/
|
||||
public String getShortName();
|
||||
|
||||
}
|
117
src/main/java/helma/framework/repository/Resource.java
Normal file
117
src/main/java/helma/framework/repository/Resource.java
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.repository;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Resource represents a pointer to some kind of information (code, skin, ...)
|
||||
* from which the content can be fetched
|
||||
*/
|
||||
public interface Resource {
|
||||
|
||||
/**
|
||||
* Returns the date the resource was last modified
|
||||
* @return last modified date
|
||||
*/
|
||||
public long lastModified();
|
||||
|
||||
/**
|
||||
* Checks wether this resource actually (still) exists
|
||||
* @return true if the resource exists
|
||||
*/
|
||||
public boolean exists();
|
||||
|
||||
/**
|
||||
* Returns the lengh of the resource's content
|
||||
* @return content length
|
||||
* @throws IOException I/O related problem
|
||||
*/
|
||||
public long getLength() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns an input stream to the content of the resource
|
||||
* @return content input stream
|
||||
* @throws IOException I/O related problem
|
||||
*/
|
||||
public InputStream getInputStream() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the content of the resource in a given encoding
|
||||
* @param encoding the character encoding
|
||||
* @return content
|
||||
* @throws IOException I/O related problem
|
||||
*/
|
||||
public String getContent(String encoding) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the content of the resource
|
||||
* @return content
|
||||
* @throws IOException I/O related problem
|
||||
*/
|
||||
public String getContent() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the name of the resource; does not include the name of the
|
||||
* repository the resource was fetched from
|
||||
* @return name of the resource
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* Returns the short name of the resource which is its name exclusive file
|
||||
* ending if it exists
|
||||
* @return short name of the resource
|
||||
*/
|
||||
public String getShortName();
|
||||
|
||||
/**
|
||||
* Returns the short name of the resource with the file extension
|
||||
* (everything following the last dot character) cut off.
|
||||
* @return the file name without the file extension
|
||||
*/
|
||||
public String getBaseName();
|
||||
|
||||
/**
|
||||
* Returns an url to the resource if the repository of this resource is
|
||||
* able to provide urls
|
||||
* @return url to the resource
|
||||
* @throws UnsupportedOperationException if resource does not support URL schema
|
||||
*/
|
||||
public URL getUrl() throws UnsupportedOperationException;
|
||||
|
||||
/**
|
||||
* Get a Resource this Resource is overloading
|
||||
* @return the overloaded resource
|
||||
*/
|
||||
public Resource getOverloadedResource();
|
||||
|
||||
/**
|
||||
* Method for registering a Resource this Resource is overloading
|
||||
* @param res the overloaded resource
|
||||
*/
|
||||
public void setOverloadedResource(Resource res);
|
||||
|
||||
/**
|
||||
* Returns the repository the resource does belong to
|
||||
* @return upper repository
|
||||
*/
|
||||
public Repository getRepository();
|
||||
|
||||
}
|
111
src/main/java/helma/framework/repository/ResourceComparator.java
Normal file
111
src/main/java/helma/framework/repository/ResourceComparator.java
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.repository;
|
||||
|
||||
import java.util.Comparator;
|
||||
import helma.framework.core.Application;
|
||||
|
||||
/**
|
||||
* Sorts resources according to the order of their repositories
|
||||
*/
|
||||
public class ResourceComparator implements Comparator {
|
||||
|
||||
// the application where the top-level repositories can be found
|
||||
protected Application app;
|
||||
|
||||
/**
|
||||
* Constructcs a ResourceComparator sorting according to the top-level
|
||||
* repositories of the given application
|
||||
* @param app application that provides the top-level repositories
|
||||
*/
|
||||
public ResourceComparator(Application app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two Repositories, Resources or RepositoryTrackers
|
||||
* @param obj1 Repository, Resource or RepositoryTrackers
|
||||
* @param obj2 Repository, Resource or RepositoryTrackers
|
||||
* @return a negative integer, zero, or a positive integer as the
|
||||
* first argument is less than, equal to, or greater than the
|
||||
* second.
|
||||
* @throws ClassCastException if the arguments' types prevent them from
|
||||
* being compared by this Comparator.
|
||||
*/
|
||||
public int compare(Object obj1, Object obj2) {
|
||||
if (obj1.equals(obj2))
|
||||
return 0;
|
||||
|
||||
Repository rep1 = getRootRepository(obj1);
|
||||
Repository rep2 = getRootRepository(obj2);
|
||||
|
||||
int pos1 = app.getRepositoryIndex(rep1);
|
||||
int pos2 = app.getRepositoryIndex(rep2);
|
||||
|
||||
if (rep1 == rep2 || (pos1 == -1 && pos2 == -1)) {
|
||||
// Same root repository, but we must not return 0 unless objects are equal
|
||||
// (see JavaDoc on java.util.TreeSet) so we compare full names
|
||||
return getFullName(obj1).compareTo(getFullName(obj2));
|
||||
}
|
||||
|
||||
return pos1 - pos2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the comparator is equal to the given comparator
|
||||
* A ResourceComparator is equal to another ResourceComparator if the
|
||||
* applications they belong to are equal
|
||||
*
|
||||
* @param obj comparator
|
||||
* @return true if the given comparator equals
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
return (obj instanceof ResourceComparator) &&
|
||||
app == ((ResourceComparator) obj).getApplication();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the application we're comparing resources for
|
||||
*
|
||||
* @return the application instance
|
||||
*/
|
||||
public Application getApplication() {
|
||||
return app;
|
||||
}
|
||||
|
||||
private Repository getRootRepository(Object obj) {
|
||||
if (obj instanceof Resource)
|
||||
return ((Resource) obj).getRepository()
|
||||
.getRootRepository();
|
||||
if (obj instanceof Repository)
|
||||
return ((Repository) obj).getRootRepository();
|
||||
|
||||
// something we can't compare
|
||||
throw new IllegalArgumentException("Can't compare "+obj);
|
||||
}
|
||||
|
||||
private String getFullName(Object obj) {
|
||||
if (obj instanceof Resource)
|
||||
return ((Resource) obj).getName();
|
||||
if (obj instanceof Repository)
|
||||
return ((Repository) obj).getName();
|
||||
|
||||
// something we can't compare
|
||||
throw new IllegalArgumentException("Can't compare "+obj);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.repository;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A utility class that allows Resource consumers to track changes
|
||||
* on resources.
|
||||
*/
|
||||
public class ResourceTracker {
|
||||
|
||||
Resource resource;
|
||||
long lastModified;
|
||||
|
||||
public ResourceTracker(Resource resource) {
|
||||
this.resource = resource;
|
||||
markClean();
|
||||
}
|
||||
|
||||
public boolean hasChanged() throws IOException {
|
||||
return lastModified != resource.lastModified();
|
||||
}
|
||||
|
||||
public void markClean() {
|
||||
lastModified = resource.lastModified();
|
||||
}
|
||||
|
||||
public Resource getResource() {
|
||||
return resource;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2006 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.repository;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class SingleFileRepository implements Repository {
|
||||
|
||||
final Resource res;
|
||||
final Repository parent;
|
||||
final Repository[] repositories;
|
||||
final LinkedList resources = new LinkedList();
|
||||
final LinkedList allResources = new LinkedList();
|
||||
final boolean isScriptFile;
|
||||
|
||||
/**
|
||||
* Constructs a SingleFileRepository using the given argument
|
||||
* @param initArgs absolute path to the script file
|
||||
*/
|
||||
public SingleFileRepository(String initArgs) {
|
||||
this(new File(initArgs), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a SingleFileRepository using the given argument
|
||||
* @param file the script file
|
||||
*/
|
||||
public SingleFileRepository(File file) {
|
||||
this(file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a SingleFileRepository using the given argument
|
||||
* @param file the script file
|
||||
* @param parent the parent repository, or null
|
||||
*/
|
||||
public SingleFileRepository(File file, Repository parent) {
|
||||
this.parent = parent;
|
||||
res = new FileResource(file, this);
|
||||
allResources.add(res);
|
||||
isScriptFile = file.getName().endsWith(".js");
|
||||
if (isScriptFile) {
|
||||
repositories = new Repository[] { new FakeGlobal() };
|
||||
} else {
|
||||
repositories = AbstractRepository.emptyRepositories;
|
||||
resources.add(res);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checksum of the repository and all its content. Implementations
|
||||
* should make sure
|
||||
*
|
||||
* @return checksum
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public long getChecksum() throws IOException {
|
||||
return res.lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the repository.
|
||||
*
|
||||
* @return name of the repository
|
||||
*/
|
||||
public String getShortName() {
|
||||
return res.getShortName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the repository; this is a full name including all
|
||||
* parent repositories.
|
||||
*
|
||||
* @return full name of the repository
|
||||
*/
|
||||
public String getName() {
|
||||
return res.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this repository's logical script root repository.
|
||||
*
|
||||
* @return top-level repository
|
||||
* @see {isScriptRoot()}
|
||||
*/
|
||||
public Repository getRootRepository() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this repository's parent repository.
|
||||
* Returns null if this repository already is the top-level repository
|
||||
*
|
||||
* @return the parent repository
|
||||
*/
|
||||
public Repository getParentRepository() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the repository is to be considered a top-level
|
||||
* repository from a scripting point of view. For example, a zip
|
||||
* file within a file repository is not a root repository from
|
||||
* a physical point of view, but from the scripting point of view it is.
|
||||
*
|
||||
* @return true if the repository is to be considered a top-level script repository
|
||||
*/
|
||||
public boolean isScriptRoot() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the repository if does not exist yet
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public void create() throws IOException {
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the repository actually (or still) exists
|
||||
*
|
||||
* @return true if the repository exists
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public boolean exists() throws IOException {
|
||||
return res.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this repository's direct child repositories
|
||||
*
|
||||
* @return direct repositories
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public Repository[] getRepositories() throws IOException {
|
||||
return repositories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all direct and indirect resources
|
||||
*
|
||||
* @return resources recursive
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public List getAllResources() throws IOException {
|
||||
return resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all direct resources
|
||||
*
|
||||
* @return direct resources
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public Iterator getResources() throws IOException {
|
||||
return resources.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific direct resource of the repository
|
||||
*
|
||||
* @param resourceName name of the child resource to return
|
||||
* @return specified child resource
|
||||
*/
|
||||
public Resource getResource(String resourceName) {
|
||||
if (!isScriptFile && res.getName().equals(resourceName)) {
|
||||
return res;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date the repository was last modified.
|
||||
*
|
||||
* @return last modified date
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public long lastModified() throws IOException {
|
||||
return res.lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return our single resource.
|
||||
* @return the wrapped resource
|
||||
*/
|
||||
protected Resource getResource() {
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether some other object is "equal to" this one.
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
return (obj instanceof SingleFileRepository &&
|
||||
res.equals(((SingleFileRepository) obj).res));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash code value for the object.
|
||||
*/
|
||||
public int hashCode() {
|
||||
return res.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the object.
|
||||
*/
|
||||
public String toString() {
|
||||
return new StringBuffer("SingleFileRepository[")
|
||||
.append(res.getName()).append("]").toString();
|
||||
}
|
||||
|
||||
class FakeGlobal implements Repository {
|
||||
|
||||
/**
|
||||
* Checksum of the repository and all its content. Implementations
|
||||
* should make sure
|
||||
*
|
||||
* @return checksum
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public long getChecksum() throws IOException {
|
||||
return res.lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the repository.
|
||||
*
|
||||
* @return name of the repository
|
||||
*/
|
||||
public String getShortName() {
|
||||
// we need to return "Global" here in order to be recognized as
|
||||
// global code folder - that's the whole purpose of this class
|
||||
return "Global";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the repository; this is a full name including all
|
||||
* parent repositories.
|
||||
*
|
||||
* @return full name of the repository
|
||||
*/
|
||||
public String getName() {
|
||||
return res.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this repository's logical script root repository.
|
||||
*
|
||||
* @return top-level repository
|
||||
* @see {isScriptRoot()}
|
||||
*/
|
||||
public Repository getRootRepository() {
|
||||
return SingleFileRepository.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this repository's parent repository.
|
||||
* Returns null if this repository already is the top-level repository
|
||||
*
|
||||
* @return the parent repository
|
||||
*/
|
||||
public Repository getParentRepository() {
|
||||
return SingleFileRepository.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the repository is to be considered a top-level
|
||||
* repository from a scripting point of view. For example, a zip
|
||||
* file within a file repository is not a root repository from
|
||||
* a physical point of view, but from the scripting point of view it is.
|
||||
*
|
||||
* @return true if the repository is to be considered a top-level script repository
|
||||
*/
|
||||
public boolean isScriptRoot() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the repository if does not exist yet
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public void create() throws IOException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the repository actually (or still) exists
|
||||
*
|
||||
* @return true if the repository exists
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public boolean exists() throws IOException {
|
||||
return res.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this repository's direct child repositories
|
||||
*
|
||||
* @return direct repositories
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public Repository[] getRepositories() throws IOException {
|
||||
return AbstractRepository.emptyRepositories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all direct and indirect resources
|
||||
*
|
||||
* @return resources recursive
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public List getAllResources() throws IOException {
|
||||
return allResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all direct resources
|
||||
*
|
||||
* @return direct resources
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public Iterator getResources() throws IOException {
|
||||
return allResources.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific direct resource of the repository
|
||||
*
|
||||
* @param resourceName name of the child resource to return
|
||||
* @return specified child resource
|
||||
*/
|
||||
public Resource getResource(String resourceName) {
|
||||
if (res.getName().equals(resourceName)) {
|
||||
return res;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date the repository was last modified.
|
||||
*
|
||||
* @return last modified date
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public long lastModified() throws IOException {
|
||||
return res.lastModified();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
251
src/main/java/helma/framework/repository/ZipRepository.java
Normal file
251
src/main/java/helma/framework/repository/ZipRepository.java
Normal file
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.repository;
|
||||
|
||||
import helma.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public final class ZipRepository extends AbstractRepository {
|
||||
|
||||
// zip file serving sub-repositories and zip file resources
|
||||
private File file;
|
||||
|
||||
// the nested directory depth of this repository
|
||||
private int depth;
|
||||
|
||||
String entryPath;
|
||||
|
||||
private long lastModified = -1;
|
||||
|
||||
/**
|
||||
* Constructs a ZipRespository using the given argument
|
||||
* @param initArgs absolute path to the zip file
|
||||
*/
|
||||
public ZipRepository(String initArgs) {
|
||||
this(new File(initArgs), null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a ZipRespository using the given argument
|
||||
* @param file zip file
|
||||
*/
|
||||
public ZipRepository(File file) {
|
||||
this(file, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a ZipRepository using the given zip file as top-level
|
||||
* repository
|
||||
* @param file a zip file
|
||||
* @param parent the parent repository, or null
|
||||
*/
|
||||
public ZipRepository(File file, Repository parent) {
|
||||
this(file, parent, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a ZipRepository using the zip entryName belonging to the given
|
||||
* zip file and top-level repository
|
||||
* @param file a zip file
|
||||
* @param zipentry zip entryName
|
||||
* @param parent repository
|
||||
*/
|
||||
private ZipRepository(File file, Repository parent, ZipEntry zipentry) {
|
||||
// make sure our file has an absolute path,
|
||||
// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4117557
|
||||
if (file.isAbsolute()) {
|
||||
this.file = file;
|
||||
} else {
|
||||
this.file = file.getAbsoluteFile();
|
||||
}
|
||||
this.parent = parent;
|
||||
|
||||
if (zipentry == null) {
|
||||
name = shortName = file.getName();
|
||||
depth = 0;
|
||||
entryPath = "";
|
||||
} else {
|
||||
String[] pathArray = StringUtils.split(zipentry.getName(), "/");
|
||||
depth = pathArray.length;
|
||||
shortName = pathArray[depth - 1];
|
||||
entryPath = zipentry.getName();
|
||||
name = new StringBuffer(parent.getName())
|
||||
.append('/').append(shortName).toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a java.util.zip.ZipFile for this repository. It is the caller's
|
||||
* responsability to call close() in it when it is no longer needed.
|
||||
* @return a ZipFile for reading
|
||||
* @throws IOException
|
||||
*/
|
||||
protected ZipFile getZipFile() throws IOException {
|
||||
return new ZipFile(file);
|
||||
}
|
||||
|
||||
public synchronized void update() {
|
||||
if (file.lastModified() != lastModified ||
|
||||
repositories == null ||
|
||||
resources == null) {
|
||||
lastModified = file.lastModified();
|
||||
ZipFile zipfile = null;
|
||||
|
||||
try {
|
||||
zipfile = getZipFile();
|
||||
Enumeration en = zipfile.entries();
|
||||
HashMap newRepositories = new HashMap();
|
||||
HashMap newResources = new HashMap();
|
||||
|
||||
while (en.hasMoreElements()) {
|
||||
ZipEntry entry = (ZipEntry) en.nextElement();
|
||||
String eName = entry.getName();
|
||||
|
||||
if (!eName.regionMatches(0, entryPath, 0, entryPath.length())) {
|
||||
// names don't match - not a child of ours
|
||||
continue;
|
||||
}
|
||||
String[] entrypath = StringUtils.split(eName, "/");
|
||||
if (depth > 0 && !shortName.equals(entrypath[depth-1])) {
|
||||
// catch case where our name is Foo and other's is FooBar
|
||||
continue;
|
||||
}
|
||||
|
||||
// create new repositories and resources for all entries with a
|
||||
// path depth of this.depth + 1
|
||||
if (entrypath.length == depth + 1 && !entry.isDirectory()) {
|
||||
// create a new child resource
|
||||
ZipResource resource = new ZipResource(entry.getName(), this);
|
||||
newResources.put(resource.getShortName(), resource);
|
||||
} else if (entrypath.length > depth) {
|
||||
// create a new child repository
|
||||
if (!newRepositories.containsKey(entrypath[depth])) {
|
||||
ZipEntry child = composeChildEntry(entrypath[depth]);
|
||||
ZipRepository rep = new ZipRepository(file, this, child);
|
||||
newRepositories.put(entrypath[depth], rep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories = (Repository[]) newRepositories.values()
|
||||
.toArray(new Repository[newRepositories.size()]);
|
||||
resources = newResources;
|
||||
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
repositories = emptyRepositories;
|
||||
if (resources == null) {
|
||||
resources = new HashMap();
|
||||
} else {
|
||||
resources.clear();
|
||||
}
|
||||
|
||||
} finally {
|
||||
try {
|
||||
// unlocks the zip file in the underlying filesystem
|
||||
zipfile.close();
|
||||
} catch (Exception ex) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ZipEntry composeChildEntry(String name) {
|
||||
if (entryPath == null || entryPath.length() == 0) {
|
||||
return new ZipEntry(name);
|
||||
} else if (entryPath.endsWith("/")) {
|
||||
return new ZipEntry(entryPath + name);
|
||||
} else {
|
||||
return new ZipEntry(entryPath + "/" + name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create a child resource for this repository
|
||||
*/
|
||||
protected Resource createResource(String name) {
|
||||
return new ZipResource(entryPath + "/" + name, this);
|
||||
}
|
||||
|
||||
public long getChecksum() {
|
||||
return file.lastModified();
|
||||
}
|
||||
|
||||
public boolean exists() {
|
||||
ZipFile zipfile = null;
|
||||
try {
|
||||
/* a ZipFile needs to be created to see if the zip file actually
|
||||
exists; this is not cached to provide blocking the zip file in
|
||||
the underlying filesystem */
|
||||
zipfile = getZipFile();
|
||||
return true;
|
||||
} catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
// unlocks the zip file in the underlying filesystem
|
||||
zipfile.close();
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void create() {
|
||||
// we do not create zip files as it makes no sense
|
||||
throw new UnsupportedOperationException("create() not implemented for ZipRepository");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the repository is to be considered a top-level
|
||||
* repository from a scripting point of view. For example, a zip
|
||||
* file within a file repository is not a root repository from
|
||||
* a physical point of view, but from the scripting point of view it is.
|
||||
*
|
||||
* @return true if the repository is to be considered a top-level script repository
|
||||
*/
|
||||
public boolean isScriptRoot() {
|
||||
return depth == 0;
|
||||
}
|
||||
|
||||
public long lastModified() {
|
||||
return file.lastModified();
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return 17 + (37 * file.hashCode()) + (37 * name.hashCode());
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof ZipRepository)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ZipRepository rep = (ZipRepository) obj;
|
||||
return (file.equals(rep.file) && name.equals(rep.name));
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new StringBuffer("ZipRepository[").append(name).append("]").toString();
|
||||
}
|
||||
|
||||
}
|
170
src/main/java/helma/framework/repository/ZipResource.java
Normal file
170
src/main/java/helma/framework/repository/ZipResource.java
Normal file
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.framework.repository;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public final class ZipResource extends AbstractResource {
|
||||
|
||||
private String entryName;
|
||||
private ZipRepository repository;
|
||||
private String name;
|
||||
private String shortName;
|
||||
private String baseName;
|
||||
|
||||
protected ZipResource(String zipentryName, ZipRepository repository) {
|
||||
this.entryName = zipentryName;
|
||||
this.repository = repository;
|
||||
|
||||
int lastSlash = entryName.lastIndexOf('/');
|
||||
|
||||
shortName = entryName.substring(lastSlash + 1);
|
||||
name = new StringBuffer(repository.getName()).append('/')
|
||||
.append(shortName).toString();
|
||||
|
||||
// base name is short name with extension cut off
|
||||
int lastDot = shortName.lastIndexOf(".");
|
||||
baseName = (lastDot == -1) ? shortName : shortName.substring(0, lastDot);
|
||||
}
|
||||
|
||||
public long lastModified() {
|
||||
return repository.lastModified();
|
||||
}
|
||||
|
||||
public InputStream getInputStream() throws IOException {
|
||||
ZipFile zipfile = null;
|
||||
try {
|
||||
zipfile = repository.getZipFile();
|
||||
ZipEntry entry = zipfile.getEntry(entryName);
|
||||
if (entry == null) {
|
||||
throw new IOException("Zip resource " + this + " does not exist");
|
||||
}
|
||||
int size = (int) entry.getSize();
|
||||
byte[] buf = new byte[size];
|
||||
InputStream in = zipfile.getInputStream(entry);
|
||||
int read = 0;
|
||||
while (read < size) {
|
||||
int r = in.read(buf, read, size-read);
|
||||
if (r == -1)
|
||||
break;
|
||||
read += r;
|
||||
}
|
||||
in.close();
|
||||
return new ByteArrayInputStream(buf);
|
||||
} finally {
|
||||
zipfile.close();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean exists() {
|
||||
ZipFile zipfile = null;
|
||||
try {
|
||||
zipfile = repository.getZipFile();
|
||||
return (zipfile.getEntry(entryName) != null);
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
zipfile.close();
|
||||
} catch (Exception ex) {}
|
||||
}
|
||||
}
|
||||
|
||||
public String getContent(String encoding) throws IOException {
|
||||
ZipFile zipfile = null;
|
||||
try {
|
||||
zipfile = repository.getZipFile();
|
||||
ZipEntry entry = zipfile.getEntry(entryName);
|
||||
if (entry == null) {
|
||||
throw new IOException("Zip resource " + this + " does not exist");
|
||||
}
|
||||
InputStream in = zipfile.getInputStream(entry);
|
||||
int size = (int) entry.getSize();
|
||||
byte[] buf = new byte[size];
|
||||
int read = 0;
|
||||
while (read < size) {
|
||||
int r = in.read(buf, read, size-read);
|
||||
if (r == -1)
|
||||
break;
|
||||
read += r;
|
||||
}
|
||||
in.close();
|
||||
return encoding == null ?
|
||||
new String(buf) :
|
||||
new String(buf, encoding);
|
||||
} finally {
|
||||
if (zipfile != null) {
|
||||
zipfile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getContent() throws IOException {
|
||||
return getContent(null);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public String getBaseName() {
|
||||
return baseName;
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
// TODO: we might want to return a Jar URL
|
||||
// http://java.sun.com/j2se/1.5.0/docs/api/java/net/JarURLConnection.html
|
||||
throw new UnsupportedOperationException("getUrl() not implemented for ZipResource");
|
||||
}
|
||||
|
||||
public long getLength() {
|
||||
ZipFile zipfile = null;
|
||||
try {
|
||||
zipfile = repository.getZipFile();
|
||||
return zipfile.getEntry(entryName).getSize();
|
||||
} catch (Exception ex) {
|
||||
return 0;
|
||||
} finally {
|
||||
try {
|
||||
zipfile.close();
|
||||
} catch (Exception ex) {}
|
||||
}
|
||||
}
|
||||
|
||||
public Repository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return 17 + name.hashCode();
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof ZipResource && name.equals(((ZipResource) obj).name);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
622
src/main/java/helma/image/ColorQuantizer.java
Normal file
622
src/main/java/helma/image/ColorQuantizer.java
Normal file
|
@ -0,0 +1,622 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.image;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.awt.image.IndexColorModel;
|
||||
|
||||
/*
|
||||
* Modifications by Juerg Lehni:
|
||||
*
|
||||
* - Ported to Java from C
|
||||
* - Support for alpha-channels.
|
||||
* - Returns a BufferedImage of TYPE_BYTE_INDEXED with a IndexColorModel.
|
||||
* - Dithering of images through helma.image.DiffusionFilterOp by setting
|
||||
* the dither parameter to true.
|
||||
* - Support for a transparent color, which is correctly rendered by GIFEncoder.
|
||||
* All pixels with alpha < 0x80 are converted to this color when the parameter
|
||||
* alphaToBitmask is set to true.
|
||||
*/
|
||||
/*
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% %
|
||||
% %
|
||||
% %
|
||||
% QQQ U U AAA N N TTTTT IIIII ZZZZZ EEEEE %
|
||||
% Q Q U U A A NN N T I ZZ E %
|
||||
% Q Q U U AAAAA N N N T I ZZZ EEEEE %
|
||||
% Q QQ U U A A N NN T I ZZ E %
|
||||
% QQQQ UUU A A N N T IIIII ZZZZZ EEEEE %
|
||||
% %
|
||||
% %
|
||||
% Methods to Reduce the Number of Unique Colors in an Image %
|
||||
% %
|
||||
% %
|
||||
% Software Design %
|
||||
% John Cristy %
|
||||
% July 1992 %
|
||||
% %
|
||||
% %
|
||||
% Copyright (C) 2003 ImageMagick Studio, a non-profit organization dedicated %
|
||||
% to making software imaging solutions freely available. %
|
||||
% %
|
||||
% Permission is hereby granted, free of charge, to any person obtaining a %
|
||||
% copy of this software and associated documentation files ("ImageMagick"), %
|
||||
% to deal in ImageMagick without restriction, including without limitation %
|
||||
% the rights to use, copy, modify, merge, publish, distribute, sublicense, %
|
||||
% and/or sell copies of ImageMagick, and to permit persons to whom the %
|
||||
% ImageMagick is furnished to do so, subject to the following conditions: %
|
||||
% %
|
||||
% The above copyright notice and this permission notice shall be included in %
|
||||
% all copies or substantial portions of ImageMagick. %
|
||||
% %
|
||||
% The software is provided "as is", without warranty of any kind, express or %
|
||||
% implied, including but not limited to the warranties of merchantability, %
|
||||
% fitness for a particular purpose and noninfringement. In no event shall %
|
||||
% ImageMagick Studio be liable for any claim, damages or other liability, %
|
||||
% whether in an action of contract, tort or otherwise, arising from, out of %
|
||||
% or in connection with ImageMagick or the use or other dealings in %
|
||||
% ImageMagick. %
|
||||
% %
|
||||
% Except as contained in this notice, the name of the ImageMagick Studio %
|
||||
% shall not be used in advertising or otherwise to promote the sale, use or %
|
||||
% other dealings in ImageMagick without prior written authorization from the %
|
||||
% ImageMagick Studio. %
|
||||
% %
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%
|
||||
% Realism in computer graphics typically requires using 24 bits/pixel to
|
||||
% generate an image. Yet many graphic display devices do not contain the
|
||||
% amount of memory necessary to match the spatial and color resolution of
|
||||
% the human eye. The Quantize methods takes a 24 bit image and reduces
|
||||
% the number of colors so it can be displayed on raster device with less
|
||||
% bits per pixel. In most instances, the quantized image closely
|
||||
% resembles the original reference image.
|
||||
%
|
||||
% A reduction of colors in an image is also desirable for image
|
||||
% transmission and real-time animation.
|
||||
%
|
||||
% QuantizeImage() takes a standard RGB or monochrome images and quantizes
|
||||
% them down to some fixed number of colors.
|
||||
%
|
||||
% For purposes of color allocation, an image is a set of n pixels, where
|
||||
% each pixel is a point in RGB space. RGB space is a 3-dimensional
|
||||
% vector space, and each pixel, Pi, is defined by an ordered triple of
|
||||
% red, green, and blue coordinates, (Ri, Gi, Bi).
|
||||
%
|
||||
% Each primary color component (red, green, or blue) represents an
|
||||
% intensity which varies linearly from 0 to a maximum value, Cmax, which
|
||||
% corresponds to full saturation of that color. Color allocation is
|
||||
% defined over a domain consisting of the cube in RGB space with opposite
|
||||
% vertices at (0,0,0) and (Cmax, Cmax, Cmax). QUANTIZE requires Cmax =
|
||||
% 255.
|
||||
%
|
||||
% The algorithm maps this domain onto a tree in which each node
|
||||
% represents a cube within that domain. In the following discussion
|
||||
% these cubes are defined by the coordinate of two opposite vertices:
|
||||
% The vertex nearest the origin in RGB space and the vertex farthest from
|
||||
% the origin.
|
||||
%
|
||||
% The tree's root node represents the the entire domain, (0,0,0) through
|
||||
% (Cmax,Cmax,Cmax). Each lower level in the tree is generated by
|
||||
% subdividing one node's cube into eight smaller cubes of equal size.
|
||||
% This corresponds to bisecting the parent cube with planes passing
|
||||
% through the midpoints of each edge.
|
||||
%
|
||||
% The basic algorithm operates in three phases: Classification,
|
||||
% Reduction, and Assignment. Classification builds a color description
|
||||
% tree for the image. Reduction collapses the tree until the number it
|
||||
% represents, at most, the number of colors desired in the output image.
|
||||
% Assignment defines the output image's color map and sets each pixel's
|
||||
% color by restorage_class in the reduced tree. Our goal is to minimize
|
||||
% the numerical discrepancies between the original colors and quantized
|
||||
% colors (quantization error).
|
||||
%
|
||||
% Classification begins by initializing a color description tree of
|
||||
% sufficient depth to represent each possible input color in a leaf.
|
||||
% However, it is impractical to generate a fully-formed color description
|
||||
% tree in the storage_class phase for realistic values of Cmax. If
|
||||
% colors components in the input image are quantized to k-bit precision,
|
||||
% so that Cmax= 2k-1, the tree would need k levels below the root node to
|
||||
% allow representing each possible input color in a leaf. This becomes
|
||||
% prohibitive because the tree's total number of nodes is 1 +
|
||||
% sum(i=1, k, 8k).
|
||||
%
|
||||
% A complete tree would require 19,173,961 nodes for k = 8, Cmax = 255.
|
||||
% Therefore, to avoid building a fully populated tree, QUANTIZE: (1)
|
||||
% Initializes data structures for nodes only as they are needed; (2)
|
||||
% Chooses a maximum depth for the tree as a function of the desired
|
||||
% number of colors in the output image (currently log2(colormap size)).
|
||||
%
|
||||
% For each pixel in the input image, storage_class scans downward from
|
||||
% the root of the color description tree. At each level of the tree it
|
||||
% identifies the single node which represents a cube in RGB space
|
||||
% containing the pixel's color. It updates the following data for each
|
||||
% such node:
|
||||
%
|
||||
% n1: Number of pixels whose color is contained in the RGB cube which
|
||||
% this node represents;
|
||||
%
|
||||
% n2: Number of pixels whose color is not represented in a node at
|
||||
% lower depth in the tree; initially, n2 = 0 for all nodes except
|
||||
% leaves of the tree.
|
||||
%
|
||||
% Sr, Sg, Sb: Sums of the red, green, and blue component values for all
|
||||
% pixels not classified at a lower depth. The combination of these sums
|
||||
% and n2 will ultimately characterize the mean color of a set of
|
||||
% pixels represented by this node.
|
||||
%
|
||||
% E: The distance squared in RGB space between each pixel contained
|
||||
% within a node and the nodes' center. This represents the
|
||||
% quantization error for a node.
|
||||
%
|
||||
% Reduction repeatedly prunes the tree until the number of nodes with n2
|
||||
% > 0 is less than or equal to the maximum number of colors allowed in
|
||||
% the output image. On any given iteration over the tree, it selects
|
||||
% those nodes whose E count is minimal for pruning and merges their color
|
||||
% statistics upward. It uses a pruning threshold, Ep, to govern node
|
||||
% selection as follows:
|
||||
%
|
||||
% Ep = 0
|
||||
% while number of nodes with (n2 > 0) > required maximum number of colors
|
||||
% prune all nodes such that E <= Ep
|
||||
% Set Ep to minimum E in remaining nodes
|
||||
%
|
||||
% This has the effect of minimizing any quantization error when merging
|
||||
% two nodes together.
|
||||
%
|
||||
% When a node to be pruned has offspring, the pruning procedure invokes
|
||||
% itself recursively in order to prune the tree from the leaves upward.
|
||||
% n2, Sr, Sg, and Sb in a node being pruned are always added to the
|
||||
% corresponding data in that node's parent. This retains the pruned
|
||||
% node's color characteristics for later averaging.
|
||||
%
|
||||
% For each node, n2 pixels exist for which that node represents the
|
||||
% smallest volume in RGB space containing those pixel's colors. When n2
|
||||
% > 0 the node will uniquely define a color in the output image. At the
|
||||
% beginning of reduction, n2 = 0 for all nodes except a the leaves of
|
||||
% the tree which represent colors present in the input image.
|
||||
%
|
||||
% The other pixel count, n1, indicates the total number of colors within
|
||||
% the cubic volume which the node represents. This includes n1 - n2
|
||||
% pixels whose colors should be defined by nodes at a lower level in the
|
||||
% tree.
|
||||
%
|
||||
% Assignment generates the output image from the pruned tree. The output
|
||||
% image consists of two parts: (1) A color map, which is an array of
|
||||
% color descriptions (RGB triples) for each color present in the output
|
||||
% image; (2) A pixel array, which represents each pixel as an index
|
||||
% into the color map array.
|
||||
%
|
||||
% First, the assignment phase makes one pass over the pruned color
|
||||
% description tree to establish the image's color map. For each node
|
||||
% with n2 > 0, it divides Sr, Sg, and Sb by n2 . This produces the mean
|
||||
% color of all pixels that classify no lower than this node. Each of
|
||||
% these colors becomes an entry in the color map.
|
||||
%
|
||||
% Finally, the assignment phase reclassifies each pixel in the pruned
|
||||
% tree to identify the deepest node containing the pixel's color. The
|
||||
% pixel's value in the pixel array becomes the index of this node's mean
|
||||
% color in the color map.
|
||||
%
|
||||
% This method is based on a similar algorithm written by Paul Raveling.
|
||||
%
|
||||
%
|
||||
*/
|
||||
|
||||
public class ColorQuantizer {
|
||||
public static final int MAX_NODES = 266817;
|
||||
public static final int MAX_TREE_DEPTH = 8;
|
||||
public static final int MAX_CHILDREN = 16;
|
||||
public static final int MAX_RGB = 255;
|
||||
|
||||
static class ClosestColor {
|
||||
int distance;
|
||||
int colorIndex;
|
||||
}
|
||||
|
||||
static class Node {
|
||||
Cube cube;
|
||||
Node parent;
|
||||
Node children[];
|
||||
int numChildren;
|
||||
|
||||
int id;
|
||||
int level;
|
||||
|
||||
int uniqueCount;
|
||||
|
||||
int totalRed;
|
||||
int totalGreen;
|
||||
int totalBlue;
|
||||
int totalAlpha;
|
||||
long quantizeError;
|
||||
|
||||
int colorIndex;
|
||||
|
||||
Node(Cube cube) {
|
||||
this(cube, 0, 0, null);
|
||||
this.parent = this;
|
||||
}
|
||||
|
||||
Node(Cube cube, int id, int level, Node parent) {
|
||||
this.cube = cube;
|
||||
this.parent = parent;
|
||||
this.id = id;
|
||||
this.level = level;
|
||||
this.children = new Node[MAX_CHILDREN];
|
||||
this.numChildren = 0;
|
||||
if (parent != null) {
|
||||
parent.children[id] = this;
|
||||
parent.numChildren++;
|
||||
}
|
||||
cube.numNodes++;
|
||||
}
|
||||
|
||||
void pruneLevel() {
|
||||
// Traverse any children.
|
||||
if (numChildren > 0)
|
||||
for (int id = 0; id < MAX_CHILDREN; id++)
|
||||
if (children[id] != null)
|
||||
children[id].pruneLevel();
|
||||
if (level == cube.depth)
|
||||
prune();
|
||||
}
|
||||
|
||||
void pruneToCubeDepth() {
|
||||
// Traverse any children.
|
||||
if (numChildren > 0)
|
||||
for (int id = 0; id < MAX_CHILDREN; id++)
|
||||
if (children[id] != null)
|
||||
children[id].pruneToCubeDepth();
|
||||
if (level > cube.depth)
|
||||
prune();
|
||||
}
|
||||
|
||||
void prune() {
|
||||
// Traverse any children.
|
||||
if (numChildren > 0)
|
||||
for (int id = 0; id < MAX_CHILDREN; id++)
|
||||
if (children[id] != null)
|
||||
children[id].prune();
|
||||
// Merge color statistics into parent.
|
||||
parent.uniqueCount += uniqueCount;
|
||||
parent.totalRed += totalRed;
|
||||
parent.totalGreen += totalGreen;
|
||||
parent.totalBlue += totalBlue;
|
||||
parent.totalAlpha += totalAlpha;
|
||||
parent.children[id] = null;
|
||||
parent.numChildren--;
|
||||
cube.numNodes--;
|
||||
}
|
||||
|
||||
void reduce(long pruningThreshold) {
|
||||
// Traverse any children.
|
||||
if (numChildren > 0)
|
||||
for (int id = 0; id < MAX_CHILDREN; id++)
|
||||
if (children[id] != null)
|
||||
children[id].reduce(pruningThreshold);
|
||||
if (quantizeError <= pruningThreshold)
|
||||
prune();
|
||||
else {
|
||||
// Find minimum pruning threshold.
|
||||
if (uniqueCount > 0)
|
||||
cube.numColors++;
|
||||
if (quantizeError < cube.nextThreshold)
|
||||
cube.nextThreshold = quantizeError;
|
||||
}
|
||||
}
|
||||
|
||||
void findClosestColor(int red, int green, int blue, int alpha, ClosestColor closest) {
|
||||
// Traverse any children.
|
||||
if (numChildren > 0)
|
||||
for (int id = 0; id < MAX_CHILDREN; id++)
|
||||
if (children[id] != null)
|
||||
children[id].findClosestColor(red, green, blue, alpha, closest);
|
||||
if (uniqueCount != 0) {
|
||||
// Determine if this color is "closest".
|
||||
int dr = (cube.colorMap[0][colorIndex] & 0xff) - red;
|
||||
int dg = (cube.colorMap[1][colorIndex] & 0xff) - green;
|
||||
int db = (cube.colorMap[2][colorIndex] & 0xff) - blue;
|
||||
int da = (cube.colorMap[3][colorIndex] & 0xff) - alpha;
|
||||
int distance = da * da + dr * dr + dg * dg + db * db;
|
||||
if (distance < closest.distance) {
|
||||
closest.distance = distance;
|
||||
closest.colorIndex = colorIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int fillColorMap(byte colorMap[][], int index) {
|
||||
// Traverse any children.
|
||||
if (numChildren > 0)
|
||||
for (int id = 0; id < MAX_CHILDREN; id++)
|
||||
if (children[id] != null)
|
||||
index = children[id].fillColorMap(colorMap, index);
|
||||
if (uniqueCount != 0) {
|
||||
// Colormap entry is defined by the mean color in this cube.
|
||||
colorMap[0][index] = (byte) (totalRed / uniqueCount + 0.5);
|
||||
colorMap[1][index] = (byte) (totalGreen / uniqueCount + 0.5);
|
||||
colorMap[2][index] = (byte) (totalBlue / uniqueCount + 0.5);
|
||||
colorMap[3][index] = (byte) (totalAlpha / uniqueCount + 0.5);
|
||||
colorIndex = index++;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
static class Cube {
|
||||
Node root;
|
||||
|
||||
int numColors;
|
||||
boolean addTransparency;
|
||||
// firstColor is set to 1 when when addTransparency is true!
|
||||
int firstColor;
|
||||
byte colorMap[][];
|
||||
|
||||
long nextThreshold;
|
||||
|
||||
int numNodes;
|
||||
int depth;
|
||||
|
||||
Cube(int maxColors) {
|
||||
this.depth = getDepth(maxColors);
|
||||
this.numColors = 0;
|
||||
root = new Node(this);
|
||||
}
|
||||
|
||||
int getDepth(int numColors) {
|
||||
// Depth of color tree is: Log4(colormap size)+2.
|
||||
int depth;
|
||||
for (depth = 1; numColors != 0; depth++)
|
||||
numColors >>= 2;
|
||||
if (depth > MAX_TREE_DEPTH)
|
||||
depth = MAX_TREE_DEPTH;
|
||||
if (depth < 2)
|
||||
depth = 2;
|
||||
return depth;
|
||||
}
|
||||
|
||||
void classifyImageColors(BufferedImage image, boolean alphaToBitmask) {
|
||||
addTransparency = false;
|
||||
firstColor = 0;
|
||||
|
||||
Node node, child;
|
||||
int x, px, y, index, level, id, count;
|
||||
int pixel, red, green, blue, alpha;
|
||||
int bisect, midRed, midGreen, midBlue, midAlpha;
|
||||
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
// Classify the first 256 colors to a tree depth of MAX_TREE_DEPTH.
|
||||
int levelThreshold = MAX_TREE_DEPTH;
|
||||
// create a BufferedImage of only 1 pixel height for fetching the rows
|
||||
// of the image in the correct format (ARGB)
|
||||
// This speeds up things by more than factor 2, compared to the standard
|
||||
// BufferedImage.getRGB solution
|
||||
BufferedImage row = new BufferedImage(width, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = row.createGraphics();
|
||||
int pixels[] = ((DataBufferInt) row.getRaster().getDataBuffer()).getData();
|
||||
// make sure alpha values do not add up for each row:
|
||||
g2d.setComposite(AlphaComposite.Src);
|
||||
// calculate scanline by scanline in order to safe memory.
|
||||
// It also seems to run faster like that
|
||||
for (y = 0; y < height; y++) {
|
||||
g2d.drawImage(image, null, 0, -y);
|
||||
// now pixels contains the rgb values of the row y!
|
||||
if (numNodes > MAX_NODES) {
|
||||
// Prune one level if the color tree is too large.
|
||||
root.pruneLevel();
|
||||
depth--;
|
||||
}
|
||||
for (x = 0; x < width;) {
|
||||
pixel = pixels[x];
|
||||
red = (pixel >> 16) & 0xff;
|
||||
green = (pixel >> 8) & 0xff;
|
||||
blue = (pixel >> 0) & 0xff;
|
||||
alpha = (pixel >> 24) & 0xff;
|
||||
if (alphaToBitmask)
|
||||
alpha = alpha < 0x80 ? 0 : 0xff;
|
||||
|
||||
// skip same pixels, but count them
|
||||
px = x;
|
||||
for (++x; x < width; x++)
|
||||
if (pixels[x] != pixel)
|
||||
break;
|
||||
count = x - px;
|
||||
|
||||
// Start at the root and descend the color cube tree.
|
||||
if (alpha > 0) {
|
||||
index = MAX_TREE_DEPTH - 1;
|
||||
bisect = (MAX_RGB + 1) >> 1;
|
||||
midRed = bisect;
|
||||
midGreen = bisect;
|
||||
midBlue = bisect;
|
||||
midAlpha = bisect;
|
||||
node = root;
|
||||
for (level = 1; level <= levelThreshold; level++) {
|
||||
id = (((red >> index) & 0x01) << 3 |
|
||||
((green >> index) & 0x01) << 2 |
|
||||
((blue >> index) & 0x01) << 1 |
|
||||
((alpha >> index) & 0x01));
|
||||
bisect >>= 1;
|
||||
midRed += (id & 8) != 0 ? bisect : -bisect;
|
||||
midGreen += (id & 4) != 0 ? bisect : -bisect;
|
||||
midBlue += (id & 2) != 0 ? bisect : -bisect;
|
||||
midAlpha += (id & 1) != 0 ? bisect : -bisect;
|
||||
child = node.children[id];
|
||||
if (child == null) {
|
||||
// Set colors of new node to contain pixel.
|
||||
child = new Node(this, id, level, node);
|
||||
if (level == levelThreshold) {
|
||||
numColors++;
|
||||
if (numColors == 256) {
|
||||
// More than 256 colors; classify to the
|
||||
// cube_info.depth tree depth.
|
||||
levelThreshold = depth;
|
||||
root.pruneToCubeDepth();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Approximate the quantization error represented by
|
||||
// this node.
|
||||
node = child;
|
||||
int r = red - midRed;
|
||||
int g = green - midGreen;
|
||||
int b = blue - midBlue;
|
||||
int a = alpha - midAlpha;
|
||||
node.quantizeError += count * (r * r + g * g + b * b + a * a);
|
||||
root.quantizeError += node.quantizeError;
|
||||
index--;
|
||||
}
|
||||
// Sum RGB for this leaf for later derivation of the mean
|
||||
// cube color.
|
||||
node.uniqueCount += count;
|
||||
node.totalRed += count * red;
|
||||
node.totalGreen += count * green;
|
||||
node.totalBlue += count * blue;
|
||||
node.totalAlpha += count * alpha;
|
||||
} else if (!addTransparency) {
|
||||
addTransparency = true;
|
||||
numColors++;
|
||||
firstColor = 1; // start at 1 as 0 will be the transparent color
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reduceImageColors(int maxColors) {
|
||||
nextThreshold = 0;
|
||||
while (numColors > maxColors) {
|
||||
long pruningThreshold = nextThreshold;
|
||||
nextThreshold = root.quantizeError - 1;
|
||||
numColors = firstColor;
|
||||
root.reduce(pruningThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
BufferedImage assignImageColors(BufferedImage image, boolean dither, boolean alphaToBitmask) {
|
||||
// Allocate image colormap.
|
||||
colorMap = new byte[4][numColors];
|
||||
root.fillColorMap(colorMap, firstColor);
|
||||
// create the right color model, depending on transparency settings:
|
||||
IndexColorModel icm;
|
||||
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
if (alphaToBitmask) {
|
||||
if (addTransparency) {
|
||||
icm = new IndexColorModel(depth, numColors, colorMap[0], colorMap[1], colorMap[2], 0);
|
||||
} else {
|
||||
icm = new IndexColorModel(depth, numColors, colorMap[0], colorMap[1], colorMap[2]);
|
||||
}
|
||||
} else {
|
||||
icm = new IndexColorModel(depth, numColors, colorMap[0], colorMap[1], colorMap[2], colorMap[3]);
|
||||
}
|
||||
|
||||
// create the indexed BufferedImage:
|
||||
BufferedImage dest = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED, icm);
|
||||
|
||||
if (dither)
|
||||
new DiffusionFilterOp().filter(image, dest);
|
||||
else {
|
||||
ClosestColor closest = new ClosestColor();
|
||||
// convert to indexed color
|
||||
byte[] dst = ((DataBufferByte) dest.getRaster().getDataBuffer()).getData();
|
||||
|
||||
// create a BufferedImage of only 1 pixel height for fetching
|
||||
// the rows of the image in the correct format (ARGB)
|
||||
// This speeds up things by more than factor 2, compared to the
|
||||
// standard BufferedImage.getRGB solution
|
||||
BufferedImage row = new BufferedImage(width, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = row.createGraphics();
|
||||
int pixels[] = ((DataBufferInt) row.getRaster().getDataBuffer()).getData();
|
||||
// make sure alpha values do not add up for each row:
|
||||
g2d.setComposite(AlphaComposite.Src);
|
||||
// calculate scanline by scanline in order to safe memory.
|
||||
// It also seems to run faster like that
|
||||
Node node;
|
||||
int x, y, i, id;
|
||||
int pixel, red, green, blue, alpha;
|
||||
int pos = 0;
|
||||
for (y = 0; y < height; y++) {
|
||||
g2d.drawImage(image, null, 0, -y);
|
||||
// now pixels contains the rgb values of the row y!
|
||||
// filter this row now:
|
||||
for (x = 0; x < width;) {
|
||||
pixel = pixels[x];
|
||||
red = (pixel >> 16) & 0xff;
|
||||
green = (pixel >> 8) & 0xff;
|
||||
blue = (pixel >> 0) & 0xff;
|
||||
alpha = (pixel >> 24) & 0xff;
|
||||
|
||||
if (alphaToBitmask)
|
||||
alpha = alpha < 128 ? 0 : 0xff;
|
||||
|
||||
byte col;
|
||||
if (alpha == 0 && addTransparency) {
|
||||
col = 0; // transparency color is at position 0 of color map
|
||||
} else {
|
||||
// walk the tree to find the cube containing that
|
||||
// color
|
||||
node = root;
|
||||
for (i = MAX_TREE_DEPTH - 1; i > 0; i--) {
|
||||
id = (((red >> i) & 0x01) << 3 |
|
||||
((green >> i) & 0x01) << 2 |
|
||||
((blue >> i) & 0x01) << 1 |
|
||||
((alpha >> i) & 0x01));
|
||||
if (node.children[id] == null)
|
||||
break;
|
||||
node = node.children[id];
|
||||
}
|
||||
|
||||
// Find the closest color.
|
||||
closest.distance = Integer.MAX_VALUE;
|
||||
node.parent.findClosestColor(red, green, blue, alpha, closest);
|
||||
col = (byte) closest.colorIndex;
|
||||
}
|
||||
|
||||
// first color
|
||||
dst[pos++] = col;
|
||||
|
||||
// next colors the same?
|
||||
for (++x; x < width; x++) {
|
||||
if (pixels[x] != pixel)
|
||||
break;
|
||||
dst[pos++] = col;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
static BufferedImage quantizeImage(BufferedImage image, int maxColors, boolean dither, boolean alphaToBitmask) {
|
||||
Cube cube = new Cube(maxColors);
|
||||
cube.classifyImageColors(image, alphaToBitmask);
|
||||
cube.reduceImageColors(maxColors);
|
||||
return cube.assignImageColors(image, dither, alphaToBitmask);
|
||||
}
|
||||
|
||||
}
|
246
src/main/java/helma/image/DiffusionFilterOp.java
Normal file
246
src/main/java/helma/image/DiffusionFilterOp.java
Normal file
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
/*
|
||||
* DiffusionFilter code from com.jhlabs.image.DiffusionFilter, Java Image Processing
|
||||
* Copyright (C) Jerry Huxtable 1998
|
||||
* http://www.jhlabs.com/ip/
|
||||
*
|
||||
* Conversion to a BufferedImageOp inspired by:
|
||||
* http://www.peter-cockerell.net:8080/java/FloydSteinberg/FloydSteinbergFilterOp.java
|
||||
*
|
||||
*/
|
||||
|
||||
package helma.image;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.*;
|
||||
import java.awt.image.*;
|
||||
|
||||
public class DiffusionFilterOp implements BufferedImageOp {
|
||||
|
||||
protected final static int[] diffusionMatrix = {
|
||||
0, 0, 0,
|
||||
0, 0, 7,
|
||||
3, 5, 1,
|
||||
};
|
||||
|
||||
private int[] matrix;
|
||||
private int sum;
|
||||
private boolean serpentine = true;
|
||||
private int[] colorMap;
|
||||
|
||||
/**
|
||||
* Construct a DiffusionFilter
|
||||
*/
|
||||
public DiffusionFilterOp() {
|
||||
setMatrix(diffusionMatrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to use a serpentine pattern for return or not.
|
||||
* This can reduce 'avalanche' artifacts in the output.
|
||||
* @param serpentine true to use serpentine pattern
|
||||
*/
|
||||
public void setSerpentine(boolean serpentine) {
|
||||
this.serpentine = serpentine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the serpentine setting
|
||||
* @return the current setting
|
||||
*/
|
||||
public boolean getSerpentine() {
|
||||
return serpentine;
|
||||
}
|
||||
|
||||
public void setMatrix(int[] matrix) {
|
||||
this.matrix = matrix;
|
||||
sum = 0;
|
||||
for (int i = 0; i < matrix.length; i++)
|
||||
sum += matrix[i];
|
||||
}
|
||||
|
||||
public int[] getMatrix() {
|
||||
return matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the filter operation
|
||||
*
|
||||
* @param src The source BufferedImage. Can be any type.
|
||||
* @param dst The destination image. If not null, must be of type TYPE_BYTE_INDEXED
|
||||
* @return A dithered version of src in a BufferedImage of type TYPE_BYTE_INDEXED
|
||||
*/
|
||||
public BufferedImage filter(BufferedImage src, BufferedImage dst) {
|
||||
|
||||
// If there's no dest. create one
|
||||
if (dst == null)
|
||||
dst = createCompatibleDestImage(src, null);
|
||||
|
||||
// Otherwise check that the provided dest is an indexed image
|
||||
else if (dst.getType() != BufferedImage.TYPE_BYTE_INDEXED) {
|
||||
throw new IllegalArgumentException("Wrong Destination Buffer type");
|
||||
}
|
||||
|
||||
DataBufferByte dstBuffer = (DataBufferByte) dst.getRaster().getDataBuffer();
|
||||
byte dstData[] = dstBuffer.getData();
|
||||
|
||||
// Other things to test are pixel bit strides, scanline stride and transfer type
|
||||
// Same goes for the source image
|
||||
|
||||
IndexColorModel icm = (IndexColorModel) dst.getColorModel();
|
||||
colorMap = new int[icm.getMapSize()];
|
||||
icm.getRGBs(colorMap);
|
||||
|
||||
int width = src.getWidth();
|
||||
int height = src.getHeight();
|
||||
|
||||
// This is the offset into the buffer of the current source pixel
|
||||
int index = 0;
|
||||
|
||||
// Loop through each pixel
|
||||
// create a BufferedImage of only 1 pixel height for fetching the rows
|
||||
// of the image in the correct format (ARGB)
|
||||
// This speeds up things by more than factor 2, compared to the standard
|
||||
// BufferedImage.getRGB solution
|
||||
BufferedImage row = new BufferedImage(width, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = row.createGraphics();
|
||||
int pixels[] = ((DataBufferInt) row.getRaster().getDataBuffer()).getData();
|
||||
// make sure alpha values do not add up for each row:
|
||||
g2d.setComposite(AlphaComposite.Src);
|
||||
// calculate scanline by scanline in order to safe memory.
|
||||
// It also seems to run faster like that
|
||||
int rowIndex = 0;
|
||||
for (int y = 0; y < height; y++, rowIndex += width) {
|
||||
g2d.drawImage(src, null, 0, -y);
|
||||
// now pixels contains the rgb values of the row y!
|
||||
boolean reverse = serpentine && (y & 1) == 1;
|
||||
int direction;
|
||||
if (reverse) {
|
||||
index = width - 1;
|
||||
direction = -1;
|
||||
} else {
|
||||
index = 0;
|
||||
direction = 1;
|
||||
}
|
||||
for (int x = 0; x < width; x++) {
|
||||
int rgb1 = pixels[index];
|
||||
int a1 = (rgb1 >> 24) & 0xff;
|
||||
int r1 = (rgb1 >> 16) & 0xff;
|
||||
int g1 = (rgb1 >> 8) & 0xff;
|
||||
int b1 = rgb1 & 0xff;
|
||||
|
||||
int idx = findIndex(r1, g1, b1, a1);
|
||||
dstData[rowIndex + index] = (byte) idx;
|
||||
|
||||
int rgb2 = colorMap[idx];
|
||||
int a2 = (rgb2 >> 24) & 0xff;
|
||||
int r2 = (rgb2 >> 16) & 0xff;
|
||||
int g2 = (rgb2 >> 8) & 0xff;
|
||||
int b2 = rgb2 & 0xff;
|
||||
|
||||
int er = r1 - r2;
|
||||
int eg = g1 - g2;
|
||||
int eb = b1 - b2;
|
||||
int ea = a1 - a2;
|
||||
|
||||
for (int i = -1; i <= 1; i++) {
|
||||
int iy = i + y;
|
||||
if (0 <= iy && iy < height) {
|
||||
for (int j = -1; j <= 1; j++) {
|
||||
int jx = j + x;
|
||||
if (0 <= jx && jx < width) {
|
||||
int w;
|
||||
if (reverse)
|
||||
w = matrix[(i + 1) * 3 - j + 1];
|
||||
else
|
||||
w = matrix[(i + 1) * 3 + j + 1];
|
||||
if (w != 0) {
|
||||
int k = reverse ? index - j : index + j;
|
||||
rgb1 = pixels[k];
|
||||
a1 = ((rgb1 >> 24) & 0xff) + ea * w / sum;
|
||||
r1 = ((rgb1 >> 16) & 0xff) + er * w / sum;
|
||||
g1 = ((rgb1 >> 8) & 0xff) + eg * w / sum;
|
||||
b1 = (rgb1 & 0xff) + eb * w / sum;
|
||||
pixels[k] =
|
||||
(clamp(a1) << 24) |
|
||||
(clamp(r1) << 16) |
|
||||
(clamp(g1) << 8) |
|
||||
clamp(b1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
index += direction;
|
||||
}
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
private static int clamp(int c) {
|
||||
if (c < 0)
|
||||
return 0;
|
||||
if (c > 255)
|
||||
return 255;
|
||||
return c;
|
||||
}
|
||||
|
||||
int findIndex(int r1, int g1, int b1, int a1)
|
||||
throws ArrayIndexOutOfBoundsException {
|
||||
int idx = 0;
|
||||
int dist = Integer.MAX_VALUE;
|
||||
for (int i = 0; i < colorMap.length; i++) {
|
||||
int rgb2 = colorMap[i];
|
||||
int da = a1 - ((rgb2 >> 24) & 0xff);
|
||||
int dr = r1 - ((rgb2 >> 16) & 0xff);
|
||||
int dg = g1 - ((rgb2 >> 8) & 0xff);
|
||||
int db = b1 - (rgb2 & 0xff);
|
||||
int newdist = da * da + dr * dr + dg * dg + db * db;
|
||||
if (newdist < dist) {
|
||||
idx = i;
|
||||
dist = newdist;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
// This always returns an indexed image
|
||||
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
|
||||
return new BufferedImage(src.getWidth(), src.getHeight(),
|
||||
BufferedImage.TYPE_BYTE_INDEXED);
|
||||
}
|
||||
|
||||
// There are no rendering hints
|
||||
public RenderingHints getRenderingHints() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// No transformation, so return the source point
|
||||
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
|
||||
if (dstPt == null)
|
||||
dstPt = new Point2D.Float();
|
||||
dstPt.setLocation(srcPt.getX(), srcPt.getY());
|
||||
return dstPt;
|
||||
}
|
||||
|
||||
// No transformation, so return the source bounds
|
||||
public Rectangle2D getBounds2D(BufferedImage src) {
|
||||
return src.getRaster().getBounds();
|
||||
}
|
||||
}
|
533
src/main/java/helma/image/GIFEncoder.java
Normal file
533
src/main/java/helma/image/GIFEncoder.java
Normal file
|
@ -0,0 +1,533 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
/*
|
||||
* The GIF encoding routines are based on the Acme libary
|
||||
*
|
||||
* The following changes and extensions were added by Juerg Lehni:
|
||||
*
|
||||
* - encode now directly works on BufferedImage objects, the ImageEncoder
|
||||
* and ImageConsumer oriented frameworks has been removed.
|
||||
* - Only BufferedImages with IndexColorModel are taken, so no more
|
||||
* palette optimization with IntHashtable objects are necessary. If the
|
||||
* BufferedImage is in wrong format, helma.image.ColorQuantizer is used to
|
||||
* convert it into a index based image.
|
||||
* - This version is much less space consuming as it only takes one sample
|
||||
* row of the rastered image at a time into memory during compression.
|
||||
* - The overall time for color reduction and gif compression should
|
||||
* therefore be greatly reduced.
|
||||
*
|
||||
* Acme Disclaimer:
|
||||
*
|
||||
* Transparency handling and variable bit size courtesy of Jack Palevich.
|
||||
*
|
||||
* Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* Visit the ACME Labs Java page for up-to-date versions of this and other
|
||||
* fine Java utilities: http://www.acme.com/java/
|
||||
*
|
||||
* GifEncoder is adapted from ppmtogif, which is based on GIFENCOD by David
|
||||
* Rowley <mgardi@watdscu.waterloo.edu>. Lempel-Zim compression
|
||||
* based on "compress".
|
||||
*
|
||||
*/
|
||||
|
||||
package helma.image;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.Raster;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
public class GIFEncoder {
|
||||
private boolean interlace = false;
|
||||
|
||||
private int width, height;
|
||||
private Raster raster;
|
||||
// DataOutput is used for compatibility with ImageIO (see helma.image.imageio.gif.GIFImageWriter)
|
||||
// otherwise, OutputStream would make much more sense here:
|
||||
private DataOutput out;
|
||||
|
||||
private int curx, cury;
|
||||
private int countdown;
|
||||
private int pass;
|
||||
private int[] row;
|
||||
|
||||
public void encode(BufferedImage bi, DataOutput out) throws IOException {
|
||||
encode(bi, out, false, null);
|
||||
}
|
||||
|
||||
public void encode(BufferedImage bi, DataOutput out, boolean interlace,
|
||||
String comment) throws IOException {
|
||||
this.out = out;
|
||||
this.interlace = interlace;
|
||||
|
||||
// make sure it's index colors:
|
||||
if (bi.getType() != BufferedImage.TYPE_BYTE_INDEXED)
|
||||
bi = ColorQuantizer.quantizeImage(bi, 256, false, true);
|
||||
|
||||
raster = bi.getRaster();
|
||||
|
||||
width = bi.getWidth();
|
||||
height = bi.getHeight();
|
||||
|
||||
int numPixels = width * height;
|
||||
|
||||
IndexColorModel icm = (IndexColorModel) bi.getColorModel();
|
||||
int transparentIndex = icm.getTransparentPixel();
|
||||
int numColors = icm.getMapSize();
|
||||
|
||||
// Figure out how many bits to use.
|
||||
int bitsPerPixel;
|
||||
if (numColors <= 2)
|
||||
bitsPerPixel = 1;
|
||||
else if (numColors <= 4)
|
||||
bitsPerPixel = 2;
|
||||
else if (numColors <= 16)
|
||||
bitsPerPixel = 4;
|
||||
else
|
||||
bitsPerPixel = 8;
|
||||
|
||||
int initCodeSize;
|
||||
|
||||
// Calculate number of bits we are expecting
|
||||
countdown = numPixels;
|
||||
|
||||
// Indicate which pass we are on (if interlace)
|
||||
pass = 0;
|
||||
|
||||
// The initial code size
|
||||
if (bitsPerPixel <= 1)
|
||||
initCodeSize = 2;
|
||||
else
|
||||
initCodeSize = bitsPerPixel;
|
||||
|
||||
// Set up the current x and y position
|
||||
curx = 0;
|
||||
cury = 0;
|
||||
row = new int[width];
|
||||
|
||||
// Write the Magic header
|
||||
writeString("GIF89a");
|
||||
|
||||
// Write out the screen width and height
|
||||
writeWord(width);
|
||||
writeWord(height);
|
||||
|
||||
// Indicate that there is a global colour map
|
||||
byte flags = (byte) 0x80; // Yes, there is a color map
|
||||
// OR in the resolution
|
||||
flags |= (byte) ((8 - 1) << 4);
|
||||
// Not sorted
|
||||
// OR in the Bits per Pixel
|
||||
flags |= (byte) ((bitsPerPixel - 1));
|
||||
// Write it out
|
||||
out.write(flags);
|
||||
|
||||
// Write out the Background colour
|
||||
out.write((byte) 0);
|
||||
|
||||
// Pixel aspect ratio - 1:1.
|
||||
//out.write((byte) 49);
|
||||
// Java's GIF reader currently has a bug, if the aspect ratio byte is
|
||||
// not zero it throws an ImageFormatException. It doesn't know that
|
||||
// 49 means a 1:1 aspect ratio. Well, whatever, zero works with all
|
||||
// the other decoders I've tried so it probably doesn't hurt.
|
||||
out.write((byte) 0);
|
||||
|
||||
// Write out the Global Colour Map
|
||||
// Turn colors into colormap entries.
|
||||
int mapSize = 1 << bitsPerPixel;
|
||||
byte[] reds = new byte[mapSize], greens = new byte[mapSize], blues = new byte[mapSize];
|
||||
icm.getReds(reds);
|
||||
icm.getGreens(greens);
|
||||
icm.getBlues(blues);
|
||||
|
||||
for (int i = 0; i < mapSize; ++i) {
|
||||
out.write(reds[i]);
|
||||
out.write(greens[i]);
|
||||
out.write(blues[i]);
|
||||
}
|
||||
|
||||
// Write out extension for transparent colour index, if necessary.
|
||||
if (transparentIndex != -1) {
|
||||
out.write((byte) '!');
|
||||
out.write((byte) 0xf9);
|
||||
out.write((byte) 4);
|
||||
out.write((byte) 1);
|
||||
out.write((byte) 0);
|
||||
out.write((byte) 0);
|
||||
out.write((byte) transparentIndex);
|
||||
out.write((byte) 0);
|
||||
}
|
||||
|
||||
// Write an Image separator
|
||||
out.write((byte) ',');
|
||||
|
||||
// Write the Image header
|
||||
writeWord(0); // leftOfs
|
||||
writeWord(0); // topOfs
|
||||
writeWord(width);
|
||||
writeWord(height);
|
||||
|
||||
// Write out whether or not the image is interlaced
|
||||
if (interlace)
|
||||
out.write((byte) 0x40);
|
||||
else
|
||||
out.write((byte) 0x00);
|
||||
|
||||
// Write out the initial code size
|
||||
out.write((byte) initCodeSize);
|
||||
|
||||
// Go and actually compress the data
|
||||
compress(initCodeSize + 1);
|
||||
|
||||
// Write out a Zero-length packet (to end the series)
|
||||
out.write((byte) 0);
|
||||
|
||||
// Write out the comment
|
||||
if (comment != null && comment.length() > 0) {
|
||||
out.write((byte) 0x21);
|
||||
out.write((byte) 0xFE);
|
||||
out.write((byte) comment.length());
|
||||
writeString(comment);
|
||||
out.write((byte) 0);
|
||||
}
|
||||
|
||||
// Write the GIF file terminator
|
||||
out.write((byte) ';');
|
||||
}
|
||||
|
||||
// Return the next pixel from the image
|
||||
int getNextPixel() throws IOException {
|
||||
if (countdown == 0)
|
||||
return -1;
|
||||
|
||||
--countdown;
|
||||
|
||||
if (curx == 0)
|
||||
row = raster.getSamples(0, cury, width, 1, 0, row);
|
||||
int index = row[curx];
|
||||
|
||||
// Bump the current X position
|
||||
++curx;
|
||||
|
||||
// If we are at the end of a scan line, set curx back to the beginning
|
||||
// If we are interlaced, bump the cury to the appropriate spot,
|
||||
// otherwise, just increment it.
|
||||
if (curx == width) {
|
||||
curx = 0;
|
||||
|
||||
if (!interlace) {
|
||||
++cury;
|
||||
} else {
|
||||
switch (pass) {
|
||||
case 0:
|
||||
cury += 8;
|
||||
if (cury >= height) {
|
||||
++pass;
|
||||
cury = 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
cury += 8;
|
||||
if (cury >= height) {
|
||||
++pass;
|
||||
cury = 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
cury += 4;
|
||||
if (cury >= height) {
|
||||
++pass;
|
||||
cury = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
cury += 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
void writeString(String str) throws IOException {
|
||||
byte[] buf = str.getBytes();
|
||||
out.write(buf);
|
||||
}
|
||||
|
||||
// Write out a word to the GIF file
|
||||
void writeWord(int w) throws IOException {
|
||||
out.write((byte) (w & 0xff));
|
||||
out.write((byte) ((w >> 8) & 0xff));
|
||||
}
|
||||
|
||||
// GIFCOMPR.C - GIF Image compression routines
|
||||
//
|
||||
// Lempel-Ziv compression based on 'compress'. GIF modifications by
|
||||
// David Rowley (mgardi@watdcsu.waterloo.edu)
|
||||
|
||||
// General DEFINEs
|
||||
|
||||
static final int BITS = 12;
|
||||
|
||||
static final int HASH_SIZE = 5003; // 80% occupancy
|
||||
|
||||
// GIF Image compression - modified 'compress'
|
||||
//
|
||||
// Based on: compress.c - File compression ala IEEE Computer, June 1984.
|
||||
//
|
||||
// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
|
||||
// Jim McKie (decvax!mcvax!jim)
|
||||
// Steve Davies (decvax!vax135!petsd!peora!srd)
|
||||
// Ken Turkowski (decvax!decwrl!turtlevax!ken)
|
||||
// James A. Woods (decvax!ihnp4!ames!jaw)
|
||||
// Joe Orost (decvax!vax135!petsd!joe)
|
||||
|
||||
private int numBits; // number of bits/code
|
||||
private int maxBits = BITS; // user settable max # bits/code
|
||||
private int maxCode; // maximum code, given numBits
|
||||
private int maxMaxCode = 1 << BITS; // should NEVER generate this code
|
||||
|
||||
final int getMaxCode(int numBits) {
|
||||
return (1 << numBits) - 1;
|
||||
}
|
||||
|
||||
private int[] hashTable = new int[HASH_SIZE];
|
||||
private int[] codeTable = new int[HASH_SIZE];
|
||||
|
||||
private int freeEntry = 0; // first unused entry
|
||||
|
||||
// block compression parameters -- after all codes are used up,
|
||||
// and compression rate changes, start over.
|
||||
private boolean clearFlag = false;
|
||||
|
||||
// Algorithm: use open addressing double hashing (no chaining) on the
|
||||
// prefix code / next character combination. We do a variant of Knuth's
|
||||
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
|
||||
// secondary probe. Here, the modular division first probe is gives way
|
||||
// to a faster exclusive-or manipulation. Also do block compression with
|
||||
// an adaptive reset, whereby the code table is cleared when the compression
|
||||
// ratio decreases, but after the table fills. The variable-length output
|
||||
// codes are re-sized at this point, and a special CLEAR code is generated
|
||||
// for the decompressor. Late addition: construct the table according to
|
||||
// file size for noticeable speed improvement on small files. Please direct
|
||||
// questions about this implementation to ames!jaw.
|
||||
|
||||
private int initBits;
|
||||
|
||||
private int clearCode;
|
||||
private int EOFCode;
|
||||
|
||||
void compress(int initBits) throws IOException {
|
||||
// Set up the globals: initBits - initial number of bits
|
||||
this.initBits = initBits;
|
||||
|
||||
// Set up the necessary values
|
||||
clearFlag = false;
|
||||
numBits = initBits;
|
||||
maxCode = getMaxCode(numBits);
|
||||
|
||||
clearCode = 1 << (initBits - 1);
|
||||
EOFCode = clearCode + 1;
|
||||
freeEntry = clearCode + 2;
|
||||
|
||||
charInit();
|
||||
|
||||
int ent = getNextPixel();
|
||||
|
||||
int hashShift = 0;
|
||||
for (int fcode = HASH_SIZE; fcode < 65536; fcode *= 2)
|
||||
++hashShift;
|
||||
hashShift = 8 - hashShift; // set hash code range bound
|
||||
|
||||
clearHash(); // clear hash table
|
||||
|
||||
output(clearCode);
|
||||
|
||||
int c;
|
||||
outerLoop: while ((c = getNextPixel()) != -1) {
|
||||
int fcode = (c << maxBits) + ent;
|
||||
int i = (c << hashShift) ^ ent; // xor hashing
|
||||
|
||||
if (hashTable[i] == fcode) {
|
||||
ent = codeTable[i];
|
||||
continue;
|
||||
} else if (hashTable[i] >= 0) { // non-empty slot
|
||||
int disp = HASH_SIZE - i; // secondary hash (after G. Knott)
|
||||
if (i == 0)
|
||||
disp = 1;
|
||||
do {
|
||||
if ((i -= disp) < 0)
|
||||
i += HASH_SIZE;
|
||||
|
||||
if (hashTable[i] == fcode) {
|
||||
ent = codeTable[i];
|
||||
continue outerLoop;
|
||||
}
|
||||
} while (hashTable[i] >= 0);
|
||||
}
|
||||
output(ent);
|
||||
ent = c;
|
||||
if (freeEntry < maxMaxCode) {
|
||||
codeTable[i] = freeEntry++; // code -> hashtable
|
||||
hashTable[i] = fcode;
|
||||
} else
|
||||
clearBlock();
|
||||
}
|
||||
// Put out the final code.
|
||||
output(ent);
|
||||
output(EOFCode);
|
||||
}
|
||||
|
||||
// output
|
||||
//
|
||||
// Output the given code.
|
||||
// Inputs:
|
||||
// code: A numBits-bit integer. If == -1, then EOF. This assumes
|
||||
// that numBits =< wordsize - 1.
|
||||
// Outputs:
|
||||
// Outputs code to the file.
|
||||
// Assumptions:
|
||||
// Chars are 8 bits long.
|
||||
// Algorithm:
|
||||
// Maintain a BITS character long buffer (so that 8 codes will
|
||||
// fit in it exactly). Use the VAX insv instruction to insert each
|
||||
// code in turn. When the buffer fills up empty it and start over.
|
||||
|
||||
int curAccum = 0;
|
||||
int curBits = 0;
|
||||
|
||||
int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F,
|
||||
0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF,
|
||||
0x7FFF, 0xFFFF };
|
||||
|
||||
void output(int code) throws IOException {
|
||||
curAccum &= masks[curBits];
|
||||
|
||||
if (curBits > 0)
|
||||
curAccum |= (code << curBits);
|
||||
else
|
||||
curAccum = code;
|
||||
|
||||
curBits += numBits;
|
||||
|
||||
while (curBits >= 8) {
|
||||
charOut((byte) (curAccum & 0xff));
|
||||
curAccum >>= 8;
|
||||
curBits -= 8;
|
||||
}
|
||||
|
||||
// If the next entry is going to be too big for the code size,
|
||||
// then increase it, if possible.
|
||||
if (freeEntry > maxCode || clearFlag) {
|
||||
if (clearFlag) {
|
||||
maxCode = getMaxCode(numBits = initBits);
|
||||
clearFlag = false;
|
||||
} else {
|
||||
++numBits;
|
||||
if (numBits == maxBits)
|
||||
maxCode = maxMaxCode;
|
||||
else
|
||||
maxCode = getMaxCode(numBits);
|
||||
}
|
||||
}
|
||||
|
||||
if (code == EOFCode) {
|
||||
// At EOF, write the rest of the buffer.
|
||||
while (curBits > 0) {
|
||||
charOut((byte) (curAccum & 0xff));
|
||||
curAccum >>= 8;
|
||||
curBits -= 8;
|
||||
}
|
||||
|
||||
charFlush();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear out the hash table
|
||||
|
||||
// table clear for block compress
|
||||
void clearBlock() throws IOException {
|
||||
clearHash();
|
||||
freeEntry = clearCode + 2;
|
||||
clearFlag = true;
|
||||
|
||||
output(clearCode);
|
||||
}
|
||||
|
||||
// reset code table
|
||||
void clearHash() {
|
||||
for (int i = 0; i < HASH_SIZE; ++i)
|
||||
hashTable[i] = -1;
|
||||
}
|
||||
|
||||
// GIF Specific routines
|
||||
|
||||
// Number of characters so far in this 'packet'
|
||||
private int a_count;
|
||||
|
||||
// Set up the 'byte output' routine
|
||||
void charInit() {
|
||||
a_count = 0;
|
||||
}
|
||||
|
||||
// Define the storage for the packet accumulator
|
||||
private byte[] accum = new byte[256];
|
||||
|
||||
// Add a character to the end of the current packet, and if it is 254
|
||||
// characters, flush the packet to disk.
|
||||
void charOut(byte c) throws IOException {
|
||||
accum[a_count++] = c;
|
||||
if (a_count >= 254)
|
||||
charFlush();
|
||||
}
|
||||
|
||||
// Flush the packet to disk, and reset the accumulator
|
||||
void charFlush() throws IOException {
|
||||
if (a_count > 0) {
|
||||
out.write(a_count);
|
||||
out.write(accum, 0, a_count);
|
||||
a_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
257
src/main/java/helma/image/ImageFilterOp.java
Normal file
257
src/main/java/helma/image/ImageFilterOp.java
Normal file
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License Version 2.0 (the
|
||||
* "License"). You may not use this file except in compliance with the License.
|
||||
* A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class does pretty much the opposite of java.awt.image.BufferedImageFilter:
|
||||
* It wraps an ImageFilter in a BufferedImageOp
|
||||
* Optimizations have been added, like the ignoring of color models
|
||||
* and the assumption of INT_RGB type for destination buffers in
|
||||
* order to speed things up by almost a factor of 4.
|
||||
*/
|
||||
|
||||
package helma.image;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.*;
|
||||
import java.awt.image.*;
|
||||
import java.util.Hashtable;
|
||||
|
||||
public class ImageFilterOp implements BufferedImageOp {
|
||||
ImageFilter filter;
|
||||
|
||||
/**
|
||||
* Construct a ImageFilterOp
|
||||
*/
|
||||
public ImageFilterOp(ImageFilter filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the filter operation
|
||||
*
|
||||
* @param src The source BufferedImage. Can be any type.
|
||||
* @param dst The destination image. If not null, must be of type
|
||||
* TYPE_INT_RGB, TYPE_INT_ARGB or TYPE_INT_ARGB_PRE
|
||||
* @return the filtered image
|
||||
*/
|
||||
public BufferedImage filter(BufferedImage src, BufferedImage dst) {
|
||||
int width = src.getWidth();
|
||||
int height = src.getHeight();
|
||||
|
||||
BufferedImageConsumer consumer = new BufferedImageConsumer(dst);
|
||||
|
||||
ImageFilter fltr = filter.getFilterInstance(consumer);
|
||||
fltr.setDimensions(width, height);
|
||||
|
||||
/*
|
||||
ColorModel cm = src.getColorModel();
|
||||
if (cm.getPixelSize() == 8) {
|
||||
// byte. indexed or gray:
|
||||
WritableRaster raster = src.getRaster();
|
||||
byte pixels[] = new byte[width];
|
||||
// calculate scanline by scanline in order to safe memory.
|
||||
// It also seems to run faster like that
|
||||
for (int y = 0; y < height; y++) {
|
||||
raster.getDataElements(0, y, width, 1, pixels);
|
||||
fltr.setPixels(0, y, width, 1, cm, pixels, 0, width);
|
||||
}
|
||||
} else {
|
||||
// integer, use the simple rgb mode:
|
||||
WritableRaster raster = src.getRaster();
|
||||
int pixels[] = new int[width];
|
||||
// calculate scanline by scanline in order to safe memory.
|
||||
// It also seems to run faster like that
|
||||
for (int y = 0; y < height; y++) {
|
||||
raster.getDataElements(0, y, width, 1, pixels);
|
||||
fltr.setPixels(0, y, width, 1, cm, pixels, 0, width);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Always work in integer mode. this is more effective, and most
|
||||
// filters convert to integer internally anyhow
|
||||
ColorModel cm = new SimpleColorModel();
|
||||
|
||||
// Create a BufferedImage of only 1 pixel height for fetching the rows of the image in the correct format (ARGB)
|
||||
// This speeds up things by more than factor 2, compared to the standard BufferedImage.getRGB solution,
|
||||
// which is supposed to be fast too. This is probably the case because drawing to BufferedImages uses
|
||||
// very optimized code which may even be hardware accelerated.
|
||||
BufferedImage row = new BufferedImage(width, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = row.createGraphics();
|
||||
int pixels[] = ((DataBufferInt)row.getRaster().getDataBuffer()).getData();
|
||||
|
||||
// Make sure alpha values do not add up for each row:
|
||||
g2d.setComposite(AlphaComposite.Src);
|
||||
// Calculate scanline by scanline in order to safe memory.
|
||||
// It also seems to run faster like that
|
||||
for (int y = 0; y < height; y++) {
|
||||
g2d.drawImage(src, null, 0, -y);
|
||||
// Now pixels contains the rgb values of the row y!
|
||||
// filter this row now:
|
||||
fltr.setPixels(0, y, width, 1, cm, pixels, 0, width);
|
||||
}
|
||||
g2d.dispose();
|
||||
// The consumer now contains the filtered image, return it.
|
||||
return consumer.getImage();
|
||||
}
|
||||
|
||||
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public RenderingHints getRenderingHints() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(BufferedImage src) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a dummy ColorModel that does nothing else than returning an
|
||||
* unchanged rgb value in getRGB, getRed, getGreen, getBlue, getAlpha.
|
||||
* This speeds up things for BufferedImages by at least a factor 1.5!
|
||||
*/
|
||||
class SimpleColorModel extends ColorModel {
|
||||
public SimpleColorModel() {
|
||||
this(32);
|
||||
}
|
||||
|
||||
public SimpleColorModel(int bits) {
|
||||
super(bits);
|
||||
}
|
||||
|
||||
public int getRGB(int rgb) {
|
||||
// This is the part that speeds up most.
|
||||
// java.awt.image.ColorModel would return the same value, but with
|
||||
// 4 function calls and a lot of shifts and ors per color!
|
||||
return rgb;
|
||||
}
|
||||
|
||||
public int getAlpha(int pixel) {
|
||||
return pixel >> 24;
|
||||
}
|
||||
|
||||
public int getRed(int pixel) {
|
||||
return (pixel >> 16) & 0xff;
|
||||
}
|
||||
|
||||
public int getGreen(int pixel) {
|
||||
return (pixel >> 8) & 0xff;
|
||||
}
|
||||
|
||||
public int getBlue(int pixel) {
|
||||
return pixel & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a dummy ImageConsumser that does nothing else than writing
|
||||
* The resulting rows from the ImageFilter into the image
|
||||
* If the image was not specified in the constructor, setDimensions
|
||||
* creates it with the given dimensions.
|
||||
*/
|
||||
class BufferedImageConsumer implements ImageConsumer {
|
||||
BufferedImage image;
|
||||
BufferedImage compatible;
|
||||
int width;
|
||||
int height;
|
||||
boolean first = true;
|
||||
|
||||
/*
|
||||
* Constructor with no compatible image. if image is null, setDimensions
|
||||
* will create a default INT_ARGB image of the size defined by the filter.
|
||||
*/
|
||||
public BufferedImageConsumer(BufferedImage image) {
|
||||
this(image, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructor with a compatible image. if image is null, setDimensions
|
||||
* will create a compatible image of the size defined by the filter.
|
||||
*/
|
||||
public BufferedImageConsumer(BufferedImage image, BufferedImage compatible) {
|
||||
this.image = image;
|
||||
this.compatible = compatible;
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void setDimensions(int w, int h) {
|
||||
if (image == null) {
|
||||
if (compatible != null) {
|
||||
// create a compatible image with the new dimensions:
|
||||
image = new BufferedImage(
|
||||
compatible.getColorModel(),
|
||||
compatible.getRaster().createCompatibleWritableRaster(w, h),
|
||||
compatible.isAlphaPremultiplied(),
|
||||
null
|
||||
);
|
||||
} else {
|
||||
// assume standard format:
|
||||
image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
}
|
||||
}
|
||||
width = image.getWidth();
|
||||
height = image.getHeight();
|
||||
}
|
||||
|
||||
public void setPixels(int x, int y, int w, int h, ColorModel model, int[] pixels, int off, int scansize) {
|
||||
// Cropping may be necessary: It's possible that the size of the
|
||||
// specified destination image is not the same as the size the
|
||||
// ImageFilter would produce!
|
||||
if (x < width && y < height) {
|
||||
if (x + w > width)
|
||||
w = width - x;
|
||||
if (y + h > height)
|
||||
h = height - y;
|
||||
|
||||
if (w > 0 && h > 0)
|
||||
image.setRGB(x, y, w, h, pixels, off, scansize);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPixels(int x, int y, int w, int h, ColorModel model, byte[] pixels, int off, int scansize) {
|
||||
if (x < width && y < height) {
|
||||
if (x + w > width)
|
||||
w = width - x;
|
||||
if (y + h > height)
|
||||
h = height - y;
|
||||
|
||||
if (w > 0 && h > 0)
|
||||
image.getRaster().setDataElements(x, y, w, h, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProperties(Hashtable props) {
|
||||
}
|
||||
|
||||
public void setColorModel(ColorModel model) {
|
||||
}
|
||||
|
||||
public void setHints(int hintflags) {
|
||||
}
|
||||
|
||||
public void imageComplete(int status) {
|
||||
}
|
||||
}
|
||||
}
|
236
src/main/java/helma/image/ImageGenerator.java
Normal file
236
src/main/java/helma/image/ImageGenerator.java
Normal file
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.image;
|
||||
|
||||
import helma.main.Server;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.*;
|
||||
import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Factory class for generating Image objects from various sources.
|
||||
*
|
||||
*/
|
||||
public abstract class ImageGenerator {
|
||||
protected static ImageGenerator generator = null;
|
||||
|
||||
/**
|
||||
* Returns an ImageGenerator singleton, creating it if necessary. If the JIMI
|
||||
* package is installed, an instance of {@link helma.image.jimi.JimiGenerator JimiGenerator}
|
||||
* will be returned. Otherwise, if the javax.imageio package is available,
|
||||
* an instance of {@link helma.image.imageio.ImageIOGenerator ImageIOGenerator}
|
||||
* is returned. Additionally, the class of the ImageGenerator implementation
|
||||
* to be used can be set using the <code>imageGenerator</code> property in either
|
||||
* the app.properties or server.properties file.
|
||||
*
|
||||
* @return a new ImageGenerator instance
|
||||
*/
|
||||
public static ImageGenerator getInstance() {
|
||||
if (generator == null) {
|
||||
// first see wether an image wrapper class was specified in
|
||||
// server.properties:
|
||||
String className = null;
|
||||
if (Server.getServer() != null) {
|
||||
className = Server.getServer().getProperty("imageGenerator");
|
||||
}
|
||||
|
||||
Class generatorClass = null;
|
||||
if (className == null) {
|
||||
// if no class is defined, try the default ones:
|
||||
try {
|
||||
// start with ImageIO
|
||||
Class.forName("javax.imageio.ImageIO");
|
||||
// if we're still here, ImageIOWrapper can be used
|
||||
className = "helma.image.imageio.ImageIOGenerator";
|
||||
} catch (ClassNotFoundException e1) {
|
||||
throw new RuntimeException("ImageIOGenerator cannot be used. Please use a custom image processing library for Java and set the imageGenerator property accordingly.");
|
||||
}
|
||||
}
|
||||
// now let's get the generator class and create an instance:
|
||||
try {
|
||||
generatorClass = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(
|
||||
"The imageGenerator class cannot be found: " + className);
|
||||
}
|
||||
try {
|
||||
generator = (ImageGenerator)generatorClass.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(
|
||||
"The ImageGenerator instance could not be created: "
|
||||
+ className);
|
||||
}
|
||||
}
|
||||
return generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param w ...
|
||||
* @param h ...
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public ImageWrapper createImage(int w, int h) {
|
||||
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
return new ImageWrapper(img, w, h, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param src ...
|
||||
*
|
||||
* @return ...
|
||||
* @throws IOException
|
||||
*/
|
||||
public ImageWrapper createImage(byte[] src) throws IOException {
|
||||
Image img = read(src);
|
||||
return img != null ? new ImageWrapper(img, this) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param filenamne ...
|
||||
*
|
||||
* @return ...
|
||||
* @throws IOException
|
||||
*/
|
||||
public ImageWrapper createImage(String filenamne)
|
||||
throws IOException {
|
||||
Image img = read(filenamne);
|
||||
return img != null ? new ImageWrapper(img, this) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url ...
|
||||
*
|
||||
* @return ...
|
||||
* @throws MalformedURLException
|
||||
* @throws IOException
|
||||
*/
|
||||
public ImageWrapper createImage(URL url)
|
||||
throws MalformedURLException, IOException {
|
||||
Image img = read(url);
|
||||
return img != null ? new ImageWrapper(img, this) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param input ...
|
||||
* @return ...
|
||||
* @throws IOException
|
||||
*/
|
||||
public ImageWrapper createImage(InputStream input)
|
||||
throws IOException {
|
||||
Image img = read(input);
|
||||
return img != null ? new ImageWrapper(img, this) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param iw ...
|
||||
* @param filter ...
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public ImageWrapper createImage(ImageWrapper iw, ImageFilter filter) {
|
||||
// use the ImagFilterOp wrapper for ImageFilters that works directly
|
||||
// on BufferedImages. The filtering is much faster like that.
|
||||
// Attention: needs testing with all the filters!
|
||||
|
||||
return createImage(iw, new ImageFilterOp(filter));
|
||||
// Image img = ImageWaiter.waitForImage(
|
||||
// Toolkit.getDefaultToolkit().createImage(
|
||||
// new FilteredImageSource(iw.getSource(), filter)));
|
||||
// return img != null ? new ImageWrapper(img, this) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iw ...
|
||||
* @param imageOp ...
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public ImageWrapper createImage(ImageWrapper iw, BufferedImageOp imageOp) {
|
||||
Image img = imageOp.filter(iw.getBufferedImage(), null);
|
||||
return img != null ? new ImageWrapper(img, this) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param filename the filename of the image to create
|
||||
*
|
||||
* @return the newly created image
|
||||
* @throws IOException
|
||||
*/
|
||||
public Image read(String filename) throws IOException {
|
||||
return ImageIO.read(new File(filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url the URL of the image to create
|
||||
*
|
||||
* @return the newly created image
|
||||
* @throws IOException
|
||||
*/
|
||||
public Image read(URL url) throws IOException {
|
||||
return ImageIO.read(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param src the data of the image to create
|
||||
*
|
||||
* @return the newly created image
|
||||
* @throws IOException
|
||||
*/
|
||||
public Image read(byte[] src) throws IOException {
|
||||
return ImageIO.read(new ByteArrayInputStream(src));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param input the data of the image to create
|
||||
*
|
||||
* @return the newly created image
|
||||
* @throws IOException
|
||||
*/
|
||||
public Image read(InputStream input) throws IOException {
|
||||
return ImageIO.read(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the image. Image format is deduced from filename.
|
||||
*
|
||||
* @param wrapper
|
||||
* @param filename
|
||||
* @param quality
|
||||
* @param alpha
|
||||
* @throws IOException
|
||||
*/
|
||||
public abstract void write(ImageWrapper wrapper, String filename,
|
||||
float quality, boolean alpha) throws IOException;
|
||||
|
||||
/**
|
||||
* Saves the image. Image format is deduced from the dataSource.
|
||||
*
|
||||
* @param wrapper
|
||||
* @param out
|
||||
* @param quality
|
||||
* @param alpha
|
||||
* @throws IOException
|
||||
*/
|
||||
public abstract void write(ImageWrapper wrapper, OutputStream out, String type,
|
||||
float quality, boolean alpha) throws IOException;
|
||||
}
|
1299
src/main/java/helma/image/ImageInfo.java
Normal file
1299
src/main/java/helma/image/ImageInfo.java
Normal file
File diff suppressed because it is too large
Load diff
111
src/main/java/helma/image/ImageWaiter.java
Normal file
111
src/main/java/helma/image/ImageWaiter.java
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.image;
|
||||
|
||||
import java.awt.Image;
|
||||
|
||||
import java.awt.image.ImageObserver;
|
||||
|
||||
/**
|
||||
* The ImageWaiter will only be used like this:
|
||||
* image = ImageWaiter.waitForImage(image);
|
||||
*/
|
||||
public class ImageWaiter implements ImageObserver {
|
||||
Image image;
|
||||
int width;
|
||||
int height;
|
||||
boolean waiting;
|
||||
boolean firstFrameLoaded;
|
||||
|
||||
private ImageWaiter(Image image) {
|
||||
this.image = image;
|
||||
waiting = true;
|
||||
firstFrameLoaded = false;
|
||||
}
|
||||
|
||||
public static Image waitForImage(Image image) {
|
||||
ImageWaiter waiter = new ImageWaiter(image);
|
||||
try {
|
||||
waiter.waitForImage();
|
||||
} finally {
|
||||
waiter.done();
|
||||
}
|
||||
return waiter.width == -1 || waiter.height == -1 ? null : image;
|
||||
}
|
||||
|
||||
private synchronized void waitForImage() {
|
||||
width = image.getWidth(this);
|
||||
height = image.getHeight(this);
|
||||
|
||||
if (width == -1 || height == -1) {
|
||||
try {
|
||||
wait(45000);
|
||||
} catch (InterruptedException x) {
|
||||
waiting = false;
|
||||
return;
|
||||
} finally {
|
||||
waiting = false;
|
||||
}
|
||||
}
|
||||
|
||||
// if width and height haven't been set, throw tantrum
|
||||
if (width == -1 || height == -1) {
|
||||
throw new RuntimeException("Error loading image");
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void done() {
|
||||
waiting = false;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
public synchronized boolean imageUpdate(Image img, int infoflags, int x,
|
||||
int y, int w, int h) {
|
||||
// check if there was an error
|
||||
if (!waiting || (infoflags & ERROR) > 0 || (infoflags & ABORT) > 0) {
|
||||
// we either timed out or there was an error.
|
||||
notifyAll();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((infoflags & WIDTH) > 0 || (infoflags & HEIGHT) > 0) {
|
||||
if ((infoflags & WIDTH) > 0) {
|
||||
width = w;
|
||||
}
|
||||
|
||||
if ((infoflags & HEIGHT) > 0) {
|
||||
height = h;
|
||||
}
|
||||
|
||||
if (width > -1 && h > -1 && firstFrameLoaded) {
|
||||
notifyAll();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((infoflags & ALLBITS) > 0 || (infoflags & FRAMEBITS) > 0) {
|
||||
firstFrameLoaded = true;
|
||||
notifyAll();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
679
src/main/java/helma/image/ImageWrapper.java
Normal file
679
src/main/java/helma/image/ImageWrapper.java
Normal file
|
@ -0,0 +1,679 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
/*
|
||||
* A few explanations:
|
||||
*
|
||||
* - this.image is either an AWT Image or a BufferedImage.
|
||||
* It depends on the ImageGenerator in what form the Image initially is.
|
||||
* (the ImageIO implementation only uses BufferedImages for example.)
|
||||
*
|
||||
* As soon as some action that needs the graphics object is performed and the
|
||||
* image is still in AWT format, it is converted to a BufferedImage
|
||||
*
|
||||
* Any internal function that performs graphical actions needs to call
|
||||
* getGraphics, never rely on this.graphics being set correctly!
|
||||
*
|
||||
* - ImageWrapper objects are created and safed by the ImageGenerator class
|
||||
* all different implementations of Imaging functionallity are implemented
|
||||
* as a ImageGenerator extending class.
|
||||
*
|
||||
*/
|
||||
|
||||
package helma.image;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.*;
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* Abstract base class for Image Wrappers.
|
||||
*/
|
||||
public class ImageWrapper {
|
||||
protected Image image;
|
||||
protected int width;
|
||||
protected int height;
|
||||
protected ImageGenerator generator;
|
||||
private Graphics2D graphics;
|
||||
|
||||
/**
|
||||
* Creates a new ImageWrapper object.
|
||||
*
|
||||
* @param image ...
|
||||
* @param width ...
|
||||
* @param height ...
|
||||
*/
|
||||
public ImageWrapper(Image image, int width, int height,
|
||||
ImageGenerator generator) {
|
||||
this.image = image;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.generator = generator;
|
||||
// graphics are turned off by default. getGraphics activates it if necessary.
|
||||
this.graphics = null;
|
||||
}
|
||||
|
||||
public ImageWrapper(Image image, ImageGenerator generator) {
|
||||
this(image, image.getWidth(null), image.getHeight(null), generator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the internal image object to a BufferedImage (if it's not
|
||||
* already) and returns it. this is necessary as not all images are of type
|
||||
* BufferedImage. e.g. images loaded from a resource with the Toolkit are
|
||||
* not. By using getBufferedImage, images are only converted to a
|
||||
* getBufferedImage when this is actually needed, which is better than
|
||||
* storing images as BufferedImage in general.
|
||||
*
|
||||
* @return the Image object as a BufferedImage
|
||||
*/
|
||||
public BufferedImage getBufferedImage() {
|
||||
if (!(image instanceof BufferedImage)) {
|
||||
BufferedImage buffered = new BufferedImage(width, height,
|
||||
BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = buffered.createGraphics();
|
||||
g2d.drawImage(image, 0, 0, null);
|
||||
g2d.dispose();
|
||||
setImage(buffered);
|
||||
}
|
||||
return (BufferedImage)image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Graphics object to directly paint to this Image. Converts the
|
||||
* internal image to a BufferedImage if necessary.
|
||||
*
|
||||
* @return the Graphics object for drawing into this image
|
||||
*/
|
||||
public Graphics2D getGraphics() {
|
||||
if (graphics == null) {
|
||||
// make sure the image is a BufferedImage and then create a graphics object
|
||||
BufferedImage img = getBufferedImage();
|
||||
graphics = img.createGraphics();
|
||||
}
|
||||
return graphics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal image and clears the stored graphics object.
|
||||
* Any code that is changing the internal image should do it through this function
|
||||
* to make sure getGraphcis() returns a valid graphics object the next time it is called.
|
||||
*/
|
||||
protected void setImage(Image img) {
|
||||
// flush image and dispose graphics before updating them
|
||||
if (graphics != null) {
|
||||
graphics.dispose();
|
||||
graphics = null;
|
||||
}
|
||||
if (image != null) {
|
||||
image.flush();
|
||||
}
|
||||
image = img;
|
||||
width = image.getWidth(null);
|
||||
height = image.getHeight(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a copy of this image.
|
||||
*
|
||||
* @return a clone of this image.
|
||||
*/
|
||||
public Object clone() {
|
||||
ImageWrapper wrapper = generator.createImage(this.width,
|
||||
this.height);
|
||||
wrapper.getGraphics().drawImage(image, 0, 0, null);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Image object represented by this ImageWrapper.
|
||||
*
|
||||
* @return the image object
|
||||
*/
|
||||
public Image getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ImageProducer of the wrapped image
|
||||
*
|
||||
* @return the images's ImageProducer
|
||||
*/
|
||||
public ImageProducer getSource() {
|
||||
return image.getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose the Graphics context and null out the image.
|
||||
*/
|
||||
public void dispose() {
|
||||
if (image != null) {
|
||||
image.flush();
|
||||
image = null;
|
||||
}
|
||||
if (graphics != null) {
|
||||
graphics.dispose();
|
||||
graphics = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the font used to write on this image.
|
||||
*/
|
||||
public void setFont(String name, int style, int size) {
|
||||
getGraphics().setFont(new Font(name, style, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color used to write/paint to this image.
|
||||
*
|
||||
* @param red ...
|
||||
* @param green ...
|
||||
* @param blue ...
|
||||
*/
|
||||
public void setColor(int red, int green, int blue) {
|
||||
getGraphics().setColor(new Color(red, green, blue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color used to write/paint to this image.
|
||||
*
|
||||
* @param color ...
|
||||
*/
|
||||
public void setColor(int color) {
|
||||
getGraphics().setColor(new Color(color));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color used to write/paint to this image.
|
||||
*
|
||||
* @param color ...
|
||||
*/
|
||||
public void setColor(Color color) {
|
||||
getGraphics().setColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color used to write/paint to this image.
|
||||
*
|
||||
* @param color ...
|
||||
*/
|
||||
public void setColor(String color) {
|
||||
getGraphics().setColor(Color.decode(color));
|
||||
}
|
||||
/**
|
||||
* Draws a string to this image at the given coordinates.
|
||||
*
|
||||
* @param str ...
|
||||
* @param x ...
|
||||
* @param y ...
|
||||
*/
|
||||
public void drawString(String str, int x, int y) {
|
||||
getGraphics().drawString(str, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a line to this image from x1/y1 to x2/y2.
|
||||
*
|
||||
* @param x1 ...
|
||||
* @param y1 ...
|
||||
* @param x2 ...
|
||||
* @param y2 ...
|
||||
*/
|
||||
public void drawLine(int x1, int y1, int x2, int y2) {
|
||||
getGraphics().drawLine(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a rectangle to this image.
|
||||
*
|
||||
* @param x ...
|
||||
* @param y ...
|
||||
* @param w ...
|
||||
* @param h ...
|
||||
*/
|
||||
public void drawRect(int x, int y, int w, int h) {
|
||||
getGraphics().drawRect(x, y, w, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws another image to this image.
|
||||
*
|
||||
* @param filename ...
|
||||
* @param x ...
|
||||
* @param y ...
|
||||
*/
|
||||
public void drawImage(String filename, int x, int y)
|
||||
throws IOException {
|
||||
Image img = generator.read(filename);
|
||||
if (img != null)
|
||||
getGraphics().drawImage(img, x, y, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws another image to this image.
|
||||
*
|
||||
* @param image ...
|
||||
* @param x ...
|
||||
* @param y ...
|
||||
*/
|
||||
public void drawImage(ImageWrapper image, int x, int y)
|
||||
throws IOException {
|
||||
getGraphics().drawImage(image.getImage(), x, y, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws another image to this image.
|
||||
*
|
||||
* @param image ...
|
||||
* @param at ...
|
||||
*/
|
||||
public void drawImage(ImageWrapper image, AffineTransform at)
|
||||
throws IOException {
|
||||
getGraphics().drawImage(image.getImage(), at, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a filled rectangle to this image.
|
||||
*
|
||||
* @param x ...
|
||||
* @param y ...
|
||||
* @param w ...
|
||||
* @param h ...
|
||||
*/
|
||||
public void fillRect(int x, int y, int w, int h) {
|
||||
getGraphics().fillRect(x, y, w, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of this image.
|
||||
*
|
||||
* @return the width of this image
|
||||
*/
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of this image.
|
||||
*
|
||||
* @return the height of this image
|
||||
*/
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crops the image.
|
||||
*
|
||||
* @param x ...
|
||||
* @param y ...
|
||||
* @param w ...
|
||||
* @param h ...
|
||||
*/
|
||||
public void crop(int x, int y, int w, int h) {
|
||||
// do not use the CropFilter any longer:
|
||||
if (image instanceof BufferedImage && x + w <= width && y + h <= height) {
|
||||
// BufferedImages define their own function for cropping:
|
||||
setImage(((BufferedImage)image).getSubimage(x, y, w, h));
|
||||
} else {
|
||||
// The internal image will be a BufferedImage after this.
|
||||
// Simply create it with the cropped dimensions and draw the image into it:
|
||||
BufferedImage buffered = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = buffered.createGraphics();
|
||||
g2d.drawImage(image, -x, -y, null);
|
||||
g2d.dispose();
|
||||
setImage(buffered);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims the image.
|
||||
*
|
||||
* @param x the x-coordinate of the pixel specifying the background color
|
||||
* @param y the y-coordinate of the pixel specifying the background color
|
||||
*/
|
||||
|
||||
public void trim(int x, int y) {
|
||||
trim(x, y, true, true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims the image.
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
* @param trimLeft
|
||||
* @param trimTop
|
||||
* @param trimRight
|
||||
* @param trimBottom
|
||||
*/
|
||||
public void trim(int x, int y, boolean trimLeft, boolean trimTop, boolean trimRight, boolean trimBottom) {
|
||||
BufferedImage bi = this.getBufferedImage();
|
||||
int color = bi.getRGB(x, y);
|
||||
int left = 0, top = 0, right = width - 1, bottom = height - 1;
|
||||
|
||||
// create a BufferedImage of only 1 pixel height for fetching the rows of the image in the correct format (ARGB)
|
||||
// This speeds up things by more than factor 2, compared to the standard BufferedImage.getRGB solution,
|
||||
// which is supposed to be fast too. This is probably the case because drawing to BufferedImages uses
|
||||
// very optimized code which may even be hardware accelerated.
|
||||
if (trimTop || trimBottom) {
|
||||
BufferedImage row = new BufferedImage(width, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = row.createGraphics();
|
||||
int pixels[] = ((DataBufferInt)row.getRaster().getDataBuffer()).getData();
|
||||
// make sure alpha values do not add up for each row:
|
||||
g2d.setComposite(AlphaComposite.Src);
|
||||
if (trimTop) {
|
||||
// top:
|
||||
for (top = 0; top < height; top++) {
|
||||
g2d.drawImage(bi, null, 0, -top);
|
||||
// now pixels contains the rgb values of the row y!
|
||||
// scan this row now:
|
||||
for (x = 0; x < width; x++) {
|
||||
if (pixels[x] != color)
|
||||
break;
|
||||
}
|
||||
if (x < width)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (trimBottom) {
|
||||
// bottom:
|
||||
for (bottom = height - 1; bottom > top; bottom--) {
|
||||
g2d.drawImage(bi, null, 0, -bottom);
|
||||
// now pixels contains the rgb values of the row y!
|
||||
// scan this row now:
|
||||
for (x = 0; x < width; x++) {
|
||||
if (pixels[x] != color)
|
||||
break;
|
||||
}
|
||||
if (x < width)
|
||||
break;
|
||||
}
|
||||
}
|
||||
g2d.dispose();
|
||||
}
|
||||
if (trimLeft || trimRight) {
|
||||
BufferedImage column = new BufferedImage(1, height, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = column.createGraphics();
|
||||
int pixels[] = ((DataBufferInt)column.getRaster().getDataBuffer()).getData();
|
||||
// make sure alpha values do not add up for each row:
|
||||
g2d.setComposite(AlphaComposite.Src);
|
||||
if (trimLeft) {
|
||||
// left:
|
||||
for (left = 0; left < width; left++) {
|
||||
g2d.drawImage(bi, null, -left, 0);
|
||||
// now pixels contains the rgb values of the row y!
|
||||
// scan this row now:
|
||||
for (y = 0; y < height; y++) {
|
||||
if (pixels[y] != color)
|
||||
break;
|
||||
}
|
||||
if (y < height)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (trimRight) {
|
||||
// right:
|
||||
for (right = width - 1; right > left; right--) {
|
||||
g2d.drawImage(bi, null, -right, 0);
|
||||
// now pixels contains the rgb values of the row y!
|
||||
// scan this row now:
|
||||
for (y = 0; y < height; y++) {
|
||||
if (pixels[y] != color)
|
||||
break;
|
||||
}
|
||||
if (y < height)
|
||||
break;
|
||||
}
|
||||
}
|
||||
g2d.dispose();
|
||||
}
|
||||
crop(left, top, right - left + 1, bottom - top + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the image using the Graphics2D approach
|
||||
*/
|
||||
protected void resize(int w, int h, boolean smooth) {
|
||||
BufferedImage buffered = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2d = buffered.createGraphics();
|
||||
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
smooth ? RenderingHints.VALUE_INTERPOLATION_BICUBIC :
|
||||
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
|
||||
);
|
||||
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
|
||||
smooth ? RenderingHints.VALUE_RENDER_QUALITY :
|
||||
RenderingHints.VALUE_RENDER_SPEED
|
||||
);
|
||||
|
||||
AffineTransform at = AffineTransform.getScaleInstance(
|
||||
(double) w / width,
|
||||
(double) h / height
|
||||
);
|
||||
g2d.drawImage(image, at, null);
|
||||
g2d.dispose();
|
||||
setImage(buffered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the image
|
||||
*
|
||||
* @param w ...
|
||||
* @param h ...
|
||||
*/
|
||||
public void resize(int w, int h) {
|
||||
double factor = Math.max(
|
||||
(double) w / width,
|
||||
(double) h / height
|
||||
);
|
||||
// If the image is scaled, used the Graphcis2D method, otherwise use AWT:
|
||||
if (factor > 1f) {
|
||||
// Scale it with the Graphics2D approach for superior quality.
|
||||
resize(w, h, true);
|
||||
} else {
|
||||
// Area averaging has the best results for shrinking of images:
|
||||
|
||||
// As getScaledInstance is asynchronous, the ImageWaiter is needed here too:
|
||||
// Image scaled = ImageWaiter.waitForImage(image.getScaledInstance(w, h, Image.SCALE_AREA_AVERAGING));
|
||||
// if (scaled == null)
|
||||
// throw new RuntimeException("Image cannot be resized.");
|
||||
|
||||
// This version is up to 4 times faster than getScaledInstance:
|
||||
ImageFilterOp filter = new ImageFilterOp(new AreaAveragingScaleFilter(w, h));
|
||||
setImage(filter.filter(getBufferedImage(), null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the image, using a fast and cheap algorithm
|
||||
*
|
||||
* @param w ...
|
||||
* @param h ...
|
||||
*/
|
||||
public void resizeFast(int w, int h) {
|
||||
resize(w, h, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the colors used in the image. Necessary before saving as GIF.
|
||||
*
|
||||
* @param colors colors the number of colors to use, usually <= 256.
|
||||
*/
|
||||
public void reduceColors(int colors) {
|
||||
reduceColors(colors, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the colors used in the image. Necessary before saving as GIF.
|
||||
*
|
||||
* @param colors colors the number of colors to use, usually <= 256.
|
||||
* @param dither ...
|
||||
*/
|
||||
public void reduceColors(int colors, boolean dither) {
|
||||
reduceColors(colors, dither, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the colors used in this image. Useful and necessary before saving
|
||||
* the image as GIF file.
|
||||
*
|
||||
* @param colors the number of colors to use, usually <= 256.
|
||||
* @param dither ...
|
||||
* @param alphaToBitmask ...
|
||||
*/
|
||||
|
||||
public void reduceColors(int colors, boolean dither, boolean alphaToBitmask) {
|
||||
setImage(ColorQuantizer.quantizeImage(getBufferedImage(), colors, dither,
|
||||
alphaToBitmask));
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the image. Image format is deduced from filename.
|
||||
*
|
||||
* @param filename ...
|
||||
* @throws IOException
|
||||
*/
|
||||
public void saveAs(String filename)
|
||||
throws IOException {
|
||||
saveAs(filename, -1f, false); // -1 means default quality
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the image. Image format is deduced from filename.
|
||||
*
|
||||
* @param filename ...
|
||||
* @param quality ...
|
||||
* @throws IOException
|
||||
*/
|
||||
public void saveAs(String filename, float quality)
|
||||
throws IOException {
|
||||
saveAs(filename, quality, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the image. Image format is deduced from filename.
|
||||
*
|
||||
* @param filename ...
|
||||
* @param quality ...
|
||||
* @param alpha ...
|
||||
* @throws IOException
|
||||
*/
|
||||
public void saveAs(String filename, float quality, boolean alpha)
|
||||
throws IOException {
|
||||
generator.write(this, checkFilename(filename), quality, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the image. Image format is deduced from mimeType.
|
||||
*
|
||||
* @param out ...
|
||||
* @param mimeType ...
|
||||
* @throws IOException
|
||||
*/
|
||||
public void saveAs(OutputStream out, String mimeType)
|
||||
throws IOException {
|
||||
generator.write(this, out, mimeType, -1f, false); // -1 means default quality
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the image. Image format is deduced from mimeType.
|
||||
*
|
||||
* @param out ...
|
||||
* @param mimeType ...
|
||||
* @param quality ...
|
||||
* @throws IOException
|
||||
*/
|
||||
public void saveAs(OutputStream out, String mimeType, float quality)
|
||||
throws IOException {
|
||||
generator.write(this, out, mimeType, quality, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the image. Image format is deduced from mimeType.
|
||||
*
|
||||
* @param out ...
|
||||
* @param mimeType ...
|
||||
* @param quality ...
|
||||
* @param alpha ...
|
||||
* @throws IOException
|
||||
*/
|
||||
public void saveAs(OutputStream out, String mimeType, float quality, boolean alpha)
|
||||
throws IOException {
|
||||
generator.write(this, out, mimeType, quality, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the palette index of the transparent color for Images with an
|
||||
* IndexColorModel. This can be used together with
|
||||
* {@link helma.image.ImageWrapper#getPixel}.
|
||||
*/
|
||||
public void setTransparentPixel(int trans) {
|
||||
BufferedImage bi = this.getBufferedImage();
|
||||
ColorModel cm = bi.getColorModel();
|
||||
if (!(cm instanceof IndexColorModel))
|
||||
throw new RuntimeException("Image is not indexed!");
|
||||
IndexColorModel icm = (IndexColorModel) cm;
|
||||
int mapSize = icm.getMapSize();
|
||||
byte reds[] = new byte[mapSize];
|
||||
byte greens[] = new byte[mapSize];
|
||||
byte blues[] = new byte[mapSize];
|
||||
icm.getReds(reds);
|
||||
icm.getGreens(greens);
|
||||
icm.getBlues(blues);
|
||||
// create the new IndexColorModel with the changed transparentPixel:
|
||||
icm = new IndexColorModel(icm.getPixelSize(), mapSize, reds, greens,
|
||||
blues, trans);
|
||||
// create a new BufferedImage with the new IndexColorModel and the old
|
||||
// raster:
|
||||
setImage(new BufferedImage(icm, bi.getRaster(), false, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pixel at x, y. If the image is indexed, it returns the
|
||||
* palette index, otherwise the rgb code of the color is returned.
|
||||
*
|
||||
* @param x the x coordinate of the pixel
|
||||
* @param y the y coordinate of the pixel
|
||||
* @return the pixel at x, y
|
||||
*/
|
||||
public int getPixel(int x, int y) {
|
||||
BufferedImage bi = this.getBufferedImage();
|
||||
if (bi.getColorModel() instanceof IndexColorModel)
|
||||
return bi.getRaster().getSample(x, y, 0);
|
||||
else
|
||||
return bi.getRGB(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to be used by write().
|
||||
* Converts file name to absolute path and creates parent directories.
|
||||
* @param filename the file name
|
||||
* @return the absolute path for the file name
|
||||
* @throws IOException if missing directories could not be created
|
||||
*/
|
||||
String checkFilename(String filename) throws IOException {
|
||||
File file = new File(filename).getAbsoluteFile();
|
||||
File parent = file.getParentFile();
|
||||
if (parent != null && !parent.exists() && !parent.mkdirs()) {
|
||||
throw new IOException("Error creating directories for " + filename);
|
||||
}
|
||||
return file.getPath();
|
||||
}
|
||||
}
|
162
src/main/java/helma/image/imageio/ImageIOGenerator.java
Normal file
162
src/main/java/helma/image/imageio/ImageIOGenerator.java
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
/*
|
||||
* ImageIOGenerator defines it's own functions for reading from various
|
||||
* resources. These return BufferedImages, therefore all the images
|
||||
* are from the beginning in that format when working with ImageIO
|
||||
*/
|
||||
|
||||
package helma.image.imageio;
|
||||
|
||||
import helma.image.ImageGenerator;
|
||||
import helma.image.ImageWrapper;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DirectColorModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
|
||||
/**
|
||||
* A wrapper for an image that uses the ImageIO Framework.
|
||||
*/
|
||||
public class ImageIOGenerator extends ImageGenerator {
|
||||
|
||||
protected void write(ImageWrapper wrapper, ImageWriter writer, float quality, boolean alpha) throws IOException {
|
||||
BufferedImage bi = wrapper.getBufferedImage();
|
||||
// Set some parameters
|
||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||
if (param.canWriteCompressed() &&
|
||||
quality >= 0.0 && quality <= 1.0) {
|
||||
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||
String[] types = param.getCompressionTypes();
|
||||
// If compression types are defined, but none is set, set the first one,
|
||||
// since setCompressionQuality, which requires MODE_EXPLICIT to be set,
|
||||
// will complain otherwise.
|
||||
if (types != null && param.getCompressionType() == null) {
|
||||
param.setCompressionType(types[0]);
|
||||
}
|
||||
param.setCompressionQuality(quality);
|
||||
}
|
||||
if (param.canWriteProgressive())
|
||||
param.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
|
||||
// if bi has type ARGB and alpha is false, we have to tell the writer to not use the alpha channel:
|
||||
// this is especially needed for jpeg files where imageio seems to produce wrong jpeg files right now...
|
||||
if (bi.getType() == BufferedImage.TYPE_INT_ARGB
|
||||
&& !alpha) {
|
||||
// create a new BufferedImage that uses a WritableRaster of bi, with all the bands except the alpha band:
|
||||
WritableRaster raster = bi.getRaster();
|
||||
WritableRaster newRaster = raster.createWritableChild(
|
||||
0, 0, raster.getWidth(), raster.getHeight(),
|
||||
0, 0, new int[] {0, 1, 2 }
|
||||
);
|
||||
// create a ColorModel that represents the one of the ARGB except the alpha channel:
|
||||
DirectColorModel cm = (DirectColorModel) bi.getColorModel();
|
||||
DirectColorModel newCM = new DirectColorModel(
|
||||
cm.getPixelSize(), cm.getRedMask(),
|
||||
cm.getGreenMask(), cm.getBlueMask());
|
||||
// now create the new buffer that is used ot write the image:
|
||||
BufferedImage rgbBuffer = new BufferedImage(newCM,
|
||||
newRaster, false, null);
|
||||
writer.write(null, new IIOImage(rgbBuffer, null,
|
||||
null), param);
|
||||
} else {
|
||||
writer.write(null, new IIOImage(bi, null, null),
|
||||
param);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the image. Image format is deduced from filename.
|
||||
*
|
||||
* @param wrapper the image to write
|
||||
* @param filename the file to write to
|
||||
* @param quality image quality
|
||||
* @param alpha to enable alpha
|
||||
* @throws IOException
|
||||
* @see helma.image.ImageGenerator#write(helma.image.ImageWrapper, java.lang.String, float, boolean)
|
||||
*/
|
||||
public void write(ImageWrapper wrapper, String filename, float quality, boolean alpha) throws IOException {
|
||||
// determine suffix:
|
||||
int pos = filename.lastIndexOf('.');
|
||||
if (pos != -1) {
|
||||
String extension = filename.substring(pos + 1, filename.length()).toLowerCase();
|
||||
|
||||
// Find a writer for that file suffix
|
||||
ImageWriter writer = null;
|
||||
Iterator iter = ImageIO.getImageWritersBySuffix(extension);
|
||||
if (iter.hasNext())
|
||||
writer = (ImageWriter)iter.next();
|
||||
if (writer != null) {
|
||||
ImageOutputStream ios = null;
|
||||
try {
|
||||
// Prepare output file
|
||||
File file = new File(filename);
|
||||
if (file.exists())
|
||||
file.delete();
|
||||
ios = ImageIO.createImageOutputStream(file);
|
||||
writer.setOutput(ios);
|
||||
this.write(wrapper, writer, quality, alpha);
|
||||
} finally {
|
||||
if (ios != null)
|
||||
ios.close();
|
||||
writer.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the image. Image format is deduced from type.
|
||||
*
|
||||
* @param wrapper the image to write
|
||||
* @param out the outputstream to write to
|
||||
* @param mimeType the mime type
|
||||
* @param quality image quality
|
||||
* @param alpha to enable alpha
|
||||
* @throws IOException
|
||||
* @see helma.image.ImageGenerator#write(helma.image.ImageWrapper, java.io.OutputStream, java.lang.String, float, boolean)
|
||||
*/
|
||||
public void write(ImageWrapper wrapper, OutputStream out, String mimeType, float quality, boolean alpha) throws IOException {
|
||||
// Find a writer for that type
|
||||
ImageWriter writer = null;
|
||||
Iterator iter = ImageIO.getImageWritersByMIMEType(mimeType);
|
||||
if (iter.hasNext())
|
||||
writer = (ImageWriter)iter.next();
|
||||
if (writer != null) {
|
||||
ImageOutputStream ios = null;
|
||||
try {
|
||||
ios = ImageIO.createImageOutputStream(out);
|
||||
writer.setOutput(ios);
|
||||
this.write(wrapper, writer, quality, alpha);
|
||||
} finally {
|
||||
if (ios != null)
|
||||
ios.close();
|
||||
writer.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
/*
|
||||
* The imageio integration is inspired by the package org.freehep.graphicsio.gif
|
||||
*/
|
||||
|
||||
package helma.image.imageio.gif;
|
||||
|
||||
import java.util.*;
|
||||
import javax.imageio.*;
|
||||
|
||||
public class GIFImageWriteParam extends ImageWriteParam {
|
||||
|
||||
public GIFImageWriteParam(Locale locale) {
|
||||
super(locale);
|
||||
canWriteProgressive = true;
|
||||
progressiveMode = MODE_DEFAULT;
|
||||
}
|
||||
|
||||
public ImageWriteParam getWriteParam(Properties properties) {
|
||||
return this;
|
||||
}
|
||||
}
|
84
src/main/java/helma/image/imageio/gif/GIFImageWriter.java
Normal file
84
src/main/java/helma/image/imageio/gif/GIFImageWriter.java
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
/*
|
||||
* The imageio integration is inspired by the package org.freehep.graphicsio.gif
|
||||
*/
|
||||
|
||||
package helma.image.imageio.gif;
|
||||
|
||||
import java.awt.image.*;
|
||||
import java.io.*;
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.metadata.*;
|
||||
|
||||
import helma.image.*;
|
||||
|
||||
public class GIFImageWriter extends ImageWriter {
|
||||
GIFEncoder encoder;
|
||||
|
||||
public GIFImageWriter(GIFImageWriterSpi originatingProvider) {
|
||||
super(originatingProvider);
|
||||
encoder = new GIFEncoder();
|
||||
}
|
||||
|
||||
public void write(IIOMetadata streamMetadata, IIOImage image,
|
||||
ImageWriteParam param) throws IOException {
|
||||
if (image == null)
|
||||
throw new IllegalArgumentException("image == null");
|
||||
|
||||
if (image.hasRaster())
|
||||
throw new UnsupportedOperationException("Cannot write rasters");
|
||||
|
||||
Object output = getOutput();
|
||||
if (output == null)
|
||||
throw new IllegalStateException("output was not set");
|
||||
|
||||
if (param == null)
|
||||
param = getDefaultWriteParam();
|
||||
|
||||
RenderedImage ri = image.getRenderedImage();
|
||||
if (!(ri instanceof BufferedImage))
|
||||
throw new IOException("RenderedImage is not a BufferedImage");
|
||||
if (!(output instanceof DataOutput))
|
||||
throw new IOException("output is not a DataOutput");
|
||||
encoder.encode((BufferedImage) ri, (DataOutput) output,
|
||||
param.getProgressiveMode() != ImageWriteParam.MODE_DISABLED, null);
|
||||
}
|
||||
|
||||
public IIOMetadata convertStreamMetadata(IIOMetadata inData,
|
||||
ImageWriteParam param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public IIOMetadata convertImageMetadata(IIOMetadata inData,
|
||||
ImageTypeSpecifier imageType, ImageWriteParam param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType,
|
||||
ImageWriteParam param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ImageWriteParam getDefaultWriteParam() {
|
||||
return new GIFImageWriteParam(getLocale());
|
||||
}
|
||||
}
|
58
src/main/java/helma/image/imageio/gif/GIFImageWriterSpi.java
Normal file
58
src/main/java/helma/image/imageio/gif/GIFImageWriterSpi.java
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
/*
|
||||
* The imageio integration is inspired by the package org.freehep.graphicsio.gif
|
||||
*/
|
||||
|
||||
package helma.image.imageio.gif;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.spi.*;
|
||||
|
||||
public class GIFImageWriterSpi extends ImageWriterSpi {
|
||||
|
||||
public GIFImageWriterSpi() {
|
||||
super(
|
||||
"Helma Object Publisher, http://helma.org/",
|
||||
"1.0",
|
||||
new String[] {"gif", "GIF"},
|
||||
new String[] {"gif", "GIF"},
|
||||
new String[] {"image/gif", "image/x-gif"},
|
||||
"helma.image.imageio.gif.GIFImageWriter",
|
||||
STANDARD_OUTPUT_TYPE,
|
||||
null,
|
||||
false, null, null, null, null,
|
||||
false, null, null, null, null
|
||||
);
|
||||
}
|
||||
|
||||
public String getDescription(Locale locale) {
|
||||
return "Graphics Interchange Format";
|
||||
}
|
||||
|
||||
public ImageWriter createWriterInstance(Object extension)
|
||||
throws IOException {
|
||||
return new GIFImageWriter(this);
|
||||
}
|
||||
|
||||
public boolean canEncodeImage(ImageTypeSpecifier type) {
|
||||
// FIXME handle # colors
|
||||
return true;
|
||||
}
|
||||
}
|
591
src/main/java/helma/main/ApplicationManager.java
Normal file
591
src/main/java/helma/main/ApplicationManager.java
Normal file
|
@ -0,0 +1,591 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.main;
|
||||
|
||||
import helma.framework.core.*;
|
||||
import helma.framework.repository.Repository;
|
||||
import helma.framework.repository.FileRepository;
|
||||
import helma.util.StringUtils;
|
||||
import org.apache.xmlrpc.XmlRpcHandler;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
|
||||
import org.eclipse.jetty.server.handler.ResourceHandler;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import helma.util.ResourceProperties;
|
||||
import helma.servlet.EmbeddedServletClient;
|
||||
|
||||
/**
|
||||
* This class is responsible for starting and stopping Helma applications.
|
||||
*/
|
||||
public class ApplicationManager implements XmlRpcHandler {
|
||||
private Hashtable descriptors;
|
||||
private Hashtable applications;
|
||||
private Hashtable xmlrpcHandlers;
|
||||
private ResourceProperties props;
|
||||
private Server server;
|
||||
private long lastModified;
|
||||
private ContextHandlerCollection context;
|
||||
private JettyServer jetty = null;
|
||||
|
||||
/**
|
||||
* Creates a new ApplicationManager object.
|
||||
*
|
||||
* @param props the properties defining the running apps
|
||||
* @param server the server instance
|
||||
*/
|
||||
public ApplicationManager(ResourceProperties props, Server server) {
|
||||
this.props = props;
|
||||
this.server = server;
|
||||
descriptors = new Hashtable();
|
||||
applications = new Hashtable();
|
||||
xmlrpcHandlers = new Hashtable();
|
||||
lastModified = 0;
|
||||
jetty = server.jetty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called regularely check applications property file
|
||||
* to create and start new applications.
|
||||
*/
|
||||
protected void checkForChanges() {
|
||||
if (props.lastModified() > lastModified && server.getApplicationsOption() == null) {
|
||||
try {
|
||||
for (Enumeration e = props.keys(); e.hasMoreElements();) {
|
||||
String appName = (String) e.nextElement();
|
||||
|
||||
if ((appName.indexOf(".") == -1) &&
|
||||
(applications.get(appName) == null)) {
|
||||
AppDescriptor appDesc = new AppDescriptor(appName);
|
||||
appDesc.start();
|
||||
appDesc.bind();
|
||||
}
|
||||
}
|
||||
|
||||
// then stop deleted ones
|
||||
for (Enumeration e = descriptors.elements(); e.hasMoreElements();) {
|
||||
AppDescriptor appDesc = (AppDescriptor) e.nextElement();
|
||||
|
||||
// check if application has been removed and should be stopped
|
||||
if (!props.containsKey(appDesc.appName)) {
|
||||
appDesc.stop();
|
||||
} else if (server.jetty != null) {
|
||||
// If application continues to run, remount
|
||||
// as the mounting options may have changed.
|
||||
AppDescriptor ndesc = new AppDescriptor(appDesc.appName);
|
||||
ndesc.app = appDesc.app;
|
||||
appDesc.unbind();
|
||||
ndesc.bind();
|
||||
descriptors.put(ndesc.appName, ndesc);
|
||||
}
|
||||
}
|
||||
} catch (Exception mx) {
|
||||
getLogger().error("Error checking applications", mx);
|
||||
}
|
||||
|
||||
lastModified = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start an application by name
|
||||
*/
|
||||
public void start(String appName) {
|
||||
AppDescriptor desc = new AppDescriptor(appName);
|
||||
desc.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind an application by name
|
||||
*/
|
||||
public void register(String appName) {
|
||||
AppDescriptor desc = (AppDescriptor) descriptors.get(appName);
|
||||
if (desc != null) {
|
||||
desc.bind();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop an application by name
|
||||
*/
|
||||
public void stop(String appName) {
|
||||
AppDescriptor desc = (AppDescriptor) descriptors.get(appName);
|
||||
if (desc != null) {
|
||||
desc.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start all applications listed in the properties
|
||||
*/
|
||||
public void startAll() {
|
||||
try {
|
||||
String[] apps = server.getApplicationsOption();
|
||||
if (apps != null) {
|
||||
for (int i = 0; i < apps.length; i++) {
|
||||
AppDescriptor desc = new AppDescriptor(apps[i]);
|
||||
desc.start();
|
||||
}
|
||||
} else {
|
||||
for (Enumeration e = props.keys(); e.hasMoreElements();) {
|
||||
String appName = (String) e.nextElement();
|
||||
|
||||
if (appName.indexOf(".") == -1) {
|
||||
String appValue = props.getProperty(appName);
|
||||
|
||||
if (appValue != null && appValue.length() > 0) {
|
||||
appName = appValue;
|
||||
}
|
||||
|
||||
AppDescriptor desc = new AppDescriptor(appName);
|
||||
desc.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Enumeration e = descriptors.elements(); e.hasMoreElements();) {
|
||||
AppDescriptor appDesc = (AppDescriptor) e.nextElement();
|
||||
appDesc.bind();
|
||||
}
|
||||
|
||||
lastModified = System.currentTimeMillis();
|
||||
} catch (Exception mx) {
|
||||
getLogger().error("Error starting applications", mx);
|
||||
mx.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all running applications.
|
||||
*/
|
||||
public void stopAll() {
|
||||
for (Enumeration en = descriptors.elements(); en.hasMoreElements();) {
|
||||
try {
|
||||
AppDescriptor appDesc = (AppDescriptor) en.nextElement();
|
||||
|
||||
appDesc.stop();
|
||||
} catch (Exception x) {
|
||||
// ignore exception in application shutdown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array containing all currently running applications.
|
||||
*/
|
||||
public Object[] getApplications() {
|
||||
return applications.values().toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an application by name.
|
||||
*/
|
||||
public Application getApplication(String name) {
|
||||
return (Application) applications.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements org.apache.xmlrpc.XmlRpcHandler.execute()
|
||||
*/
|
||||
public Object execute(String method, Vector params)
|
||||
throws Exception {
|
||||
int dot = method.indexOf(".");
|
||||
|
||||
if (dot == -1) {
|
||||
throw new Exception("Method name \"" + method +
|
||||
"\" does not specify a handler application");
|
||||
}
|
||||
|
||||
if ((dot == 0) || (dot == (method.length() - 1))) {
|
||||
throw new Exception("\"" + method + "\" is not a valid XML-RPC method name");
|
||||
}
|
||||
|
||||
String handler = method.substring(0, dot);
|
||||
String method2 = method.substring(dot + 1);
|
||||
Application app = (Application) xmlrpcHandlers.get(handler);
|
||||
|
||||
if (app == null) {
|
||||
app = (Application) xmlrpcHandlers.get("*");
|
||||
// use the original method name, the handler is resolved within the app.
|
||||
method2 = method;
|
||||
}
|
||||
|
||||
if (app == null) {
|
||||
throw new Exception("Handler \"" + handler + "\" not found for " + method);
|
||||
}
|
||||
|
||||
return app.executeXmlRpc(method2, params);
|
||||
}
|
||||
|
||||
private String getMountpoint(String mountpoint) {
|
||||
mountpoint = mountpoint.trim();
|
||||
|
||||
if ("".equals(mountpoint)) {
|
||||
return "/";
|
||||
} else if (!mountpoint.startsWith("/")) {
|
||||
return "/" + mountpoint;
|
||||
}
|
||||
|
||||
return mountpoint;
|
||||
}
|
||||
|
||||
private String joinMountpoint(String prefix, String suffix) {
|
||||
if (prefix.endsWith("/") || suffix.startsWith("/")) {
|
||||
return prefix+suffix;
|
||||
} else {
|
||||
return prefix+"/"+suffix;
|
||||
}
|
||||
}
|
||||
|
||||
private String getPathPattern(String mountpoint) {
|
||||
if (!mountpoint.startsWith("/")) {
|
||||
mountpoint = "/"+mountpoint;
|
||||
}
|
||||
|
||||
if ("/".equals(mountpoint)) {
|
||||
return "/";
|
||||
}
|
||||
|
||||
if (mountpoint.endsWith("/")) {
|
||||
return mountpoint.substring(0, mountpoint.length()-1);
|
||||
}
|
||||
|
||||
return mountpoint;
|
||||
}
|
||||
|
||||
private File getAbsoluteFile(String path) {
|
||||
// make sure our directory has an absolute path,
|
||||
// see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4117557
|
||||
File file = new File(path);
|
||||
if (file.isAbsolute()) {
|
||||
return file;
|
||||
} else {
|
||||
return file.getAbsoluteFile();
|
||||
}
|
||||
}
|
||||
|
||||
private Log getLogger() {
|
||||
return server.getLogger();
|
||||
}
|
||||
|
||||
private String findResource(String path) {
|
||||
File file = new File(path);
|
||||
if (!file.isAbsolute() && !file.exists()) {
|
||||
file = new File(server.getHopHome(), path);
|
||||
}
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner class that describes an application and its start settings.
|
||||
*/
|
||||
class AppDescriptor {
|
||||
|
||||
Application app;
|
||||
|
||||
private ContextHandler staticContext = null;
|
||||
private ServletContextHandler appContext = null;
|
||||
|
||||
String appName;
|
||||
File appDir;
|
||||
File dbDir;
|
||||
String mountpoint;
|
||||
String pathPattern;
|
||||
String staticDir;
|
||||
String protectedStaticDir;
|
||||
String staticMountpoint;
|
||||
boolean staticIndex;
|
||||
String[] staticHome;
|
||||
String xmlrpcHandlerName;
|
||||
String cookieDomain;
|
||||
String sessionCookieName;
|
||||
String protectedSessionCookie;
|
||||
String uploadLimit;
|
||||
String uploadSoftfail;
|
||||
String debug;
|
||||
Repository[] repositories;
|
||||
String servletClassName;
|
||||
|
||||
/**
|
||||
* extend apps.properties, add [appname].ignore
|
||||
*/
|
||||
String ignoreDirs;
|
||||
|
||||
/**
|
||||
* Creates an AppDescriptor from the properties.
|
||||
* @param name the application name
|
||||
*/
|
||||
AppDescriptor(String name) {
|
||||
ResourceProperties conf = props.getSubProperties(name + '.');
|
||||
appName = name;
|
||||
mountpoint = getMountpoint(conf.getProperty("mountpoint", appName));
|
||||
pathPattern = getPathPattern(mountpoint);
|
||||
staticDir = conf.getProperty("static");
|
||||
staticMountpoint = getPathPattern(conf.getProperty("staticMountpoint",
|
||||
joinMountpoint(mountpoint, "static")));
|
||||
staticIndex = "true".equalsIgnoreCase(conf.getProperty("staticIndex"));
|
||||
String home = conf.getProperty("staticHome");
|
||||
if (home == null) {
|
||||
staticHome = new String[] {"index.html", "index.htm"};
|
||||
} else {
|
||||
staticHome = StringUtils.split(home, ",");
|
||||
}
|
||||
protectedStaticDir = conf.getProperty("protectedStatic");
|
||||
|
||||
cookieDomain = conf.getProperty("cookieDomain");
|
||||
sessionCookieName = conf.getProperty("sessionCookieName");
|
||||
protectedSessionCookie = conf.getProperty("protectedSessionCookie");
|
||||
uploadLimit = conf.getProperty("uploadLimit");
|
||||
uploadSoftfail = conf.getProperty("uploadSoftfail");
|
||||
debug = conf.getProperty("debug");
|
||||
String appDirName = conf.getProperty("appdir");
|
||||
appDir = (appDirName == null) ? null : getAbsoluteFile(appDirName);
|
||||
String dbDirName = conf.getProperty("dbdir");
|
||||
dbDir = (dbDirName == null) ? null : getAbsoluteFile(dbDirName);
|
||||
servletClassName = conf.getProperty("servletClass");
|
||||
|
||||
// got ignore dirs
|
||||
ignoreDirs = conf.getProperty("ignore");
|
||||
|
||||
// read and configure app repositories
|
||||
ArrayList repositoryList = new ArrayList();
|
||||
Class[] parameters = { String.class };
|
||||
for (int i = 0; true; i++) {
|
||||
String repositoryArgs = conf.getProperty("repository." + i);
|
||||
|
||||
if (repositoryArgs != null) {
|
||||
// lookup repository implementation
|
||||
String repositoryImpl = conf.getProperty("repository." + i +
|
||||
".implementation");
|
||||
if (repositoryImpl == null) {
|
||||
// implementation not set manually, have to guess it
|
||||
if (repositoryArgs.endsWith(".zip")) {
|
||||
repositoryArgs = findResource(repositoryArgs);
|
||||
repositoryImpl = "helma.framework.repository.ZipRepository";
|
||||
} else if (repositoryArgs.endsWith(".js")) {
|
||||
repositoryArgs = findResource(repositoryArgs);
|
||||
repositoryImpl = "helma.framework.repository.SingleFileRepository";
|
||||
} else {
|
||||
repositoryArgs = findResource(repositoryArgs);
|
||||
repositoryImpl = "helma.framework.repository.FileRepository";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Repository newRepository = (Repository) Class.forName(repositoryImpl)
|
||||
.getConstructor(parameters)
|
||||
.newInstance(new Object[] {repositoryArgs});
|
||||
repositoryList.add(newRepository);
|
||||
} catch (Exception ex) {
|
||||
getLogger().error("Adding repository " + repositoryArgs + " failed. " +
|
||||
"Will not use that repository. Check your initArgs!", ex);
|
||||
}
|
||||
} else {
|
||||
// we always scan repositories 0-9, beyond that only if defined
|
||||
if (i > 9) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (appDir != null) {
|
||||
FileRepository appRep = new FileRepository(appDir);
|
||||
if (!repositoryList.contains(appRep)) {
|
||||
repositoryList.add(appRep);
|
||||
}
|
||||
} else if (repositoryList.isEmpty()) {
|
||||
repositoryList.add(new FileRepository(
|
||||
new File(server.getAppsHome(), appName)));
|
||||
}
|
||||
repositories = new Repository[repositoryList.size()];
|
||||
repositories = (Repository[]) repositoryList.toArray(repositories);
|
||||
}
|
||||
|
||||
|
||||
void start() {
|
||||
getLogger().info("Building application " + appName);
|
||||
|
||||
try {
|
||||
// create the application instance
|
||||
app = new Application(appName, server, repositories, appDir, dbDir);
|
||||
|
||||
// register ourselves
|
||||
descriptors.put(appName, this);
|
||||
applications.put(appName, app);
|
||||
|
||||
// the application is started later in the register method, when it's bound
|
||||
app.init(ignoreDirs);
|
||||
|
||||
// set application URL prefix if it isn't set in app.properties
|
||||
if (!app.hasExplicitBaseURI()) {
|
||||
app.setBaseURI(mountpoint);
|
||||
}
|
||||
|
||||
app.start();
|
||||
} catch (Exception x) {
|
||||
getLogger().error("Error creating application " + appName, x);
|
||||
x.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
getLogger().info("Stopping application " + appName);
|
||||
|
||||
// unbind application
|
||||
unbind();
|
||||
|
||||
// stop application
|
||||
try {
|
||||
app.stop();
|
||||
getLogger().info("Stopped application " + appName);
|
||||
} catch (Exception x) {
|
||||
getLogger().error("Couldn't stop app", x);
|
||||
}
|
||||
|
||||
descriptors.remove(appName);
|
||||
applications.remove(appName);
|
||||
}
|
||||
|
||||
void bind() {
|
||||
try {
|
||||
getLogger().info("Binding application " + appName + " :: " + app.hashCode() + " :: " + this.hashCode());
|
||||
|
||||
// set application URL prefix if it isn't set in app.properties
|
||||
if (!app.hasExplicitBaseURI()) {
|
||||
app.setBaseURI(mountpoint);
|
||||
}
|
||||
|
||||
// bind to Jetty HTTP server
|
||||
if (jetty != null) {
|
||||
if (context == null) {
|
||||
context = new ContextHandlerCollection();
|
||||
jetty.getHttpServer().setHandler(context);
|
||||
}
|
||||
|
||||
// if there is a static direcory specified, mount it
|
||||
if (staticDir != null) {
|
||||
|
||||
File staticContent = getAbsoluteFile(staticDir);
|
||||
|
||||
getLogger().info("Serving static from " + staticContent.getPath());
|
||||
getLogger().info("Mounting static at " + staticMountpoint);
|
||||
|
||||
ResourceHandler rhandler = new ResourceHandler();
|
||||
rhandler.setResourceBase(staticContent.getPath());
|
||||
rhandler.setWelcomeFiles(staticHome);
|
||||
|
||||
staticContext = context.addContext(staticMountpoint, "");
|
||||
staticContext.setHandler(rhandler);
|
||||
|
||||
staticContext.start();
|
||||
}
|
||||
|
||||
appContext = new ServletContextHandler(context, pathPattern, true, true);
|
||||
Class servletClass = servletClassName == null ?
|
||||
EmbeddedServletClient.class : Class.forName(servletClassName);
|
||||
ServletHolder holder = new ServletHolder(servletClass);
|
||||
holder.setInitParameter("application", appName);
|
||||
appContext.addServlet(holder, "/*");
|
||||
|
||||
if (cookieDomain != null) {
|
||||
holder.setInitParameter("cookieDomain", cookieDomain);
|
||||
}
|
||||
|
||||
if (sessionCookieName != null) {
|
||||
holder.setInitParameter("sessionCookieName", sessionCookieName);
|
||||
}
|
||||
|
||||
if (protectedSessionCookie != null) {
|
||||
holder.setInitParameter("protectedSessionCookie", protectedSessionCookie);
|
||||
}
|
||||
|
||||
if (uploadLimit != null) {
|
||||
holder.setInitParameter("uploadLimit", uploadLimit);
|
||||
}
|
||||
|
||||
if (uploadSoftfail != null) {
|
||||
holder.setInitParameter("uploadSoftfail", uploadSoftfail);
|
||||
}
|
||||
|
||||
if (debug != null) {
|
||||
holder.setInitParameter("debug", debug);
|
||||
}
|
||||
|
||||
if (protectedStaticDir != null) {
|
||||
File protectedContent = getAbsoluteFile(protectedStaticDir);
|
||||
appContext.setResourceBase(protectedContent.getPath());
|
||||
getLogger().info("Serving protected static from " +
|
||||
protectedContent.getPath());
|
||||
}
|
||||
|
||||
// Remap the context paths and start
|
||||
context.mapContexts();
|
||||
appContext.start();
|
||||
}
|
||||
|
||||
// register as XML-RPC handler
|
||||
xmlrpcHandlerName = app.getXmlRpcHandlerName();
|
||||
xmlrpcHandlers.put(xmlrpcHandlerName, app);
|
||||
} catch (Exception x) {
|
||||
getLogger().error("Couldn't bind app", x);
|
||||
x.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
void unbind() {
|
||||
getLogger().info("Unbinding application " + appName);
|
||||
|
||||
try {
|
||||
// unbind from Jetty HTTP server
|
||||
if (jetty != null) {
|
||||
if (appContext != null) {
|
||||
context.removeHandler(appContext);
|
||||
appContext.stop();
|
||||
appContext.destroy();
|
||||
appContext = null;
|
||||
}
|
||||
|
||||
if (staticContext != null) {
|
||||
context.removeHandler(staticContext);
|
||||
staticContext.stop();
|
||||
staticContext.destroy();
|
||||
staticContext = null;
|
||||
}
|
||||
context.mapContexts();
|
||||
}
|
||||
|
||||
// unregister as XML-RPC handler
|
||||
if (xmlrpcHandlerName != null) {
|
||||
xmlrpcHandlers.remove(xmlrpcHandlerName);
|
||||
}
|
||||
} catch (Exception x) {
|
||||
getLogger().error("Couldn't unbind app", x);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[AppDescriptor "+app+"]";
|
||||
}
|
||||
}
|
||||
}
|
139
src/main/java/helma/main/CommandlineRunner.java
Normal file
139
src/main/java/helma/main/CommandlineRunner.java
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.main;
|
||||
|
||||
import helma.framework.core.Application;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Helma command line runner class. This class creates and starts a single application,
|
||||
* invokes a function in, writes its return value to the console and exits.
|
||||
*
|
||||
* @author Stefan Pollach
|
||||
*/
|
||||
public class CommandlineRunner {
|
||||
|
||||
/**
|
||||
* boot method for running a request from the command line.
|
||||
* This retrieves the Helma home directory, creates the app and
|
||||
* runs the function.
|
||||
*
|
||||
* @param args command line arguments
|
||||
*
|
||||
* @throws Exception if the Helma home dir or classpath couldn't be built
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
ServerConfig config = new ServerConfig();
|
||||
String commandStr = null;
|
||||
Vector funcArgs = new Vector();
|
||||
|
||||
// get possible environment setting for helma home
|
||||
if (System.getProperty("helma.home")!=null) {
|
||||
config.setHomeDir(new File(System.getProperty("helma.home")));
|
||||
}
|
||||
|
||||
// parse arguments
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i].equals("-h") && ((i + 1) < args.length)) {
|
||||
config.setHomeDir(new File(args[++i]));
|
||||
} else if (args[i].equals("-f") && ((i + 1) < args.length)) {
|
||||
config.setPropFile(new File(args[++i]));
|
||||
} else if (commandStr != null) {
|
||||
// we're past the command str, all args for the function
|
||||
funcArgs.add (args[i]);
|
||||
} else if ((i%2)==0 && !args[i].startsWith("-")) {
|
||||
// first argument without a switch
|
||||
commandStr = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
// get server.properties from home dir or vv
|
||||
try {
|
||||
Server.guessConfig (config);
|
||||
} catch (Exception ex) {
|
||||
printUsageError(ex.toString());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String appName = null;
|
||||
String function = null;
|
||||
// now split application name + path/function-name
|
||||
try {
|
||||
int pos1 = commandStr.indexOf(".");
|
||||
appName = commandStr.substring(0, pos1);
|
||||
function = commandStr.substring(pos1+1);
|
||||
} catch (Exception ex) {
|
||||
printUsageError();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// init a server instance and start the application
|
||||
Server server = new Server(config);
|
||||
server.init();
|
||||
server.checkAppManager();
|
||||
server.startApplication(appName);
|
||||
Application app = server.getApplication(appName);
|
||||
|
||||
// execute the function
|
||||
try {
|
||||
Object result = app.executeExternal(function, funcArgs);
|
||||
if (result != null) {
|
||||
System.out.println(result.toString());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
System.out.println("Error in application " + appName + ":");
|
||||
System.out.println(ex.getMessage());
|
||||
if ("true".equals(server.getProperty("debug"))) {
|
||||
System.out.println("");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// stop the application and server
|
||||
server.stop();
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* print the usage hints and prefix them with a message.
|
||||
*/
|
||||
public static void printUsageError(String msg) {
|
||||
System.out.println(msg);
|
||||
printUsageError();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* print the usage hints
|
||||
*/
|
||||
public static void printUsageError() {
|
||||
System.out.println("");
|
||||
System.out.println("Error parsing command");
|
||||
System.out.println("");
|
||||
System.out.println("Usage: java helma.main.launcher.Commandline [options] [appname].[function] [argument-list]");
|
||||
System.out.println("");
|
||||
System.out.println("Possible options:");
|
||||
System.out.println(" -h dir Specify hop home directory");
|
||||
System.out.println(" -f file Specify server.properties file");
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
}
|
327
src/main/java/helma/main/HelmaSecurityManager.java
Normal file
327
src/main/java/helma/main/HelmaSecurityManager.java
Normal file
|
@ -0,0 +1,327 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.main;
|
||||
|
||||
import helma.framework.core.AppClassLoader;
|
||||
import java.io.FileDescriptor;
|
||||
import java.net.InetAddress;
|
||||
import java.security.Permission;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Liberal security manager for Helma system that makes sure application code
|
||||
* is not allowed to exit the VM and set a security manager.
|
||||
*
|
||||
* This class can be subclassed to implement actual security policies. It contains
|
||||
* a utility method <code>getApplication</code> that can be used to determine
|
||||
* the name of the application trying to execute the action in question, if any.
|
||||
*/
|
||||
public class HelmaSecurityManager extends SecurityManager {
|
||||
// The set of actions forbidden to application code.
|
||||
// We are pretty permissive, forbidding only System.exit()
|
||||
// and setting the security manager.
|
||||
private final static HashSet forbidden = new HashSet();
|
||||
|
||||
static {
|
||||
forbidden.add("exitVM");
|
||||
forbidden.add("setSecurityManager");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param p ...
|
||||
*/
|
||||
public void checkPermission(Permission p) {
|
||||
if (p instanceof RuntimePermission) {
|
||||
if (forbidden.contains(p.getName())) {
|
||||
Class[] classes = getClassContext();
|
||||
|
||||
for (int i = 0; i < classes.length; i++) {
|
||||
if (classes[i].getClassLoader() instanceof AppClassLoader) {
|
||||
throw new SecurityException(p.getName() +
|
||||
" not allowed for application code");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param p ...
|
||||
* @param context ...
|
||||
*/
|
||||
public void checkPermission(Permission p, Object context) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void checkCreateClassLoader() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param thread ...
|
||||
*/
|
||||
public void checkAccess(Thread thread) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param group ...
|
||||
*/
|
||||
public void checkAccess(ThreadGroup group) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param status ...
|
||||
*/
|
||||
public void checkExit(int status) {
|
||||
Class[] classes = getClassContext();
|
||||
|
||||
for (int i = 0; i < classes.length; i++) {
|
||||
if (classes[i].getClassLoader() instanceof AppClassLoader) {
|
||||
throw new SecurityException("operation not allowed for application code");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param cmd ...
|
||||
*/
|
||||
public void checkExec(String cmd) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param lib ...
|
||||
*/
|
||||
public void checkLink(String lib) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param fdesc ...
|
||||
*/
|
||||
public void checkRead(FileDescriptor fdesc) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param file ...
|
||||
*/
|
||||
public void checkRead(String file) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param file ...
|
||||
* @param context ...
|
||||
*/
|
||||
public void checkRead(String file, Object context) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param fdesc ...
|
||||
*/
|
||||
public void checkWrite(FileDescriptor fdesc) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param file ...
|
||||
*/
|
||||
public void checkWrite(String file) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param file ...
|
||||
*/
|
||||
public void checkDelete(String file) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param host ...
|
||||
* @param port ...
|
||||
*/
|
||||
public void checkConnect(String host, int port) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param host ...
|
||||
* @param port ...
|
||||
* @param context ...
|
||||
*/
|
||||
public void checkConnect(String host, int port, Object context) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param port ...
|
||||
*/
|
||||
public void checkListen(int port) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param host ...
|
||||
* @param port ...
|
||||
*/
|
||||
public void checkAccept(String host, int port) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param addr ...
|
||||
*/
|
||||
public void checkMulticast(InetAddress addr) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param addr ...
|
||||
* @param ttl ...
|
||||
*/
|
||||
public void checkMulticast(InetAddress addr, byte ttl) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void checkPropertiesAccess() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param key ...
|
||||
*/
|
||||
public void checkPropertyAccess(String key) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param window ...
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public boolean checkTopLevelWindow(Object window) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void checkPrintJobAccess() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void checkSystemClipboardAccess() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void checkAwtEventQueueAccess() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param pkg ...
|
||||
*/
|
||||
public void checkPackageAccess(String pkg) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param pkg ...
|
||||
*/
|
||||
public void checkPackageDefinition(String pkg) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void checkSetFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param clazz ...
|
||||
* @param which ...
|
||||
*/
|
||||
public void checkMemberAccess(Class clazz, int which) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param target ...
|
||||
*/
|
||||
public void checkSecurityAccess(String target) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method that returns the name of the application trying
|
||||
* to execute the code in question. Returns null if the current code
|
||||
* does not belong to any application.
|
||||
*/
|
||||
protected String getApplication() {
|
||||
Class[] classes = getClassContext();
|
||||
|
||||
for (int i = 0; i < classes.length; i++) {
|
||||
if (classes[i].getClassLoader() instanceof AppClassLoader) {
|
||||
return ((AppClassLoader) classes[i].getClassLoader()).getAppName();
|
||||
}
|
||||
}
|
||||
|
||||
// no application class loader found in stack - return null
|
||||
return null;
|
||||
}
|
||||
}
|
40
src/main/java/helma/main/HelmaShutdownHook.java
Normal file
40
src/main/java/helma/main/HelmaShutdownHook.java
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.main;
|
||||
|
||||
import helma.util.*;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* ShutdownHook that shuts down all running Helma applications on exit.
|
||||
*/
|
||||
public class HelmaShutdownHook extends Thread {
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void run() {
|
||||
System.err.println("Shutting down Helma - please stand by...");
|
||||
|
||||
Server server = Server.getServer();
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
111
src/main/java/helma/main/JettyServer.java
Normal file
111
src/main/java/helma/main/JettyServer.java
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.main;
|
||||
|
||||
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.xml.XmlConfiguration;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
|
||||
public class JettyServer {
|
||||
|
||||
// the embedded web server
|
||||
protected org.eclipse.jetty.server.Server http;
|
||||
|
||||
public static JettyServer init(Server server, ServerConfig config) throws IOException {
|
||||
File configFile = config.getConfigFile();
|
||||
if (configFile != null && configFile.exists()) {
|
||||
return new JettyServer(configFile.toURI().toURL());
|
||||
} else if (config.hasWebsrvPort()) {
|
||||
return new JettyServer(config.getWebsrvPort(), server);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private JettyServer(URL url) throws IOException {
|
||||
http = new org.eclipse.jetty.server.Server();
|
||||
|
||||
try {
|
||||
XmlConfiguration config = new XmlConfiguration(url);
|
||||
config.configure(http);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Jetty configuration problem: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private JettyServer(InetSocketAddress webPort, Server server)
|
||||
throws IOException {
|
||||
|
||||
http = new org.eclipse.jetty.server.Server();
|
||||
|
||||
// start embedded web server if port is specified
|
||||
if (webPort != null) {
|
||||
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
httpConfig.setSendServerVersion(false);
|
||||
httpConfig.setSendDateHeader(false);
|
||||
HttpConnectionFactory connectionFactory = new HttpConnectionFactory(httpConfig);
|
||||
|
||||
ServerConnector connector = new ServerConnector(http, -1, -1, connectionFactory);
|
||||
connector.setHost(webPort.getAddress().getHostAddress());
|
||||
connector.setPort(webPort.getPort());
|
||||
connector.setIdleTimeout(30000);
|
||||
connector.setSoLingerTime(-1);
|
||||
connector.setAcceptorPriorityDelta(0);
|
||||
connector.setAcceptQueueSize(0);
|
||||
|
||||
http.addConnector(connector);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public org.eclipse.jetty.server.Server getHttpServer() {
|
||||
return http;
|
||||
}
|
||||
|
||||
public void start() throws Exception {
|
||||
openListeners();
|
||||
http.start();
|
||||
}
|
||||
|
||||
public void stop() throws Exception {
|
||||
http.stop();
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
http.destroy();
|
||||
}
|
||||
|
||||
private void openListeners() throws IOException {
|
||||
// opening the listener here allows us to run on priviledged port 80 under jsvc
|
||||
// even as non-root user, because init() is called with root privileges
|
||||
// while start() will be called with the user we will actually run as
|
||||
Connector[] connectors = http.getConnectors();
|
||||
for (int i = 0; i < connectors.length; i++) {
|
||||
((ServerConnector) connectors[i]).open();
|
||||
}
|
||||
}
|
||||
}
|
805
src/main/java/helma/main/Server.java
Normal file
805
src/main/java/helma/main/Server.java
Normal file
|
@ -0,0 +1,805 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile: Server.java,v $
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.main;
|
||||
|
||||
import helma.extensions.HelmaExtension;
|
||||
import helma.framework.repository.FileResource;
|
||||
import helma.framework.core.*;
|
||||
import helma.objectmodel.db.DbSource;
|
||||
import helma.util.*;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import helma.util.ResourceProperties;
|
||||
|
||||
/**
|
||||
* Helma server main class.
|
||||
*/
|
||||
public class Server implements Runnable {
|
||||
// version string
|
||||
public static final String version = "🐜 (__builddate__)";
|
||||
|
||||
// static server instance
|
||||
private static Server server;
|
||||
|
||||
// Server home directory
|
||||
protected File hopHome;
|
||||
|
||||
// server-wide properties
|
||||
ResourceProperties appsProps;
|
||||
ResourceProperties dbProps;
|
||||
ResourceProperties sysProps;
|
||||
|
||||
// our logger
|
||||
private Log logger;
|
||||
// are we using helma.util.Logging?
|
||||
private boolean helmaLogging;
|
||||
|
||||
// server start time
|
||||
public final long starttime;
|
||||
|
||||
// if paranoid == true we only accept XML-RPC connections from
|
||||
// explicitly listed hosts.
|
||||
public boolean paranoid;
|
||||
private ApplicationManager appManager;
|
||||
private Vector extensions;
|
||||
private Thread mainThread;
|
||||
|
||||
// configuration
|
||||
ServerConfig config;
|
||||
|
||||
// map of server-wide database sources
|
||||
Hashtable dbSources;
|
||||
|
||||
// the embedded web server
|
||||
// protected Serve websrv;
|
||||
protected JettyServer jetty;
|
||||
|
||||
// the XML-RPC server
|
||||
protected WebServer xmlrpc;
|
||||
|
||||
Thread shutdownhook;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new Server instance with an array of command line options.
|
||||
* TODO make this a singleton
|
||||
* @param config the configuration
|
||||
*/
|
||||
public Server(ServerConfig config) {
|
||||
server = this;
|
||||
starttime = System.currentTimeMillis();
|
||||
|
||||
this.config = config;
|
||||
hopHome = config.getHomeDir();
|
||||
if (hopHome == null) {
|
||||
throw new RuntimeException("helma.home property not set");
|
||||
}
|
||||
|
||||
// create system properties
|
||||
sysProps = new ResourceProperties();
|
||||
if (config.hasPropFile()) {
|
||||
sysProps.addResource(new FileResource(config.getPropFile()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Static main entry point.
|
||||
* @param args the command line arguments
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
loadServer(args);
|
||||
// parse properties files etc
|
||||
server.init();
|
||||
// start the server main thread
|
||||
server.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point used by launcher.jar to load a server instance
|
||||
* @param args the command line arguments
|
||||
* @return the server instance
|
||||
*/
|
||||
public static Server loadServer(String[] args) {
|
||||
checkJavaVersion();
|
||||
|
||||
ServerConfig config = null;
|
||||
try {
|
||||
config = getConfig(args);
|
||||
} catch (Exception cex) {
|
||||
printUsageError("error reading configuration: " + cex.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
checkRunning(config);
|
||||
|
||||
// create new server instance
|
||||
server = new Server(config);
|
||||
return server;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* check if we are running on a Java 2 VM - otherwise exit with an error message
|
||||
*/
|
||||
public static void checkJavaVersion() {
|
||||
String javaVersion = System.getProperty("java.version");
|
||||
|
||||
if ((javaVersion == null) || javaVersion.startsWith("1.3")
|
||||
|| javaVersion.startsWith("1.2")
|
||||
|| javaVersion.startsWith("1.1")
|
||||
|| javaVersion.startsWith("1.0")) {
|
||||
System.err.println("This version of Helma requires Java 1.4 or greater.");
|
||||
|
||||
if (javaVersion == null) { // don't think this will ever happen, but you never know
|
||||
System.err.println("Your Java Runtime did not provide a version number. Please update to a more recent version.");
|
||||
} else {
|
||||
System.err.println("Your Java Runtime is version " + javaVersion +
|
||||
". Please update to a more recent version.");
|
||||
}
|
||||
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* parse the command line arguments, read a given server.properties file
|
||||
* and check the values given for server ports
|
||||
* @return ServerConfig if successfull
|
||||
* @throws Exception on any configuration error
|
||||
*/
|
||||
public static ServerConfig getConfig(String[] args) throws Exception {
|
||||
|
||||
ServerConfig config = new ServerConfig();
|
||||
|
||||
// get possible environment setting for helma home
|
||||
if (System.getProperty("helma.home")!=null) {
|
||||
config.setHomeDir(new File(System.getProperty("helma.home")));
|
||||
}
|
||||
|
||||
parseArgs(config, args);
|
||||
|
||||
guessConfig(config);
|
||||
|
||||
// create system properties
|
||||
ResourceProperties sysProps = new ResourceProperties();
|
||||
sysProps.addResource(new FileResource(config.getPropFile()));
|
||||
|
||||
// check if there's a property setting for those ports not specified via command line
|
||||
if (!config.hasWebsrvPort() && sysProps.getProperty("webPort") != null) {
|
||||
try {
|
||||
config.setWebsrvPort(getInetSocketAddress(sysProps.getProperty("webPort")));
|
||||
} catch (Exception portx) {
|
||||
throw new Exception("Error parsing web server port property from server.properties: " + portx);
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.hasXmlrpcPort() && sysProps.getProperty("xmlrpcPort") != null) {
|
||||
try {
|
||||
config.setXmlrpcPort(getInetSocketAddress(sysProps.getProperty("xmlrpcPort")));
|
||||
} catch (Exception portx) {
|
||||
throw new Exception("Error parsing XML-RPC server port property from server.properties: " + portx);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* parse argument list from command line and store values
|
||||
* in given ServerConfig object
|
||||
* @throws Exception when argument can't be parsed into an InetAddrPort
|
||||
* or invalid token is given.
|
||||
*/
|
||||
public static void parseArgs(ServerConfig config, String[] args) throws Exception {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i].equals("-h") && ((i + 1) < args.length)) {
|
||||
config.setHomeDir(new File(args[++i]));
|
||||
} else if (args[i].equals("-f") && ((i + 1) < args.length)) {
|
||||
config.setPropFile(new File(args[++i]));
|
||||
} else if (args[i].equals("-a") && ((i + 1) < args.length)) {
|
||||
config.setApps(StringUtils.split(args[++i]));
|
||||
} else if (args[i].equals("-x") && ((i + 1) < args.length)) {
|
||||
try {
|
||||
config.setXmlrpcPort(getInetSocketAddress(args[++i]));
|
||||
} catch (Exception portx) {
|
||||
throw new Exception("Error parsing XML-RPC server port property: " + portx);
|
||||
}
|
||||
} else if (args[i].equals("-w") && ((i + 1) < args.length)) {
|
||||
try {
|
||||
config.setWebsrvPort(getInetSocketAddress(args[++i]));
|
||||
} catch (Exception portx) {
|
||||
throw new Exception("Error parsing web server port property: " + portx);
|
||||
}
|
||||
} else if (args[i].equals("-c") && ((i + 1) < args.length)) {
|
||||
config.setConfigFile(new File(args[++i]));
|
||||
} else if (args[i].equals("-i") && ((i + 1) < args.length)) {
|
||||
// eat away the -i parameter which is meant for helma.main.launcher.Main
|
||||
i++;
|
||||
} else {
|
||||
throw new Exception("Unknown command line token: " + args[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get main property file from home dir or vice versa,
|
||||
* depending on what we have
|
||||
*/
|
||||
public static void guessConfig(ServerConfig config) throws Exception {
|
||||
// get property file from hopHome:
|
||||
if (!config.hasPropFile()) {
|
||||
if (config.hasHomeDir()) {
|
||||
config.setPropFile(new File(config.getHomeDir(), "server.properties"));
|
||||
} else {
|
||||
config.setPropFile(new File("server.properties"));
|
||||
}
|
||||
}
|
||||
|
||||
// create system properties
|
||||
ResourceProperties sysProps = new ResourceProperties();
|
||||
sysProps.addResource(new FileResource(config.getPropFile()));
|
||||
|
||||
// try to get hopHome from property file
|
||||
if (!config.hasHomeDir() && sysProps.getProperty("hophome") != null) {
|
||||
config.setHomeDir(new File(sysProps.getProperty("hophome")));
|
||||
}
|
||||
|
||||
// use the directory where server.properties is located:
|
||||
if (!config.hasHomeDir() && config.hasPropFile()) {
|
||||
config.setHomeDir(config.getPropFile().getAbsoluteFile().getParentFile());
|
||||
}
|
||||
|
||||
if (!config.hasPropFile()) {
|
||||
throw new Exception ("no server.properties found");
|
||||
}
|
||||
|
||||
if (!config.hasHomeDir()) {
|
||||
throw new Exception ("couldn't determine helma directory");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* print the usage hints and prefix them with a message.
|
||||
*/
|
||||
public static void printUsageError(String msg) {
|
||||
System.out.println(msg);
|
||||
printUsageError();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* print the usage hints
|
||||
*/
|
||||
public static void printUsageError() {
|
||||
System.out.println("");
|
||||
System.out.println("Usage: java helma.main.Server [options]");
|
||||
System.out.println("Possible options:");
|
||||
System.out.println(" -a app[,...] Specify applications to start");
|
||||
System.out.println(" -h dir Specify hop home directory");
|
||||
System.out.println(" -f file Specify server.properties file");
|
||||
System.out.println(" -c jetty.xml Specify Jetty XML configuration file");
|
||||
System.out.println(" -w [ip:]port Specify embedded web server address/port");
|
||||
System.out.println(" -x [ip:]port Specify XML-RPC address/port");
|
||||
System.out.println("");
|
||||
System.out.println("Supported formats for server ports:");
|
||||
System.out.println(" <port-number>");
|
||||
System.out.println(" <ip-address>:<port-number>");
|
||||
System.out.println(" <hostname>:<port-number>");
|
||||
System.out.println("");
|
||||
System.err.println("Usage Error - exiting");
|
||||
System.out.println("");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check wheter a server is already running on any of the given ports
|
||||
* - otherwise exit with an error message
|
||||
*/
|
||||
public static void checkRunning(ServerConfig config) {
|
||||
// check if any of the specified server ports is in use already
|
||||
try {
|
||||
if (config.hasWebsrvPort()) {
|
||||
checkPort(config.getWebsrvPort());
|
||||
}
|
||||
|
||||
if (config.hasXmlrpcPort()) {
|
||||
checkPort(config.getXmlrpcPort());
|
||||
}
|
||||
|
||||
} catch (Exception running) {
|
||||
System.out.println(running.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether a server port is available by trying to open a server socket
|
||||
*/
|
||||
private static void checkPort(InetSocketAddress endpoint) throws IOException {
|
||||
try {
|
||||
ServerSocket sock = new ServerSocket();
|
||||
sock.bind(endpoint);
|
||||
sock.close();
|
||||
} catch (IOException x) {
|
||||
throw new IOException("Error binding to " + endpoint + ": " + x.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* initialize the server
|
||||
*/
|
||||
public void init() throws IOException {
|
||||
|
||||
// set the log factory property
|
||||
String logFactory = sysProps.getProperty("loggerFactory",
|
||||
"helma.util.Logging");
|
||||
|
||||
helmaLogging = "helma.util.Logging".equals(logFactory);
|
||||
System.setProperty("org.apache.commons.logging.LogFactory", logFactory);
|
||||
|
||||
// set the current working directory to the helma home dir.
|
||||
// note that this is not a real cwd, which is not supported
|
||||
// by java. It makes sure relative to absolute path name
|
||||
// conversion is done right, so for Helma code, this should work.
|
||||
System.setProperty("user.dir", hopHome.getPath());
|
||||
|
||||
// from now on it's safe to call getLogger() because hopHome is set up
|
||||
getLogger();
|
||||
|
||||
String startMessage = "Starting Helma " + version + " on Java " +
|
||||
System.getProperty("java.version");
|
||||
|
||||
logger.info(startMessage);
|
||||
|
||||
// also print a msg to System.out
|
||||
System.out.println(startMessage);
|
||||
|
||||
logger.info("Setting Helma Home to " + hopHome);
|
||||
|
||||
|
||||
// read db.properties file in helma home directory
|
||||
String dbPropfile = sysProps.getProperty("dbPropFile");
|
||||
File file;
|
||||
if ((dbPropfile != null) && !"".equals(dbPropfile.trim())) {
|
||||
file = new File(dbPropfile);
|
||||
} else {
|
||||
file = new File(hopHome, "db.properties");
|
||||
}
|
||||
|
||||
dbProps = new ResourceProperties();
|
||||
dbProps.setIgnoreCase(false);
|
||||
dbProps.addResource(new FileResource(file));
|
||||
DbSource.setDefaultProps(dbProps);
|
||||
|
||||
// read apps.properties file
|
||||
String appsPropfile = sysProps.getProperty("appsPropFile");
|
||||
if ((appsPropfile != null) && !"".equals(appsPropfile.trim())) {
|
||||
file = new File(appsPropfile);
|
||||
} else {
|
||||
file = new File(hopHome, "apps.properties");
|
||||
}
|
||||
appsProps = new ResourceProperties();
|
||||
appsProps.setIgnoreCase(true);
|
||||
appsProps.addResource(new FileResource(file));
|
||||
|
||||
paranoid = "true".equalsIgnoreCase(sysProps.getProperty("paranoid"));
|
||||
|
||||
String language = sysProps.getProperty("language");
|
||||
String country = sysProps.getProperty("country");
|
||||
String timezone = sysProps.getProperty("timezone");
|
||||
|
||||
if ((language != null) && (country != null)) {
|
||||
Locale.setDefault(new Locale(language, country));
|
||||
}
|
||||
|
||||
if (timezone != null) {
|
||||
TimeZone.setDefault(TimeZone.getTimeZone(timezone));
|
||||
}
|
||||
|
||||
// logger.debug("Locale = " + Locale.getDefault());
|
||||
// logger.debug("TimeZone = " +
|
||||
// TimeZone.getDefault().getDisplayName(Locale.getDefault()));
|
||||
|
||||
dbSources = new Hashtable();
|
||||
|
||||
// try to load the extensions
|
||||
extensions = new Vector();
|
||||
if (sysProps.getProperty("extensions") != null) {
|
||||
initExtensions();
|
||||
}
|
||||
jetty = JettyServer.init(this, config);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* initialize extensions
|
||||
*/
|
||||
private void initExtensions() {
|
||||
StringTokenizer tok = new StringTokenizer(sysProps.getProperty("extensions"), ",");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String extClassName = tok.nextToken().trim();
|
||||
|
||||
try {
|
||||
Class extClass = Class.forName(extClassName);
|
||||
HelmaExtension ext = (HelmaExtension) extClass.newInstance();
|
||||
ext.init(this);
|
||||
extensions.add(ext);
|
||||
logger.info("Loaded: " + extClassName);
|
||||
} catch (Throwable e) {
|
||||
logger.error("Error loading extension " + extClassName + ": " + e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void start() {
|
||||
// Start running, finishing setup and then entering a loop to check changes
|
||||
// in the apps.properties file.
|
||||
mainThread = new Thread(this);
|
||||
mainThread.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
mainThread = null;
|
||||
appManager.stopAll();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
getLogger().info("Shutting down Helma");
|
||||
|
||||
if (jetty != null) {
|
||||
try {
|
||||
jetty.stop();
|
||||
jetty.destroy();
|
||||
} catch (Exception x) {
|
||||
// exception in jettx stop. ignore.
|
||||
}
|
||||
}
|
||||
|
||||
if (xmlrpc != null) {
|
||||
try {
|
||||
xmlrpc.shutdown();
|
||||
} catch (Exception x) {
|
||||
// exception in xmlrpc server shutdown, ignore.
|
||||
}
|
||||
}
|
||||
|
||||
if (helmaLogging) {
|
||||
Logging.shutdown();
|
||||
}
|
||||
|
||||
server = null;
|
||||
|
||||
try {
|
||||
Runtime.getRuntime().removeShutdownHook(shutdownhook);
|
||||
// HACK: running the shutdownhook seems to be necessary in order
|
||||
// to prevent it from blocking garbage collection of helma
|
||||
// classes/classloaders. Since we already set server to null it
|
||||
// won't do anything anyhow.
|
||||
shutdownhook.start();
|
||||
shutdownhook = null;
|
||||
} catch (Exception x) {
|
||||
// invalid shutdown hook or already shutting down. ignore.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main method of the Server. Basically, we set up Applications and than
|
||||
* periodically check for changes in the apps.properties file, shutting down
|
||||
* apps or starting new ones.
|
||||
*/
|
||||
public void run() {
|
||||
try {
|
||||
if (config.hasXmlrpcPort()) {
|
||||
InetSocketAddress xmlrpcPort = config.getXmlrpcPort();
|
||||
String xmlparser = sysProps.getProperty("xmlparser");
|
||||
|
||||
if (xmlparser != null) {
|
||||
XmlRpc.setDriver(xmlparser);
|
||||
}
|
||||
|
||||
if (xmlrpcPort.getAddress() != null) {
|
||||
xmlrpc = new WebServer(xmlrpcPort.getPort(), xmlrpcPort.getAddress());
|
||||
} else {
|
||||
xmlrpc = new WebServer(xmlrpcPort.getPort());
|
||||
}
|
||||
|
||||
if (paranoid) {
|
||||
xmlrpc.setParanoid(true);
|
||||
|
||||
String xallow = sysProps.getProperty("allowXmlRpc");
|
||||
|
||||
if (xallow != null) {
|
||||
StringTokenizer st = new StringTokenizer(xallow, " ,;");
|
||||
|
||||
while (st.hasMoreTokens())
|
||||
xmlrpc.acceptClient(st.nextToken());
|
||||
}
|
||||
}
|
||||
xmlrpc.start();
|
||||
logger.info("Starting XML-RPC server on port " + (xmlrpcPort));
|
||||
}
|
||||
|
||||
appManager = new ApplicationManager(appsProps, this);
|
||||
|
||||
if (xmlrpc != null) {
|
||||
xmlrpc.addHandler("$default", appManager);
|
||||
}
|
||||
|
||||
// add shutdown hook to close running apps and servers on exit
|
||||
shutdownhook = new HelmaShutdownHook();
|
||||
Runtime.getRuntime().addShutdownHook(shutdownhook);
|
||||
} catch (Exception x) {
|
||||
throw new RuntimeException("Error setting up Server", x);
|
||||
}
|
||||
|
||||
// set the security manager.
|
||||
// the default implementation is helma.main.HelmaSecurityManager.
|
||||
try {
|
||||
String secManClass = sysProps.getProperty("securityManager");
|
||||
|
||||
if (secManClass != null) {
|
||||
SecurityManager secMan = (SecurityManager) Class.forName(secManClass)
|
||||
.newInstance();
|
||||
|
||||
System.setSecurityManager(secMan);
|
||||
logger.info("Setting security manager to " + secManClass);
|
||||
}
|
||||
} catch (Exception x) {
|
||||
logger.error("Error setting security manager", x);
|
||||
}
|
||||
|
||||
// start applications
|
||||
appManager.startAll();
|
||||
|
||||
// start embedded web server
|
||||
if (jetty != null) {
|
||||
try {
|
||||
jetty.start();
|
||||
} catch (Exception m) {
|
||||
throw new RuntimeException("Error starting embedded web server", m);
|
||||
}
|
||||
}
|
||||
|
||||
while (Thread.currentThread() == mainThread) {
|
||||
try {
|
||||
Thread.sleep(3000L);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
|
||||
try {
|
||||
appManager.checkForChanges();
|
||||
} catch (Exception x) {
|
||||
logger.warn("Caught in app manager loop: " + x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure this server has an ApplicationManager (e.g. used when
|
||||
* accessed from CommandlineRunner)
|
||||
*/
|
||||
public void checkAppManager() {
|
||||
if (appManager == null) {
|
||||
appManager = new ApplicationManager(appsProps, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an Iterator over the applications currently running on this Server.
|
||||
*/
|
||||
public Object[] getApplications() {
|
||||
return appManager.getApplications();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an Application by name
|
||||
*/
|
||||
public Application getApplication(String name) {
|
||||
return appManager.getApplication(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a logger to use for output in this server.
|
||||
*/
|
||||
public Log getLogger() {
|
||||
if (logger == null) {
|
||||
if (helmaLogging) {
|
||||
// set up system properties for helma.util.Logging
|
||||
String logDir = sysProps.getProperty("logdir", "log");
|
||||
|
||||
if (!"console".equals(logDir)) {
|
||||
// try to get the absolute logdir path
|
||||
|
||||
// set up helma.logdir system property
|
||||
File dir = new File(logDir);
|
||||
if (!dir.isAbsolute()) {
|
||||
dir = new File(hopHome, logDir);
|
||||
}
|
||||
|
||||
logDir = dir.getAbsolutePath();
|
||||
}
|
||||
System.setProperty("helma.logdir", logDir);
|
||||
}
|
||||
logger = LogFactory.getLog("helma.server");
|
||||
}
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Home directory of this server.
|
||||
*/
|
||||
public File getHopHome() {
|
||||
return hopHome;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the explicit list of apps if started with -a option
|
||||
* @return
|
||||
*/
|
||||
public String[] getApplicationsOption() {
|
||||
return config.getApps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the main Server instance.
|
||||
*/
|
||||
public static Server getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Server's XML-RPC web server.
|
||||
*/
|
||||
public static WebServer getXmlRpcServer() {
|
||||
return server.xmlrpc;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param key ...
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getProperty(String key) {
|
||||
return (String) sysProps.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the server.properties for this server
|
||||
* @return the server.properties
|
||||
*/
|
||||
public ResourceProperties getProperties() {
|
||||
return sysProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the server-wide db.properties
|
||||
* @return the server-wide db.properties
|
||||
*/
|
||||
public ResourceProperties getDbProperties() {
|
||||
return dbProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the apps.properties entries for a given application
|
||||
* @param appName the app name
|
||||
* @return the apps.properties subproperties for the given app
|
||||
*/
|
||||
public ResourceProperties getAppsProperties(String appName) {
|
||||
if (appName == null) {
|
||||
return appsProps;
|
||||
} else {
|
||||
return appsProps.getSubProperties(appName + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public File getAppsHome() {
|
||||
String appHome = sysProps.getProperty("appHome", "");
|
||||
|
||||
if (appHome.trim().length() != 0) {
|
||||
return new File(appHome);
|
||||
} else {
|
||||
return new File(hopHome, "apps");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public File getDbHome() {
|
||||
String dbHome = sysProps.getProperty("dbHome", "");
|
||||
|
||||
if (dbHome.trim().length() != 0) {
|
||||
return new File(dbHome);
|
||||
} else {
|
||||
return new File(hopHome, "db");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Vector getExtensions() {
|
||||
return extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param name ...
|
||||
*/
|
||||
public void startApplication(String name) {
|
||||
appManager.start(name);
|
||||
appManager.register(name);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param name ...
|
||||
*/
|
||||
public void stopApplication(String name) {
|
||||
appManager.stop(name);
|
||||
}
|
||||
|
||||
private static InetSocketAddress getInetSocketAddress(String inetAddrPort)
|
||||
throws UnknownHostException {
|
||||
InetAddress addr = null;
|
||||
int c = inetAddrPort.indexOf(':');
|
||||
if (c >= 0) {
|
||||
String a = inetAddrPort.substring(0, c);
|
||||
if (a.indexOf('/') > 0)
|
||||
a = a.substring(a.indexOf('/') + 1);
|
||||
inetAddrPort = inetAddrPort.substring(c + 1);
|
||||
|
||||
if (a.length() > 0 && !"0.0.0.0".equals(a)) {
|
||||
addr = InetAddress.getByName(a);
|
||||
}
|
||||
}
|
||||
int port = Integer.parseInt(inetAddrPort);
|
||||
return new InetSocketAddress(addr, port);
|
||||
}
|
||||
}
|
||||
|
||||
|
106
src/main/java/helma/main/ServerConfig.java
Normal file
106
src/main/java/helma/main/ServerConfig.java
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.main;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* Utility class for server config
|
||||
*/
|
||||
|
||||
public class ServerConfig {
|
||||
|
||||
private InetSocketAddress xmlrpcPort = null;
|
||||
private InetSocketAddress websrvPort = null;
|
||||
private File propFile = null;
|
||||
private File homeDir = null;
|
||||
private File configFile = null;
|
||||
private String[] apps = null;
|
||||
|
||||
public boolean hasPropFile() {
|
||||
return (propFile != null);
|
||||
}
|
||||
|
||||
public boolean hasConfigFile() {
|
||||
return (configFile != null);
|
||||
}
|
||||
|
||||
public boolean hasHomeDir() {
|
||||
return (homeDir != null);
|
||||
}
|
||||
|
||||
public boolean hasXmlrpcPort() {
|
||||
return (xmlrpcPort != null);
|
||||
}
|
||||
|
||||
public boolean hasWebsrvPort() {
|
||||
return (websrvPort != null);
|
||||
}
|
||||
|
||||
public boolean hasApps() {
|
||||
return (apps != null);
|
||||
}
|
||||
|
||||
public InetSocketAddress getXmlrpcPort() {
|
||||
return xmlrpcPort;
|
||||
}
|
||||
|
||||
public void setXmlrpcPort(InetSocketAddress xmlrpcPort) {
|
||||
this.xmlrpcPort = xmlrpcPort;
|
||||
}
|
||||
|
||||
public InetSocketAddress getWebsrvPort() {
|
||||
return websrvPort;
|
||||
}
|
||||
|
||||
public void setWebsrvPort(InetSocketAddress websrvPort) {
|
||||
this.websrvPort = websrvPort;
|
||||
}
|
||||
|
||||
public File getPropFile() {
|
||||
return propFile;
|
||||
}
|
||||
|
||||
public void setPropFile(File propFile) {
|
||||
this.propFile = propFile == null ? null : propFile.getAbsoluteFile();
|
||||
}
|
||||
|
||||
public File getHomeDir() {
|
||||
return homeDir;
|
||||
}
|
||||
|
||||
public void setHomeDir(File homeDir) {
|
||||
this.homeDir = homeDir == null ? null : homeDir.getAbsoluteFile();
|
||||
}
|
||||
|
||||
public File getConfigFile() {
|
||||
return configFile;
|
||||
}
|
||||
|
||||
public void setConfigFile(File configFile) {
|
||||
this.configFile = configFile == null ? null : configFile.getAbsoluteFile();
|
||||
}
|
||||
|
||||
public String[] getApps() {
|
||||
return apps;
|
||||
}
|
||||
|
||||
public void setApps(String[] apps) {
|
||||
this.apps = apps;
|
||||
}
|
||||
}
|
59
src/main/java/helma/main/launcher/Commandline.java
Normal file
59
src/main/java/helma/main/launcher/Commandline.java
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.main.launcher;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
|
||||
/**
|
||||
* Helma bootstrap class. Figures out Helma home directory, sets up class path and
|
||||
* lauchnes main class. This class must be invoked from a jar file in order to work.
|
||||
*
|
||||
* @author Stefan Pollach
|
||||
*/
|
||||
public class Commandline {
|
||||
|
||||
/**
|
||||
* boot method for running a request from the command line.
|
||||
* This retrieves the Helma home directory, creates the
|
||||
* classpath, get the request properties, creates the app and
|
||||
* runs it
|
||||
*-
|
||||
* @param args command line arguments
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
String installDir = Main.getInstallDir(args);
|
||||
|
||||
ClassLoader loader = Main.createClassLoader(installDir);
|
||||
|
||||
// get the main server class
|
||||
Class clazz = loader.loadClass("helma.main.CommandlineRunner");
|
||||
Class[] cargs = new Class[]{args.getClass()};
|
||||
Method main = clazz.getMethod("main", cargs);
|
||||
Object[] nargs = new Object[]{args};
|
||||
|
||||
// and invoke the static main(String, String[]) method
|
||||
main.invoke(null, nargs);
|
||||
} catch (Exception x) {
|
||||
// unable to get Helma installation dir from launcher jar
|
||||
x.printStackTrace();
|
||||
System.err.println("Unable to get Helma installation directory: ");
|
||||
System.err.println(x.getMessage());
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
}
|
233
src/main/java/helma/main/launcher/Main.java
Normal file
233
src/main/java/helma/main/launcher/Main.java
Normal file
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.main.launcher;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Helma bootstrap class. Basically this is a convenience wrapper that takes over
|
||||
* the job of setting the class path and helma install directory before launching
|
||||
* the static main(String[]) method in <code>helma.main.Server</code>. This class
|
||||
* should be invoked from a jar file in the Helma install directory in order to
|
||||
* be able to set up class and install paths.
|
||||
*/
|
||||
public class Main {
|
||||
private Class serverClass;
|
||||
private Object server;
|
||||
|
||||
private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
|
||||
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
|
||||
|
||||
/**
|
||||
* Helma boot method. This retrieves the Helma home directory, creates the
|
||||
* classpath and invokes main() in helma.main.Server.
|
||||
*
|
||||
* @param args command line arguments
|
||||
*
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
Main main = new Main();
|
||||
main.init(args);
|
||||
main.start();
|
||||
}
|
||||
|
||||
public void init(String[] args) {
|
||||
try {
|
||||
String installDir = getInstallDir(args);
|
||||
ClassLoader loader = createClassLoader(installDir);
|
||||
// get the main server class
|
||||
serverClass = loader.loadClass("helma.main.Server");
|
||||
Class[] cargs = new Class[]{args.getClass()};
|
||||
Method loadServer = serverClass.getMethod("loadServer", cargs);
|
||||
Object[] nargs = new Object[]{args};
|
||||
// and invoke the static loadServer(String[]) method
|
||||
server = loadServer.invoke(null, nargs);
|
||||
Method init = serverClass.getMethod("init", EMPTY_CLASS_ARRAY);
|
||||
init.invoke(server, EMPTY_OBJECT_ARRAY);
|
||||
} catch (Exception x) {
|
||||
// unable to get Helma installation dir from launcher jar
|
||||
System.err.println("Unable to load Helma: ");
|
||||
x.printStackTrace();
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
try {
|
||||
Method start = serverClass.getMethod("start", EMPTY_CLASS_ARRAY);
|
||||
start.invoke(server, EMPTY_OBJECT_ARRAY);
|
||||
} catch (Exception x) {
|
||||
// unable to get Helma installation dir from launcher jar
|
||||
System.err.println("Unable to start Helma: ");
|
||||
x.printStackTrace();
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
try {
|
||||
Method start = serverClass.getMethod("stop", EMPTY_CLASS_ARRAY);
|
||||
start.invoke(server, EMPTY_OBJECT_ARRAY);
|
||||
} catch (Exception x) {
|
||||
// unable to get Helma installation dir from launcher jar
|
||||
System.err.println("Unable to stop Helma: ");
|
||||
x.printStackTrace();
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
try {
|
||||
Method start = serverClass.getMethod("shutdown", EMPTY_CLASS_ARRAY);
|
||||
start.invoke(server, EMPTY_OBJECT_ARRAY);
|
||||
} catch (Exception x) {
|
||||
// unable to get Helma installation dir from launcher jar
|
||||
System.err.println("Unable to shutdown Helma: ");
|
||||
x.printStackTrace();
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
static void addJars(ArrayList jarlist, File dir) throws MalformedURLException {
|
||||
File[] files = dir.listFiles(new FilenameFilter() {
|
||||
public boolean accept(File dir, String name) {
|
||||
String n = name.toLowerCase();
|
||||
return n.endsWith(".jar") || n.endsWith(".zip"); //$NON-NLS-1$//$NON-NLS-2$
|
||||
}
|
||||
});
|
||||
|
||||
if (files != null) {
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
jarlist.add(new URL("file:" + files[i].getAbsolutePath())); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a server-wide ClassLoader from our install directory.
|
||||
* This will be used as parent ClassLoader for all application
|
||||
* ClassLoaders.
|
||||
*
|
||||
* @param installDir
|
||||
* @return the main classloader we'll be using
|
||||
* @throws MalformedURLException
|
||||
*/
|
||||
public static ClassLoader createClassLoader(String installDir)
|
||||
throws MalformedURLException {
|
||||
|
||||
// decode installDir in case it is URL-encoded
|
||||
installDir = URLDecoder.decode(installDir);
|
||||
|
||||
// set up the class path
|
||||
File libdir = new File(installDir, "lib");
|
||||
ArrayList jarlist = new ArrayList();
|
||||
|
||||
// add all jar files from the lib directory
|
||||
addJars(jarlist, libdir);
|
||||
|
||||
// add all jar files from the lib/ext directory
|
||||
addJars(jarlist, new File(libdir, "ext")); //$NON-NLS-1$
|
||||
|
||||
URL[] urls = new URL[jarlist.size()];
|
||||
|
||||
jarlist.toArray(urls);
|
||||
|
||||
// find out if system classes should be excluded from class path
|
||||
String excludeSystemClasses = System.getProperty("helma.excludeSystemClasses");
|
||||
|
||||
ClassLoader loader;
|
||||
|
||||
if ("true".equalsIgnoreCase(excludeSystemClasses)) {
|
||||
loader = new URLClassLoader(urls, null);
|
||||
} else {
|
||||
loader = new URLClassLoader(urls);
|
||||
}
|
||||
|
||||
// set the new class loader as context class loader
|
||||
Thread.currentThread().setContextClassLoader(loader);
|
||||
return loader;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Helma install directory from the command line -i argument or
|
||||
* from the Jar URL from which this class was loaded. Additionally, the
|
||||
* System property "helma.home" is set to the install directory path.
|
||||
*
|
||||
* @param args
|
||||
* @return the base install directory we're running in
|
||||
* @throws IOException
|
||||
* @throws MalformedURLException
|
||||
*/
|
||||
public static String getInstallDir(String[] args)
|
||||
throws IOException, MalformedURLException {
|
||||
// check if home directory is set via command line arg. If not,
|
||||
// we'll get it from the location of the jar file this class
|
||||
// has been loaded from.
|
||||
String installDir = null;
|
||||
|
||||
// first, try to get helma home dir from command line options
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i].equals("-i") && ((i + 1) < args.length)) {
|
||||
installDir = args[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
// try to get Helma installation directory
|
||||
if (installDir == null) {
|
||||
URL launcherUrl = ClassLoader.getSystemClassLoader()
|
||||
.getResource("helma/main/launcher/Main.class"); //$NON-NLS-1$
|
||||
|
||||
// this is a JAR URL of the form
|
||||
// jar:<url>!/{entry}
|
||||
// we strip away the jar: prefix and the !/{entry} suffix
|
||||
// to get the original jar file URL
|
||||
|
||||
String jarUrl = launcherUrl.toString();
|
||||
|
||||
if (!jarUrl.startsWith("jar:") || jarUrl.indexOf("!") < 0) {
|
||||
installDir = System.getProperty("user.dir");
|
||||
System.err.println("Warning: Helma install dir not set by -i parameter ");
|
||||
System.err.println(" and not started from launcher.jar. Using ");
|
||||
System.err.println(" current working directory as install dir.");
|
||||
} else {
|
||||
jarUrl = jarUrl.substring(4);
|
||||
|
||||
int excl = jarUrl.indexOf("!");
|
||||
jarUrl = jarUrl.substring(0, excl);
|
||||
launcherUrl = new URL(jarUrl);
|
||||
|
||||
File f = new File(launcherUrl.getPath()).getAbsoluteFile();
|
||||
|
||||
installDir = f.getParentFile().getCanonicalPath();
|
||||
}
|
||||
}
|
||||
// set System property
|
||||
System.setProperty("helma.home", installDir);
|
||||
// and return install dir
|
||||
return installDir;
|
||||
}
|
||||
|
||||
}
|
3
src/main/java/helma/main/launcher/manifest.txt
Normal file
3
src/main/java/helma/main/launcher/manifest.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
Main-Class: helma.main.launcher.Main
|
||||
|
||||
|
33
src/main/java/helma/objectmodel/ConcurrencyException.java
Normal file
33
src/main/java/helma/objectmodel/ConcurrencyException.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel;
|
||||
|
||||
|
||||
/**
|
||||
* Thrown when more than one thrad tries to modify a Node. The evaluator
|
||||
* will normally catch this and try again after a period of time.
|
||||
*/
|
||||
public class ConcurrencyException extends Error {
|
||||
/**
|
||||
* Creates a new ConcurrencyException object.
|
||||
*
|
||||
* @param msg ...
|
||||
*/
|
||||
public ConcurrencyException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
32
src/main/java/helma/objectmodel/DatabaseException.java
Normal file
32
src/main/java/helma/objectmodel/DatabaseException.java
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel;
|
||||
|
||||
|
||||
/**
|
||||
* Thrown on any kind of Database-Error
|
||||
*/
|
||||
public class DatabaseException extends RuntimeException {
|
||||
/**
|
||||
* Creates a new DatabaseException object.
|
||||
*
|
||||
* @param msg ...
|
||||
*/
|
||||
public DatabaseException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
118
src/main/java/helma/objectmodel/IDatabase.java
Normal file
118
src/main/java/helma/objectmodel/IDatabase.java
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel;
|
||||
|
||||
import helma.framework.core.Application;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Interface that is implemented by Database wrappers
|
||||
*/
|
||||
public interface IDatabase {
|
||||
|
||||
/**
|
||||
* Initialize the database with the given db directory and application.
|
||||
*
|
||||
* @param dbHome
|
||||
* @param app
|
||||
*/
|
||||
public void init(File dbHome, Application app);
|
||||
|
||||
/**
|
||||
* Let the database know we're shutting down.
|
||||
*/
|
||||
public void shutdown();
|
||||
|
||||
/**
|
||||
* Get the next ID from the db's ID generator
|
||||
* @return a unique id
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public String nextID() throws ObjectNotFoundException;
|
||||
|
||||
|
||||
/**
|
||||
* Get the node from the database specified by the given key.
|
||||
*
|
||||
* @param transaction
|
||||
* @param key
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws ObjectNotFoundException if no object exists for the key.
|
||||
*/
|
||||
public INode getNode(ITransaction transaction, String key)
|
||||
throws IOException, ObjectNotFoundException;
|
||||
|
||||
/**
|
||||
* Insert a node with the given key
|
||||
*
|
||||
* @param transaction
|
||||
* @param key
|
||||
* @param node
|
||||
* @throws IOException
|
||||
*/
|
||||
public void insertNode(ITransaction transaction, String key, INode node)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Update a node with the given key
|
||||
*
|
||||
* @param transaction
|
||||
* @param key
|
||||
* @param node
|
||||
* @throws IOException
|
||||
*/
|
||||
public void updateNode(ITransaction transaction, String key, INode node)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Delete the node specified by the given key.
|
||||
*
|
||||
* @param transaction ...
|
||||
* @param key ...
|
||||
* @throws IOException ...
|
||||
*/
|
||||
public void deleteNode(ITransaction transaction, String key)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Begin a new transaction.
|
||||
*
|
||||
* @return the transaction
|
||||
*/
|
||||
public ITransaction beginTransaction();
|
||||
|
||||
/**
|
||||
* Commit a transaction, making all changes persistent
|
||||
*
|
||||
* @param transaction
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public void commitTransaction(ITransaction transaction)
|
||||
throws DatabaseException;
|
||||
|
||||
/**
|
||||
* Abort a transaction, rolling back all changes.
|
||||
*
|
||||
* @param transaction
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public void abortTransaction(ITransaction transaction)
|
||||
throws DatabaseException;
|
||||
}
|
260
src/main/java/helma/objectmodel/INode.java
Normal file
260
src/main/java/helma/objectmodel/INode.java
Normal file
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel;
|
||||
|
||||
import helma.framework.IPathElement;
|
||||
import helma.objectmodel.db.DbMapping;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Interface that all Nodes implement. Currently, there are two implementations:
|
||||
* Transient nodes which only exist in memory, and persistent Nodes, which are
|
||||
* stored in a database (either the internal Object DB or an external relational DB).
|
||||
*/
|
||||
public interface INode extends INodeState, IPathElement {
|
||||
/**
|
||||
* Get the node's ID.
|
||||
*/
|
||||
public String getID();
|
||||
|
||||
/**
|
||||
* Get the node's name.
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* Set the node's {@link DbMapping}.
|
||||
*/
|
||||
public void setDbMapping(DbMapping dbmap);
|
||||
|
||||
/**
|
||||
* Get the node's {@link DbMapping}.
|
||||
*/
|
||||
public DbMapping getDbMapping();
|
||||
|
||||
/**
|
||||
* Get the node's state flag.
|
||||
* @return one of the constants defined in the {@link INodeState} interface.
|
||||
*/
|
||||
public int getState();
|
||||
|
||||
/**
|
||||
* Set the node's state flag.
|
||||
* @param s one of the constants defined in the {@link INodeState} interface.
|
||||
*/
|
||||
public void setState(int s);
|
||||
|
||||
/**
|
||||
* Set the node's name.
|
||||
*/
|
||||
public void setName(String name);
|
||||
|
||||
/**
|
||||
* Get the node's last modification timestamp.
|
||||
*/
|
||||
public long lastModified();
|
||||
|
||||
/**
|
||||
* Get the node's creation timestamp.
|
||||
*/
|
||||
public long created();
|
||||
|
||||
/**
|
||||
* Returns true if this node is an unnamed node.
|
||||
*/
|
||||
public boolean isAnonymous();
|
||||
|
||||
/**
|
||||
* Return the node's prototype name.
|
||||
*/
|
||||
public String getPrototype();
|
||||
|
||||
/**
|
||||
* Set the node's prototype name.
|
||||
*/
|
||||
public void setPrototype(String prototype);
|
||||
|
||||
/**
|
||||
* Get the cache node associated with this node.
|
||||
*/
|
||||
public INode getCacheNode();
|
||||
|
||||
/**
|
||||
* Clear the cache node associated with this node.
|
||||
*/
|
||||
public void clearCacheNode();
|
||||
|
||||
/**
|
||||
* Get the node's path.
|
||||
*/
|
||||
public String getPath();
|
||||
|
||||
/**
|
||||
* Get the node's parent node.
|
||||
*/
|
||||
public INode getParent();
|
||||
|
||||
/**
|
||||
* Set an explicit select clause for the node's subnodes
|
||||
*/
|
||||
public void setSubnodeRelation(String clause);
|
||||
|
||||
/**
|
||||
* Get the node's explicit subnode select clause if one was set, or null
|
||||
*/
|
||||
public String getSubnodeRelation();
|
||||
|
||||
/**
|
||||
* Get the number the node's direct child nodes.
|
||||
*/
|
||||
public int numberOfNodes();
|
||||
|
||||
/**
|
||||
* Add a child node to this node.
|
||||
*/
|
||||
public INode addNode(INode node);
|
||||
|
||||
/**
|
||||
* Add a child node to this node at the given position
|
||||
*/
|
||||
public INode addNode(INode node, int where);
|
||||
|
||||
/**
|
||||
* Create a new named property with a node value
|
||||
*/
|
||||
public INode createNode(String name);
|
||||
|
||||
/**
|
||||
* Create a new unnamed child node at the given position.
|
||||
*/
|
||||
public INode createNode(String name, int where);
|
||||
|
||||
/**
|
||||
* Get an enumeration of this node's unnamed child nodes
|
||||
*/
|
||||
public Enumeration getSubnodes();
|
||||
|
||||
/**
|
||||
* Get a named child node with the given name or id.
|
||||
*/
|
||||
public INode getSubnode(String name);
|
||||
|
||||
/**
|
||||
* GEt an unnamed child node at the given position
|
||||
*/
|
||||
public INode getSubnodeAt(int index);
|
||||
|
||||
/**
|
||||
* Returns the position of the child or -1.
|
||||
*/
|
||||
public int contains(INode node);
|
||||
|
||||
/**
|
||||
* Remove this node from the database.
|
||||
*/
|
||||
public boolean remove();
|
||||
|
||||
/**
|
||||
* Remove the given node from this node's child nodes.
|
||||
*/
|
||||
public void removeNode(INode node);
|
||||
|
||||
/**
|
||||
* Get an enumeration over the node's properties.
|
||||
*/
|
||||
public Enumeration properties();
|
||||
|
||||
/**
|
||||
* Get a property with the given name.
|
||||
*/
|
||||
public IProperty get(String name);
|
||||
|
||||
/**
|
||||
* Get a string property with the given name.
|
||||
*/
|
||||
public String getString(String name);
|
||||
|
||||
/**
|
||||
* Get a boolean property with the given name.
|
||||
*/
|
||||
public boolean getBoolean(String name);
|
||||
|
||||
/**
|
||||
* Get a date property with the given name.
|
||||
*/
|
||||
public Date getDate(String name);
|
||||
|
||||
/**
|
||||
* Get an integer property with the given name.
|
||||
*/
|
||||
public long getInteger(String name);
|
||||
|
||||
/**
|
||||
* Get a float property with the given name.
|
||||
*/
|
||||
public double getFloat(String name);
|
||||
|
||||
/**
|
||||
* Get a node property with the given name.
|
||||
*/
|
||||
public INode getNode(String name);
|
||||
|
||||
/**
|
||||
* Get a Java object property with the given name.
|
||||
*/
|
||||
public Object getJavaObject(String name);
|
||||
|
||||
/**
|
||||
* Set the property with the given name to the given string value.
|
||||
*/
|
||||
public void setString(String name, String value);
|
||||
|
||||
/**
|
||||
* Set the property with the given name to the given boolean value.
|
||||
*/
|
||||
public void setBoolean(String name, boolean value);
|
||||
|
||||
/**
|
||||
* Set the property with the given name to the given date value.
|
||||
*/
|
||||
public void setDate(String name, Date value);
|
||||
|
||||
/**
|
||||
* Set the property with the given name to the given integer value.
|
||||
*/
|
||||
public void setInteger(String name, long value);
|
||||
|
||||
/**
|
||||
* Set the property with the given name to the given float value.
|
||||
*/
|
||||
public void setFloat(String name, double value);
|
||||
|
||||
/**
|
||||
* Set the property with the given name to the given node value.
|
||||
*/
|
||||
public void setNode(String name, INode value);
|
||||
|
||||
/**
|
||||
* Set the property with the given name to the given Java object value.
|
||||
*/
|
||||
public void setJavaObject(String name, Object value);
|
||||
|
||||
/**
|
||||
* Unset the property with the given name..
|
||||
*/
|
||||
public void unset(String name);
|
||||
}
|
30
src/main/java/helma/objectmodel/INodeState.java
Normal file
30
src/main/java/helma/objectmodel/INodeState.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel;
|
||||
|
||||
/**
|
||||
* Interface that defines states of nodes
|
||||
*/
|
||||
public interface INodeState {
|
||||
public final static int TRANSIENT = -3;
|
||||
public final static int VIRTUAL = -2;
|
||||
public final static int INVALID = -1;
|
||||
public final static int CLEAN = 0;
|
||||
public final static int NEW = 1;
|
||||
public final static int MODIFIED = 2;
|
||||
public final static int DELETED = 3;
|
||||
}
|
102
src/main/java/helma/objectmodel/IProperty.java
Normal file
102
src/main/java/helma/objectmodel/IProperty.java
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Interface that is implemented by node properties.
|
||||
*/
|
||||
public interface IProperty {
|
||||
public static final int STRING = 1;
|
||||
public static final int BOOLEAN = 2;
|
||||
public static final int DATE = 3;
|
||||
public static final int INTEGER = 4;
|
||||
public static final int FLOAT = 5;
|
||||
public static final int NODE = 6;
|
||||
public static final int JAVAOBJECT = 7;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public int getType();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Object getValue();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public INode getNodeValue();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getStringValue();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public boolean getBooleanValue();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public long getIntegerValue();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public double getFloatValue();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Date getDateValue();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Object getJavaObjectValue();
|
||||
}
|
47
src/main/java/helma/objectmodel/ITransaction.java
Normal file
47
src/main/java/helma/objectmodel/ITransaction.java
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel;
|
||||
|
||||
|
||||
/**
|
||||
* This interface is kept for databases that are able
|
||||
* to run transactions.
|
||||
*/
|
||||
public interface ITransaction {
|
||||
|
||||
public final int ADDED = 0;
|
||||
public final int UPDATED = 1;
|
||||
public final int DELETED = 2;
|
||||
|
||||
/**
|
||||
* Complete the transaction by making its changes persistent.
|
||||
*/
|
||||
public void commit() throws DatabaseException;
|
||||
|
||||
/**
|
||||
* Rollback the transaction, forgetting the changed items
|
||||
*/
|
||||
public void abort() throws DatabaseException;
|
||||
|
||||
/**
|
||||
* Adds a resource to the list of resources encompassed by this transaction
|
||||
*
|
||||
* @param res the resource to add
|
||||
* @param status the status of the resource (ADDED|UPDATED|DELETED)
|
||||
*/
|
||||
public void addResource(Object res, int status) throws DatabaseException;
|
||||
}
|
92
src/main/java/helma/objectmodel/NodeEvent.java
Normal file
92
src/main/java/helma/objectmodel/NodeEvent.java
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* This is passed to NodeListeners when a node is modified.
|
||||
*/
|
||||
public class NodeEvent implements Serializable {
|
||||
public static final int CONTENT_CHANGED = 0;
|
||||
public static final int PROPERTIES_CHANGED = 1;
|
||||
public static final int NODE_REMOVED = 2;
|
||||
public static final int NODE_RENAMED = 3;
|
||||
public static final int SUBNODE_ADDED = 4;
|
||||
public static final int SUBNODE_REMOVED = 5;
|
||||
public int type;
|
||||
public String id;
|
||||
public transient INode node;
|
||||
public transient Object arg;
|
||||
|
||||
/**
|
||||
* Creates a new NodeEvent object.
|
||||
*
|
||||
* @param node ...
|
||||
* @param type ...
|
||||
*/
|
||||
public NodeEvent(INode node, int type) {
|
||||
super();
|
||||
this.node = node;
|
||||
this.id = node.getID();
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new NodeEvent object.
|
||||
*
|
||||
* @param node ...
|
||||
* @param type ...
|
||||
* @param arg ...
|
||||
*/
|
||||
public NodeEvent(INode node, int type, Object arg) {
|
||||
super();
|
||||
this.node = node;
|
||||
this.id = node.getID();
|
||||
this.type = type;
|
||||
this.arg = arg;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String toString() {
|
||||
switch (type) {
|
||||
case CONTENT_CHANGED:
|
||||
return "NodeEvent: content changed";
|
||||
|
||||
case PROPERTIES_CHANGED:
|
||||
return "NodeEvent: properties changed";
|
||||
|
||||
case NODE_REMOVED:
|
||||
return "NodeEvent: node removed";
|
||||
|
||||
case NODE_RENAMED:
|
||||
return "NodeEvent: node moved";
|
||||
|
||||
case SUBNODE_ADDED:
|
||||
return "NodeEvent: subnode added";
|
||||
|
||||
case SUBNODE_REMOVED:
|
||||
return "NodeEvent: subnode removed";
|
||||
}
|
||||
|
||||
return "NodeEvent: invalid type";
|
||||
}
|
||||
}
|
117
src/main/java/helma/objectmodel/ObjectCache.java
Normal file
117
src/main/java/helma/objectmodel/ObjectCache.java
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel;
|
||||
|
||||
import helma.framework.core.Application;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Interface Helma object cache classes need to implement.
|
||||
*
|
||||
*/
|
||||
public interface ObjectCache {
|
||||
|
||||
/**
|
||||
* Set the {@link helma.framework.core.Application Application} instance
|
||||
* for the cache.
|
||||
* @param app the app instance
|
||||
*/
|
||||
void init(Application app);
|
||||
|
||||
/**
|
||||
* Called when the application holding the cache is stopped.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* Called when the application's properties have been updated to let
|
||||
* the cache implementation update its settings.
|
||||
* @param props
|
||||
*/
|
||||
void updateProperties(Properties props);
|
||||
|
||||
/**
|
||||
* Returns true if the collection contains an element for the key.
|
||||
*
|
||||
* @param key the key that we are looking for
|
||||
*/
|
||||
boolean containsKey(Object key);
|
||||
|
||||
/**
|
||||
* Returns the number of keys in object array <code>keys</code> that
|
||||
* were not found in the Map.
|
||||
* Those keys that are contained in the Map are nulled out in the array.
|
||||
* @param keys an array of key objects we are looking for
|
||||
* @see ObjectCache#containsKey
|
||||
*/
|
||||
int containsKeys(Object[] keys);
|
||||
|
||||
/**
|
||||
* Gets the object associated with the specified key in the
|
||||
* hashtable.
|
||||
* @param key the specified key
|
||||
* @return the element for the key or null if the key
|
||||
* is not defined in the hash table.
|
||||
* @see ObjectCache#put
|
||||
*/
|
||||
Object get(Object key);
|
||||
|
||||
/**
|
||||
* Puts the specified element into the hashtable, using the specified
|
||||
* key. The element may be retrieved by doing a get() with the same key.
|
||||
* The key and the element cannot be null.
|
||||
* @param key the specified key in the hashtable
|
||||
* @param value the specified element
|
||||
* @exception NullPointerException If the value of the element
|
||||
* is equal to null.
|
||||
* @see ObjectCache#get
|
||||
* @return the old value of the key, or null if it did not have one.
|
||||
*/
|
||||
Object put(Object key, Object value);
|
||||
|
||||
/**
|
||||
* Removes the element corresponding to the key. Does nothing if the
|
||||
* key is not present.
|
||||
* @param key the key that needs to be removed
|
||||
* @return the value of key, or null if the key was not found.
|
||||
*/
|
||||
Object remove(Object key);
|
||||
|
||||
/**
|
||||
* Removes all items currently stored in the cache.
|
||||
*
|
||||
* @return true if the operation succeeded
|
||||
*/
|
||||
boolean clear();
|
||||
|
||||
/**
|
||||
* Return the number of objects currently stored in the cache.
|
||||
* @return the number of cached items
|
||||
*/
|
||||
int size();
|
||||
|
||||
/**
|
||||
* Return an array with all objects currently contained in the cache.
|
||||
*/
|
||||
Object[] getCachedObjects();
|
||||
|
||||
/**
|
||||
* Returns a map of statistics about the cache
|
||||
*/
|
||||
java.util.Map<String,Object> getStatistics();
|
||||
}
|
33
src/main/java/helma/objectmodel/ObjectNotFoundException.java
Normal file
33
src/main/java/helma/objectmodel/ObjectNotFoundException.java
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel;
|
||||
|
||||
|
||||
/**
|
||||
* Thrown when an object could not found in the database where
|
||||
* it was expected.
|
||||
*/
|
||||
public class ObjectNotFoundException extends Exception {
|
||||
/**
|
||||
* Creates a new ObjectNotFoundException object.
|
||||
*
|
||||
* @param msg ...
|
||||
*/
|
||||
public ObjectNotFoundException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
606
src/main/java/helma/objectmodel/TransientNode.java
Normal file
606
src/main/java/helma/objectmodel/TransientNode.java
Normal file
|
@ -0,0 +1,606 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel;
|
||||
|
||||
import helma.framework.IPathElement;
|
||||
import helma.framework.core.Application;
|
||||
import helma.framework.core.RequestEvaluator;
|
||||
import helma.objectmodel.db.DbMapping;
|
||||
import helma.objectmodel.db.Relation;
|
||||
import helma.objectmodel.db.Node;
|
||||
import helma.util.*;
|
||||
import java.io.*;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* A transient implementation of INode. An instance of this class can't be
|
||||
* made persistent by reachability from a persistent node. To make a persistent-capable
|
||||
* object, class helma.objectmodel.db.Node has to be used.
|
||||
*/
|
||||
public class TransientNode implements INode, Serializable {
|
||||
private static long idgen = 0;
|
||||
protected Hashtable propMap;
|
||||
protected Hashtable nodeMap;
|
||||
protected Vector nodes;
|
||||
protected TransientNode parent;
|
||||
transient String prototype;
|
||||
protected long created;
|
||||
protected long lastmodified;
|
||||
protected String id;
|
||||
protected String name;
|
||||
private final Application app;
|
||||
|
||||
// is the main identity a named property or an anonymous node in a collection?
|
||||
protected boolean anonymous = false;
|
||||
transient DbMapping dbmap;
|
||||
INode cacheNode;
|
||||
|
||||
/**
|
||||
* Creates a new TransientNode object.
|
||||
*/
|
||||
public TransientNode(Application app) {
|
||||
id = generateID();
|
||||
name = id;
|
||||
created = lastmodified = System.currentTimeMillis();
|
||||
this.app=app;
|
||||
}
|
||||
|
||||
private TransientNode() {
|
||||
app=null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new TransientNode object with a given name
|
||||
*/
|
||||
public TransientNode(Application app, String n) {
|
||||
id = generateID();
|
||||
name = (n == null || n.length() == 0) ? id : n;
|
||||
// HACK - decrease creation and last-modified timestamp by 1 so we notice
|
||||
// modifications that take place immediately after object creation
|
||||
created = lastmodified = System.currentTimeMillis() - 1;
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public static String generateID() {
|
||||
// make transient ids differ from persistent ones
|
||||
// and are unique within on runtime session
|
||||
return "t" + idgen++;
|
||||
}
|
||||
|
||||
public void setDbMapping(DbMapping dbmap) {
|
||||
this.dbmap = dbmap;
|
||||
}
|
||||
|
||||
public DbMapping getDbMapping() {
|
||||
return dbmap;
|
||||
}
|
||||
|
||||
public String getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean isAnonymous() {
|
||||
return anonymous;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getElementName() {
|
||||
return anonymous ? id : name;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return TRANSIENT;
|
||||
}
|
||||
|
||||
public void setState(int s) {
|
||||
// state always is TRANSIENT on this kind of node
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return getFullName(null);
|
||||
}
|
||||
|
||||
public String getFullName(INode root) {
|
||||
String divider = null;
|
||||
StringBuffer b = new StringBuffer();
|
||||
TransientNode p = this;
|
||||
|
||||
while ((p != null) && (p.parent != null) && (p != root)) {
|
||||
if (divider != null) {
|
||||
b.insert(0, divider);
|
||||
} else {
|
||||
divider = "/";
|
||||
}
|
||||
|
||||
b.insert(0, p.getElementName());
|
||||
p = p.parent;
|
||||
}
|
||||
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
// if (name.indexOf('/') > -1)
|
||||
// throw new RuntimeException ("The name of the node must not contain \"/\".");
|
||||
if ((name == null) || (name.trim().length() == 0)) {
|
||||
this.name = id;
|
||||
} else {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPrototype() {
|
||||
// if prototype is null, it's a vanilla HopObject.
|
||||
if (prototype == null) {
|
||||
return "HopObject";
|
||||
}
|
||||
|
||||
return prototype;
|
||||
}
|
||||
|
||||
public void setPrototype(String proto) {
|
||||
this.prototype = proto;
|
||||
}
|
||||
|
||||
public INode getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setSubnodeRelation(String rel) {
|
||||
throw new UnsupportedOperationException("Can't set subnode relation for non-persistent Node.");
|
||||
}
|
||||
|
||||
public String getSubnodeRelation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public int numberOfNodes() {
|
||||
return (nodes == null) ? 0 : nodes.size();
|
||||
}
|
||||
|
||||
public INode addNode(INode elem) {
|
||||
return addNode(elem, numberOfNodes());
|
||||
}
|
||||
|
||||
public INode addNode(INode elem, int where) {
|
||||
if ((where < 0) || (where > numberOfNodes())) {
|
||||
where = numberOfNodes();
|
||||
}
|
||||
|
||||
String n = elem.getName();
|
||||
|
||||
if (n.indexOf('/') > -1) {
|
||||
throw new RuntimeException("The name of a node must not contain \"/\" (slash).");
|
||||
}
|
||||
|
||||
if ((nodeMap != null) && (nodeMap.get(elem.getID()) != null)) {
|
||||
nodes.removeElement(elem);
|
||||
where = Math.min(where, numberOfNodes());
|
||||
nodes.insertElementAt(elem, where);
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
if (nodeMap == null) {
|
||||
nodeMap = new Hashtable();
|
||||
}
|
||||
|
||||
if (nodes == null) {
|
||||
nodes = new Vector();
|
||||
}
|
||||
|
||||
nodeMap.put(elem.getID(), elem);
|
||||
nodes.insertElementAt(elem, where);
|
||||
|
||||
if (elem instanceof TransientNode) {
|
||||
TransientNode node = (TransientNode) elem;
|
||||
|
||||
if (node.parent == null) {
|
||||
node.parent = this;
|
||||
node.anonymous = true;
|
||||
}
|
||||
}
|
||||
|
||||
lastmodified = System.currentTimeMillis();
|
||||
return elem;
|
||||
}
|
||||
|
||||
public INode createNode() {
|
||||
return createNode(null, 0); // where is ignored since this is an anonymous node
|
||||
}
|
||||
|
||||
public INode createNode(int where) {
|
||||
return createNode(null, where);
|
||||
}
|
||||
|
||||
public INode createNode(String nm) {
|
||||
return createNode(nm, numberOfNodes()); // where is usually ignored (if nm != null)
|
||||
}
|
||||
|
||||
public INode createNode(String nm, int where) {
|
||||
boolean anon = false;
|
||||
|
||||
if ((nm == null) || "".equals(nm.trim())) {
|
||||
anon = true;
|
||||
}
|
||||
|
||||
INode n = new TransientNode(app, nm);
|
||||
|
||||
if (anon) {
|
||||
addNode(n, where);
|
||||
} else {
|
||||
setNode(nm, n);
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
public IPathElement getParentElement() {
|
||||
return getParent();
|
||||
}
|
||||
|
||||
public IPathElement getChildElement(String name) {
|
||||
return getNode(name);
|
||||
}
|
||||
|
||||
public INode getSubnode(String name) {
|
||||
StringTokenizer st = new StringTokenizer(name, "/");
|
||||
TransientNode retval = this;
|
||||
TransientNode runner;
|
||||
|
||||
while (st.hasMoreTokens() && (retval != null)) {
|
||||
runner = retval;
|
||||
|
||||
String next = st.nextToken().trim().toLowerCase();
|
||||
|
||||
if ("".equals(next)) {
|
||||
retval = this;
|
||||
} else {
|
||||
retval = (runner.nodeMap == null) ? null
|
||||
: (TransientNode) runner.nodeMap.get(next);
|
||||
}
|
||||
|
||||
if (retval == null) {
|
||||
retval = (TransientNode) runner.getNode(next);
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
public INode getSubnodeAt(int index) {
|
||||
return (nodes == null) ? null : (INode) nodes.elementAt(index);
|
||||
}
|
||||
|
||||
public int contains(INode n) {
|
||||
if ((n == null) || (nodes == null)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return nodes.indexOf(n);
|
||||
}
|
||||
|
||||
public boolean remove() {
|
||||
if (anonymous) {
|
||||
parent.unset(name);
|
||||
} else {
|
||||
parent.removeNode(this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void removeNode(INode node) {
|
||||
// IServer.getLogger().log ("removing: "+ node);
|
||||
releaseNode(node);
|
||||
|
||||
TransientNode n = (TransientNode) node;
|
||||
|
||||
if ((n.getParent() == this) && n.anonymous) {
|
||||
|
||||
// remove all subnodes, giving them a chance to destroy themselves.
|
||||
Vector v = new Vector(); // removeElement modifies the Vector we are enumerating, so we are extra careful.
|
||||
|
||||
for (Enumeration e3 = n.getSubnodes(); e3.hasMoreElements();) {
|
||||
v.addElement(e3.nextElement());
|
||||
}
|
||||
|
||||
int m = v.size();
|
||||
|
||||
for (int i = 0; i < m; i++) {
|
||||
n.removeNode((TransientNode) v.elementAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* "Physically" remove a subnode from the subnodes table.
|
||||
* the logical stuff necessary for keeping data consistent is done elsewhere (in removeNode).
|
||||
*/
|
||||
protected void releaseNode(INode node) {
|
||||
if ((nodes == null) || (nodeMap == null)) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int runner = nodes.indexOf(node);
|
||||
|
||||
// this is due to difference between .equals() and ==
|
||||
while ((runner > -1) && (nodes.elementAt(runner) != node))
|
||||
runner = nodes.indexOf(node, Math.min(nodes.size() - 1, runner + 1));
|
||||
|
||||
if (runner > -1) {
|
||||
nodes.removeElementAt(runner);
|
||||
}
|
||||
|
||||
nodeMap.remove(node.getName().toLowerCase());
|
||||
lastmodified = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Enumeration getSubnodes() {
|
||||
return (nodes == null) ? new Vector().elements() : nodes.elements();
|
||||
}
|
||||
|
||||
/**
|
||||
* property-related
|
||||
*/
|
||||
public Enumeration properties() {
|
||||
return (propMap == null) ? new EmptyEnumeration() : propMap.keys();
|
||||
}
|
||||
|
||||
private TransientProperty getProperty(String propname) {
|
||||
TransientProperty prop = (propMap == null) ? null
|
||||
: (TransientProperty) propMap.get(correctPropertyName(propname));
|
||||
|
||||
// check if we have to create a virtual node
|
||||
if ((prop == null) && (dbmap != null)) {
|
||||
Relation rel = dbmap.getPropertyRelation(propname);
|
||||
|
||||
if ((rel != null) && rel.isVirtual()) {
|
||||
prop = makeVirtualNode(propname, rel);
|
||||
}
|
||||
}
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
||||
private TransientProperty makeVirtualNode(String propname, Relation rel) {
|
||||
INode node = new Node(rel.getPropName(), rel.getPrototype(),
|
||||
dbmap.getWrappedNodeManager());
|
||||
|
||||
node.setDbMapping(rel.getVirtualMapping());
|
||||
setNode(propname, node);
|
||||
|
||||
return (TransientProperty) propMap.get(correctPropertyName(propname));
|
||||
}
|
||||
|
||||
public IProperty get(String propname) {
|
||||
return getProperty(propname);
|
||||
}
|
||||
|
||||
public String getString(String propname, String defaultValue) {
|
||||
String propValue = getString(propname);
|
||||
|
||||
return (propValue == null) ? defaultValue : propValue;
|
||||
}
|
||||
|
||||
public String getString(String propname) {
|
||||
TransientProperty prop = getProperty(propname);
|
||||
|
||||
try {
|
||||
return prop.getStringValue();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getInteger(String propname) {
|
||||
TransientProperty prop = getProperty(propname);
|
||||
|
||||
try {
|
||||
return prop.getIntegerValue();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public double getFloat(String propname) {
|
||||
TransientProperty prop = getProperty(propname);
|
||||
|
||||
try {
|
||||
return prop.getFloatValue();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public Date getDate(String propname) {
|
||||
TransientProperty prop = getProperty(propname);
|
||||
|
||||
try {
|
||||
return prop.getDateValue();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean getBoolean(String propname) {
|
||||
TransientProperty prop = getProperty(propname);
|
||||
|
||||
try {
|
||||
return prop.getBooleanValue();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public INode getNode(String propname) {
|
||||
TransientProperty prop = getProperty(propname);
|
||||
|
||||
try {
|
||||
return prop.getNodeValue();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object getJavaObject(String propname) {
|
||||
TransientProperty prop = getProperty(propname);
|
||||
|
||||
try {
|
||||
return prop.getJavaObjectValue();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// create a property if it doesn't exist for this name
|
||||
private TransientProperty initProperty(String propname) {
|
||||
if (propMap == null) {
|
||||
propMap = new Hashtable();
|
||||
}
|
||||
|
||||
propname = propname.trim();
|
||||
String cpn = correctPropertyName(propname);
|
||||
TransientProperty prop = (TransientProperty) propMap.get(cpn);
|
||||
|
||||
if (prop == null) {
|
||||
prop = new TransientProperty(propname, this);
|
||||
propMap.put(cpn, prop);
|
||||
}
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
||||
public void setString(String propname, String value) {
|
||||
TransientProperty prop = initProperty(propname);
|
||||
prop.setStringValue(value);
|
||||
lastmodified = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void setInteger(String propname, long value) {
|
||||
TransientProperty prop = initProperty(propname);
|
||||
prop.setIntegerValue(value);
|
||||
lastmodified = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void setFloat(String propname, double value) {
|
||||
TransientProperty prop = initProperty(propname);
|
||||
prop.setFloatValue(value);
|
||||
lastmodified = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void setBoolean(String propname, boolean value) {
|
||||
TransientProperty prop = initProperty(propname);
|
||||
prop.setBooleanValue(value);
|
||||
lastmodified = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void setDate(String propname, Date value) {
|
||||
TransientProperty prop = initProperty(propname);
|
||||
prop.setDateValue(value);
|
||||
lastmodified = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void setJavaObject(String propname, Object value) {
|
||||
TransientProperty prop = initProperty(propname);
|
||||
prop.setJavaObjectValue(value);
|
||||
lastmodified = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void setNode(String propname, INode value) {
|
||||
TransientProperty prop = initProperty(propname);
|
||||
prop.setNodeValue(value);
|
||||
|
||||
// check if the main identity of this node is as a named property
|
||||
// or as an anonymous node in a collection
|
||||
if (value instanceof TransientNode) {
|
||||
TransientNode n = (TransientNode) value;
|
||||
|
||||
if (n.parent == null) {
|
||||
n.name = propname;
|
||||
n.parent = this;
|
||||
n.anonymous = false;
|
||||
}
|
||||
}
|
||||
|
||||
lastmodified = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void unset(String propname) {
|
||||
if (propMap != null && propname != null) {
|
||||
propMap.remove(correctPropertyName(propname));
|
||||
lastmodified = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
public long lastModified() {
|
||||
return lastmodified;
|
||||
}
|
||||
|
||||
public long created() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "TransientNode " + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache node for this node. This can
|
||||
* be used to store transient cache data per node
|
||||
* from Javascript.
|
||||
*/
|
||||
public synchronized INode getCacheNode() {
|
||||
if (cacheNode == null) {
|
||||
cacheNode = new TransientNode(app);
|
||||
}
|
||||
|
||||
return cacheNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cache node for this node.
|
||||
*/
|
||||
public synchronized void clearCacheNode() {
|
||||
cacheNode = null;
|
||||
}
|
||||
|
||||
private String correctPropertyName(String propname) {
|
||||
return app.correctPropertyName(propname);
|
||||
}
|
||||
}
|
346
src/main/java/helma/objectmodel/TransientProperty.java
Normal file
346
src/main/java/helma/objectmodel/TransientProperty.java
Normal file
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A property implementation for Nodes stored inside a database.
|
||||
*/
|
||||
public final class TransientProperty implements IProperty, Serializable {
|
||||
protected String propname;
|
||||
protected TransientNode node;
|
||||
public String svalue;
|
||||
public boolean bvalue;
|
||||
public long lvalue;
|
||||
public double dvalue;
|
||||
public INode nvalue;
|
||||
public Object jvalue;
|
||||
public int type;
|
||||
|
||||
/**
|
||||
* Creates a new Property object.
|
||||
*
|
||||
* @param node ...
|
||||
*/
|
||||
public TransientProperty(TransientNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Property object.
|
||||
*
|
||||
* @param propname ...
|
||||
* @param node ...
|
||||
*/
|
||||
public TransientProperty(String propname, TransientNode node) {
|
||||
this.propname = propname;
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getName() {
|
||||
return propname;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Object getValue() {
|
||||
switch (type) {
|
||||
case STRING:
|
||||
return svalue;
|
||||
|
||||
case BOOLEAN:
|
||||
return new Boolean(bvalue);
|
||||
|
||||
case INTEGER:
|
||||
return new Long(lvalue);
|
||||
|
||||
case FLOAT:
|
||||
return new Double(dvalue);
|
||||
|
||||
case DATE:
|
||||
return new Date(lvalue);
|
||||
|
||||
case NODE:
|
||||
return nvalue;
|
||||
|
||||
case JAVAOBJECT:
|
||||
return jvalue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param value ...
|
||||
*/
|
||||
public void setStringValue(String value) {
|
||||
if (type == NODE) {
|
||||
this.nvalue = null;
|
||||
}
|
||||
|
||||
if (type == JAVAOBJECT) {
|
||||
this.jvalue = null;
|
||||
}
|
||||
|
||||
type = STRING;
|
||||
this.svalue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param value ...
|
||||
*/
|
||||
public void setIntegerValue(long value) {
|
||||
if (type == NODE) {
|
||||
this.nvalue = null;
|
||||
}
|
||||
|
||||
if (type == JAVAOBJECT) {
|
||||
this.jvalue = null;
|
||||
}
|
||||
|
||||
type = INTEGER;
|
||||
this.lvalue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param value ...
|
||||
*/
|
||||
public void setFloatValue(double value) {
|
||||
if (type == NODE) {
|
||||
this.nvalue = null;
|
||||
}
|
||||
|
||||
if (type == JAVAOBJECT) {
|
||||
this.jvalue = null;
|
||||
}
|
||||
|
||||
type = FLOAT;
|
||||
this.dvalue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param value ...
|
||||
*/
|
||||
public void setDateValue(Date value) {
|
||||
if (type == NODE) {
|
||||
this.nvalue = null;
|
||||
}
|
||||
|
||||
if (type == JAVAOBJECT) {
|
||||
this.jvalue = null;
|
||||
}
|
||||
|
||||
type = DATE;
|
||||
this.lvalue = value.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param value ...
|
||||
*/
|
||||
public void setBooleanValue(boolean value) {
|
||||
if (type == NODE) {
|
||||
this.nvalue = null;
|
||||
}
|
||||
|
||||
if (type == JAVAOBJECT) {
|
||||
this.jvalue = null;
|
||||
}
|
||||
|
||||
type = BOOLEAN;
|
||||
this.bvalue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param value ...
|
||||
*/
|
||||
public void setNodeValue(INode value) {
|
||||
if (type == JAVAOBJECT) {
|
||||
this.jvalue = null;
|
||||
}
|
||||
|
||||
type = NODE;
|
||||
this.nvalue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param value ...
|
||||
*/
|
||||
public void setJavaObjectValue(Object value) {
|
||||
if (type == NODE) {
|
||||
this.nvalue = null;
|
||||
}
|
||||
|
||||
type = JAVAOBJECT;
|
||||
this.jvalue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getStringValue() {
|
||||
switch (type) {
|
||||
case STRING:
|
||||
return svalue;
|
||||
|
||||
case BOOLEAN:
|
||||
return "" + bvalue;
|
||||
|
||||
case DATE:
|
||||
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
return format.format(new Date(lvalue));
|
||||
|
||||
case INTEGER:
|
||||
return Long.toString(lvalue);
|
||||
|
||||
case FLOAT:
|
||||
return Double.toString(dvalue);
|
||||
|
||||
case NODE:
|
||||
return nvalue.getName();
|
||||
|
||||
case JAVAOBJECT:
|
||||
return (jvalue == null) ? null : jvalue.toString();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String toString() {
|
||||
return getStringValue();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public long getIntegerValue() {
|
||||
if (type == INTEGER) {
|
||||
return lvalue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public double getFloatValue() {
|
||||
if (type == FLOAT) {
|
||||
return dvalue;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Date getDateValue() {
|
||||
if (type == DATE) {
|
||||
return new Date(lvalue);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public boolean getBooleanValue() {
|
||||
if (type == BOOLEAN) {
|
||||
return bvalue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public INode getNodeValue() {
|
||||
if (type == NODE) {
|
||||
return nvalue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Object getJavaObjectValue() {
|
||||
if (type == JAVAOBJECT) {
|
||||
return jvalue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
126
src/main/java/helma/objectmodel/db/DbColumn.java
Normal file
126
src/main/java/helma/objectmodel/db/DbColumn.java
Normal file
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* A class that encapsulates the Column name and data type of a column in a
|
||||
* relational table.
|
||||
*/
|
||||
public final class DbColumn {
|
||||
private final String name;
|
||||
private final int type;
|
||||
private final Relation relation;
|
||||
|
||||
private final boolean isId;
|
||||
private final boolean isPrototype;
|
||||
private final boolean isName;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public DbColumn(String name, int type, Relation rel, DbMapping dbmap) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.relation = rel;
|
||||
|
||||
if (relation != null) {
|
||||
relation.setColumnType(type);
|
||||
}
|
||||
|
||||
isId = name.equalsIgnoreCase(dbmap.getIDField());
|
||||
isPrototype = name.equalsIgnoreCase(dbmap.getPrototypeField());
|
||||
isName = name.equalsIgnoreCase(dbmap.getNameField());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this columns SQL data type.
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the relation associated with this column. May be null.
|
||||
*/
|
||||
public Relation getRelation() {
|
||||
return relation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this column serves as ID field for the prototype.
|
||||
*/
|
||||
public boolean isIdField() {
|
||||
return isId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this column serves as prototype field for the prototype.
|
||||
*/
|
||||
public boolean isPrototypeField() {
|
||||
return isPrototype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this column serves as name field for the prototype.
|
||||
*/
|
||||
public boolean isNameField() {
|
||||
return isName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this field is mapped by the prototype's db mapping.
|
||||
*/
|
||||
public boolean isMapped() {
|
||||
// Note: not sure if check for primitive or reference relation is really
|
||||
// needed, but we did it before, so we leave it in for safety.
|
||||
return isId || isPrototype || isName ||
|
||||
(relation != null && relation.isPrimitiveOrReference());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether values for this column need to be quoted in insert/update
|
||||
* stmts
|
||||
*
|
||||
* @return true if values need to be wrapped in quotes
|
||||
*/
|
||||
public boolean needsQuotes() {
|
||||
switch (type) {
|
||||
case Types.CHAR:
|
||||
case Types.VARCHAR:
|
||||
case Types.LONGVARCHAR:
|
||||
case Types.BINARY:
|
||||
case Types.VARBINARY:
|
||||
case Types.LONGVARBINARY:
|
||||
case Types.DATE:
|
||||
case Types.TIME:
|
||||
case Types.TIMESTAMP:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
147
src/main/java/helma/objectmodel/db/DbKey.java
Normal file
147
src/main/java/helma/objectmodel/db/DbKey.java
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/**
|
||||
* This is the internal representation of a database key. It is constructed
|
||||
* from the logical table (type) name and the object's primary key
|
||||
* within the table. Currently only single keys are supported.
|
||||
*/
|
||||
public final class DbKey implements Key, Serializable {
|
||||
// the name of the prototype which defines the storage of this object.
|
||||
// this is the name of the object's prototype, or one of its ancestors.
|
||||
// If null, the object is stored in the embedded db.
|
||||
private String storageName;
|
||||
|
||||
// the id that defines this key's object within the above storage space
|
||||
private String id;
|
||||
|
||||
// lazily initialized hashcode
|
||||
private transient int hashcode = 0;
|
||||
|
||||
static final long serialVersionUID = 1618863960930966588L;
|
||||
|
||||
/**
|
||||
* make a key for a persistent Object, describing its datasource and id.
|
||||
*/
|
||||
public DbKey(DbMapping dbmap, String id) {
|
||||
if (id == null) {
|
||||
throw new IllegalArgumentException("id null in DbKey");
|
||||
}
|
||||
this.id = id;
|
||||
this.storageName = (dbmap == null) ? null : dbmap.getStorageTypeName();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param what the other key to be compared with this one
|
||||
*
|
||||
* @return true if both keys are identical
|
||||
*/
|
||||
public boolean equals(Object what) {
|
||||
if (what == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(what instanceof DbKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DbKey k = (DbKey) what;
|
||||
|
||||
// storageName is an interned string (by DbMapping, from where we got it)
|
||||
// so we can compare by using == instead of the equals method.
|
||||
return (storageName == k.storageName) && ((id == k.id) || id.equals(k.id));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return this key's hash code
|
||||
*/
|
||||
public int hashCode() {
|
||||
if (hashcode == 0) {
|
||||
hashcode = (storageName == null) ? (17 + (37 * id.hashCode()))
|
||||
: (17 + (37 * storageName.hashCode()) +
|
||||
(+37 * id.hashCode()));
|
||||
}
|
||||
|
||||
return hashcode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return the key of this key's object's parent object
|
||||
*/
|
||||
public Key getParentKey() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return the unique storage name for this key's object
|
||||
*/
|
||||
public String getStorageName() {
|
||||
return storageName;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return this key's object's id
|
||||
*/
|
||||
public String getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return a string representation for this key
|
||||
*/
|
||||
public String toString() {
|
||||
return (storageName == null) ? ("[" + id + "]") : (storageName + "[" + id + "]");
|
||||
}
|
||||
|
||||
// We implement write/readObject to set storageName
|
||||
// to the interned version of the string.
|
||||
|
||||
private void writeObject(ObjectOutputStream stream) throws IOException {
|
||||
stream.writeObject(storageName);
|
||||
stream.writeObject(id);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream stream)
|
||||
throws IOException, ClassNotFoundException {
|
||||
storageName = (String) stream.readObject();
|
||||
id = (String) stream.readObject();
|
||||
// if storageName is not null, set it to the interned version
|
||||
if (storageName != null) {
|
||||
storageName = storageName.intern();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1647
src/main/java/helma/objectmodel/db/DbMapping.java
Normal file
1647
src/main/java/helma/objectmodel/db/DbMapping.java
Normal file
File diff suppressed because it is too large
Load diff
294
src/main/java/helma/objectmodel/db/DbSource.java
Normal file
294
src/main/java/helma/objectmodel/db/DbSource.java
Normal file
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import helma.util.ResourceProperties;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Properties;
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* This class describes a releational data source (URL, driver, user and password).
|
||||
*/
|
||||
public class DbSource {
|
||||
private static ResourceProperties defaultProps = null;
|
||||
private Properties conProps;
|
||||
private final String name;
|
||||
private ResourceProperties props, subProps;
|
||||
protected String url;
|
||||
private String driver;
|
||||
private boolean isOracle, isMySQL, isPostgreSQL, isH2;
|
||||
private long lastRead = 0L;
|
||||
private Hashtable dbmappings = new Hashtable();
|
||||
// compute hashcode statically because it's expensive and we need it often
|
||||
private int hashcode;
|
||||
// thread local connection holder for non-transactor threads
|
||||
private ThreadLocal connection;
|
||||
|
||||
/**
|
||||
* Creates a new DbSource object.
|
||||
*
|
||||
* @param name the db source name
|
||||
* @param props the properties
|
||||
* @throws ClassNotFoundException if the JDBC driver couldn't be loaded
|
||||
*/
|
||||
public DbSource(String name, ResourceProperties props)
|
||||
throws ClassNotFoundException {
|
||||
this.name = name;
|
||||
this.props = props;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JDBC connection to the db source.
|
||||
*
|
||||
* @return a JDBC connection
|
||||
*
|
||||
* @throws ClassNotFoundException if the JDBC driver couldn't be loaded
|
||||
* @throws SQLException if the connection couldn't be created
|
||||
*/
|
||||
public synchronized Connection getConnection()
|
||||
throws ClassNotFoundException, SQLException {
|
||||
Connection con;
|
||||
Transactor tx = Transactor.getInstance();
|
||||
if (tx != null) {
|
||||
con = tx.getConnection(this);
|
||||
} else {
|
||||
con = getThreadLocalConnection();
|
||||
}
|
||||
|
||||
boolean fileUpdated = props.lastModified() > lastRead ||
|
||||
(defaultProps != null && defaultProps.lastModified() > lastRead);
|
||||
|
||||
if (con == null || con.isClosed() || fileUpdated) {
|
||||
init();
|
||||
con = DriverManager.getConnection(url, conProps);
|
||||
|
||||
// If we wanted to use SQL transactions, we'd set autoCommit to
|
||||
// false here and make commit/rollback invocations in Transactor methods;
|
||||
// System.err.println ("Created new Connection to "+url);
|
||||
if (tx != null) {
|
||||
tx.registerConnection(this, con);
|
||||
} else {
|
||||
connection.set(con);
|
||||
}
|
||||
}
|
||||
|
||||
return con;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for connections not managed by a Helma transactor
|
||||
* @return a thread local tested connection, or null
|
||||
*/
|
||||
private Connection getThreadLocalConnection() {
|
||||
if (connection == null) {
|
||||
connection = new ThreadLocal();
|
||||
return null;
|
||||
}
|
||||
Connection con = (Connection) connection.get();
|
||||
if (con != null) {
|
||||
// test if connection is still ok
|
||||
try {
|
||||
Statement stmt = con.createStatement();
|
||||
stmt.execute("SELECT 1");
|
||||
stmt.close();
|
||||
} catch (SQLException sx) {
|
||||
try {
|
||||
con.close();
|
||||
} catch (SQLException ignore) {/* nothing to do */}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return con;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the db properties to newProps, and return the old properties.
|
||||
* @param newProps the new properties to use for this db source
|
||||
* @return the old properties
|
||||
* @throws ClassNotFoundException if jdbc driver class couldn't be found
|
||||
*/
|
||||
public synchronized ResourceProperties switchProperties(ResourceProperties newProps)
|
||||
throws ClassNotFoundException {
|
||||
ResourceProperties oldProps = props;
|
||||
props = newProps;
|
||||
init();
|
||||
return oldProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the db source from the properties
|
||||
*
|
||||
* @throws ClassNotFoundException if the JDBC driver couldn't be loaded
|
||||
*/
|
||||
private synchronized void init() throws ClassNotFoundException {
|
||||
lastRead = (defaultProps == null) ? props.lastModified()
|
||||
: Math.max(props.lastModified(),
|
||||
defaultProps.lastModified());
|
||||
// refresh sub-properties for this DbSource
|
||||
subProps = props.getSubProperties(name + '.');
|
||||
// use properties hashcode for ourselves
|
||||
hashcode = subProps.hashCode();
|
||||
// get JDBC URL and driver class name
|
||||
url = subProps.getProperty("url");
|
||||
driver = subProps.getProperty("driver");
|
||||
// sanity checks
|
||||
if (url == null) {
|
||||
throw new NullPointerException(name+".url is not defined in db.properties");
|
||||
}
|
||||
if (driver == null) {
|
||||
throw new NullPointerException(name+".driver class not defined in db.properties");
|
||||
}
|
||||
// test if this is an Oracle or MySQL driver
|
||||
isOracle = driver.startsWith("oracle.jdbc.driver");
|
||||
isMySQL = driver.startsWith("com.mysql.jdbc") ||
|
||||
driver.startsWith("org.gjt.mm.mysql");
|
||||
isPostgreSQL = driver.equals("org.postgresql.Driver");
|
||||
isH2 = driver.equals("org.h2.Driver");
|
||||
// test if driver class is available
|
||||
Class.forName(driver);
|
||||
|
||||
// set up driver connection properties
|
||||
conProps=new Properties();
|
||||
String prop = subProps.getProperty("user");
|
||||
if (prop != null) {
|
||||
conProps.put("user", prop);
|
||||
}
|
||||
prop = subProps.getProperty("password");
|
||||
if (prop != null) {
|
||||
conProps.put("password", prop);
|
||||
}
|
||||
|
||||
// read any remaining extra properties to be passed to the driver
|
||||
for (Enumeration e = subProps.keys(); e.hasMoreElements(); ) {
|
||||
String key = (String) e.nextElement();
|
||||
|
||||
// filter out properties we alread have
|
||||
if ("url".equalsIgnoreCase(key) ||
|
||||
"driver".equalsIgnoreCase(key) ||
|
||||
"user".equalsIgnoreCase(key) ||
|
||||
"password".equalsIgnoreCase(key)) {
|
||||
continue;
|
||||
}
|
||||
conProps.setProperty(key, subProps.getProperty(key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class name of the JDBC driver
|
||||
*
|
||||
* @return the class name of the JDBC driver
|
||||
*/
|
||||
public String getDriverName() {
|
||||
return driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the db dource
|
||||
*
|
||||
* @return the name of the db dource
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default (server-wide) properties
|
||||
*
|
||||
* @param props server default db.properties
|
||||
*/
|
||||
public static void setDefaultProps(ResourceProperties props) {
|
||||
defaultProps = props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this DbSource represents an Oracle database
|
||||
*
|
||||
* @return true if we're using an oracle JDBC driver
|
||||
*/
|
||||
public boolean isOracle() {
|
||||
return isOracle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this DbSource represents a MySQL database
|
||||
*
|
||||
* @return true if we're using a MySQL JDBC driver
|
||||
*/
|
||||
public boolean isMySQL() {
|
||||
return isMySQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this DbSource represents a PostgreSQL database
|
||||
*
|
||||
* @return true if we're using a PostgreSQL JDBC driver
|
||||
*/
|
||||
public boolean isPostgreSQL() {
|
||||
return isPostgreSQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this DbSource represents a H2 database
|
||||
*
|
||||
* @return true if we're using a H2 JDBC driver
|
||||
*/
|
||||
public boolean isH2() {
|
||||
return isH2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a dbmapping by its table name.
|
||||
*
|
||||
* @param dbmap the DbMapping instance to register
|
||||
*/
|
||||
protected void registerDbMapping(DbMapping dbmap) {
|
||||
if (!dbmap.inheritsStorage() && dbmap.getTableName() != null) {
|
||||
dbmappings.put(dbmap.getTableName().toUpperCase(), dbmap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a DbMapping instance for the given table name.
|
||||
*
|
||||
* @param tablename the table name
|
||||
* @return the matching DbMapping instance
|
||||
*/
|
||||
protected DbMapping getDbMapping(String tablename) {
|
||||
return (DbMapping) dbmappings.get(tablename.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash code value for the object.
|
||||
*/
|
||||
public int hashCode() {
|
||||
return hashcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether some other object is "equal to" this one.
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof DbSource && subProps.equals(((DbSource) obj).subProps);
|
||||
}
|
||||
}
|
48
src/main/java/helma/objectmodel/db/IDGenerator.java
Normal file
48
src/main/java/helma/objectmodel/db/IDGenerator.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import helma.framework.core.Application;
|
||||
|
||||
|
||||
/**
|
||||
* An interface for objects that generate IDs (Strings) that are
|
||||
* unique for a specific type.
|
||||
*/
|
||||
public interface IDGenerator {
|
||||
|
||||
/**
|
||||
* Init the ID generator for the given application.
|
||||
*
|
||||
* @param app
|
||||
*/
|
||||
public void init(Application app);
|
||||
|
||||
/**
|
||||
* Shut down the ID generator.
|
||||
*/
|
||||
public void shutdown();
|
||||
|
||||
/**
|
||||
* Generate a new ID for a specific type.
|
||||
*
|
||||
* @param dbmap
|
||||
* @return
|
||||
*/
|
||||
public String generateID(DbMapping dbmap) throws Exception;
|
||||
|
||||
}
|
47
src/main/java/helma/objectmodel/db/Key.java
Normal file
47
src/main/java/helma/objectmodel/db/Key.java
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
|
||||
/**
|
||||
* This is the interface for the internal representation of an object key.
|
||||
*
|
||||
*/
|
||||
public interface Key {
|
||||
|
||||
/**
|
||||
* Get the key's parent key
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Key getParentKey();
|
||||
|
||||
/**
|
||||
* Get the key's ID part
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getID();
|
||||
|
||||
/**
|
||||
* Get the key's storage type name
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getStorageName();
|
||||
|
||||
}
|
169
src/main/java/helma/objectmodel/db/MultiKey.java
Normal file
169
src/main/java/helma/objectmodel/db/MultiKey.java
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This is the internal representation of a database key with multiple
|
||||
* columns. It is constructed from the logical table (type) name and the
|
||||
* column name/column value pairs that identify the key's object
|
||||
*
|
||||
* NOTE: This class doesn't fully support the Key interface - getID always
|
||||
* returns null since there is no unique key (at least we don't know about it).
|
||||
*/
|
||||
public final class MultiKey implements Key, Serializable {
|
||||
// the name of the prototype which defines the storage of this object.
|
||||
// this is the name of the object's prototype, or one of its ancestors.
|
||||
// If null, the object is stored in the embedded db.
|
||||
private String storageName;
|
||||
|
||||
// the id that defines this key's object within the above storage space
|
||||
private Map parts;
|
||||
|
||||
// lazily initialized hashcode
|
||||
private transient int hashcode = 0;
|
||||
|
||||
static final long serialVersionUID = -9173409137561990089L;
|
||||
|
||||
|
||||
/**
|
||||
* Make a key for a persistent Object, describing its datasource and key parts.
|
||||
*/
|
||||
public MultiKey(DbMapping dbmap, Map parts) {
|
||||
this.parts = parts;
|
||||
this.storageName = getStorageNameFromParts(dbmap, parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual dbmapping prototype name out of the parts map if possible.
|
||||
* This is necessary to implement references to unspecified prototype targets.
|
||||
* @param dbmap the nominal/static dbmapping
|
||||
* @param parts the parts map
|
||||
* @return the actual dbmapping name
|
||||
*/
|
||||
private String getStorageNameFromParts(DbMapping dbmap, Map parts) {
|
||||
if (dbmap == null)
|
||||
return null;
|
||||
String protoName = (String) parts.get("$prototype");
|
||||
if (protoName != null) {
|
||||
DbMapping dynamap = dbmap.app.getDbMapping(protoName);
|
||||
if (dynamap != null) {
|
||||
return (dynamap.getStorageTypeName());
|
||||
}
|
||||
}
|
||||
return dbmap.getStorageTypeName();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param what the other key to be compared with this one
|
||||
*
|
||||
* @return true if both keys are identical
|
||||
*/
|
||||
public boolean equals(Object what) {
|
||||
if (what == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(what instanceof MultiKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MultiKey k = (MultiKey) what;
|
||||
|
||||
// storageName is an interned string (by DbMapping, from where we got it)
|
||||
// so we can compare by using == instead of the equals method.
|
||||
return (storageName == k.storageName) &&
|
||||
((parts == k.parts) || parts.equals(k.parts));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return this key's hash code
|
||||
*/
|
||||
public int hashCode() {
|
||||
if (hashcode == 0) {
|
||||
hashcode = (storageName == null) ? (17 + (37 * parts.hashCode()))
|
||||
: (17 + (37 * storageName.hashCode()) +
|
||||
(+37 * parts.hashCode()));
|
||||
}
|
||||
|
||||
return hashcode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return the key of this key's object's parent object
|
||||
*/
|
||||
public Key getParentKey() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return the unique storage name for this key's object
|
||||
*/
|
||||
public String getStorageName() {
|
||||
return storageName;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return this key's object's id
|
||||
*/
|
||||
public String getID() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return a string representation for this key
|
||||
*/
|
||||
public String toString() {
|
||||
return (storageName == null) ? ("[" + parts + "]") : (storageName + "[" + parts + "]");
|
||||
}
|
||||
|
||||
// We implement write/readObject to set storageName
|
||||
// to the interned version of the string.
|
||||
|
||||
private void writeObject(ObjectOutputStream stream) throws IOException {
|
||||
stream.writeObject(storageName);
|
||||
stream.writeObject(parts);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream stream)
|
||||
throws IOException, ClassNotFoundException {
|
||||
storageName = (String) stream.readObject();
|
||||
parts = (Map) stream.readObject();
|
||||
// if storageName is not null, set it to the interned version
|
||||
if (storageName != null) {
|
||||
storageName = storageName.intern();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
2574
src/main/java/helma/objectmodel/db/Node.java
Normal file
2574
src/main/java/helma/objectmodel/db/Node.java
Normal file
File diff suppressed because it is too large
Load diff
29
src/main/java/helma/objectmodel/db/NodeChangeListener.java
Normal file
29
src/main/java/helma/objectmodel/db/NodeChangeListener.java
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface NodeChangeListener {
|
||||
|
||||
/**
|
||||
* Called when a transaction is committed that has created, modified,
|
||||
* deleted or changed the child collection one or more nodes.
|
||||
*/
|
||||
public void nodesChanged(List inserted, List updated, List deleted, List parents);
|
||||
|
||||
}
|
164
src/main/java/helma/objectmodel/db/NodeHandle.java
Normal file
164
src/main/java/helma/objectmodel/db/NodeHandle.java
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import helma.objectmodel.INodeState;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This class is a handle or reference to a Node. This is to abstract from different
|
||||
* methods of reference: Transient Nodes are referred to directly, while persistent
|
||||
* nodes are referred to via key/node manager.
|
||||
*
|
||||
* A handle is used to refer to a node in a safe way over a longer period.
|
||||
* While a direct reference may point to a node that has been evicted from the cache
|
||||
* and reinstanciated since being set, NodeHandle will always return an up-to-date
|
||||
* instance of its node.
|
||||
*
|
||||
* Helma tries to ensure the following rules on NodeHandles:
|
||||
* <ol>
|
||||
* <li> For transient nodes there exists only one NodeHandle.</li>
|
||||
* <li> If a transient node becomes persistent its node handle is notified and
|
||||
* converted into a persistent NodeHandle.</li>
|
||||
* </ol>
|
||||
* These two properties guarantee that NodeHandle comparisons are easy and usually correct.
|
||||
*
|
||||
*/
|
||||
public final class NodeHandle implements INodeState, Serializable {
|
||||
static final long serialVersionUID = 3067763116576910931L;
|
||||
|
||||
// direct reference to the node
|
||||
private Node node;
|
||||
|
||||
// the node's key
|
||||
private Key key;
|
||||
|
||||
/**
|
||||
* Builds a handle for a node. This constructor is package private in order to make
|
||||
* sure only one NodeHandle exists per transient node. Use {@link Node#getHandle()}
|
||||
* to get a Node's handle.
|
||||
* @param node the node
|
||||
*/
|
||||
NodeHandle(Node node) {
|
||||
int state = node.getState();
|
||||
|
||||
if (state == TRANSIENT) {
|
||||
this.node = node;
|
||||
key = null;
|
||||
} else {
|
||||
this.node = null;
|
||||
key = node.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a handle given a node's retrieval information. At the time this is called,
|
||||
* the node is ususally not yet created. It will be fetched on demand when accessed by
|
||||
* application code.
|
||||
* @param key the key
|
||||
*/
|
||||
public NodeHandle(Key key) {
|
||||
this.node = null;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node described by this node handle
|
||||
*/
|
||||
public Node getNode(WrappedNodeManager nodemgr) {
|
||||
if (node != null) {
|
||||
return node;
|
||||
}
|
||||
return nodemgr.getNode(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the node is available without fetching it from the node manager
|
||||
* @return true if we alreay have a reference to our node
|
||||
*/
|
||||
public boolean hasNode() {
|
||||
return node != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key for the node described by this handle.
|
||||
* This will return null for transient Nodes.
|
||||
*/
|
||||
public Key getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID for the node described by this handle.
|
||||
* This may only be called on persistent Nodes.
|
||||
*/
|
||||
public String getID() {
|
||||
if (key == null) {
|
||||
return node.getID();
|
||||
}
|
||||
return key.getID();
|
||||
}
|
||||
|
||||
private Object getObject() {
|
||||
if (node != null) {
|
||||
return node;
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param other ...
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof NodeHandle) {
|
||||
Object obj1 = getObject();
|
||||
Object obj2 = ((NodeHandle) other).getObject();
|
||||
return obj1 == obj2 || obj1.equals(obj2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is to notify the handle that the underlying node is becoming
|
||||
* persistent and we have to refer to it via the key from now on.
|
||||
*/
|
||||
protected void becomePersistent() {
|
||||
if (node != null) {
|
||||
key = node.getKey();
|
||||
node = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String toString() {
|
||||
if (node != null) {
|
||||
return "NodeHandle[transient:" + node + "]";
|
||||
} else {
|
||||
return "NodeHandle[" + key + "]";
|
||||
}
|
||||
}
|
||||
}
|
1890
src/main/java/helma/objectmodel/db/NodeManager.java
Normal file
1890
src/main/java/helma/objectmodel/db/NodeManager.java
Normal file
File diff suppressed because it is too large
Load diff
62
src/main/java/helma/objectmodel/db/ParentInfo.java
Normal file
62
src/main/java/helma/objectmodel/db/ParentInfo.java
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import helma.util.StringUtils;
|
||||
|
||||
|
||||
/**
|
||||
* This class describes a parent relation between releational nodes.
|
||||
*/
|
||||
public class ParentInfo {
|
||||
public final String propname;
|
||||
public final String virtualname;
|
||||
public final String collectionname;
|
||||
public final boolean isroot;
|
||||
|
||||
/**
|
||||
* Creates a new ParentInfo object.
|
||||
*
|
||||
* @param desc a single parent info descriptor
|
||||
*/
|
||||
public ParentInfo(String desc) {
|
||||
|
||||
// [named] isn't used anymore, we just want to keep the parsing compatible.
|
||||
int n = desc.indexOf("[named]");
|
||||
desc = n > -1 ? desc.substring(0, n) : desc;
|
||||
|
||||
String[] parts = StringUtils.split(desc, ".");
|
||||
|
||||
propname = parts.length > 0 ? parts[0].trim() : null;
|
||||
virtualname = parts.length > 1 ? parts[1].trim() : null;
|
||||
collectionname = parts.length > 2 ? parts[2].trim() : null;
|
||||
|
||||
isroot = "root".equalsIgnoreCase(propname);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string representation of the parent info
|
||||
*/
|
||||
public String toString() {
|
||||
StringBuffer b = new StringBuffer("ParentInfo[").append(propname);
|
||||
if (virtualname != null)
|
||||
b.append(".").append(virtualname);
|
||||
if (collectionname != null)
|
||||
b.append(".").append(collectionname);
|
||||
return b.append("]").toString();
|
||||
}
|
||||
}
|
547
src/main/java/helma/objectmodel/db/Property.java
Normal file
547
src/main/java/helma/objectmodel/db/Property.java
Normal file
|
@ -0,0 +1,547 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import helma.objectmodel.INode;
|
||||
import helma.objectmodel.IProperty;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.sql.Timestamp;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A property implementation for Nodes stored inside a database. Basically
|
||||
* the same as for transient nodes, with a few hooks added.
|
||||
*/
|
||||
public final class Property implements IProperty, Serializable, Cloneable, Comparable {
|
||||
static final long serialVersionUID = -1022221688349192379L;
|
||||
private String propname;
|
||||
private Node node;
|
||||
private Object value;
|
||||
private int type;
|
||||
transient boolean dirty;
|
||||
|
||||
/**
|
||||
* Creates a new Property object.
|
||||
*
|
||||
* @param node ...
|
||||
*/
|
||||
public Property(Node node) {
|
||||
this.node = node;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Property object.
|
||||
*
|
||||
* @param propname ...
|
||||
* @param node ...
|
||||
*/
|
||||
public Property(String propname, Node node) {
|
||||
this.propname = propname;
|
||||
this.node = node;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Property object.
|
||||
*
|
||||
* @param propname ...
|
||||
* @param node ...
|
||||
* @param valueNode ...
|
||||
*/
|
||||
public Property(String propname, Node node, Node valueNode) {
|
||||
this(propname, node);
|
||||
type = NODE;
|
||||
value = (valueNode == null) ? null : valueNode.getHandle();
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream in) throws IOException {
|
||||
try {
|
||||
propname = in.readUTF();
|
||||
node = (Node) in.readObject();
|
||||
type = in.readInt();
|
||||
|
||||
switch (type) {
|
||||
case STRING:
|
||||
value = in.readObject();
|
||||
|
||||
break;
|
||||
|
||||
case BOOLEAN:
|
||||
value = in.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
|
||||
|
||||
break;
|
||||
|
||||
case INTEGER:
|
||||
value = new Long(in.readLong());
|
||||
|
||||
break;
|
||||
|
||||
case DATE:
|
||||
value = new Date(in.readLong());
|
||||
|
||||
break;
|
||||
|
||||
case FLOAT:
|
||||
value = new Double(in.readDouble());
|
||||
|
||||
break;
|
||||
|
||||
case NODE:
|
||||
value = in.readObject();
|
||||
|
||||
break;
|
||||
|
||||
case JAVAOBJECT:
|
||||
value = in.readObject();
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (ClassNotFoundException x) {
|
||||
throw new IOException(x.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream out) throws IOException {
|
||||
out.writeUTF(propname);
|
||||
out.writeObject(node);
|
||||
out.writeInt(type);
|
||||
|
||||
switch (type) {
|
||||
case STRING:
|
||||
out.writeObject(value);
|
||||
|
||||
break;
|
||||
|
||||
case BOOLEAN:
|
||||
out.writeBoolean(((Boolean) value).booleanValue());
|
||||
|
||||
break;
|
||||
|
||||
case INTEGER:
|
||||
out.writeLong(((Long) value).longValue());
|
||||
|
||||
break;
|
||||
|
||||
case DATE:
|
||||
out.writeLong(((Date) value).getTime());
|
||||
|
||||
break;
|
||||
|
||||
case FLOAT:
|
||||
out.writeDouble(((Double) value).doubleValue());
|
||||
|
||||
break;
|
||||
|
||||
case NODE:
|
||||
out.writeObject(value);
|
||||
|
||||
break;
|
||||
|
||||
case JAVAOBJECT:
|
||||
|
||||
if ((value != null) && !(value instanceof Serializable)) {
|
||||
out.writeObject(null);
|
||||
} else {
|
||||
out.writeObject(value);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the property
|
||||
*
|
||||
* @return this property's name
|
||||
*/
|
||||
public String getName() {
|
||||
return propname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the property
|
||||
*/
|
||||
protected void setName(String name) {
|
||||
this.propname = name;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return the property's value in its native class
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return the property's type as defined in helma.objectmodel.IProperty.java
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly set the value of this property.
|
||||
*/
|
||||
protected void setValue(Object value, int type) {
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param str ...
|
||||
*/
|
||||
public void setStringValue(String str) {
|
||||
type = STRING;
|
||||
value = str;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param l ...
|
||||
*/
|
||||
public void setIntegerValue(long l) {
|
||||
type = INTEGER;
|
||||
value = new Long(l);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param d ...
|
||||
*/
|
||||
public void setFloatValue(double d) {
|
||||
type = FLOAT;
|
||||
value = new Double(d);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param date ...
|
||||
*/
|
||||
public void setDateValue(Date date) {
|
||||
type = DATE;
|
||||
// normalize from java.sql.* Date subclasses
|
||||
if (date != null && date.getClass() != Date.class) {
|
||||
value = new Date(date.getTime());
|
||||
} else {
|
||||
value = date;
|
||||
}
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param bool ...
|
||||
*/
|
||||
public void setBooleanValue(boolean bool) {
|
||||
type = BOOLEAN;
|
||||
value = bool ? Boolean.TRUE : Boolean.FALSE;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param node ...
|
||||
*/
|
||||
public void setNodeValue(Node node) {
|
||||
type = NODE;
|
||||
value = (node == null) ? null : node.getHandle();
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param handle ...
|
||||
*/
|
||||
public void setNodeHandle(NodeHandle handle) {
|
||||
type = NODE;
|
||||
value = handle;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public NodeHandle getNodeHandle() {
|
||||
if (type == NODE) {
|
||||
return (NodeHandle) value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param rel the Relation
|
||||
*/
|
||||
public void convertToNodeReference(Relation rel) {
|
||||
if ((value != null) && !(value instanceof NodeHandle)) {
|
||||
if (rel.usesPrimaryKey()) {
|
||||
value = new NodeHandle(new DbKey(rel.otherType, value.toString()));
|
||||
} else {
|
||||
value = new NodeHandle(new MultiKey(rel.otherType, rel.getKeyParts(node)));
|
||||
}
|
||||
}
|
||||
|
||||
type = NODE;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param obj ...
|
||||
*/
|
||||
public void setJavaObjectValue(Object obj) {
|
||||
type = JAVAOBJECT;
|
||||
value = obj;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String getStringValue() {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case STRING:
|
||||
case BOOLEAN:
|
||||
case INTEGER:
|
||||
case FLOAT:
|
||||
case JAVAOBJECT:
|
||||
return value.toString();
|
||||
|
||||
case DATE:
|
||||
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
return format.format((Date) value);
|
||||
|
||||
case NODE:
|
||||
return ((NodeHandle) value).getID();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String toString() {
|
||||
return getStringValue();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public long getIntegerValue() {
|
||||
if (type == INTEGER) {
|
||||
return ((Long) value).longValue();
|
||||
}
|
||||
|
||||
if (type == FLOAT) {
|
||||
return ((Double) value).longValue();
|
||||
}
|
||||
|
||||
if (type == BOOLEAN) {
|
||||
return ((Boolean) value).booleanValue() ? 1 : 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return Long.parseLong(getStringValue());
|
||||
} catch (Exception x) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public double getFloatValue() {
|
||||
if (type == FLOAT) {
|
||||
return ((Double) value).doubleValue();
|
||||
}
|
||||
|
||||
if (type == INTEGER) {
|
||||
return ((Long) value).doubleValue();
|
||||
}
|
||||
|
||||
try {
|
||||
return Double.parseDouble(getStringValue());
|
||||
} catch (Exception x) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Date getDateValue() {
|
||||
if (type == DATE) {
|
||||
return (Date) value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Timestamp getTimestampValue() {
|
||||
if ((type == DATE) && (value != null)) {
|
||||
return new Timestamp(((Date) value).getTime());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public boolean getBooleanValue() {
|
||||
if (type == BOOLEAN) {
|
||||
return ((Boolean) value).booleanValue();
|
||||
}
|
||||
|
||||
return 0 != getIntegerValue();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public INode getNodeValue() {
|
||||
if ((type == NODE) && (value != null)) {
|
||||
NodeHandle nhandle = (NodeHandle) value;
|
||||
|
||||
return nhandle.getNode(node.nmgr);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Object getJavaObjectValue() {
|
||||
if (type == JAVAOBJECT) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Comparable#compareTo(java.lang.Object)
|
||||
*
|
||||
* The following cases throw a ClassCastException
|
||||
* - Properties of a different type
|
||||
* - Properties of boolean or node type
|
||||
*/
|
||||
public int compareTo(Object obj) {
|
||||
Property p = (Property) obj;
|
||||
int ptype = p.getType();
|
||||
Object pvalue = p.getValue();
|
||||
|
||||
if (type==NODE || ptype==NODE ||
|
||||
type == BOOLEAN || ptype == BOOLEAN) {
|
||||
throw new ClassCastException("uncomparable values " + this + "(" + type + ") : " + p + "(" + ptype + ")");
|
||||
}
|
||||
if (value==null && pvalue == null) {
|
||||
return 0;
|
||||
} else if (value == null) {
|
||||
return 1;
|
||||
} if (pvalue == null) {
|
||||
return -1;
|
||||
}
|
||||
if (type != ptype) {
|
||||
// float/integer sometimes get mixed up in Rhino
|
||||
if ((type == FLOAT && ptype == INTEGER) || (type == INTEGER && ptype == FLOAT))
|
||||
return Double.compare(((Number) value).doubleValue(), ((Number) pvalue).doubleValue());
|
||||
throw new ClassCastException("uncomparable values " + this + "(" + type + ") : " + p + "(" + ptype + ")");
|
||||
|
||||
}
|
||||
if (!(value instanceof Comparable)) {
|
||||
throw new ClassCastException("uncomparable value " + value + "(" + value.getClass() + ")");
|
||||
}
|
||||
// System.err.println("COMPARING: " + value.getClass() + " TO " + pvalue.getClass());
|
||||
return ((Comparable) value).compareTo(pvalue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if object o is equal to this property.
|
||||
*
|
||||
* @param obj the object to compare to
|
||||
* @return true if this equals obj
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (!(obj instanceof Property))
|
||||
return false;
|
||||
Property p = (Property) obj;
|
||||
return value == null ? p.value == null : value.equals(p.value);
|
||||
}
|
||||
}
|
1552
src/main/java/helma/objectmodel/db/Relation.java
Normal file
1552
src/main/java/helma/objectmodel/db/Relation.java
Normal file
File diff suppressed because it is too large
Load diff
268
src/main/java/helma/objectmodel/db/SegmentedSubnodeList.java
Normal file
268
src/main/java/helma/objectmodel/db/SegmentedSubnodeList.java
Normal file
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 2009 Helma Project. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class SegmentedSubnodeList extends SubnodeList {
|
||||
|
||||
transient Segment[] segments = null;
|
||||
static int SEGLENGTH = 1000;
|
||||
|
||||
transient private int subnodeCount = -1;
|
||||
|
||||
/**
|
||||
* Creates a new subnode list
|
||||
* @param node the node we belong to
|
||||
*/
|
||||
public SegmentedSubnodeList(Node node) {
|
||||
super(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified object to this list performing
|
||||
* custom ordering
|
||||
*
|
||||
* @param handle element to be inserted.
|
||||
*/
|
||||
public synchronized boolean add(NodeHandle handle) {
|
||||
if (!hasRelationalNodes() || segments == null) {
|
||||
return super.add(handle);
|
||||
}
|
||||
if (subnodeCount == -1) {
|
||||
update();
|
||||
}
|
||||
subnodeCount++;
|
||||
segments[segments.length - 1].length += 1;
|
||||
return list.add(handle);
|
||||
}
|
||||
/**
|
||||
* Adds the specified object to the list at the given position
|
||||
* @param index the index to insert the element at
|
||||
* @param handle the object to add
|
||||
*/
|
||||
public synchronized void add(int index, NodeHandle handle) {
|
||||
if (!hasRelationalNodes() || segments == null) {
|
||||
super.add(index, handle);
|
||||
return;
|
||||
}
|
||||
if (subnodeCount == -1) {
|
||||
update();
|
||||
}
|
||||
subnodeCount++;
|
||||
list.add(index, handle);
|
||||
// shift segment indices by one
|
||||
int s = getSegment(index);
|
||||
segments[s].length += 1;
|
||||
for (int i = s + 1; i < segments.length; i++) {
|
||||
segments[i].startIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public NodeHandle get(int index) {
|
||||
if (!hasRelationalNodes() || segments == null) {
|
||||
return super.get(index);
|
||||
}
|
||||
if (index < 0 || index >= subnodeCount) {
|
||||
return null;
|
||||
}
|
||||
loadSegment(getSegment(index), false);
|
||||
return (NodeHandle) list.get(index);
|
||||
}
|
||||
|
||||
public synchronized boolean contains(Object object) {
|
||||
if (!hasRelationalNodes() || segments == null) {
|
||||
return super.contains(object);
|
||||
}
|
||||
if (list.contains(object)) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
if (loadSegment(i, false).contains(object)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public synchronized int indexOf(Object object) {
|
||||
if (!hasRelationalNodes() || segments == null) {
|
||||
return super.indexOf(object);
|
||||
}
|
||||
int index;
|
||||
if ((index = list.indexOf(object)) > -1) {
|
||||
return index;
|
||||
}
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
if ((index = loadSegment(i, false).indexOf(object)) > -1) {
|
||||
return segments[i].startIndex + index;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the object specified by the given index-position
|
||||
* @param index the index-position of the NodeHandle to remove
|
||||
*/
|
||||
public synchronized Object remove(int index) {
|
||||
if (!hasRelationalNodes() || segments == null) {
|
||||
return super.remove(index);
|
||||
}
|
||||
if (subnodeCount == -1) {
|
||||
update();
|
||||
}
|
||||
Object removed = list.remove(index);
|
||||
int s = getSegment(index);
|
||||
segments[s].length -= 1;
|
||||
for (int i = s + 1; i < segments.length; i++) {
|
||||
segments[i].startIndex -= 1;
|
||||
}
|
||||
subnodeCount--;
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the given Object from this List
|
||||
* @param object the NodeHandle to remove
|
||||
*/
|
||||
public synchronized boolean remove(Object object) {
|
||||
if (!hasRelationalNodes() || segments == null) {
|
||||
return super.remove(object);
|
||||
}
|
||||
if (subnodeCount == -1) {
|
||||
update();
|
||||
}
|
||||
int index = indexOf(object);
|
||||
if (index > -1) {
|
||||
list.remove(object);
|
||||
int s = getSegment(index);
|
||||
segments[s].length -= 1;
|
||||
for (int i = s + 1; i < segments.length; i++) {
|
||||
segments[i].startIndex -= 1;
|
||||
}
|
||||
subnodeCount--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public synchronized Object[] toArray() {
|
||||
if (!hasRelationalNodes() || segments == null) {
|
||||
return super.toArray();
|
||||
}
|
||||
node.nmgr.logEvent("Warning: toArray() called on large segmented collection: " + node);
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
loadSegment(i, false);
|
||||
}
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
private int getSegment(int index) {
|
||||
for (int i = 1; i < segments.length; i++) {
|
||||
if (index < segments[i].startIndex) {
|
||||
return i - 1;
|
||||
}
|
||||
}
|
||||
return segments.length - 1;
|
||||
}
|
||||
|
||||
private List loadSegment(int seg, boolean deep) {
|
||||
Segment segment = segments[seg];
|
||||
if (segment != null && !segment.loaded) {
|
||||
Relation rel = getSubnodeRelation().getClone();
|
||||
rel.offset = segment.startIndex;
|
||||
int expectedSize = rel.maxSize = segment.length;
|
||||
List seglist = deep ?
|
||||
node.nmgr.getNodes(node, rel) :
|
||||
node.nmgr.getNodeIDs(node, rel);
|
||||
int actualSize = seglist.size();
|
||||
if (actualSize != expectedSize) {
|
||||
node.nmgr.logEvent("Inconsistent segment size in " + node + ": " + segment);
|
||||
}
|
||||
int listSize = list.size();
|
||||
for (int i = 0; i < actualSize; i++) {
|
||||
if (segment.startIndex + i < listSize) {
|
||||
list.set(segment.startIndex + i, seglist.get(i));
|
||||
} else {
|
||||
list.add(seglist.get(i));
|
||||
}
|
||||
// FIXME how to handle inconsistencies?
|
||||
}
|
||||
segment.loaded = true;
|
||||
return seglist;
|
||||
}
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
protected synchronized void update() {
|
||||
if (!hasRelationalNodes()) {
|
||||
segments = null;
|
||||
super.update();
|
||||
return;
|
||||
}
|
||||
// also reload if the type mapping has changed.
|
||||
long lastChange = getLastSubnodeChange();
|
||||
if (lastChange != lastSubnodeFetch) {
|
||||
// count nodes in db without fetching anything
|
||||
subnodeCount = node.nmgr.countNodes(node, getSubnodeRelation());
|
||||
if (subnodeCount > SEGLENGTH) {
|
||||
float size = subnodeCount;
|
||||
int nsegments = (int) Math.ceil(size / SEGLENGTH);
|
||||
int remainder = (int) size % SEGLENGTH;
|
||||
segments = new Segment[nsegments];
|
||||
for (int s = 0; s < nsegments; s++) {
|
||||
int length = (s == nsegments - 1 && remainder > 0) ?
|
||||
remainder : SEGLENGTH;
|
||||
segments[s] = new Segment(s * SEGLENGTH, length);
|
||||
}
|
||||
list = new ArrayList((int) size + 5);
|
||||
for (int i = 0; i < size; i++) {
|
||||
list.add(null);
|
||||
}
|
||||
} else {
|
||||
segments = null;
|
||||
super.update();
|
||||
}
|
||||
lastSubnodeFetch = lastChange;
|
||||
}
|
||||
}
|
||||
|
||||
public int size() {
|
||||
if (!hasRelationalNodes() || segments == null) {
|
||||
return super.size();
|
||||
}
|
||||
return subnodeCount;
|
||||
}
|
||||
|
||||
class Segment {
|
||||
|
||||
int startIndex, length;
|
||||
boolean loaded;
|
||||
|
||||
Segment(int startIndex, int length) {
|
||||
this.startIndex = startIndex;
|
||||
this.length = length;
|
||||
this.loaded = false;
|
||||
}
|
||||
|
||||
int endIndex() {
|
||||
return startIndex + length;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Segment{startIndex: " + startIndex + ", length: " + length + "}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
189
src/main/java/helma/objectmodel/db/SubnodeList.java
Normal file
189
src/main/java/helma/objectmodel/db/SubnodeList.java
Normal file
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Container implementation for subnode collections.
|
||||
*/
|
||||
public class SubnodeList implements Serializable {
|
||||
|
||||
protected Node node;
|
||||
protected List list;
|
||||
|
||||
transient protected long lastSubnodeFetch = 0;
|
||||
transient protected long lastSubnodeChange = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Hide/disable zero argument constructor for subclasses
|
||||
*/
|
||||
private SubnodeList() {}
|
||||
|
||||
/**
|
||||
* Creates a new subnode list
|
||||
* @param node the node we belong to
|
||||
*/
|
||||
public SubnodeList(Node node) {
|
||||
this.node = node;
|
||||
this.list = new ArrayList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified object to this list performing
|
||||
* custom ordering
|
||||
*
|
||||
* @param handle element to be inserted.
|
||||
*/
|
||||
public boolean add(NodeHandle handle) {
|
||||
return list.add(handle);
|
||||
}
|
||||
/**
|
||||
* Adds the specified object to the list at the given position
|
||||
* @param idx the index to insert the element at
|
||||
* @param handle the object to add
|
||||
*/
|
||||
public void add(int idx, NodeHandle handle) {
|
||||
list.add(idx, handle);
|
||||
}
|
||||
|
||||
public NodeHandle get(int index) {
|
||||
if (index < 0 || index >= list.size()) {
|
||||
return null;
|
||||
}
|
||||
return (NodeHandle) list.get(index);
|
||||
}
|
||||
|
||||
public Node getNode(int index) {
|
||||
Node retval = null;
|
||||
NodeHandle handle = get(index);
|
||||
|
||||
if (handle != null) {
|
||||
retval = handle.getNode(node.nmgr);
|
||||
// Legacy alarm!
|
||||
if ((retval != null) && (retval.parentHandle == null) &&
|
||||
!node.nmgr.isRootNode(retval)) {
|
||||
retval.setParent(node);
|
||||
retval.anonymous = true;
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
public boolean contains(Object object) {
|
||||
return list.contains(object);
|
||||
}
|
||||
|
||||
public int indexOf(Object object) {
|
||||
return list.indexOf(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the object specified by the given index-position
|
||||
* @param idx the index-position of the NodeHandle to remove
|
||||
*/
|
||||
public Object remove (int idx) {
|
||||
return list.remove(idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the given Object from this List
|
||||
* @param obj the NodeHandle to remove
|
||||
*/
|
||||
public boolean remove (Object obj) {
|
||||
return list.remove(obj);
|
||||
}
|
||||
|
||||
public Object[] toArray() {
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the list.
|
||||
* @return the list size
|
||||
*/
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
protected void update() {
|
||||
// also reload if the type mapping has changed.
|
||||
long lastChange = getLastSubnodeChange();
|
||||
if (lastChange != lastSubnodeFetch) {
|
||||
Relation rel = getSubnodeRelation();
|
||||
if (rel != null && rel.aggressiveLoading && rel.groupby == null) {
|
||||
list = node.nmgr.getNodes(node, rel);
|
||||
} else {
|
||||
list = node.nmgr.getNodeIDs(node, rel);
|
||||
}
|
||||
lastSubnodeFetch = lastChange;
|
||||
}
|
||||
}
|
||||
|
||||
protected void prefetch(int start, int length) {
|
||||
if (start < 0 || start >= size()) {
|
||||
return;
|
||||
}
|
||||
length = (length < 0) ?
|
||||
size() - start : Math.min(length, size() - start);
|
||||
if (length < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
DbMapping dbmap = getSubnodeMapping();
|
||||
|
||||
if (dbmap.isRelational()) {
|
||||
Relation rel = getSubnodeRelation();
|
||||
node.nmgr.prefetchNodes(node, rel, this, start, length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a serial number indicating the last change in subnode collection
|
||||
* @return a serial number that increases with each subnode change
|
||||
*/
|
||||
protected long getLastSubnodeChange() {
|
||||
// include dbmap.getLastTypeChange to also reload if the type mapping has changed.
|
||||
long checkSum = lastSubnodeChange + node.dbmap.getLastTypeChange();
|
||||
Relation rel = getSubnodeRelation();
|
||||
return rel == null || rel.aggressiveCaching ?
|
||||
checkSum : checkSum + rel.otherType.getLastDataChange();
|
||||
}
|
||||
|
||||
protected synchronized void markAsChanged() {
|
||||
lastSubnodeChange += 1;
|
||||
}
|
||||
|
||||
protected boolean hasRelationalNodes() {
|
||||
DbMapping dbmap = getSubnodeMapping();
|
||||
return (dbmap != null && dbmap.isRelational()
|
||||
&& ((node.getState() != Node.TRANSIENT && node.getState() != Node.NEW)
|
||||
|| node.getSubnodeRelation() != null));
|
||||
}
|
||||
|
||||
protected DbMapping getSubnodeMapping() {
|
||||
return node.dbmap == null ? null : node.dbmap.getSubnodeMapping();
|
||||
}
|
||||
|
||||
protected Relation getSubnodeRelation() {
|
||||
return node.dbmap == null ? null : node.dbmap.getSubnodeRelation();
|
||||
}
|
||||
}
|
114
src/main/java/helma/objectmodel/db/SyntheticKey.java
Normal file
114
src/main/java/helma/objectmodel/db/SyntheticKey.java
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This is the internal key for an object that is not - or not directly - fetched from a db,
|
||||
* but derived from another object. This is useful for all kinds of object accessed via a
|
||||
* symbolic name from another object, like objects mounted via a property name column,
|
||||
* virtual nodes and groupby nodes.
|
||||
*/
|
||||
public final class SyntheticKey implements Key, Serializable {
|
||||
|
||||
// the parent key
|
||||
private final Key parentKey;
|
||||
|
||||
// the name relative to the parent key
|
||||
private final String name;
|
||||
|
||||
// lazily initialized hashcode
|
||||
private transient int hashcode = 0;
|
||||
|
||||
static final long serialVersionUID = -693454133259421857L;
|
||||
|
||||
/**
|
||||
* Make a symbolic key for an object using its parent key and its property name/id.
|
||||
* @param key the parent key
|
||||
* @param name the property or collection name
|
||||
*/
|
||||
public SyntheticKey(Key key, String name) {
|
||||
this.parentKey = key;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this key equals obj
|
||||
* @param obj another object
|
||||
* @return true if obj represents the same key as this
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof SyntheticKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SyntheticKey k = (SyntheticKey) obj;
|
||||
|
||||
return parentKey.equals(k.parentKey) &&
|
||||
((name == k.name) || name.equals(k.name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash-code for this key
|
||||
* @return the hash-code
|
||||
*/
|
||||
public int hashCode() {
|
||||
if (hashcode == 0) {
|
||||
hashcode = 17 + (37 * name.hashCode()) +
|
||||
(37 * parentKey.hashCode());
|
||||
}
|
||||
|
||||
return hashcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent key part of this key
|
||||
* @return the parent key
|
||||
*/
|
||||
public Key getParentKey() {
|
||||
return parentKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID part of this key
|
||||
* @return the id part
|
||||
*/
|
||||
public String getID() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the storage name for this key. This alwys returns null for symbolic keys.
|
||||
* @return null
|
||||
*/
|
||||
public String getStorageName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation for this key
|
||||
* @return a string representation for this key
|
||||
*/
|
||||
public String toString() {
|
||||
return parentKey + "/" + name;
|
||||
}
|
||||
}
|
578
src/main/java/helma/objectmodel/db/Transactor.java
Normal file
578
src/main/java/helma/objectmodel/db/Transactor.java
Normal file
|
@ -0,0 +1,578 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import helma.objectmodel.DatabaseException;
|
||||
import helma.objectmodel.ITransaction;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.Statement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
/**
|
||||
* A subclass of thread that keeps track of changed nodes and triggers
|
||||
* changes in the database when a transaction is commited.
|
||||
*/
|
||||
public class Transactor {
|
||||
|
||||
// The associated node manager
|
||||
NodeManager nmgr;
|
||||
|
||||
// List of nodes to be updated
|
||||
private Map dirtyNodes;
|
||||
|
||||
// List of visited clean nodes
|
||||
private Map cleanNodes;
|
||||
|
||||
// List of nodes whose child index has been modified
|
||||
private Set parentNodes;
|
||||
|
||||
// Is a transaction in progress?
|
||||
private volatile boolean active;
|
||||
private volatile boolean killed;
|
||||
|
||||
// Transaction for the embedded database
|
||||
protected ITransaction txn;
|
||||
|
||||
// Transactions for SQL data sources
|
||||
private Map<DbSource, Connection> sqlConnections;
|
||||
|
||||
// Set of SQL connections that already have been verified
|
||||
private Map<DbSource, Long> testedConnections;
|
||||
|
||||
// when did the current transaction start?
|
||||
private long tstart;
|
||||
|
||||
// a name to log the transaction. For HTTP transactions this is the rerquest path
|
||||
private String tname;
|
||||
|
||||
// the thread we're associated with
|
||||
private Thread thread;
|
||||
|
||||
private static final ThreadLocal txtor = new ThreadLocal();
|
||||
|
||||
/**
|
||||
* Creates a new Transactor object.
|
||||
*
|
||||
* @param nmgr the NodeManager used to fetch and persist nodes.
|
||||
*/
|
||||
private Transactor(NodeManager nmgr) {
|
||||
this.thread = Thread.currentThread();
|
||||
this.nmgr = nmgr;
|
||||
|
||||
dirtyNodes = new LinkedHashMap();
|
||||
cleanNodes = new HashMap();
|
||||
parentNodes = new HashSet();
|
||||
|
||||
sqlConnections = new HashMap<DbSource, Connection>();
|
||||
testedConnections = new HashMap<DbSource, Long>();
|
||||
active = false;
|
||||
killed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transactor for the current thread or null if none exists.
|
||||
* @return the transactor associated with the current thread
|
||||
*/
|
||||
public static Transactor getInstance() {
|
||||
return (Transactor) txtor.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transactor for the current thread or throw a IllegalStateException if none exists.
|
||||
* @return the transactor associated with the current thread
|
||||
* @throws IllegalStateException if no transactor is associated with the current thread
|
||||
*/
|
||||
public static Transactor getInstanceOrFail() throws IllegalStateException {
|
||||
Transactor tx = (Transactor) txtor.get();
|
||||
if (tx == null)
|
||||
throw new IllegalStateException("Operation requires a Transactor, " +
|
||||
"but current thread does not have one.");
|
||||
return tx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transactor for the current thread, creating a new one if none exists.
|
||||
* @param nmgr the NodeManager used to create the transactor
|
||||
* @return the transactor associated with the current thread
|
||||
*/
|
||||
public static Transactor getInstance(NodeManager nmgr) {
|
||||
Transactor t = (Transactor) txtor.get();
|
||||
if (t == null) {
|
||||
t = new Transactor(nmgr);
|
||||
txtor.set(t);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a Node as modified/created/deleted during this transaction
|
||||
*
|
||||
* @param node ...
|
||||
*/
|
||||
public void visitDirtyNode(Node node) {
|
||||
if (node != null) {
|
||||
Key key = node.getKey();
|
||||
|
||||
dirtyNodes.put(key, node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmark a Node that has previously been marked as modified during the transaction
|
||||
*
|
||||
* @param node ...
|
||||
*/
|
||||
public void dropDirtyNode(Node node) {
|
||||
if (node != null) {
|
||||
Key key = node.getKey();
|
||||
|
||||
dirtyNodes.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a dirty Node from this transaction.
|
||||
* @param key the key
|
||||
* @return the dirty node associated with the key, or null
|
||||
*/
|
||||
public Node getDirtyNode(Key key) {
|
||||
return (Node) dirtyNodes.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep a reference to an unmodified Node local to this transaction
|
||||
*
|
||||
* @param node the node to register
|
||||
*/
|
||||
public void visitCleanNode(Node node) {
|
||||
if (node != null) {
|
||||
Key key = node.getKey();
|
||||
|
||||
if (!cleanNodes.containsKey(key)) {
|
||||
cleanNodes.put(key, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep a reference to an unmodified Node local to this transaction
|
||||
*
|
||||
* @param key the key to register with
|
||||
* @param node the node to register
|
||||
*/
|
||||
public void visitCleanNode(Key key, Node node) {
|
||||
if (node != null) {
|
||||
if (!cleanNodes.containsKey(key)) {
|
||||
cleanNodes.put(key, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop a reference to an unmodified Node previously registered with visitCleanNode().
|
||||
* @param key the key
|
||||
*/
|
||||
public void dropCleanNode(Key key) {
|
||||
cleanNodes.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a reference to an unmodified Node local to this transaction
|
||||
*
|
||||
* @param key ...
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public Node getCleanNode(Object key) {
|
||||
return (key == null) ? null : (Node) cleanNodes.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param node ...
|
||||
*/
|
||||
public void visitParentNode(Node node) {
|
||||
parentNodes.add(node);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if a transaction is currently active.
|
||||
* @return true if currently a transaction is active
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the thread associated with this transactor is alive.
|
||||
* This is a proxy to Thread.isAlive().
|
||||
* @return true if the thread running this transactor is currently alive.
|
||||
*/
|
||||
public boolean isAlive() {
|
||||
return thread != null && thread.isAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a db connection with this transactor thread.
|
||||
* @param src the db source
|
||||
* @param con the connection
|
||||
*/
|
||||
public void registerConnection(DbSource src, Connection con) {
|
||||
sqlConnections.put(src, con);
|
||||
// we assume a freshly created connection is ok.
|
||||
testedConnections.put(src, new Long(System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a db connection that was previously registered with this transactor thread.
|
||||
* @param src the db source
|
||||
* @return the connection
|
||||
*/
|
||||
public Connection getConnection(DbSource src) {
|
||||
Connection con = sqlConnections.get(src);
|
||||
Long tested = testedConnections.get(src);
|
||||
long now = System.currentTimeMillis();
|
||||
if (con != null && (tested == null || now - tested.longValue() > 60000)) {
|
||||
// Check if the connection is still alive by executing a simple statement.
|
||||
try {
|
||||
Statement stmt = con.createStatement();
|
||||
if (src.isOracle()) {
|
||||
stmt.execute("SELECT 1 FROM DUAL");
|
||||
} else {
|
||||
stmt.execute("SELECT 1");
|
||||
}
|
||||
stmt.close();
|
||||
testedConnections.put(src, new Long(now));
|
||||
} catch (SQLException sx) {
|
||||
try {
|
||||
con.close();
|
||||
} catch (SQLException ignore) {/* nothing to do */}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return con;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new transaction with the given name.
|
||||
*
|
||||
* @param name The name of the transaction. This is usually the request
|
||||
* path for the underlying HTTP request.
|
||||
*
|
||||
* @throws Exception ...
|
||||
*/
|
||||
public synchronized void begin(String name) throws Exception {
|
||||
if (killed) {
|
||||
throw new DatabaseException("Transaction started on killed thread");
|
||||
} else if (active) {
|
||||
abort();
|
||||
}
|
||||
|
||||
dirtyNodes.clear();
|
||||
cleanNodes.clear();
|
||||
parentNodes.clear();
|
||||
txn = nmgr.db.beginTransaction();
|
||||
active = true;
|
||||
tstart = System.currentTimeMillis();
|
||||
tname = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the current transaction, persisting all changes to DB.
|
||||
*
|
||||
* @throws Exception ...
|
||||
*/
|
||||
public synchronized void commit() throws Exception {
|
||||
if (killed) {
|
||||
throw new DatabaseException("commit() called on killed transactor thread");
|
||||
} else if (!active) {
|
||||
return;
|
||||
}
|
||||
int inserted = 0;
|
||||
int updated = 0;
|
||||
int deleted = 0;
|
||||
|
||||
ArrayList insertedNodes = null;
|
||||
ArrayList updatedNodes = null;
|
||||
ArrayList deletedNodes = null;
|
||||
ArrayList modifiedParentNodes = null;
|
||||
// if nodemanager has listeners collect dirty nodes
|
||||
boolean hasListeners = nmgr.hasNodeChangeListeners();
|
||||
|
||||
if (hasListeners) {
|
||||
insertedNodes = new ArrayList();
|
||||
updatedNodes = new ArrayList();
|
||||
deletedNodes = new ArrayList();
|
||||
modifiedParentNodes = new ArrayList();
|
||||
}
|
||||
|
||||
if (!dirtyNodes.isEmpty()) {
|
||||
Object[] dirty = dirtyNodes.values().toArray();
|
||||
|
||||
// the set to collect DbMappings to be marked as changed
|
||||
HashSet dirtyDbMappings = new HashSet();
|
||||
Log eventLog = nmgr.app.getEventLog();
|
||||
|
||||
for (int i = 0; i < dirty.length; i++) {
|
||||
Node node = (Node) dirty[i];
|
||||
|
||||
// update nodes in db
|
||||
int nstate = node.getState();
|
||||
|
||||
if (nstate == Node.NEW) {
|
||||
nmgr.insertNode(nmgr.db, txn, node);
|
||||
dirtyDbMappings.add(node.getDbMapping());
|
||||
node.setState(Node.CLEAN);
|
||||
|
||||
// register node with nodemanager cache
|
||||
nmgr.registerNode(node);
|
||||
|
||||
if (hasListeners) {
|
||||
insertedNodes.add(node);
|
||||
}
|
||||
|
||||
inserted++;
|
||||
if (eventLog.isDebugEnabled()) {
|
||||
eventLog.debug("inserted node: " + node.getPrototype() + "/" +
|
||||
node.getID());
|
||||
}
|
||||
} else if (nstate == Node.MODIFIED) {
|
||||
// only mark DbMapping as dirty if updateNode returns true
|
||||
if (nmgr.updateNode(nmgr.db, txn, node)) {
|
||||
dirtyDbMappings.add(node.getDbMapping());
|
||||
}
|
||||
node.setState(Node.CLEAN);
|
||||
|
||||
// update node with nodemanager cache
|
||||
nmgr.registerNode(node);
|
||||
|
||||
if (hasListeners) {
|
||||
updatedNodes.add(node);
|
||||
}
|
||||
|
||||
updated++;
|
||||
if (eventLog.isDebugEnabled()) {
|
||||
eventLog.debug("updated node: " + node.getPrototype() + "/" +
|
||||
node.getID());
|
||||
}
|
||||
} else if (nstate == Node.DELETED) {
|
||||
nmgr.deleteNode(nmgr.db, txn, node);
|
||||
dirtyDbMappings.add(node.getDbMapping());
|
||||
|
||||
// remove node from nodemanager cache
|
||||
nmgr.evictNode(node);
|
||||
|
||||
if (hasListeners) {
|
||||
deletedNodes.add(node);
|
||||
}
|
||||
|
||||
deleted++;
|
||||
if (eventLog.isDebugEnabled()) {
|
||||
eventLog.debug("removed node: " + node.getPrototype() + "/" +
|
||||
node.getID());
|
||||
}
|
||||
}
|
||||
|
||||
node.clearWriteLock();
|
||||
}
|
||||
|
||||
// set last data change times in db-mappings
|
||||
// long now = System.currentTimeMillis();
|
||||
for (Iterator i = dirtyDbMappings.iterator(); i.hasNext(); ) {
|
||||
DbMapping dbm = (DbMapping) i.next();
|
||||
if (dbm != null) {
|
||||
dbm.setLastDataChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (!parentNodes.isEmpty()) {
|
||||
// set last subnode change times in parent nodes
|
||||
for (Iterator i = parentNodes.iterator(); i.hasNext(); ) {
|
||||
Node node = (Node) i.next();
|
||||
node.markSubnodesChanged();
|
||||
if (hasListeners) {
|
||||
modifiedParentNodes.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasListeners) {
|
||||
nmgr.fireNodeChangeEvent(insertedNodes, updatedNodes,
|
||||
deletedNodes, modifiedParentNodes);
|
||||
}
|
||||
|
||||
// clear the node collections
|
||||
recycle();
|
||||
|
||||
if (active) {
|
||||
active = false;
|
||||
nmgr.db.commitTransaction(txn);
|
||||
txn = null;
|
||||
}
|
||||
|
||||
StringBuffer msg = new StringBuffer(tname).append(" done in ")
|
||||
.append(now - tstart).append(" millis");
|
||||
if(inserted + updated + deleted > 0) {
|
||||
msg.append(" [+")
|
||||
.append(inserted).append(", ~")
|
||||
.append(updated).append(", -")
|
||||
.append(deleted).append("]");
|
||||
}
|
||||
nmgr.app.logAccess(msg.toString());
|
||||
|
||||
// unset transaction name
|
||||
tname = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort the current transaction, rolling back all changes made.
|
||||
*/
|
||||
public synchronized void abort() {
|
||||
Object[] dirty = dirtyNodes.values().toArray();
|
||||
|
||||
// evict dirty nodes from cache
|
||||
for (int i = 0; i < dirty.length; i++) {
|
||||
Node node = (Node) dirty[i];
|
||||
|
||||
// Declare node as invalid, so it won't be used by other threads
|
||||
// that want to write on it and remove it from cache
|
||||
nmgr.evictNode(node);
|
||||
node.clearWriteLock();
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// set last subnode change times in parent nodes
|
||||
for (Iterator i = parentNodes.iterator(); i.hasNext(); ) {
|
||||
Node node = (Node) i.next();
|
||||
node.markSubnodesChanged();
|
||||
}
|
||||
|
||||
// clear the node collections
|
||||
recycle();
|
||||
// close any JDBC connections associated with this transactor thread
|
||||
closeConnections();
|
||||
|
||||
if (active) {
|
||||
active = false;
|
||||
|
||||
if (txn != null) {
|
||||
nmgr.db.abortTransaction(txn);
|
||||
txn = null;
|
||||
}
|
||||
|
||||
nmgr.app.logAccess(tname + " aborted after " +
|
||||
(System.currentTimeMillis() - tstart) + " millis");
|
||||
}
|
||||
|
||||
// unset transaction name
|
||||
tname = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill this transaction thread. Used as last measure only.
|
||||
*/
|
||||
public synchronized void kill() {
|
||||
|
||||
killed = true;
|
||||
thread.interrupt();
|
||||
|
||||
// Interrupt the thread if it has not noticed the flag (e.g. because it is busy
|
||||
// reading from a network socket).
|
||||
if (thread.isAlive()) {
|
||||
thread.interrupt();
|
||||
try {
|
||||
thread.join(1000);
|
||||
} catch (InterruptedException ir) {
|
||||
// interrupted by other thread
|
||||
}
|
||||
}
|
||||
|
||||
if (thread.isAlive() && "true".equals(nmgr.app.getProperty("requestTimeoutStop"))) {
|
||||
// still running - check if we ought to stop() it
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
if (thread.isAlive()) {
|
||||
// thread is still running, pull emergency break
|
||||
nmgr.app.logEvent("Stopping Thread for Transactor " + this);
|
||||
thread.stop();
|
||||
}
|
||||
} catch (InterruptedException ir) {
|
||||
// interrupted by other thread
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all open JDBC connections
|
||||
*/
|
||||
public void closeConnections() {
|
||||
if (sqlConnections != null) {
|
||||
for (Iterator i = sqlConnections.values().iterator(); i.hasNext();) {
|
||||
try {
|
||||
Connection con = (Connection) i.next();
|
||||
|
||||
con.close();
|
||||
nmgr.app.logEvent("Closing DB connection: " + con);
|
||||
} catch (Exception ignore) {
|
||||
// exception closing db connection, ignore
|
||||
}
|
||||
}
|
||||
|
||||
sqlConnections.clear();
|
||||
testedConnections.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear collections and throw them away. They may have grown large,
|
||||
* so the benefit of keeping them (less GC) needs to be weighted against
|
||||
* the potential increas in memory usage.
|
||||
*/
|
||||
private synchronized void recycle() {
|
||||
// clear the node collections to ease garbage collection
|
||||
dirtyNodes.clear();
|
||||
cleanNodes.clear();
|
||||
parentNodes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the current transaction. This is usually the request
|
||||
* path for the underlying HTTP request.
|
||||
*/
|
||||
public String getTransactionName() {
|
||||
return tname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of this Transactor thread
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public String toString() {
|
||||
return "Transactor[" + tname + "]";
|
||||
}
|
||||
}
|
354
src/main/java/helma/objectmodel/db/WrappedNodeManager.java
Normal file
354
src/main/java/helma/objectmodel/db/WrappedNodeManager.java
Normal file
|
@ -0,0 +1,354 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.db;
|
||||
|
||||
import helma.objectmodel.ObjectNotFoundException;
|
||||
|
||||
import java.util.Vector;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A wrapper around NodeManager that catches most Exceptions, or rethrows them as RuntimeExceptions.
|
||||
* The idea behind this is that we don't care a lot about Exception classes, since Hop programming is done
|
||||
* in JavaScript which doesn't know about them (except for the exception message).
|
||||
*/
|
||||
public final class WrappedNodeManager {
|
||||
NodeManager nmgr;
|
||||
|
||||
/**
|
||||
* Creates a new WrappedNodeManager object.
|
||||
*
|
||||
* @param nmgr ...
|
||||
*/
|
||||
public WrappedNodeManager(NodeManager nmgr) {
|
||||
this.nmgr = nmgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a node given its id and DbMapping
|
||||
*
|
||||
* @param id
|
||||
* @param dbmap
|
||||
* @return
|
||||
*/
|
||||
public Node getNode(String id, DbMapping dbmap) {
|
||||
return getNode(new DbKey(dbmap, id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a node given its key
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public Node getNode(Key key) {
|
||||
Transactor tx = checkLocalTransactor();
|
||||
try {
|
||||
beginLocalTransaction(tx, "getNode");
|
||||
Node node = nmgr.getNode(key);
|
||||
commitLocalTransaction(tx);
|
||||
return node;
|
||||
} catch (ObjectNotFoundException x) {
|
||||
abortLocalTransaction(tx);
|
||||
return null;
|
||||
} catch (Exception x) {
|
||||
abortLocalTransaction(tx);
|
||||
nmgr.app.logError("Error retrieving Node for " + key, x);
|
||||
throw new RuntimeException("Error retrieving Node", x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node specified by the given id and Relation.
|
||||
*
|
||||
* @param home
|
||||
* @param id
|
||||
* @param rel
|
||||
* @return
|
||||
*/
|
||||
public Node getNode(Node home, String id, Relation rel) {
|
||||
Transactor tx = checkLocalTransactor();
|
||||
try {
|
||||
beginLocalTransaction(tx, "getNode");
|
||||
Node node = nmgr.getNode(home, id, rel);
|
||||
commitLocalTransaction(tx);
|
||||
return node;
|
||||
} catch (ObjectNotFoundException x) {
|
||||
abortLocalTransaction(tx);
|
||||
return null;
|
||||
} catch (Exception x) {
|
||||
abortLocalTransaction(tx);
|
||||
nmgr.app.logError("Error retrieving Node \"" + id + "\" from " + home, x);
|
||||
throw new RuntimeException("Error retrieving Node", x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of nodes contained in the collection of the given
|
||||
* Node specified by the given Relation.
|
||||
*
|
||||
* @param home
|
||||
* @param rel
|
||||
* @return
|
||||
*/
|
||||
public List getNodes(Node home, Relation rel) {
|
||||
Transactor tx = checkLocalTransactor();
|
||||
try {
|
||||
beginLocalTransaction(tx, "getNodes");
|
||||
List list = nmgr.getNodes(home, rel);
|
||||
commitLocalTransaction(tx);
|
||||
return list;
|
||||
} catch (Exception x) {
|
||||
abortLocalTransaction(tx);
|
||||
throw new RuntimeException("Error retrieving Nodes", x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of IDs of nodes contained in the given Node's
|
||||
* collection specified by the given Relation.
|
||||
*
|
||||
* @param home
|
||||
* @param rel
|
||||
* @return
|
||||
*/
|
||||
public List getNodeIDs(Node home, Relation rel) {
|
||||
try {
|
||||
return nmgr.getNodeIDs(home, rel);
|
||||
} catch (Exception x) {
|
||||
throw new RuntimeException("Error retrieving NodeIDs", x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see helma.objectmodel.db.NodeManager#updateSubnodeList(Node, Relation)
|
||||
*/
|
||||
/* public int updateSubnodeList (Node home, Relation rel) {
|
||||
try {
|
||||
return nmgr.updateSubnodeList(home, rel);
|
||||
} catch (Exception x) {
|
||||
throw new RuntimeException("Error retrieving NodeIDs", x);
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Count the nodes contained in the given Node's collection
|
||||
* specified by the given Relation.
|
||||
*
|
||||
* @param home
|
||||
* @param rel
|
||||
* @return
|
||||
*/
|
||||
public int countNodes(Node home, Relation rel) {
|
||||
try {
|
||||
return nmgr.countNodes(home, rel);
|
||||
} catch (Exception x) {
|
||||
throw new RuntimeException("Error counting Nodes", x);
|
||||
}
|
||||
}
|
||||
|
||||
public void prefetchNodes(Node node, Relation rel, SubnodeList list,
|
||||
int start, int length) {
|
||||
try {
|
||||
nmgr.prefetchNodes(node, rel, list, start, length);
|
||||
} catch (Exception x) {
|
||||
throw new RuntimeException("Error prefetching nodes", x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a node from the database
|
||||
*
|
||||
* @param node
|
||||
*/
|
||||
public void deleteNode(Node node) {
|
||||
Transactor tx = checkLocalTransactor();
|
||||
try {
|
||||
beginLocalTransaction(tx, "deleteNode");
|
||||
nmgr.deleteNode(node);
|
||||
commitLocalTransaction(tx);
|
||||
} catch (Exception x) {
|
||||
abortLocalTransaction(tx);
|
||||
throw new RuntimeException("Error deleting Node", x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of property names from the given node.
|
||||
* TODO: this retrieves access names of child nodes, not property names
|
||||
*
|
||||
* @param home
|
||||
* @param rel
|
||||
* @return
|
||||
*/
|
||||
public Vector getPropertyNames(Node home, Relation rel) {
|
||||
try {
|
||||
return nmgr.getPropertyNames(home, rel);
|
||||
} catch (Exception x) {
|
||||
throw new RuntimeException("Error retrieving property names ", x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a node with the object cache using its primary key.
|
||||
*
|
||||
* @param node
|
||||
*/
|
||||
public void registerNode(Node node) {
|
||||
nmgr.registerNode(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a node with the object cache using the given key.
|
||||
*
|
||||
* @param node
|
||||
*/
|
||||
public void registerNode(Node node, Key key) {
|
||||
nmgr.registerNode(node, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evict a node from the object cache
|
||||
*
|
||||
* @param node
|
||||
*/
|
||||
public void evictNode(Node node) {
|
||||
nmgr.evictNode(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely evict the object with the given key from the object cache
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
public void evictNodeByKey(Key key) {
|
||||
nmgr.evictNodeByKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evict the object with the given key from the object cache
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
public void evictKey(Key key) {
|
||||
nmgr.evictKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new id for an object specified by the DbMapping
|
||||
*
|
||||
* @param map the DbMapping to generate an id for
|
||||
* @return a new unique id
|
||||
*/
|
||||
public String generateID(DbMapping map) {
|
||||
try {
|
||||
return nmgr.generateID(map);
|
||||
} catch (Exception x) {
|
||||
throw new RuntimeException(x.toString(), x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application's root node.
|
||||
*/
|
||||
public Node getRootNode() {
|
||||
Transactor tx = checkLocalTransactor();
|
||||
try {
|
||||
beginLocalTransaction(tx, "getRootNode");
|
||||
Node node = nmgr.getRootNode();
|
||||
commitLocalTransaction(tx);
|
||||
return node;
|
||||
} catch (Exception x) {
|
||||
abortLocalTransaction(tx);
|
||||
throw new RuntimeException(x.toString(), x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given node is the application's root node.
|
||||
*/
|
||||
public boolean isRootNode(Node node) {
|
||||
return nmgr.isRootNode(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all objects in the object cache
|
||||
*/
|
||||
public Object[] getCacheEntries() {
|
||||
return nmgr.getCacheEntries();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an entry to the application's event log
|
||||
*
|
||||
* @param msg event message
|
||||
*/
|
||||
public void logEvent(String msg) {
|
||||
nmgr.app.logEvent(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DbMapping corresponding to a type name
|
||||
*
|
||||
* @param name a type name
|
||||
* @return the corresponding DbMapping
|
||||
*/
|
||||
public DbMapping getDbMapping(String name) {
|
||||
return nmgr.app.getDbMapping(name);
|
||||
}
|
||||
|
||||
// helper methods to wrap execution inside local transactions
|
||||
|
||||
private Transactor checkLocalTransactor() {
|
||||
Transactor tx = Transactor.getInstance();
|
||||
if (tx != null) {
|
||||
// transactor already associated with current thread - return null
|
||||
return null;
|
||||
}
|
||||
return Transactor.getInstance(nmgr);
|
||||
}
|
||||
|
||||
private void beginLocalTransaction(Transactor tx, String name) {
|
||||
if (tx != null) {
|
||||
try {
|
||||
tx.begin(name);
|
||||
} catch (Exception x) {
|
||||
nmgr.app.logError("Error in beginLocalTransaction", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void commitLocalTransaction(Transactor tx) {
|
||||
if (tx != null) {
|
||||
try {
|
||||
tx.commit();
|
||||
} catch (Exception x) {
|
||||
nmgr.app.logError("Error in commitLocalTransaction", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void abortLocalTransaction(Transactor tx) {
|
||||
if (tx != null) {
|
||||
try {
|
||||
tx.abort();
|
||||
} catch (Exception x) {
|
||||
nmgr.app.logError("Error in abortLocalTransaction", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
src/main/java/helma/objectmodel/dom/XmlConstants.java
Normal file
25
src/main/java/helma/objectmodel/dom/XmlConstants.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.dom;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface XmlConstants {
|
||||
public final String NAMESPACE = "http://www.helma.org/docs/guide/features/database";
|
||||
public final String DATEFORMAT = "dd.MM.yyyy HH:mm:ss z";
|
||||
}
|
528
src/main/java/helma/objectmodel/dom/XmlConverter.java
Normal file
528
src/main/java/helma/objectmodel/dom/XmlConverter.java
Normal file
|
@ -0,0 +1,528 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.dom;
|
||||
|
||||
import helma.objectmodel.INode;
|
||||
import helma.util.SystemProperties;
|
||||
import org.w3c.dom.*;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class XmlConverter implements XmlConstants {
|
||||
private boolean DEBUG = false;
|
||||
private boolean sparse = false;
|
||||
private Properties props;
|
||||
private char defaultSeparator = '_';
|
||||
private int offset = 0;
|
||||
|
||||
/**
|
||||
* Creates a new XmlConverter object.
|
||||
*/
|
||||
public XmlConverter() {
|
||||
props = new SystemProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new XmlConverter object.
|
||||
*
|
||||
* @param propFile ...
|
||||
*/
|
||||
public XmlConverter(String propFile) {
|
||||
props = new SystemProperties(propFile);
|
||||
extractProperties(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new XmlConverter object.
|
||||
*
|
||||
* @param propFile ...
|
||||
*/
|
||||
public XmlConverter(File propFile) {
|
||||
this(propFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new XmlConverter object.
|
||||
*
|
||||
* @param props ...
|
||||
*/
|
||||
public XmlConverter(Properties props) {
|
||||
this.props = props;
|
||||
extractProperties(props);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param desc ...
|
||||
* @param helmaNode ...
|
||||
*
|
||||
* @return ...
|
||||
*
|
||||
* @throws RuntimeException ...
|
||||
*/
|
||||
public INode convert(String desc, INode helmaNode)
|
||||
throws RuntimeException {
|
||||
try {
|
||||
return convert(new URL(desc), helmaNode);
|
||||
} catch (MalformedURLException notanurl) {
|
||||
try {
|
||||
return convert(new File(desc), helmaNode);
|
||||
} catch (FileNotFoundException notfound) {
|
||||
throw new RuntimeException("couldn't read xml: " + desc);
|
||||
}
|
||||
} catch (IOException ioerror) {
|
||||
throw new RuntimeException("couldn't read xml: " + desc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param file ...
|
||||
* @param helmaNode ...
|
||||
*
|
||||
* @return ...
|
||||
*
|
||||
* @throws RuntimeException ...
|
||||
* @throws FileNotFoundException ...
|
||||
*/
|
||||
public INode convert(File file, INode helmaNode)
|
||||
throws RuntimeException, FileNotFoundException {
|
||||
return convert(new FileInputStream(file), helmaNode);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param url ...
|
||||
* @param helmaNode ...
|
||||
*
|
||||
* @return ...
|
||||
*
|
||||
* @throws RuntimeException ...
|
||||
* @throws IOException ...
|
||||
* @throws MalformedURLException ...
|
||||
*/
|
||||
public INode convert(URL url, INode helmaNode)
|
||||
throws RuntimeException, IOException, MalformedURLException {
|
||||
return convert(url.openConnection().getInputStream(), helmaNode);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param in ...
|
||||
* @param helmaNode ...
|
||||
*
|
||||
* @return ...
|
||||
*
|
||||
* @throws RuntimeException ...
|
||||
*/
|
||||
public INode convert(InputStream in, INode helmaNode)
|
||||
throws RuntimeException {
|
||||
Document document = XmlUtil.parse(in);
|
||||
|
||||
if ((document != null) && (document.getDocumentElement() != null)) {
|
||||
return convert(document.getDocumentElement(), helmaNode, new HashMap());
|
||||
} else {
|
||||
return helmaNode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param xml ...
|
||||
* @param helmaNode ...
|
||||
*
|
||||
* @return ...
|
||||
*
|
||||
* @throws RuntimeException ...
|
||||
*/
|
||||
public INode convertFromString(String xml, INode helmaNode)
|
||||
throws RuntimeException {
|
||||
Document document = XmlUtil.parse(new InputSource(new StringReader(xml)));
|
||||
|
||||
if ((document != null) && (document.getDocumentElement() != null)) {
|
||||
return convert(document.getDocumentElement(), helmaNode, new HashMap());
|
||||
} else {
|
||||
return helmaNode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param element ...
|
||||
* @param helmaNode ...
|
||||
* @param nodeCache ...
|
||||
*
|
||||
* @return ...
|
||||
*/
|
||||
public INode convert(Element element, INode helmaNode, Map nodeCache) {
|
||||
offset++;
|
||||
|
||||
// previousNode is used to cache previous nodes with the same prototype
|
||||
// so we can reset it in the nodeCache after we've run
|
||||
Object previousNode = null;
|
||||
|
||||
if (DEBUG) {
|
||||
debug("reading " + element.getNodeName());
|
||||
}
|
||||
|
||||
String prototype = props.getProperty(element.getNodeName() + "._prototype");
|
||||
|
||||
if ((prototype == null) && !sparse) {
|
||||
prototype = "HopObject";
|
||||
}
|
||||
|
||||
// if we have a prototype (either explicit or implicit "hopobject"),
|
||||
// set it on the Helma node and store it in the node cache.
|
||||
if (prototype != null) {
|
||||
helmaNode.setName(element.getNodeName());
|
||||
helmaNode.setPrototype(prototype);
|
||||
previousNode = nodeCache.put(prototype, helmaNode);
|
||||
}
|
||||
|
||||
// check attributes of the current element
|
||||
attributes(element, helmaNode, nodeCache);
|
||||
|
||||
// check child nodes of the current element
|
||||
if (element.hasChildNodes()) {
|
||||
children(element, helmaNode, nodeCache);
|
||||
}
|
||||
|
||||
// if it exists, restore the previous node we've replaced in the node cache.
|
||||
if (previousNode != null) {
|
||||
nodeCache.put(prototype, previousNode);
|
||||
}
|
||||
|
||||
offset--;
|
||||
|
||||
return helmaNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse xml children and create hopobject-children
|
||||
*/
|
||||
private INode children(Element element, helma.objectmodel.INode helmaNode,
|
||||
Map nodeCache) {
|
||||
NodeList list = element.getChildNodes();
|
||||
int len = list.getLength();
|
||||
boolean nodeIsInitialized = !nodeCache.isEmpty();
|
||||
StringBuffer textcontent = new StringBuffer();
|
||||
String domKey;
|
||||
String helmaKey;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
// loop through the list of children
|
||||
org.w3c.dom.Node childNode = list.item(i);
|
||||
|
||||
// if the current node hasn't been initialized yet, try if it can
|
||||
// be initialized and converted from one of the child elements.
|
||||
if (!nodeIsInitialized) {
|
||||
if (childNode.getNodeType() == Node.ELEMENT_NODE) {
|
||||
convert((Element) childNode, helmaNode, nodeCache);
|
||||
|
||||
if (helmaNode.getPrototype() != null) {
|
||||
return helmaNode;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// if it's text content of this element -> append to StringBuffer
|
||||
if ((childNode.getNodeType() == Node.TEXT_NODE) ||
|
||||
(childNode.getNodeType() == Node.CDATA_SECTION_NODE)) {
|
||||
textcontent.append(childNode.getNodeValue().trim());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// it's some kind of element (property or child)
|
||||
if (childNode.getNodeType() == Node.ELEMENT_NODE) {
|
||||
Element childElement = (Element) childNode;
|
||||
|
||||
// get the basic key we have to look for in the properties-table
|
||||
domKey = element.getNodeName() + "." + childElement.getNodeName();
|
||||
|
||||
// is there a childtext-2-property mapping?
|
||||
if ((props != null) && props.containsKey(domKey + "._text")) {
|
||||
helmaKey = props.getProperty(domKey + "._text");
|
||||
|
||||
if (helmaKey.equals("")) {
|
||||
// if property is set but without value, read elementname for this mapping
|
||||
helmaKey = childElement.getNodeName().replace(':',
|
||||
defaultSeparator);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
debug("childtext-2-property mapping, helmaKey " + helmaKey +
|
||||
" for domKey " + domKey);
|
||||
}
|
||||
|
||||
// check if helmaKey contains an explicit prototype name in which to
|
||||
// set the property.
|
||||
int dot = helmaKey.indexOf(".");
|
||||
|
||||
if (dot > -1) {
|
||||
String prototype = helmaKey.substring(0, dot);
|
||||
INode node = (INode) nodeCache.get(prototype);
|
||||
|
||||
helmaKey = helmaKey.substring(dot + 1);
|
||||
|
||||
if ((node != null) && (node.getString(helmaKey) == null)) {
|
||||
node.setString(helmaKey, XmlUtil.getTextContent(childNode));
|
||||
}
|
||||
} else if (helmaNode.getString(helmaKey) == null) {
|
||||
helmaNode.setString(helmaKey, XmlUtil.getTextContent(childNode));
|
||||
|
||||
if (DEBUG) {
|
||||
debug("childtext-2-property mapping, setting " + helmaKey +
|
||||
" as string");
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// is there a simple child-2-property mapping?
|
||||
// (lets the user define to use only one element and make this a property
|
||||
// and simply ignore other elements of the same name)
|
||||
if ((props != null) && props.containsKey(domKey + "._property")) {
|
||||
helmaKey = props.getProperty(domKey + "._property");
|
||||
|
||||
// if property is set but without value, read elementname for this mapping:
|
||||
if (helmaKey.equals("")) {
|
||||
helmaKey = childElement.getNodeName().replace(':',
|
||||
defaultSeparator);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
debug("child-2-property mapping, helmaKey " + helmaKey +
|
||||
" for domKey " + domKey);
|
||||
}
|
||||
|
||||
// get the node on which to opererate, depending on the helmaKey
|
||||
// value from the properties file.
|
||||
INode node = helmaNode;
|
||||
int dot = helmaKey.indexOf(".");
|
||||
|
||||
if (dot > -1) {
|
||||
String prototype = helmaKey.substring(0, dot);
|
||||
|
||||
if (!prototype.equalsIgnoreCase(node.getPrototype())) {
|
||||
node = (INode) nodeCache.get(prototype);
|
||||
}
|
||||
|
||||
helmaKey = helmaKey.substring(dot + 1);
|
||||
}
|
||||
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.getNode(helmaKey) == null) {
|
||||
convert(childElement, node.createNode(helmaKey), nodeCache);
|
||||
|
||||
if (DEBUG) {
|
||||
debug("read " + childElement.toString() +
|
||||
node.getNode(helmaKey).toString());
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// map it to one of the children-lists
|
||||
helma.objectmodel.INode newHelmaNode = null;
|
||||
String childrenMapping = props.getProperty(element.getNodeName() +
|
||||
"._children");
|
||||
|
||||
// do we need a mapping directly among _children of helmaNode?
|
||||
// can either be through property elname._children=_all or elname._children=childname
|
||||
if ((childrenMapping != null) &&
|
||||
(childrenMapping.equals("_all") ||
|
||||
childrenMapping.equals(childElement.getNodeName()))) {
|
||||
newHelmaNode = convert(childElement, helmaNode.createNode(null),
|
||||
nodeCache);
|
||||
}
|
||||
|
||||
// in which virtual subnode collection should objects of this type be stored?
|
||||
helmaKey = props.getProperty(domKey);
|
||||
|
||||
if ((helmaKey == null) && !sparse) {
|
||||
helmaKey = childElement.getNodeName().replace(':', defaultSeparator);
|
||||
}
|
||||
|
||||
if (helmaKey == null) {
|
||||
// we don't map this child element itself since we do
|
||||
// sparse parsing, but there may be something of interest
|
||||
// in the child's attributes and child elements.
|
||||
attributes(childElement, helmaNode, nodeCache);
|
||||
children(childElement, helmaNode, nodeCache);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the node on which to opererate, depending on the helmaKey
|
||||
// value from the properties file.
|
||||
INode node = helmaNode;
|
||||
int dot = helmaKey.indexOf(".");
|
||||
|
||||
if (dot > -1) {
|
||||
String prototype = helmaKey.substring(0, dot);
|
||||
|
||||
if (!prototype.equalsIgnoreCase(node.getPrototype())) {
|
||||
node = (INode) nodeCache.get(prototype);
|
||||
}
|
||||
|
||||
helmaKey = helmaKey.substring(dot + 1);
|
||||
}
|
||||
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// try to get the virtual node
|
||||
INode worknode = null;
|
||||
|
||||
if ("_children".equals(helmaKey)) {
|
||||
worknode = node;
|
||||
} else {
|
||||
worknode = node.getNode(helmaKey);
|
||||
|
||||
if (worknode == null) {
|
||||
// if virtual node doesn't exist, create it
|
||||
worknode = helmaNode.createNode(helmaKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
debug("mounting child " + childElement.getNodeName() +
|
||||
" at worknode " + worknode.toString());
|
||||
}
|
||||
|
||||
// now mount it, possibly re-using the helmaNode that's been created before
|
||||
if (newHelmaNode != null) {
|
||||
worknode.addNode(newHelmaNode);
|
||||
} else {
|
||||
convert(childElement, worknode.createNode(null), nodeCache);
|
||||
}
|
||||
}
|
||||
|
||||
// forget about other types (comments etc)
|
||||
continue;
|
||||
}
|
||||
|
||||
// if there's some text content for this element, map it:
|
||||
if ((textcontent.length() > 0) && !sparse) {
|
||||
helmaKey = props.getProperty(element.getNodeName() + "._text");
|
||||
|
||||
if (helmaKey == null) {
|
||||
helmaKey = "text";
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
debug("setting text " + textcontent + " to property " + helmaKey +
|
||||
" of object " + helmaNode);
|
||||
}
|
||||
|
||||
helmaNode.setString(helmaKey, textcontent.toString().trim());
|
||||
}
|
||||
|
||||
return helmaNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* set element's attributes as properties of helmaNode
|
||||
*/
|
||||
private INode attributes(Element element, INode helmaNode, Map nodeCache) {
|
||||
NamedNodeMap nnm = element.getAttributes();
|
||||
int len = nnm.getLength();
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
org.w3c.dom.Node attr = nnm.item(i);
|
||||
String helmaKey = props.getProperty(element.getNodeName() + "._attribute." +
|
||||
attr.getNodeName());
|
||||
|
||||
// unless we only map explicit attributes, use attribute name as property name
|
||||
// in case no property name was defined.
|
||||
if ((helmaKey == null) && !sparse) {
|
||||
helmaKey = attr.getNodeName().replace(':', defaultSeparator);
|
||||
}
|
||||
|
||||
if (helmaKey != null) {
|
||||
// check if the mapping contains the prototype to which
|
||||
// the property should be applied
|
||||
int dot = helmaKey.indexOf(".");
|
||||
|
||||
if (dot > -1) {
|
||||
String prototype = helmaKey.substring(0, dot);
|
||||
INode node = (INode) nodeCache.get(prototype);
|
||||
|
||||
if (node != null) {
|
||||
node.setString(helmaKey.substring(dot + 1), attr.getNodeValue());
|
||||
}
|
||||
} else if (helmaNode.getPrototype() != null) {
|
||||
helmaNode.setString(helmaKey, attr.getNodeValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return helmaNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* utility function
|
||||
*/
|
||||
private void extractProperties(Properties props) {
|
||||
if (props.containsKey("separator")) {
|
||||
defaultSeparator = props.getProperty("separator").charAt(0);
|
||||
}
|
||||
|
||||
sparse = "sparse".equalsIgnoreCase(props.getProperty("_mode"));
|
||||
}
|
||||
|
||||
/** for testing */
|
||||
void debug(Object msg) {
|
||||
for (int i = 0; i < offset; i++) {
|
||||
System.out.print(" ");
|
||||
}
|
||||
|
||||
System.out.println(msg.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param args ...
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
}
|
||||
}
|
474
src/main/java/helma/objectmodel/dom/XmlDatabase.java
Normal file
474
src/main/java/helma/objectmodel/dom/XmlDatabase.java
Normal file
|
@ -0,0 +1,474 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.dom;
|
||||
|
||||
import helma.objectmodel.*;
|
||||
import helma.objectmodel.db.NodeManager;
|
||||
import helma.objectmodel.db.Node;
|
||||
import helma.framework.core.Application;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* A simple XML-database
|
||||
*/
|
||||
public final class XmlDatabase implements IDatabase {
|
||||
|
||||
protected File dbHomeDir;
|
||||
protected Application app;
|
||||
protected NodeManager nmgr;
|
||||
protected XmlIDGenerator idgen;
|
||||
|
||||
// character encoding to use when writing files.
|
||||
// use standard encoding by default.
|
||||
protected String encoding = null;
|
||||
|
||||
/**
|
||||
* Initializes the database from an application.
|
||||
* @param app
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public void init(File dbHome, Application app) throws DatabaseException {
|
||||
this.app = app;
|
||||
nmgr = app.getNodeManager();
|
||||
dbHomeDir = dbHome;
|
||||
|
||||
if (!dbHomeDir.exists() && !dbHomeDir.mkdirs()) {
|
||||
throw new DatabaseException("Can't create database directory "+dbHomeDir);
|
||||
}
|
||||
|
||||
if (!dbHomeDir.canWrite()) {
|
||||
throw new DatabaseException("No write permission for database directory "+dbHomeDir);
|
||||
}
|
||||
|
||||
File stylesheet = new File(dbHomeDir, "helma.xsl");
|
||||
// if style sheet doesn't exist, copy it
|
||||
if (!stylesheet.exists()) {
|
||||
copyStylesheet(stylesheet);
|
||||
}
|
||||
|
||||
this.encoding = app.getCharset();
|
||||
|
||||
// get the initial id generator value
|
||||
long idBaseValue;
|
||||
try {
|
||||
idBaseValue = Long.parseLong(app.getProperty("idBaseValue", "1"));
|
||||
// 0 and 1 are reserved for root nodes
|
||||
idBaseValue = Math.max(1L, idBaseValue);
|
||||
} catch (NumberFormatException ignore) {
|
||||
idBaseValue = 1L;
|
||||
}
|
||||
|
||||
ITransaction txn = null;
|
||||
|
||||
try {
|
||||
txn = beginTransaction();
|
||||
|
||||
try {
|
||||
idgen = getIDGenerator(txn);
|
||||
|
||||
if (idgen.getValue() < idBaseValue) {
|
||||
idgen.setValue(idBaseValue);
|
||||
}
|
||||
} catch (ObjectNotFoundException notfound) {
|
||||
// will start with idBaseValue+1
|
||||
idgen = new XmlIDGenerator(idBaseValue);
|
||||
}
|
||||
|
||||
// check if we need to set the id generator to a base value
|
||||
Node node = null;
|
||||
|
||||
try {
|
||||
getNode(txn, "0");
|
||||
} catch (ObjectNotFoundException notfound) {
|
||||
node = new Node("root", "0", "Root", nmgr.safe);
|
||||
node.setDbMapping(app.getDbMapping("root"));
|
||||
insertNode(txn, node.getID(), node);
|
||||
// register node with nodemanager cache
|
||||
// nmgr.registerNode(node);
|
||||
}
|
||||
|
||||
try {
|
||||
getNode(txn, "1");
|
||||
} catch (ObjectNotFoundException notfound) {
|
||||
node = new Node("users", "1", null, nmgr.safe);
|
||||
node.setDbMapping(app.getDbMapping("__userroot__"));
|
||||
insertNode(txn, node.getID(), node);
|
||||
// register node with nodemanager cache
|
||||
// nmgr.registerNode(node);
|
||||
}
|
||||
|
||||
commitTransaction(txn);
|
||||
} catch (Exception x) {
|
||||
x.printStackTrace();
|
||||
|
||||
try {
|
||||
abortTransaction(txn);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
throw (new DatabaseException("Error initializing db"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to copy style sheet for XML files to database directory
|
||||
*/
|
||||
private void copyStylesheet(File destination) {
|
||||
InputStream in = null;
|
||||
FileOutputStream out = null;
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
|
||||
try {
|
||||
in = getClass().getResourceAsStream("helma.xsl");
|
||||
out = new FileOutputStream(destination);
|
||||
while ((read = in.read(buffer, 0, buffer.length)) > 0) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
} catch (IOException iox) {
|
||||
System.err.println("Error copying db style sheet: "+iox);
|
||||
} finally {
|
||||
try {
|
||||
if (out != null)
|
||||
out.close();
|
||||
if (in != null)
|
||||
in.close();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut down the database
|
||||
*/
|
||||
public void shutdown() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new transaction.
|
||||
*
|
||||
* @return the new tranaction object
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public ITransaction beginTransaction() throws DatabaseException {
|
||||
return new XmlTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* committ the given transaction, makint its changes persistent
|
||||
*
|
||||
* @param txn
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public void commitTransaction(ITransaction txn) throws DatabaseException {
|
||||
if (idgen.dirty) {
|
||||
try {
|
||||
saveIDGenerator(txn);
|
||||
idgen.dirty = false;
|
||||
} catch (IOException x) {
|
||||
throw new DatabaseException(x.toString());
|
||||
}
|
||||
}
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort the given transaction
|
||||
*
|
||||
* @param txn
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public void abortTransaction(ITransaction txn) throws DatabaseException {
|
||||
txn.abort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id for the next new object to be stored.
|
||||
*
|
||||
* @return the id for the next new object to be stored
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public String nextID() throws ObjectNotFoundException {
|
||||
if (idgen == null) {
|
||||
getIDGenerator(null);
|
||||
}
|
||||
|
||||
return idgen.newID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id-generator for this database.
|
||||
*
|
||||
* @param txn
|
||||
* @return the id-generator for this database
|
||||
* @throws ObjectNotFoundException
|
||||
*/
|
||||
public XmlIDGenerator getIDGenerator(ITransaction txn)
|
||||
throws ObjectNotFoundException {
|
||||
File file = new File(dbHomeDir, "idgen.xml");
|
||||
|
||||
this.idgen = XmlIDGenerator.getIDGenerator(file);
|
||||
|
||||
return idgen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the id-generator to file.
|
||||
*
|
||||
* @param txn
|
||||
* @throws IOException
|
||||
*/
|
||||
public void saveIDGenerator(ITransaction txn)
|
||||
throws IOException {
|
||||
File tmp = File.createTempFile("idgen.xml.", ".tmp", dbHomeDir);
|
||||
|
||||
XmlIDGenerator.saveIDGenerator(idgen, tmp);
|
||||
|
||||
File file = new File(dbHomeDir, "idgen.xml");
|
||||
if (file.exists() && !file.canWrite()) {
|
||||
throw new IOException("No write permission for "+file);
|
||||
}
|
||||
Resource res = new Resource(file, tmp);
|
||||
txn.addResource(res, ITransaction.ADDED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a Node from the database.
|
||||
*
|
||||
* @param txn the current transaction
|
||||
* @param kstr the key
|
||||
* @return the object associated with the given key
|
||||
* @throws IOException if an I/O error occurred loading the object.
|
||||
* @throws ObjectNotFoundException if no object is stored by this key.
|
||||
*/
|
||||
public INode getNode(ITransaction txn, String kstr)
|
||||
throws IOException, ObjectNotFoundException {
|
||||
File f = new File(dbHomeDir, kstr + ".xml");
|
||||
|
||||
if (!f.exists()) {
|
||||
throw new ObjectNotFoundException("Object not found for key " + kstr);
|
||||
}
|
||||
|
||||
try {
|
||||
XmlDatabaseReader reader = new XmlDatabaseReader(nmgr);
|
||||
Node node = reader.read(f);
|
||||
|
||||
return node;
|
||||
} catch (ParserConfigurationException x) {
|
||||
app.logError("Error reading " +f, x);
|
||||
throw new IOException(x.toString());
|
||||
} catch (SAXException x) {
|
||||
app.logError("Error reading " +f, x);
|
||||
throw new IOException(x.toString());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Save a node with the given key. Writes the node to a temporary file
|
||||
* which is copied to its final name when the transaction is committed.
|
||||
*
|
||||
* @param txn
|
||||
* @param kstr
|
||||
* @param node
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public void insertNode(ITransaction txn, String kstr, INode node)
|
||||
throws IOException {
|
||||
File f = new File(dbHomeDir, kstr + ".xml");
|
||||
|
||||
if (f.exists()) {
|
||||
throw new IOException("Object already exists for key " + kstr);
|
||||
}
|
||||
|
||||
// apart from the above check insertNode() is equivalent to updateNode()
|
||||
updateNode(txn, kstr, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a node with the given key. Writes the node to a temporary file
|
||||
* which is copied to its final name when the transaction is committed.
|
||||
*
|
||||
* @param txn
|
||||
* @param kstr
|
||||
* @param node
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public void updateNode(ITransaction txn, String kstr, INode node)
|
||||
throws IOException {
|
||||
XmlWriter writer = null;
|
||||
File tmp = File.createTempFile(kstr + ".xml.", ".tmp", dbHomeDir);
|
||||
|
||||
if (encoding != null) {
|
||||
writer = new XmlWriter(tmp, encoding);
|
||||
} else {
|
||||
writer = new XmlWriter(tmp);
|
||||
}
|
||||
|
||||
writer.setMaxLevels(1);
|
||||
writer.write(node);
|
||||
writer.close();
|
||||
|
||||
File file = new File(dbHomeDir, kstr+".xml");
|
||||
if (file.exists() && !file.canWrite()) {
|
||||
throw new IOException("No write permission for "+file);
|
||||
}
|
||||
Resource res = new Resource(file, tmp);
|
||||
txn.addResource(res, ITransaction.ADDED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an element from the database as deleted
|
||||
*
|
||||
* @param txn
|
||||
* @param kstr
|
||||
* @throws IOException
|
||||
*/
|
||||
public void deleteNode(ITransaction txn, String kstr)
|
||||
throws IOException {
|
||||
Resource res = new Resource(new File(dbHomeDir, kstr+".xml"), null);
|
||||
txn.addResource(res, ITransaction.DELETED);
|
||||
}
|
||||
|
||||
/**
|
||||
* set the file encoding to use
|
||||
*
|
||||
* @param encoding the database's file encoding
|
||||
*/
|
||||
public void setEncoding(String encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the file encoding used by this database
|
||||
*
|
||||
* @return the file encoding used by this database
|
||||
*/
|
||||
public String getEncoding() {
|
||||
return encoding;
|
||||
}
|
||||
|
||||
class XmlTransaction implements ITransaction {
|
||||
|
||||
ArrayList writeFiles = new ArrayList();
|
||||
ArrayList deleteFiles = new ArrayList();
|
||||
|
||||
/**
|
||||
* Complete the transaction by making its changes persistent.
|
||||
*/
|
||||
public void commit() throws DatabaseException {
|
||||
// move through updated/created files and persist them
|
||||
int l = writeFiles.size();
|
||||
for (int i=0; i<l; i++) {
|
||||
Resource res = (Resource) writeFiles.get(i);
|
||||
try {
|
||||
// because of a Java/Windows quirk, we have to delete
|
||||
// the existing file before trying to overwrite it
|
||||
if (res.file.exists()) {
|
||||
res.file.delete();
|
||||
}
|
||||
// move temporary file to permanent name
|
||||
if (res.tmpfile.renameTo(res.file)) {
|
||||
// success - delete tmp file
|
||||
res.tmpfile.delete();
|
||||
} else {
|
||||
// error - leave tmp file and print a message
|
||||
app.logError("*** Error committing "+res.file);
|
||||
app.logError("*** Committed version is in "+res.tmpfile);
|
||||
}
|
||||
} catch (SecurityException ignore) {
|
||||
// shouldn't happen
|
||||
}
|
||||
}
|
||||
|
||||
// move through deleted files and delete them
|
||||
l = deleteFiles.size();
|
||||
for (int i=0; i<l; i++) {
|
||||
Resource res = (Resource) deleteFiles.get(i);
|
||||
// delete files enlisted as deleted
|
||||
try {
|
||||
res.file.delete();
|
||||
} catch (SecurityException ignore) {
|
||||
// shouldn't happen
|
||||
}
|
||||
}
|
||||
// clear registered resources
|
||||
writeFiles.clear();
|
||||
deleteFiles.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the transaction, forgetting the changed items
|
||||
*/
|
||||
public void abort() throws DatabaseException {
|
||||
int l = writeFiles.size();
|
||||
for (int i=0; i<l; i++) {
|
||||
Resource res = (Resource) writeFiles.get(i);
|
||||
// delete tmp files created by this transaction
|
||||
try {
|
||||
res.tmpfile.delete();
|
||||
} catch (SecurityException ignore) {
|
||||
// shouldn't happen
|
||||
}
|
||||
}
|
||||
|
||||
// clear registered resources
|
||||
writeFiles.clear();
|
||||
deleteFiles.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a resource to the list of resources encompassed by this transaction
|
||||
*
|
||||
* @param res the resource to add
|
||||
* @param status the status of the resource (ADDED|UPDATED|DELETED)
|
||||
*/
|
||||
public void addResource(Object res, int status)
|
||||
throws DatabaseException {
|
||||
if (status == DELETED) {
|
||||
deleteFiles.add(res);
|
||||
} else {
|
||||
writeFiles.add(res);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A holder class for two files, the temporary file and the permanent one
|
||||
*/
|
||||
class Resource {
|
||||
File tmpfile;
|
||||
File file;
|
||||
|
||||
public Resource(File file, File tmpfile) {
|
||||
this.file = file;
|
||||
this.tmpfile = tmpfile;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class IDGenerator {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
259
src/main/java/helma/objectmodel/dom/XmlDatabaseReader.java
Normal file
259
src/main/java/helma/objectmodel/dom/XmlDatabaseReader.java
Normal file
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* Helma License Notice
|
||||
*
|
||||
* The contents of this file are subject to the Helma License
|
||||
* Version 2.0 (the "License"). You may not use this file except in
|
||||
* compliance with the License. A copy of the License is available at
|
||||
* http://adele.helma.org/download/helma/license.txt
|
||||
*
|
||||
* Copyright 1998-2003 Helma Software. All Rights Reserved.
|
||||
*
|
||||
* $RCSfile$
|
||||
* $Author$
|
||||
* $Revision$
|
||||
* $Date$
|
||||
*/
|
||||
|
||||
package helma.objectmodel.dom;
|
||||
|
||||
import helma.objectmodel.db.*;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public final class XmlDatabaseReader extends DefaultHandler implements XmlConstants {
|
||||
static SAXParserFactory factory = SAXParserFactory.newInstance();
|
||||
private NodeManager nmgr = null;
|
||||
private Node currentNode;
|
||||
private String elementType = null;
|
||||
private String elementName = null;
|
||||
private StringBuffer charBuffer = null;
|
||||
Hashtable propMap = null;
|
||||
SubnodeList subnodes = null;
|
||||
|
||||
/**
|
||||
* Creates a new XmlDatabaseReader object.
|
||||
*
|
||||
* @param nmgr ...
|
||||
*/
|
||||
public XmlDatabaseReader(NodeManager nmgr) {
|
||||
this.nmgr = nmgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* read an InputSource with xml-content.
|
||||
*/
|
||||
public Node read(File file)
|
||||
throws ParserConfigurationException, SAXException, IOException {
|
||||
if (nmgr == null) {
|
||||
throw new RuntimeException("can't create a new Node without a NodeManager");
|
||||
}
|
||||
|
||||
SAXParser parser = factory.newSAXParser();
|
||||
|
||||
currentNode = null;
|
||||
|
||||
parser.parse(file, this);
|
||||
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param namespaceURI ...
|
||||
* @param localName ...
|
||||
* @param qName ...
|
||||
* @param atts ...
|
||||
*/
|
||||
public void startElement(String namespaceURI, String localName, String qName,
|
||||
Attributes atts) {
|
||||
// System.err.println ("XML-READ: startElement "+namespaceURI+", "+localName+", "+qName+", "+atts.getValue("id"));
|
||||
// discard the first element called xmlroot
|
||||
if ("xmlroot".equals(qName) && (currentNode == null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if currentNode is null, this must be the hopobject node
|
||||
if ("hopobject".equals(qName) && (currentNode == null)) {
|
||||
String id = atts.getValue("id");
|
||||
String name = atts.getValue("name");
|
||||
String prototype = atts.getValue("prototype");
|
||||
|
||||
if ("".equals(prototype)) {
|
||||
prototype = "hopobject";
|
||||
}
|
||||
|
||||
try {
|
||||
long created = Long.parseLong(atts.getValue("created"));
|
||||
long lastmodified = Long.parseLong(atts.getValue("lastModified"));
|
||||
|
||||
currentNode = new Node(name, id, prototype, nmgr.safe, created,
|
||||
lastmodified);
|
||||
} catch (NumberFormatException e) {
|
||||
currentNode = new Node(name, id, prototype, nmgr.safe);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// find out what kind of element this is by looking at
|
||||
// the number and names of attributes.
|
||||
String idref = atts.getValue("idref");
|
||||
|
||||
if (idref != null) {
|
||||
// a hopobject reference.
|
||||
NodeHandle handle = makeNodeHandle(atts);
|
||||
|
||||
if ("hop:child".equals(qName)) {
|
||||
if (subnodes == null) {
|
||||
subnodes = currentNode.createSubnodeList();
|
||||
}
|
||||
|
||||
subnodes.add(handle);
|
||||
} else if ("hop:parent".equals(qName)) {
|
||||
currentNode.setParentHandle(handle);
|
||||
} else {
|
||||
// property name may be encoded as "propertyname" attribute,
|
||||
// otherwise it is the element name
|
||||
String propName = atts.getValue("propertyname");
|
||||
|
||||
if (propName == null) {
|
||||
propName = qName;
|
||||
}
|
||||
|
||||
Property prop = new Property(propName, currentNode);
|
||||
|
||||
prop.setNodeHandle(handle);
|
||||
|
||||
if (propMap == null) {
|
||||
propMap = new Hashtable();
|
||||
currentNode.setPropMap(propMap);
|
||||
}
|
||||
|
||||
propMap.put(correctPropertyName(propName), prop);
|
||||
}
|
||||
} else {
|
||||
// a primitive property
|
||||
elementType = atts.getValue("type");
|
||||
|
||||
if (elementType == null) {
|
||||
elementType = "string";
|
||||
}
|
||||
|
||||
// property name may be encoded as "propertyname" attribute,
|
||||
// otherwise it is the element name
|
||||
elementName = atts.getValue("propertyname");
|
||||
|
||||
if (elementName == null) {
|
||||
elementName = qName;
|
||||
}
|
||||
|
||||
if (charBuffer == null) {
|
||||
charBuffer = new StringBuffer();
|
||||
} else {
|
||||
charBuffer.setLength(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String correctPropertyName(String propName) {
|
||||
return this.currentNode.getDbMapping().getApplication().correctPropertyName(propName);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param ch ...
|
||||
* @param start ...
|
||||
* @param length ...
|
||||
*
|
||||
* @throws SAXException ...
|
||||
*/
|
||||
public void characters(char[] ch, int start, int length)
|
||||
throws SAXException {
|
||||
// append chars to char buffer
|
||||
if (elementType != null) {
|
||||
charBuffer.append(ch, start, length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param namespaceURI ...
|
||||
* @param localName ...
|
||||
* @param qName ...
|
||||
*
|
||||
* @throws SAXException ...
|
||||
*/
|
||||
public void endElement(String namespaceURI, String localName, String qName)
|
||||
throws SAXException {
|
||||
if (elementType != null) {
|
||||
Property prop = new Property(elementName, currentNode);
|
||||
String charValue = charBuffer.toString();
|
||||
|
||||
charBuffer.setLength(0);
|
||||
|
||||
if ("boolean".equals(elementType)) {
|
||||
if ("true".equals(charValue)) {
|
||||
prop.setBooleanValue(true);
|
||||
} else {
|
||||
prop.setBooleanValue(false);
|
||||
}
|
||||
} else if ("date".equals(elementType)) {
|
||||
SimpleDateFormat format = new SimpleDateFormat(DATEFORMAT);
|
||||
|
||||
try {
|
||||
Date date = format.parse(charValue);
|
||||
|
||||
prop.setDateValue(date);
|
||||
} catch (ParseException e) {
|
||||
prop.setStringValue(charValue);
|
||||
}
|
||||
} else if ("float".equals(elementType)) {
|
||||
prop.setFloatValue((new Double(charValue)).doubleValue());
|
||||
} else if ("integer".equals(elementType)) {
|
||||
prop.setIntegerValue((new Long(charValue)).longValue());
|
||||
} else {
|
||||
prop.setStringValue(charValue);
|
||||
}
|
||||
|
||||
if (propMap == null) {
|
||||
propMap = new Hashtable();
|
||||
currentNode.setPropMap(propMap);
|
||||
}
|
||||
|
||||
propMap.put(correctPropertyName(elementName), prop);
|
||||
elementName = null;
|
||||
elementType = null;
|
||||
charValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
// create a node handle from a node reference DOM element
|
||||
private NodeHandle makeNodeHandle(Attributes atts) {
|
||||
String idref = atts.getValue("idref");
|
||||
String protoref = atts.getValue("prototyperef");
|
||||
DbMapping dbmap = null;
|
||||
|
||||
if (protoref != null) {
|
||||
dbmap = nmgr.getDbMapping(protoref);
|
||||
}
|
||||
|
||||
return new NodeHandle(new DbKey(dbmap, idref));
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue