chg: replaced ant with gradle

This commit is contained in:
Tobi Schäfer 2020-03-16 16:53:52 +01:00
parent cee0be52e0
commit 5cbeb9f01d
609 changed files with 87626 additions and 638 deletions

View 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);
}
}

View 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();
}

View 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";
}
}

View 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 {
}

View 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;
/**
* 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");
}
}

View 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;
}
}

View 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);
}
}

View 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;
}

View 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();
}

View 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;
}

View 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);
}
}

View 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;
}
}

View 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);
}
}

View 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;
}
}
}

View 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();
}
}

File diff suppressed because it is too large Load diff

View 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");
}
}

View 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();
}
}

View 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 + "]";
}
}

File diff suppressed because it is too large Load diff

View 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();
}
}
}

View 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 + "]";
}
}
}

File diff suppressed because it is too large Load diff

View 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();
}
}

View 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();
}
}
}
}

View 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);
}
}

View 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;
}
}
}

File diff suppressed because it is too large Load diff

View 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);
}
}

View 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;
}
}

View 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;
}
}

View 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();
}
}

View 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.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;
}
}

View 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();
}
}

View 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();
}
}

View 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 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();
}
}

View 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();
}

View 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();
}

View 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);
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}

View 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();
}
}

View 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();
}
}

View 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);
}
}

View 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();
}
}

View 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;
}
}
}

View 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) {
}
}
}

View 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;
}

File diff suppressed because it is too large Load diff

View 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;
}
}

View 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();
}
}

View 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();
}
}
}
}

View file

@ -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;
}
}

View 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());
}
}

View 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;
}
}

View 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+"]";
}
}
}

View 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("");
}
}

View 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;
}
}

View 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();
}
}
}

View 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();
}
}
}

View 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);
}
}

View 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;
}
}

View 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);
}
}
}

View 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;
}
}

View file

@ -0,0 +1,3 @@
Main-Class: helma.main.launcher.Main

View 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);
}
}

View 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);
}
}

View 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;
}

View 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);
}

View 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;
}

View 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();
}

View 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;
}

View 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";
}
}

View 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();
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}
}

View 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();
}
}
}

File diff suppressed because it is too large Load diff

View 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);
}
}

View 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;
}

View 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();
}

View 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();
}
}
}

File diff suppressed because it is too large Load diff

View 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);
}

View 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 + "]";
}
}
}

File diff suppressed because it is too large Load diff

View 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();
}
}

View 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);
}
}

File diff suppressed because it is too large Load diff

View 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 + "}";
}
}
}

View 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();
}
}

View 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;
}
}

View 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 + "]";
}
}

View 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);
}
}
}
}

View 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";
}

View 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) {
}
}

View 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 {
}
}

View 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