Check in current Hop snapshot

This commit is contained in:
hns 2000-12-29 17:57:21 +00:00
commit 09d1fdd4e3
85 changed files with 16603 additions and 0 deletions

View file

@ -0,0 +1,21 @@
// ApplicationStoppedException.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.framework;
import FESI.Exceptions.EcmaScriptException;
/**
* This is thrown when a request is made to a stopped
* application
*/
public class ApplicationStoppedException extends RuntimeException {
public ApplicationStoppedException () {
super ("The application has been stopped");
}
}

View file

@ -0,0 +1,17 @@
// FrameworkException.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.framework;
/**
* The basic exception class used to tell when certain things go
* wrong in evaluation of requests.
*/
public class FrameworkException extends RuntimeException {
public FrameworkException (String msg) {
super (msg);
}
}

View file

@ -0,0 +1,20 @@
// RemoteApp.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.framework;
import java.rmi.*;
/**
* RMI interface for an application. Currently only execute is used and supported.
*/
public interface IRemoteApp extends Remote {
public ResponseTrans execute (RequestTrans param) throws RemoteException;
public ResponseTrans get (String path, String sessionID) throws RemoteException;
public void ping () throws RemoteException;
}

View file

@ -0,0 +1,34 @@
// RedirectException.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.framework;
import FESI.Exceptions.EcmaScriptException;
/**
* RedirectException is thrown internally when a response is redirected to a
* new URL.
*/
public class RedirectException extends EcmaScriptException {
String url;
public RedirectException (String url) {
super ("Redirection Request to "+url);
this.url = url;
}
public String getMessage () {
return url;
}
public void printStackTrace(java.io.PrintStream s) {
}
public void printStackTrace(java.io.PrintWriter w) {
}
}

View file

@ -0,0 +1,52 @@
// RequestTrans.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.framework;
import java.io.*;
import java.util.*;
import helma.objectmodel.*;
/**
* 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 {
public String path;
public String session;
private Hashtable values;
// this is used to hold the EcmaScript form data object
public transient Object data;
public RequestTrans () {
super ();
values = new Hashtable ();
}
public RequestTrans (Hashtable values) {
this.values = values;
}
public void set (String name, Object value) {
values.put (name, value);
}
public Enumeration keys () {
return values.keys ();
}
public Object get (String name) {
try {
return values.get (name);
} catch (Exception x) {
return null;
}
}
public Hashtable getReqData () {
return values;
}
}

View file

@ -0,0 +1,207 @@
// ResponseTrans.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.framework;
import java.io.*;
import java.util.*;
import helma.objectmodel.*;
import helma.util.*;
/**
* A Transmitter for a response to the servlet client. Objects of this
* class are directly exposed to JavaScript as global property res.
*/
public class ResponseTrans implements Serializable {
public String contentType = "text/html";
// the page body
private char[] body = null;
// contains the redirect URL
public String redirect = null;
// cookies
public String cookieKeys[];
public String cookieValues[];
public int cookieDays[];
int nCookies = 0;
// used to allow or disable client side caching
public boolean cache = true;
// the buffer used to build the body
private transient StringBuffer buffer = null;
// these are used to implement the _as_string variants for Hop templates.
private transient Stack buffers;
public ResponseTrans () {
super ();
}
public void reset () {
if (buffer != null)
buffer.setLength (0);
redirect = null;
}
/**
* This is called before a template is rendered as string (xxx_as_string) to redirect the output
* to a new string buffer.
*/
public void pushStringBuffer () {
if (buffers == null)
buffers = new Stack();
if (buffer != null)
buffers.push (buffer);
buffer = new StringBuffer (128);
}
/**
* Returns the content of the current string buffer and switches back to the previos one.
*/
public String popStringBuffer () {
StringBuffer b = buffer;
buffer = buffers.empty() ? null : (StringBuffer) buffers.pop ();
return b.toString ();
}
/**
* Append a string to the response unchanged.
*/
public void write (Object what) {
if (what != null) {
if (buffer == null)
buffer = new StringBuffer (512);
buffer.append (what.toString ());
}
}
/**
* Replace special characters with entities, including <, > and ", thus allowing
* no HTML tags.
*/
public void encode (Object what) {
if (what != null) {
if (buffer == null)
buffer = new StringBuffer (512);
HtmlEncoder.encodeAll (what.toString (), buffer);
}
}
/**
* Replace special characters with entities but leave <, > and ", allowing HTML tags
* in the response.
*/
public void format (Object what) {
if (what != null) {
if (buffer == null)
buffer = new StringBuffer (512);
HtmlEncoder.encode (what.toString (), buffer);
}
}
/**
* Replace special characters with entities, including <, > and ", thus allowing
* no HTML tags.
*/
public void encodeXml (Object what) {
if (what != null) {
if (buffer == null)
buffer = new StringBuffer (512);
HtmlEncoder.encodeXml (what.toString (), buffer);
}
}
public void append (String what) {
if (what != null) {
if (buffer == null)
buffer = new StringBuffer (512);
buffer.append (what);
}
}
public void redirect (String url) throws RedirectException {
redirect = url;
throw new RedirectException (url);
}
/**
* This has to be called after writin to this response has finished and before it is shipped back to the
* web server. Transforms the string buffer into a char array to minimize size.
*/
public void close () {
if (buffer != null)
body = new String (buffer).toCharArray();
}
public String getContentString () {
return body == null ? "" : new String (body);
}
public int getContentLength () {
if (body != null)
return body.length;
return 0;
}
public String getContentType () {
return contentType;
}
public synchronized void setCookie (String key, String value) {
setCookie (key, value, -1);
}
public synchronized void setCookie (String key, String value, int days) {
if (nCookies == 0) {
cookieKeys = new String [3];
cookieValues = new String [3];
cookieDays = new int [3];
}
if (nCookies == cookieKeys.length) {
String nk[] = new String [nCookies+3];
System.arraycopy (cookieKeys, 0, nk, 0, nCookies);
String nv[] = new String [nCookies+3];
System.arraycopy (cookieValues, 0, nv, 0, nCookies);
int nd[] = new int [nCookies+3];
System.arraycopy (cookieDays, 0, nd, 0, nCookies);
cookieKeys = nk;
cookieValues = nv;
cookieDays = nd;
}
cookieKeys [nCookies] = key;
cookieValues [nCookies] = value;
cookieDays [nCookies] = days;
nCookies += 1;
}
public void resetCookies () {
nCookies = 0;
}
public int countCookies () {
return nCookies;
}
public int getDaysAt (int i) {
return cookieDays[i];
}
public String getKeyAt (int i) {
return cookieKeys[i];
}
public String getValueAt (int i) {
return cookieValues[i];
}
}

View file

@ -0,0 +1,18 @@
// TimeoutException.java
// Copyright (c) Hannes Wallnöfer 1998-2000
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 {
public TimeoutException () {
super ("Request timed out");
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,214 @@
// ESMail.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.framework.extensions;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
import java.io.*;
import java.util.*;
import helma.framework.core.*;
import helma.objectmodel.*;
import FESI.Data.*;
import FESI.Interpreter.*;
import FESI.Exceptions.*;
/**
* A JavaScript wrapper around a JavaMail message class to send
* mail via SMTP from HOP
*/
public class ESMail extends ESObject implements Serializable {
INode node;
MailExtension mailx;
Properties mprops;
MimeMessage message;
Multipart multipart;
StringBuffer buffer;
int status;
public static final int OK=0;
public static final int SUBJECT=10;
public static final int TEXT=11;
public static final int MIMEPART=12;
public static final int TO=20;
public static final int CC=21;
public static final int BCC=22;
public static final int FROM=23;
public static final int REPLYTO=24;
public static final int SEND=30;
public ESMail (MailExtension mailx) {
super (mailx.esMailPrototype, mailx.evaluator);
this.status = OK;
this.mailx = mailx;
this.mprops = mailx.mprops;
// create some properties and get the default Session
try {
Properties props = new Properties();
props.put ("mail.smtp.host", mprops.getProperty ("smtp", "mail"));
Session session = Session.getDefaultInstance(props, null);
message = new MimeMessage (session);
} catch (Throwable t) {
IServer.getLogger().log ("caught in mail constructor: "+t);
}
}
public void setStatus (int status) {
// Only register the first error that occurrs
if (this.status == 0)
this.status = status;
}
public int getStatus () {
return status;
}
public ESValue getProperty(String propertyName, int hash) throws EcmaScriptException {
if ("status".equalsIgnoreCase (propertyName))
return new ESNumber (status);
return super.getProperty (propertyName, hash);
}
/**
*
*/
public void setText (ESValue val) throws Exception {
if (buffer == null)
buffer = new StringBuffer ();
if (val != null)
buffer.append (val.toString ());
}
public void addPart (ESValue val[]) throws Exception {
if (val == null || val.length == 0) return;
if (multipart == null) {
multipart = new MimeMultipart ();
}
for (int i=0; i<val.length; i++) {
INode node = getNode (val[i]);
if (node != null) {
BodyPart part = new MimeBodyPart ();
IServer.getLogger().log ("Adding MimePart: "+node.getContentType ());
NodeDataSource nds=new NodeDataSource (node);
part.setDataHandler(new DataHandler(nds));
// part.setFileName(filename);
// part.setDataHandler (new javax.activation.DataHandler (node.getContent(), node.getContentType ()));
multipart.addBodyPart (part);
} else if (val[i] != null) {
BodyPart part = new MimeBodyPart ();
part.setContent (val[i].toString (), "text/plain");
multipart.addBodyPart (part);
}
}
}
public void setSubject (ESValue val) throws Exception {
if (val == null)
return;
message.setSubject (MimeUtility.encodeWord (val.toString (), "iso-8859-1", null));
}
public void setReplyTo (ESValue add) throws Exception {
String addstring = add.toString ();
if (addstring.indexOf ("@") < 0)
throw new AddressException ();
Address replyTo[] = new Address[1];
replyTo[0] = new InternetAddress (addstring);
message.setReplyTo (replyTo);
}
public void setFrom (ESValue add[]) throws Exception {
String addstring = add[0].toString ();
if (addstring.indexOf ("@") < 0)
throw new AddressException ();
Address address = null;
if (add.length > 1)
address = new InternetAddress (addstring, MimeUtility.encodeWord (add[1].toString (), "iso-8859-1", null));
else
address = new InternetAddress (addstring);
message.setFrom (address);
}
public void addTo (ESValue add[]) throws Exception {
String addstring = add[0].toString ();
if (addstring.indexOf ("@") < 0)
throw new AddressException ();
Address address = null;
if (add.length > 1)
address = new InternetAddress (addstring, MimeUtility.encodeWord (add[1].toString (), "iso-8859-1", null));
else
address = new InternetAddress (addstring);
message.addRecipient (Message.RecipientType.TO, address);
}
public void addCC (ESValue add[]) throws Exception {
String addstring = add[0].toString ();
if (addstring.indexOf ("@") < 0)
throw new AddressException ();
Address address = null;
if (add.length > 1)
address = new InternetAddress (addstring, MimeUtility.encodeWord (add[1].toString (), "iso-8859-1", null));
else
address = new InternetAddress (addstring);
message.addRecipient (Message.RecipientType.CC, address);
}
public void addBCC (ESValue add[]) throws Exception {
String addstring = add[0].toString ();
if (addstring.indexOf ("@") < 0)
throw new AddressException ();
Address address = null;
if (add.length > 1)
address = new InternetAddress (addstring, MimeUtility.encodeWord (add[1].toString (), "iso-8859-1", null));
else
address = new InternetAddress (addstring);
message.addRecipient (Message.RecipientType.BCC, address);
}
public void send () throws Exception {
if (buffer != null)
message.setText (buffer.toString ());
else if (multipart != null)
message.setContent (multipart);
else
message.setText ("");
Transport.send (message);
}
private final INode getNode (Object obj) {
if (obj == null)
return null;
if (obj instanceof ESNode)
return ((ESNode) obj).getNode ();
if (obj instanceof ESWrapper) {
Object n = ((ESWrapper) obj).getJavaObject();
if (n instanceof INode)
return (INode) n;
}
return null;
}
}

View file

@ -0,0 +1,419 @@
// FtpExtension.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.framework.extensions;
import helma.objectmodel.*;
import FESI.Parser.*;
import FESI.AST.*;
import FESI.Interpreter.*;
import FESI.Exceptions.*;
import FESI.Extensions.*;
import FESI.Data.*;
import java.io.*;
import com.oroinc.net.ftp.*;
/**
* A FTP-client object that allows to do some FTP from HOP applications.
* FTP support is far from complete but can easily be extended if more
* functionality is needed.
* This uses the NetComponent classes from savarese.org (ex oroinc.com).
*/
class ESFtpClient extends ESObject {
private FTPClient ftpclient;
private String server;
private Exception lastError = null;
private File localDir = null;
/**
* Create a new FTP Client
*
* @param prototype The prototype object for the FTP object
* @param evaluator The current evaluator
*/
ESFtpClient(ESObject prototype, Evaluator evaluator, ESValue srvstr) {
super(prototype, evaluator);
this.server = srvstr.toString ();
}
ESFtpClient(ESObject prototype, Evaluator evaluator) {
super(prototype, evaluator);
}
public String getESClassName() {
return "FtpClient";
}
public String toString() {
return "[FtpClient]";
}
public String toDetailString() {
return "ES:[Object: builtin " + this.getClass().getName() + ":" +
this.toString() + "]";
}
ESValue getLastError() throws EcmaScriptException {
if (lastError == null) {
return ESNull.theNull;
} else {
return ESLoader.normalizeValue(lastError, evaluator);
}
}
/**
* Login to the FTP server
*
* @param arguments The argument list
* @return true if successful, false otherwise
*/
ESValue login(ESValue arguments[]) throws EcmaScriptException {
if (server == null)
return ESBoolean.makeBoolean(false);
try {
ftpclient = new FTPClient ();
ftpclient.connect (server);
ftpclient.login (arguments[0].toString(), arguments[1].toString());
return ESBoolean.makeBoolean (true);
} catch (Exception x) {
return ESBoolean.makeBoolean (false);
} catch (NoClassDefFoundError x) {
return ESBoolean.makeBoolean (false);
}
}
ESValue cd (ESValue arguments[]) throws EcmaScriptException {
if (ftpclient == null)
return ESBoolean.makeBoolean(false);
try {
ftpclient.changeWorkingDirectory (arguments[0].toString ());
return ESBoolean.makeBoolean(true);
} catch (Exception wrong) {}
return ESBoolean.makeBoolean(false);
}
ESValue mkdir (ESValue arguments[]) throws EcmaScriptException {
if (ftpclient == null)
return ESBoolean.makeBoolean(false);
try {
return ESBoolean.makeBoolean(ftpclient.makeDirectory (arguments[0].toString ()));
} catch (Exception wrong) {}
return ESBoolean.makeBoolean(false);
}
ESValue lcd (ESValue arguments[]) throws EcmaScriptException {
try {
localDir = new File (arguments[0].toString());
if (!localDir.exists())
localDir.mkdirs();
return ESBoolean.makeBoolean(true);
} catch (Exception wrong) {}
return ESBoolean.makeBoolean(false);
}
ESValue putFile(ESValue arguments[]) throws EcmaScriptException {
if (ftpclient == null)
return ESBoolean.makeBoolean(false);
try {
String fn = arguments[0].toString();
File f = localDir == null ? new File (fn) : new File (localDir, fn);
InputStream fin = new BufferedInputStream (new FileInputStream (f));
ftpclient.storeFile (arguments[1].toString (), fin);
fin.close ();
return ESBoolean.makeBoolean(true);
} catch (Exception wrong) {}
return ESBoolean.makeBoolean(false);
}
ESValue putString(ESValue arguments[]) throws EcmaScriptException {
if (ftpclient == null)
return ESBoolean.makeBoolean(false);
try {
byte[] bytes = null;
// check if this already is a byte array
if (arguments[0] instanceof ESArrayWrapper) {
Object o = ((ESArrayWrapper) arguments[0]).toJavaObject ();
if (o instanceof byte[])
bytes = (byte[]) o;
}
if (bytes == null)
bytes = arguments[0].toString().getBytes();
ByteArrayInputStream bin = new ByteArrayInputStream (bytes);
ftpclient.storeFile (arguments[1].toString (), bin);
return ESBoolean.makeBoolean(true);
} catch (Exception wrong) {}
return ESBoolean.makeBoolean(false);
}
ESValue getFile(ESValue arguments[]) throws EcmaScriptException {
if (ftpclient == null )
return ESBoolean.makeBoolean(false);
try {
String fn = arguments[0].toString();
File f = localDir == null ? new File (fn) : new File (localDir, fn);
OutputStream out = new BufferedOutputStream (new FileOutputStream(f));
ftpclient.retrieveFile (arguments[0].toString (), out);
out.close ();
return ESBoolean.makeBoolean(true);
} catch (Exception wrong) {}
return ESBoolean.makeBoolean(false);
}
ESValue getString(ESValue arguments[]) throws EcmaScriptException {
if (ftpclient == null )
return ESNull.theNull;
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream ();
ftpclient.retrieveFile (arguments[0].toString (), bout);
return new ESString (bout.toString ());
} catch (Exception wrong) {}
return ESNull.theNull;
}
/**
* Disconnect from FTP server
*
* @param arguments The argument list
* @return true if successful, false otherwise
*/
ESValue logout (ESValue arguments[]) throws EcmaScriptException {
if (ftpclient != null) {
try {
ftpclient.logout ();
} catch (IOException ignore) {}
try {
ftpclient.disconnect ();
} catch (IOException ignore) {}
}
return ESBoolean.makeBoolean (true);
}
ESValue binary (ESValue arguments[]) throws EcmaScriptException {
if (ftpclient != null) {
try {
ftpclient.setFileType (FTP.BINARY_FILE_TYPE);
return ESBoolean.makeBoolean (true);
} catch (IOException ignore) {}
}
return ESBoolean.makeBoolean (false);
}
ESValue ascii (ESValue arguments[]) throws EcmaScriptException {
if (ftpclient != null) {
try {
ftpclient.setFileType (FTP.ASCII_FILE_TYPE);
return ESBoolean.makeBoolean (true);
} catch (IOException ignore) {}
}
return ESBoolean.makeBoolean (false);
}
}
public class FtpExtension extends Extension {
private transient Evaluator evaluator = null;
private ESObject esFtpPrototype = null;
public FtpExtension () {
super();
}
class GlobalObjectFtpClient extends BuiltinFunctionObject {
GlobalObjectFtpClient(String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject, ESValue[] arguments)
throws EcmaScriptException {
return doConstruct(thisObject, arguments);
}
public ESObject doConstruct(ESObject thisObject, ESValue[] arguments)
throws EcmaScriptException {
ESFtpClient ftp = null;
if (arguments.length != 1)
throw new EcmaScriptException("FtpClient requires 1 argument");
ftp = new ESFtpClient (esFtpPrototype,
this.evaluator,
arguments[0]);
return ftp;
}
}
class FtpClientLogin extends BuiltinFunctionObject {
FtpClientLogin(String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject,
ESValue[] arguments)
throws EcmaScriptException {
ESFtpClient ftp = (ESFtpClient) thisObject;
return ftp.login (arguments);
}
}
class FtpClientCD extends BuiltinFunctionObject {
FtpClientCD(String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject,
ESValue[] arguments)
throws EcmaScriptException {
ESFtpClient ftp = (ESFtpClient) thisObject;
return ftp.cd (arguments);
}
}
class FtpClientMKDIR extends BuiltinFunctionObject {
FtpClientMKDIR(String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject,
ESValue[] arguments)
throws EcmaScriptException {
ESFtpClient ftp = (ESFtpClient) thisObject;
return ftp.mkdir (arguments);
}
}
class FtpClientLCD extends BuiltinFunctionObject {
FtpClientLCD(String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject,
ESValue[] arguments)
throws EcmaScriptException {
ESFtpClient ftp = (ESFtpClient) thisObject;
return ftp.lcd (arguments);
}
}
class FtpClientPutFile extends BuiltinFunctionObject {
FtpClientPutFile (String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject,
ESValue[] arguments)
throws EcmaScriptException {
ESFtpClient ftp = (ESFtpClient) thisObject;
return ftp.putFile (arguments);
}
}
class FtpClientPutString extends BuiltinFunctionObject {
FtpClientPutString (String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject,
ESValue[] arguments)
throws EcmaScriptException {
ESFtpClient ftp = (ESFtpClient) thisObject;
return ftp.putString (arguments);
}
}
class FtpClientGetFile extends BuiltinFunctionObject {
FtpClientGetFile (String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject,
ESValue[] arguments)
throws EcmaScriptException {
ESFtpClient ftp = (ESFtpClient) thisObject;
return ftp.getFile (arguments);
}
}
class FtpClientGetString extends BuiltinFunctionObject {
FtpClientGetString (String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject,
ESValue[] arguments)
throws EcmaScriptException {
ESFtpClient ftp = (ESFtpClient) thisObject;
return ftp.getString (arguments);
}
}
class FtpClientLogout extends BuiltinFunctionObject {
FtpClientLogout(String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject,
ESValue[] arguments)
throws EcmaScriptException {
ESFtpClient ftp = (ESFtpClient) thisObject;
return ftp.logout (arguments);
}
}
class FtpClientBinary extends BuiltinFunctionObject {
FtpClientBinary(String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject,
ESValue[] arguments)
throws EcmaScriptException {
ESFtpClient ftp = (ESFtpClient) thisObject;
return ftp.binary (arguments);
}
}
class FtpClientAscii extends BuiltinFunctionObject {
FtpClientAscii(String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject,
ESValue[] arguments)
throws EcmaScriptException {
ESFtpClient ftp = (ESFtpClient) thisObject;
return ftp.ascii (arguments);
}
}
public void initializeExtension(Evaluator evaluator) throws EcmaScriptException {
this.evaluator = evaluator;
GlobalObject go = evaluator.getGlobalObject();
ObjectPrototype op = (ObjectPrototype) evaluator.getObjectPrototype();
FunctionPrototype fp = (FunctionPrototype) evaluator.getFunctionPrototype();
esFtpPrototype = new ESFtpClient (op, evaluator);
ESObject globalFtpObject = new GlobalObjectFtpClient("FtpClient", evaluator, fp);
globalFtpObject.putHiddenProperty("prototype",esFtpPrototype);
globalFtpObject.putHiddenProperty("length",new ESNumber(1));
esFtpPrototype.putHiddenProperty("login", new FtpClientLogin("login", evaluator, fp));
esFtpPrototype.putHiddenProperty("cd", new FtpClientCD("cd", evaluator, fp));
esFtpPrototype.putHiddenProperty("mkdir", new FtpClientMKDIR("mkdir", evaluator, fp));
esFtpPrototype.putHiddenProperty("lcd", new FtpClientLCD("lcd", evaluator, fp));
esFtpPrototype.putHiddenProperty("putFile", new FtpClientPutFile("putFile", evaluator, fp));
esFtpPrototype.putHiddenProperty("putString", new FtpClientPutString("putString", evaluator, fp));
esFtpPrototype.putHiddenProperty("getFile", new FtpClientGetFile("getFile", evaluator, fp));
esFtpPrototype.putHiddenProperty("getString", new FtpClientGetString("getString", evaluator, fp));
esFtpPrototype.putHiddenProperty("logout", new FtpClientLogout("logout", evaluator, fp));
esFtpPrototype.putHiddenProperty("binary", new FtpClientBinary("binary", evaluator, fp));
esFtpPrototype.putHiddenProperty("ascii", new FtpClientAscii("ascii", evaluator, fp));
go.putHiddenProperty("FtpClient", globalFtpObject);
}
}

View file

@ -0,0 +1,127 @@
// ImageExtension.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.framework.extensions;
import helma.objectmodel.*;
import helma.util.*;
import helma.image.*;
import FESI.Interpreter.*;
import FESI.Exceptions.*;
import FESI.Extensions.*;
import FESI.Data.*;
import java.io.*;
import java.util.*;
import java.rmi.Naming;
/**
* Extension to do Image manipulation from HOP.
*/
public class ImageExtension extends Extension {
protected Evaluator evaluator = null;
static boolean remote = false;
public ImageExtension () {
super();
}
class GlobalObjectImage extends BuiltinFunctionObject {
ImageExtension imagex;
ImageGenerator imggen;
GlobalObjectImage (String name, Evaluator evaluator, FunctionPrototype fp, ImageExtension imagex) {
super(fp, evaluator, name, 1);
this.imagex = imagex;
}
public ESValue callFunction(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
return doConstruct(thisObject, arguments);
}
public ESObject doConstruct(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
Object img = null;
IRemoteGenerator rgen = null;
try {
if (imggen == null && !remote) {
try {
imggen = new ImageGenerator ();
} catch (UnsatisfiedLinkError noawt) {
remote = true;
}
}
if (remote)
rgen = (IRemoteGenerator) Naming.lookup ("//localhost:3033/server");
if (arguments.length == 1) {
if (arguments[0] instanceof ESArrayWrapper) {
Object obj = ((ESArrayWrapper) arguments[0]).toJavaObject ();
if (obj instanceof byte[]) {
img = remote ?
(Object) rgen.createImage ((byte[]) obj) :
(Object) imggen.createImage ((byte[]) obj);
}
} else if (arguments[0] instanceof ESString) {
String imgurl = arguments[0].toString ();
img = remote ?
(Object) rgen.createPaintableImage (imgurl) :
(Object) imggen.createPaintableImage (imgurl);
}
} else if (arguments.length == 2) {
if (arguments[0].isNumberValue () && arguments[1].isNumberValue ()) {
img = remote ?
(Object) rgen.createPaintableImage (arguments[0].toInt32(), arguments[1].toInt32()) :
(Object) imggen.createPaintableImage (arguments[0].toInt32(), arguments[1].toInt32());
}
}
} catch (Exception error) {
System.err.println ("Error creating Image: "+error);
}
if (img == null)
throw new EcmaScriptException ("Error creating image: Bad parameters or setup problem.");
return new ESWrapper (img, this.evaluator);
}
}
/**
* Called by the evaluator after the extension is loaded.
*/
public void initializeExtension(Evaluator evaluator) throws EcmaScriptException {
this.evaluator = evaluator;
GlobalObject go = evaluator.getGlobalObject();
FunctionPrototype fp = (FunctionPrototype) evaluator.getFunctionPrototype();
ESObject image = new GlobalObjectImage ("Image", evaluator, fp, this); // the Image constructor
go.putHiddenProperty("Image", image); // register the constructor for a Image object.
}
}

View file

@ -0,0 +1,255 @@
// MailExtension.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.framework.extensions;
import helma.objectmodel.*;
import helma.util.*;
import FESI.Interpreter.*;
import FESI.Exceptions.*;
import FESI.Extensions.*;
import FESI.Data.*;
import java.io.*;
import java.util.*;
/**
* Extension to create and send mail messages via SMTP from HOP applications
*/
public class MailExtension extends Extension {
protected Evaluator evaluator = null;
protected ObjectPrototype esMailPrototype = null;
protected Properties mprops;
public MailExtension () {
super();
}
public void setProperties (Properties props) {
this.mprops = props;
}
/**
* Called by the evaluator after the extension is loaded.
*/
public void initializeExtension(Evaluator evaluator) throws EcmaScriptException {
this.evaluator = evaluator;
GlobalObject go = evaluator.getGlobalObject();
FunctionPrototype fp = (FunctionPrototype) evaluator.getFunctionPrototype();
ESObject op = evaluator.getObjectPrototype();
esMailPrototype = new ObjectPrototype(op, evaluator); // the Node prototype
ESObject mail = new GlobalObjectMail ("Mail", evaluator, fp, this); // the Mail constructor
go.putHiddenProperty("Mail", mail); // register the constructor for a Mail object.
// methods for sending mail from JS...
ESObject p = new MailSetText ("setText", evaluator, fp);
esMailPrototype.putHiddenProperty("setText", p);
esMailPrototype.putHiddenProperty("addText", p);
esMailPrototype.putHiddenProperty("addPart", new MailAddPart ("addPart", evaluator, fp));
esMailPrototype.putHiddenProperty("setSubject", new MailSetSubject ("setSubject", evaluator, fp));
esMailPrototype.putHiddenProperty("setReplyTo", new MailSetReplyTo ("setReplyTo", evaluator, fp));
esMailPrototype.putHiddenProperty("setFrom", new MailSetFrom ("setFrom", evaluator, fp));
p = new MailAddTo ("addTo", evaluator, fp);
esMailPrototype.putHiddenProperty("addTo", p);
esMailPrototype.putHiddenProperty("setTo", p);
p = new MailAddCC ("addCC", evaluator, fp);
esMailPrototype.putHiddenProperty("addCC", p);
esMailPrototype.putHiddenProperty("setCC", p);
p = new MailAddBCC ("addBCC", evaluator, fp);
esMailPrototype.putHiddenProperty("addBCC", p);
esMailPrototype.putHiddenProperty("setBCC", p);
esMailPrototype.putHiddenProperty("send", new MailSend ("send", evaluator, fp));
}
class GlobalObjectMail extends BuiltinFunctionObject {
MailExtension mailx;
GlobalObjectMail (String name, Evaluator evaluator, FunctionPrototype fp, MailExtension mailx) {
super(fp, evaluator, name, 1);
this.mailx = mailx;
}
public ESValue callFunction(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
return doConstruct(thisObject, arguments);
}
public ESObject doConstruct(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
ESMail mail = null;
if (arguments.length == 0) {
mail = new ESMail (mailx);
} else {
mail = new ESMail (mailx);
// should/could do something with extra arguments...
}
return mail;
}
}
class MailSetText extends BuiltinFunctionObject {
MailSetText (String name, Evaluator evaluator, FunctionPrototype fp) {
super (fp, evaluator, name, 1);
}
public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
ESMail mail = (ESMail) thisObject;
if (arguments.length == 1) try {
mail.setText (arguments[0]);
} catch (Exception x) {
mail.setStatus (ESMail.TEXT);
return ESBoolean.makeBoolean(false);
}
return ESBoolean.makeBoolean(true);
}
}
class MailAddPart extends BuiltinFunctionObject {
MailAddPart (String name, Evaluator evaluator, FunctionPrototype fp) {
super (fp, evaluator, name, 1);
}
public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
ESMail mail = (ESMail) thisObject;
try {
mail.addPart (arguments);
} catch (Exception x) {
mail.setStatus (ESMail.MIMEPART);
return ESBoolean.makeBoolean(false);
}
return ESBoolean.makeBoolean(true);
}
}
class MailSetSubject extends BuiltinFunctionObject {
MailSetSubject (String name, Evaluator evaluator, FunctionPrototype fp) {
super (fp, evaluator, name, 1);
}
public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
ESMail mail = (ESMail) thisObject;
if (arguments.length == 1) try {
mail.setSubject (arguments[0]);
} catch (Exception x) {
mail.setStatus (ESMail.SUBJECT);
return ESBoolean.makeBoolean(false);
}
return ESBoolean.makeBoolean(true);
}
}
class MailSetReplyTo extends BuiltinFunctionObject {
MailSetReplyTo (String name, Evaluator evaluator, FunctionPrototype fp) {
super (fp, evaluator, name, 1);
}
public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
ESMail mail = (ESMail) thisObject;
if (arguments.length == 1) try {
mail.setReplyTo (arguments[0]);
} catch (Exception x) {
mail.setStatus (ESMail.REPLYTO);
return ESBoolean.makeBoolean(false);
}
return ESBoolean.makeBoolean(true);
}
}
class MailSetFrom extends BuiltinFunctionObject {
MailSetFrom (String name, Evaluator evaluator, FunctionPrototype fp) {
super (fp, evaluator, name, 1);
}
public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
ESMail mail = (ESMail) thisObject;
try {
mail.setFrom (arguments);
} catch (Exception x) {
mail.setStatus (ESMail.FROM);
return ESBoolean.makeBoolean(false);
}
return ESBoolean.makeBoolean(true);
}
}
class MailAddTo extends BuiltinFunctionObject {
MailAddTo (String name, Evaluator evaluator, FunctionPrototype fp) {
super (fp, evaluator, name, 1);
}
public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
ESMail mail = (ESMail) thisObject;
try {
mail.addTo (arguments);
} catch (Exception x) {
mail.setStatus (ESMail.TO);
return ESBoolean.makeBoolean(false);
}
return ESBoolean.makeBoolean(true);
}
}
class MailAddCC extends BuiltinFunctionObject {
MailAddCC (String name, Evaluator evaluator, FunctionPrototype fp) {
super (fp, evaluator, name, 1);
}
public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
ESMail mail = (ESMail) thisObject;
try {
mail.addCC (arguments);
} catch (Exception x) {
mail.setStatus (ESMail.CC);
return ESBoolean.makeBoolean(false);
}
return ESBoolean.makeBoolean(true);
}
}
class MailAddBCC extends BuiltinFunctionObject {
MailAddBCC (String name, Evaluator evaluator, FunctionPrototype fp) {
super (fp, evaluator, name, 1);
}
public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
ESMail mail = (ESMail) thisObject;
try {
mail.addBCC (arguments);
} catch (Exception x) {
mail.setStatus (ESMail.BCC);
return ESBoolean.makeBoolean(false);
}
return ESBoolean.makeBoolean(true);
}
}
class MailSend extends BuiltinFunctionObject {
MailSend (String name, Evaluator evaluator, FunctionPrototype fp) {
super (fp, evaluator, name, 1);
}
public ESValue callFunction (ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
ESMail mail = (ESMail) thisObject;
try {
mail.send ();
} catch (Exception x) {
IServer.getLogger().log ("Error sending mail: "+x);
mail.setStatus (ESMail.SEND);
return ESBoolean.makeBoolean(false);
}
return ESBoolean.makeBoolean(true);
}
}
}

View file

@ -0,0 +1,47 @@
// ActivatedImageWrapper.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.image;
import java.awt.*;
import java.awt.image.*;
import com.activated.jimi.*;
import com.activated.jimi.util.*;
/**
* A wrapper for an image that uses the Activated version of JIMI.
*/
public class ActivatedImageWrapper extends ImageWrapper {
public ActivatedImageWrapper (Image img, Graphics g, int width, int height,
ImageGenerator imggen) throws ClassNotFoundException {
super (img, g, width, height, imggen);
Class.forName ("com.activated.jimi.Jimi");
}
public void reduceColors (int colors) {
try {
ColorReducer redux = new ColorReducer (colors, true);
img = redux.getColorReducedImage (img);
} catch (Exception x) {
throw new RuntimeException (x.getMessage ());
}
}
public void saveAs (String filename) {
try {
Jimi.putImage (img, filename);
} catch (JimiException x) {
throw new RuntimeException (x.getMessage ());
}
}
}

View file

@ -0,0 +1,24 @@
// IRemoteGenerator.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.image;
import java.util.*;
import java.rmi.*;
import java.io.*;
/**
* RMI interface for accessing remote image generators.
*/
public interface IRemoteGenerator extends Remote {
public IRemoteImage createPaintableImage (int w, int h) throws RemoteException;
public IRemoteImage createPaintableImage (byte src[]) throws RemoteException;
public IRemoteImage createPaintableImage (String urlstring) throws RemoteException;
public IRemoteImage createImage (byte src[]) throws RemoteException;
}

View file

@ -0,0 +1,37 @@
// ActivatedImageWrapper.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.image;
import java.util.*;
import java.rmi.*;
import java.io.*;
/**
* RMI interface for accessing images on remote image servers.
*/
public interface IRemoteImage extends Remote {
public void setFont (String name, int style, int size) throws RemoteException;
public void setColor (int color) throws RemoteException;
public void setColor (int r, int g, int b) throws RemoteException;
public void reduceColors (int colors) throws RemoteException;
public void drawString (String str, int x, int y) throws RemoteException;
public void drawRect (int x, int y, int w, int h) throws RemoteException;
public void drawLine (int x1, int y1, int x2, int y2) throws RemoteException;
public void fillRect (int x, int y, int w, int h) throws RemoteException;
public int getWidth () throws RemoteException;
public int getHeight () throws RemoteException;
public void crop (int x, int y, int w, int h) throws RemoteException;
public void resize (int w, int h) throws RemoteException;
public void saveAs (String filename) throws RemoteException;
public void readFrom (String filename) throws RemoteException;
public byte[] getBytes (String type) throws RemoteException;
public void setBytes (byte[] bytes, String type) throws RemoteException;
}

View file

@ -0,0 +1,133 @@
// ImageGenerator.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.image;
import java.awt.*;
import java.net.URL;
/**
* This creates an invisible frame in order to be able to create images
* from Java. (Java needs a window context in order to user the Image class).
*/
public class ImageGenerator extends Window {
public ImageGenerator () {
super (new Frame() {
public void setVisible (boolean b) {
// This frame can never be shown
}
public synchronized void dispose() {
try {
getToolkit().getSystemEventQueue();
super.dispose();
} catch (Exception e) {
// untrusted code not allowed to dispose
}
}
}
);
this.setBounds (0, 0, 0, 0);
this.setVisible (true);
}
public ImageWrapper createPaintableImage (int w, int h) {
Image img = createImage (w, h);
Graphics g = img.getGraphics ();
ImageWrapper rimg = null;
try {
try {
rimg = new ActivatedImageWrapper (img, g, w, h, this);
} catch (NoClassDefFoundError notfound) {
rimg = new SunImageWrapper (img, g, w, h, this);
} catch (ClassNotFoundException notfound) {
rimg = new SunImageWrapper (img, g, w, h, this);
}
} catch (Exception x) {}
return rimg;
}
public ImageWrapper createPaintableImage (byte src[]) {
ImageWrapper rimg = null;
MediaTracker tracker = new MediaTracker (this);
try {
Image img1 = Toolkit.getDefaultToolkit ().createImage (src);
tracker.addImage (img1, 0);
tracker.waitForAll ();
int w = img1.getWidth (null);
int h = img1.getHeight (null);
Image img = createImage (w, h);
Graphics g = img.getGraphics ();
g.drawImage (img1, 0, 0, null);
try {
rimg = new ActivatedImageWrapper (img, g, w, h, this);
} catch (ClassNotFoundException notfound) {
rimg = new SunImageWrapper (img, g, w, h, this);
} catch (NoClassDefFoundError notfound) {
rimg = new SunImageWrapper (img, g, w, h, this);
}
} catch (Exception x) {}
return rimg;
}
public ImageWrapper createImage (byte src[]) {
ImageWrapper rimg = null;
MediaTracker tracker = new MediaTracker (this);
try {
Image img = Toolkit.getDefaultToolkit ().createImage (src);
tracker.addImage (img, 0);
tracker.waitForAll ();
int w = img.getWidth (null);
int h = img.getHeight (null);
try {
rimg = new ActivatedImageWrapper (img, null, w, h, this);
} catch (ClassNotFoundException notfound) {
rimg = new SunImageWrapper (img, null, w, h, this);
} catch (NoClassDefFoundError notfound) {
rimg = new SunImageWrapper (img, null, w, h, this);
}
} catch (Exception x) {}
return rimg;
}
public ImageWrapper createPaintableImage (String urlstring) {
ImageWrapper rimg = null;
MediaTracker tracker = new MediaTracker (this);
try {
URL url = new URL (urlstring);
Image img1 = Toolkit.getDefaultToolkit ().getImage (url);
tracker.addImage (img1, 0);
tracker.waitForAll ();
int w = img1.getWidth (null);
int h = img1.getHeight (null);
Image img = createImage (w, h);
Graphics g = img.getGraphics ();
g.drawImage (img1, 0, 0, null);
try {
rimg = new ActivatedImageWrapper (img, g, w, h, this);
} catch (ClassNotFoundException notfound) {
rimg = new SunImageWrapper (img, g, w, h, this);
} catch (NoClassDefFoundError notfound) {
rimg = new SunImageWrapper (img, g, w, h, this);
}
} catch (Exception x) {
x.printStackTrace ();
}
return rimg;
}
public Image createImage (String filename) {
Image img = null;
MediaTracker tracker = new MediaTracker (this);
try {
img = Toolkit.getDefaultToolkit ().getImage (filename);
tracker.addImage (img, 0);
tracker.waitForAll ();
} catch (Exception x) {
x.printStackTrace ();
}
return img;
}
}

View file

@ -0,0 +1,245 @@
// ImageWrapper.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.image;
import java.awt.*;
import java.awt.image.*;
import java.util.*;
import java.rmi.*;
import java.rmi.server.*;
import java.io.*;
/**
* Abstract base class for Image Wrappers.
*/
public abstract class ImageWrapper {
Image img;
Graphics g;
int width, height;
int fontstyle, fontsize;
String fontname;
ImageGenerator imggen;
public ImageWrapper (Image img, Graphics g, int width, int height, ImageGenerator imggen) {
this.img = img;
this.g = g;
this.width = width;
this.height = height;
this.imggen = imggen;
if (g != null) {
Font f = g.getFont ();
fontname = f.getName ();
fontstyle = f.getStyle ();
fontsize = f.getSize ();
}
}
/**
* image manipulation methods
*/
public void setFont (String name, int style, int size) {
this.fontname = name;
this.fontstyle = style;
this.fontsize = size;
g.setFont (new Font (name, style, size));
}
public void setColor (int red, int green, int blue) {
g.setColor (new Color (red, green, blue));
}
public void setColor (int color) {
g.setColor (new Color (color));
}
public void drawString (String str, int x, int y) {
g.drawString (str, x, y);
}
public void drawLine (int x1, int y1, int x2, int y2) {
g.drawLine (x1, y1, x2, y2);
}
public void drawRect (int x, int y, int w, int h) {
g.drawRect (x, y, w, h);
}
public void drawImage (String filename, int x, int y) {
try {
Image i = imggen.createImage (filename);
g.drawImage (i, x, y, null);
} catch (Exception ignore) {}
}
public void fillRect (int x, int y, int w, int h) {
g.fillRect (x, y, w, h);
}
public int getWidth () {
return width;
}
public int getHeight () {
return height;
}
public void crop (int x, int y, int w, int h) {
ImageFilter filter = new CropImageFilter (x, y, w, h);
img = Toolkit.getDefaultToolkit ().createImage(new FilteredImageSource(img.getSource(), filter));
}
public void resize (int w, int h) {
ImageFilter filter = new AreaAveragingScaleFilter (w, h);
img = Toolkit.getDefaultToolkit ().createImage(new FilteredImageSource(img.getSource(), filter));
}
public abstract void reduceColors (int colors);
public abstract void saveAs (String filename);
public void readFrom (String filename) {
throw new RuntimeException ("Image.readFrom() is currently not implemented.");
}
public byte[] getBytes (String type) {
throw new RuntimeException ("Image.getBytes() is currently not implemented.");
}
public void setBytes (byte[] bytes, String type) {
throw new RuntimeException ("Image.setBytes() is currently not implemented.");
}
public void fillString (String str) {
Filler filler = new Filler (0, 0, width, height);
filler.layout (str);
}
public void fillString (String str, int x, int y, int w, int h) {
Filler filler = new Filler (x, y, w, h);
filler.layout (str);
}
class Filler {
int x, y, w, h;
int addedSpace = 0;
int xLeft, yLeft;
int realHeight;
transient Vector lines;
public Filler (int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
public void layout (String str) {
int size = fontsize;
lines = new Vector ();
while (!splitMessage (str, size) && size > 10) {
lines.setSize (0);
size = Math.max (2, size-1);
}
Font oldfont = g.getFont ();
g.setFont (new Font (fontname, fontstyle, size));
int l = lines.size();
for (int i = 0; i < l; i++) {
((Line) lines.elementAt (i)).paint (g, xLeft/2, yLeft/2 + y);
}
g.setFont (oldfont);
}
private boolean splitMessage (String string, int size) {
Font font = new Font (fontname, fontstyle, size);
FontMetrics metrics = Toolkit.getDefaultToolkit ().getFontMetrics (font);
int longestLine = 0;
int heightSoFar = 0;
int heightIncrement = (int) (0.84f * metrics.getHeight ());
StringTokenizer tk = new StringTokenizer (string);
StringBuffer buffer = new StringBuffer();
int spaceWidth = metrics.stringWidth(" ");
int currentLine = 0;
int currentWidth = 0;
int maxWidth = w - 2, maxHeight = h + addedSpace - 2;
while (tk.hasMoreTokens()) {
String nextToken = tk.nextToken();
int nextWidth = metrics.stringWidth(nextToken);
if ((currentWidth + nextWidth >= maxWidth && currentWidth != 0)) {
Line line = new Line (buffer.toString(), x, heightSoFar, metrics);
lines.addElement (line);
if (line.textwidth > longestLine)
longestLine = line.textwidth;
buffer = new StringBuffer();
currentWidth = 0;
heightSoFar += heightIncrement;
}
buffer.append (nextToken);
buffer.append (" ");
currentWidth += (nextWidth + spaceWidth);
if (1.18*heightSoFar > maxHeight && fontsize > 10)
return false;
}
if (! "".equals (buffer.toString().trim())) {
Line line = new Line (buffer.toString(), x, heightSoFar, metrics);
lines.addElement (line);
if (line.textwidth > longestLine)
longestLine = line.textwidth;
if (longestLine > maxWidth && fontsize > 10)
return false;
heightSoFar += heightIncrement;
}
xLeft = w - longestLine;
yLeft = addedSpace + h - heightSoFar;
realHeight = heightSoFar;
return (1.18*heightSoFar <= maxHeight);
}
}
class Line implements Serializable {
String text;
int xoff, yoff;
FontMetrics fm;
public int textwidth, len;
int ascent;
public Line (String text, int xoff, int yoff, FontMetrics fm) {
this.text = text.trim();
len = text.length();
this.xoff = xoff;
this.yoff = yoff;
this.fm = fm;
textwidth = (len == 0) ? 0 : fm.stringWidth(this.text);
ascent = (int) (0.9f * fm.getAscent());
}
public void paint (Graphics g, int xadd, int yadd) {
g.drawString (text, xoff+xadd, yoff+ascent+yadd);
}
public boolean contains (int y) {
return (y < yoff+fm.getHeight()) ? true : false;
}
}
}

View file

@ -0,0 +1,106 @@
// RemoteImage.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.image;
import java.awt.*;
import java.awt.image.*;
import java.util.*;
import java.rmi.*;
import java.rmi.server.*;
/**
* Implementation of an image that is accessible via RMI.
*/
public class RemoteImage extends UnicastRemoteObject implements IRemoteImage {
ImageWrapper wrapped;
public RemoteImage (ImageWrapper wrapped) throws RemoteException {
this.wrapped = wrapped;
}
public void setFont (String name, int style, int size) {
wrapped.setFont (name, style, size);
}
public void setColor (int red, int green, int blue) {
wrapped.setColor (red, green, blue);
}
public void setColor (int color) {
wrapped.setColor (color);
}
public void drawString (String str, int x, int y) {
wrapped.drawString (str, x, y);
}
public void drawLine (int x1, int y1, int x2, int y2) {
wrapped.drawLine (x1, y1, x2, y2);
}
public void drawRect (int x, int y, int w, int h) {
wrapped.drawRect (x, y, w, h);
}
public void drawImage (String filename, int x, int y) {
wrapped.drawImage (filename, x, y);
}
public void fillRect (int x, int y, int w, int h) {
wrapped.fillRect (x, y, w, h);
}
public int getWidth () {
return wrapped.getWidth();
}
public int getHeight () {
return wrapped.getHeight();
}
public void crop (int x, int y, int w, int h) {
wrapped.crop (x, y, w, h);
}
public void resize (int w, int h) {
wrapped.resize (w, h);
}
public void reduceColors (int colors) {
wrapped.reduceColors (colors);
}
public void saveAs (String filename) {
wrapped.saveAs (filename);
}
public void readFrom (String filename) {
wrapped.readFrom (filename);
}
public byte[] getBytes (String type) {
return wrapped.getBytes (type);
}
public void setBytes (byte[] bytes, String type) {
wrapped.setBytes (bytes, type);
}
public void fillString (String str) {
wrapped.fillString (str);
}
public void fillString (String str, int x, int y, int w, int h) {
wrapped.fillString (str, x, y, w, h);
}
}

View file

@ -0,0 +1,90 @@
// Server.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.image;
import java.awt.*;
import java.util.*;
import java.io.*;
import java.net.URL;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
/**
* Implementation of RMI Image Generator. This accepts only connection from localhost.
*/
public class Server extends UnicastRemoteObject implements IRemoteGenerator {
static int port = 3033;
ImageGenerator imggen;
public static void main (String args[]) throws Exception {
new Server ();
}
public Server () throws Exception {
imggen = new ImageGenerator ();
// the following seems not to be necessary after all ...
// System.setSecurityManager(new RMISecurityManager());
System.out.println ("Starting server on port "+port);
LocateRegistry.createRegistry (port);
try {
Naming.bind ("//:"+port+"/server", this);
} catch (Exception x) {
System.out.println ("error binding remote objects: " + x);
}
}
public IRemoteImage createPaintableImage (int x, int y) throws RemoteException {
try {
String client = RemoteServer.getClientHost ();
if (!InetAddress.getLocalHost ().equals (InetAddress.getByName (client)))
throw new RemoteException ("Access Denied");
} catch (ServerNotActiveException ignore) {
} catch (UnknownHostException ignore) {}
return new RemoteImage (imggen.createPaintableImage (x, y));
}
public IRemoteImage createPaintableImage (byte[] bytes) throws RemoteException {
try {
String client = RemoteServer.getClientHost ();
if (!InetAddress.getLocalHost ().equals (InetAddress.getByName (client)))
throw new RemoteException ("Access Denied");
} catch (ServerNotActiveException ignore) {
} catch (UnknownHostException ignore) {}
return new RemoteImage (imggen.createPaintableImage (bytes));
}
public IRemoteImage createPaintableImage (String url) throws RemoteException {
try {
String client = RemoteServer.getClientHost ();
if (!InetAddress.getLocalHost ().equals (InetAddress.getByName (client)))
throw new RemoteException ("Access Denied");
} catch (ServerNotActiveException ignore) {
} catch (UnknownHostException ignore) {}
return new RemoteImage (imggen.createPaintableImage (url));
}
public IRemoteImage createImage (byte[] bytes) throws RemoteException {
try {
String client = RemoteServer.getClientHost ();
if (!InetAddress.getLocalHost ().equals (InetAddress.getByName (client)))
throw new RemoteException ("Access Denied");
} catch (ServerNotActiveException ignore) {
} catch (UnknownHostException ignore) {}
return new RemoteImage (imggen.createImage (bytes));
}
}

View file

@ -0,0 +1,56 @@
// ActivatedImageWrapper.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.image;
import java.awt.*;
import java.awt.image.*;
import com.sun.jimi.core.*;
import com.sun.jimi.core.util.*;
import Acme.JPM.Encoders.GifEncoder;
import java.io.IOException;
import java.io.FileOutputStream;
/**
* A wrapper for an image that uses the Sun version of JIMI available at
* http://java.sun.com/products/jimi.
*/
public class SunImageWrapper extends ImageWrapper {
public SunImageWrapper (Image img, Graphics g, int width, int height,
ImageGenerator imggen) throws ClassNotFoundException {
super (img, g, width, height, imggen);
Class.forName ("com.sun.jimi.core.Jimi");
}
public void reduceColors (int colors) {
try {
ColorReducer redux = new ColorReducer (colors, true);
img = redux.getColorReducedImage (img);
} catch (Exception x) {
throw new RuntimeException (x.getMessage ());
}
}
public void saveAs (String filename) {
try {
if (filename.toLowerCase().endsWith (".gif")) {
// sun's jimi package doesn't encode gifs, use Acme encoder
FileOutputStream fout = new FileOutputStream (filename);
GifEncoder enc = new GifEncoder (img, fout);
enc.encode ();
fout.close ();
} else {
Jimi.putImage (img, filename);
}
} catch (JimiException x) {
throw new RuntimeException (x.getMessage ());
} catch (IOException iox) {
throw new RuntimeException (iox.getMessage ());
}
}
}

View file

@ -0,0 +1,161 @@
// LanguageTag.java
// $Id$
// (c) COPYRIGHT MIT, INRIA and Keio, 1999
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.mime;
import java.util.*;
import java.io.*;
/**
* This class is used to represent parsed Language tags,
* It creates a representation from a string based representation
* of the Language tag, as defined in RFC 1766
* NOTE, we don't check that languages are defined according to ISO 639
*/
public class LanguageTag implements Serializable, Cloneable {
public static int NO_MATCH = -1;
public static int MATCH_LANGUAGE = 1;
public static int MATCH_SPECIFIC_LANGUAGE = 2;
// subtag is not dialect as subtype can be
// dialect or country identification or script variation, etc...
public static int MATCH_SUBTAG = 3;
public static int MATCH_SPECIFIC_SUBTAG = 4;
/**
* String representation of the language
*
* @serial
*/
protected String language = null ;
/**
* String representation of subtag
*
* @serial
*/
protected String subtag = null ;
/**
* external form of this language tag
*
* @serial
*/
protected String external = null ;
/**
* How good the given LanguageTag matches the receiver of the method ?
* This method returns a matching level among:
* <dl>
* <dt>NO_MATCH<dd>Language not matching,</dd>
* <dt>MATCH_LANGUAGE<dd>Languages match roughly (with *),</dd>
* <dt>MATCH_SPECIFIC_LANGUAGE<dd>Languages match exactly,</dd>
* <dt>MATCH_SUBTAG<dd>Languages match, subtags matches roughly</dd>
* <dt>MATCH_SPECIFIC_SUBAG<dd>Languages match, subtag matches exactly</dd>
* </dl>
* The matches are ranked from worst match to best match, a simple
* Max ( match[i], matched) will give the best match.
* @param other The other LanguageTag to match against ourself.
*/
public int match (LanguageTag other) {
int match = NO_MATCH;
// match types:
if ( language.equals("*") || other.language.equals("*") ) {
match = MATCH_LANGUAGE;
} else if ( ! language.equalsIgnoreCase(other.language) ) {
return NO_MATCH ;
} else {
match = MATCH_SPECIFIC_LANGUAGE;
}
// match subtypes:
if ((subtag == null) || (other.subtag == null))
return match;
if ( subtag.equals("*") || other.subtag.equals("*") ) {
match = MATCH_SUBTAG ;
} else if ( ! subtag.equalsIgnoreCase(other.subtag) ) {
return NO_MATCH;
} else {
match = MATCH_SPECIFIC_SUBTAG;
}
return match;
}
/**
* A printable representation of this LanguageTag.
* The printed representation is guaranteed to be parseable by the
* String constructor.
*/
public String toString () {
if ( external == null ) {
if (subtag != null) {
external = language + "-" + subtag;
} else {
external = language;
}
}
return external ;
}
/**
* Get the language
* @return The language, encoded as a String.
*/
public String getLanguage() {
return language;
}
/**
* Get the subtag
* @return The subtag, encoded as a string
*/
public String getSubtag() {
return language;
}
/**
* Construct a Language tag from a spec
* @parameter spec, A string representing a LangateTag
*/
public LanguageTag(String spec) {
int strl = spec.length() ;
int start = 0, look = -1 ;
// skip leading/trailing blanks:
while ((start < strl) && (spec.charAt (start)) <= ' ')
start++ ;
while ((strl > start) && (spec.charAt (strl-1) <= ' '))
strl-- ;
// get the type:
StringBuffer sb = new StringBuffer () ;
while ((start < strl) && ((look = spec.charAt(start)) != '-')
&& ((look = spec.charAt(start)) != ';')) {
sb.append ((char) look) ;
start++ ;
}
this.language = sb.toString() ;
if ( look == '-' ) {
start++ ;
sb.setLength(0) ;
while ((start < strl)
&& ((look = spec.charAt(start)) > ' ') && (look != ';')) {
sb.append ((char) look) ;
start++ ;
}
this.subtag = sb.toString() ;
}
}
/**
* construct directly a language tag
* it NEEDS both language and subtype parameters
*/
public LanguageTag(String language, String subtag) {
this.language = language;
this.subtag = subtag;
}
}

View file

@ -0,0 +1,51 @@
// MimeHeaderHolder.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.mime;
import java.io.*;
public interface MimeHeaderHolder {
/**
* A new header has been parsed.
* @param name The name of the encountered header.
* @param buf The byte buffer containing the value.
* @param off Offset of the header value in the above buffer.
* @param len Length of the value in the above header.
* @exception MimeParserException if the parsing failed
*/
public void notifyHeader(String name, byte buf[], int off, int len)
throws MimeParserException;
/**
* The parsing is now about to start, take any appropriate action.
* This hook can return a <strong>true</strong> boolean value to enforce
* the MIME parser into transparent mode (eg the parser will <em>not</em>
* try to parse any headers.
* <p>This hack is primarily defined for HTTP/0.9 support, it might
* also be usefull for other hacks.
* @param parser The Mime parser.
* @return A boolean <strong>true</strong> if the MimeParser shouldn't
* continue the parsing, <strong>false</strong> otherwise.
* @exception MimeParserException if the parsing failed
* @exception IOException if an IO error occurs.
*/
public boolean notifyBeginParsing(MimeParser parser)
throws MimeParserException, IOException;
/**
* All the headers have been parsed, take any appropriate actions.
* @param parser The Mime parser.
* @exception MimeParserException if the parsing failed
* @exception IOException if an IO error occurs.
*/
public void notifyEndParsing(MimeParser parser)
throws MimeParserException, IOException;
}

View file

@ -0,0 +1,147 @@
// MimeHeaders.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.mime;
import java.io.*;
import java.util.*;
/**
* The most stupid MIME header holder.
* This class uses a hashtable mapping header names (as String), to header
* values (as String). Header names are lowered before entering the hashtable.
*/
public class MimeHeaders implements MimeHeaderHolder {
Hashtable headers = null;
MimeParser parser = null;
/**
* A new header has been parsed.
* @param name The name of the encountered header.
* @param buf The byte buffer containing the value.
* @param off Offset of the header value in the above buffer.
* @param len Length of the value in the above header.
* @exception MimeParserException if the parsing failed
*/
public void notifyHeader(String name, byte buf[], int off, int len)
throws MimeParserException
{
String lname = name.toLowerCase();
String oldval = null;
if ( headers == null ) {
headers = new Hashtable(5);
} else {
oldval = (String) headers.get(lname);
}
String newval = ((oldval != null)
? oldval + "," + new String(buf, 0, off, len)
: new String(buf, 0, off, len));
headers.put(lname, newval);
}
/**
* The parsing is now about to start, take any appropriate action.
* This hook can return a <strong>true</strong> boolean value to enforce
* the MIME parser into transparent mode (eg the parser will <em>not</em>
* try to parse any headers.
* <p>This hack is primarily defined for HTTP/0.9 support, it might
* also be usefull for other hacks.
* @param parser The Mime parser.
* @return A boolean <strong>true</strong> if the MimeParser shouldn't
* continue the parsing, <strong>false</strong> otherwise.
* @exception IOException if an IO error occurs.
*/
public boolean notifyBeginParsing(MimeParser parser)
throws IOException
{
return false;
}
/**
* All the headers have been parsed, take any appropriate actions.
* @param parser The Mime parser.
* @exception IOException if an IO error occurs.
*/
public void notifyEndParsing(MimeParser parser)
throws IOException
{
return;
}
/**
* Set a header value.
* @param name The header name.
* @param value The header value.
*/
public void setValue(String name, String value) {
if ( headers == null )
headers = new Hashtable(5);
headers.put(name.toLowerCase(), value);
}
/**
* Retreive a header value.
* @param name The name of the header.
* @return The value for this header, or <strong>null</strong> if
* undefined.
*/
public String getValue(String name) {
return ((headers != null)
? (String) headers.get(name.toLowerCase())
: null);
}
/**
* Enumerate the headers defined by the holder.
* @return A enumeration of header names, or <strong>null</strong> if no
* header is defined.
*/
public Enumeration enumerateHeaders() {
if ( headers == null )
return null;
return headers.keys();
}
/**
* Get the entity stream attached to these headers, if any.
* @return An InputStream instance, or <strong>null</strong> if no
* entity available.
*/
public InputStream getInputStream() {
return ((parser != null) ? parser.getInputStream() : null);
}
/**
* Dump all headers to the given stream.
* @param out The stream to dump to.
*/
public void dump(PrintStream out) {
Enumeration names = enumerateHeaders();
if ( names != null ) {
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
out.println(name+": "+headers.get(name));
}
}
}
public MimeHeaders(MimeParser parser) {
this.parser = parser;
}
public MimeHeaders() {
}
}

View file

@ -0,0 +1,25 @@
// MimeHeadersFactory.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.mime;
/**
* A Mime header factory, that will build instances of the MimeHeaders class
* to hold MIME headers.
*/
public class MimeHeadersFactory implements MimeParserFactory {
/**
* Create a new header holder to hold the parser's result.
* @param parser The parser that has something to parse.
* @return A MimeParserHandler compliant object.
*/
public MimeHeaderHolder createHeaderHolder(MimeParser parser) {
return new MimeHeaders(parser);
}
}

View file

@ -0,0 +1,228 @@
// MimeParser.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.mime;
import java.util.*;
import java.io.* ;
/**
* The MimeParser class parses an input MIME stream.
*/
public class MimeParser {
protected int ch = -1 ;
protected InputStream input = null ;
protected byte buffer[] = new byte[128] ;
protected int bsize = 0 ;
/**
* The factory used to create new MIME header holders.
*/
protected MimeParserFactory factory = null ;
protected void expect (int car)
throws MimeParserException, IOException
{
if ( car != ch ) {
String sc = (new Character((char) car)).toString() ;
String se = (new Character((char) ch)).toString() ;
throw new MimeParserException ("expecting "
+ sc + "("+car+")"
+ " got "
+ se + "("+ch+")\n"
+ "context: "
+ new String (buffer, 0, 0, bsize)
+ "\n") ;
}
ch = input.read() ;
}
protected void skipSpaces ()
throws MimeParserException, IOException
{
while ( (ch == ' ') || (ch == '\t') )
ch = input.read() ;
}
protected final void append (int c) {
if ( bsize+1 >= buffer.length ) {
byte nb[] = new byte[buffer.length*2] ;
System.arraycopy (buffer, 0, nb, 0, buffer.length) ;
buffer = nb ;
}
buffer[bsize++] = (byte) c ;
}
/*
* Get the header name:
*/
protected String parse822HeaderName ()
throws MimeParserException, IOException
{
bsize = 0 ;
while ( (ch >= 32) && (ch != ':') ) {
append ((char) ch) ;
ch = input.read() ;
}
expect (':') ;
if ( bsize <= 0 )
throw new MimeParserException ("expected a header name.") ;
return new String (buffer, 0, 0, bsize) ;
}
/*
* Get the header body, still trying to be 822 compliant *and* HTTP
* robust, which is unfortunatelly a contrdiction.
*/
protected void parse822HeaderBody ()
throws MimeParserException, IOException
{
bsize = 0 ;
skipSpaces () ;
loop:
while ( true ) {
switch (ch) {
case -1:
break loop ;
case '\r':
if ( (ch = input.read()) != '\n' ) {
append ('\r') ;
continue ;
}
// no break intentional
case '\n':
// do as if '\r' had been received. This defeats 822, but
// makes HTTP more "robust". I wish HTTP were a binary
// protocol.
switch (ch = input.read()) {
case ' ': case '\t':
do {
ch = input.read () ;
} while ((ch == ' ') || (ch == '\t')) ;
append(ch);
break ;
default:
break loop ;
}
break ;
default:
append ((char) ch) ;
break ;
}
ch = input.read() ;
}
return ;
}
/*
* Parse the given input stream for an HTTP 1.1 token.
*/
protected String parseToken (boolean lower)
throws MimeParserException, IOException
{
bsize = 0 ;
while ( true ) {
switch ( ch ) {
// CTLs
case -1:
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
case 8: case 9: case 10: case 11: case 12: case 13: case 14:
case 15: case 16: case 17: case 18: case 19: case 20: case 21:
case 22: case 23: case 24: case 25: case 26: case 27: case 28:
case 29: case 30: case 31:
// tspecials
case '(': case ')': case '<': case '>': case '@':
case ',': case ';': case ':': case '\\': case '\"':
case '/': case '[': case ']': case '?': case '=':
case '{': case '}': case ' ':
return new String (buffer, 0, 0, bsize) ;
default:
append ((char) (lower
? Character.toLowerCase((char) ch)
: ch)) ;
}
ch = input.read() ;
}
}
protected void parse822Headers(MimeHeaderHolder msg)
throws MimeParserException, IOException
{
while ( true ) {
if ( ch == '\r' ) {
if ( (ch = input.read()) == '\n' )
return ;
} else if ( ch == '\n' ) {
return ;
}
String name = parse822HeaderName () ;
skipSpaces() ;
parse822HeaderBody () ;
msg.notifyHeader(name, buffer, 0, bsize);
}
}
public MimeHeaderHolder parse()
throws MimeParserException, IOException
{
MimeHeaderHolder msg = factory.createHeaderHolder(this);
ch = input.read() ;
cached = true ;
if ( ! msg.notifyBeginParsing(this) ) {
if ( ! cached )
ch = input.read();
parse822Headers (msg) ;
}
msg.notifyEndParsing(this);
return msg;
}
boolean cached = false ;
public int read()
throws IOException
{
if ( cached )
cached = false;
else
ch = input.read();
return ch;
}
public void unread(int ch) {
if ( cached )
throw new RuntimeException("cannot unread more then once !");
this.ch = ch;
cached = true;
}
/**
* Get the message body, as an input stream.
* @return The input stream used by the parser to get data, after
* a call to <code>parse</code>, this input stream contains exactly
* the body of the message.
*/
public InputStream getInputStream () {
return input ;
}
/**
* Create an instance of the MIMEParser class.
* @param in The input stream to be parsed as a MIME stream.
* @param factory The factory used to create MIME header holders.
*/
public MimeParser (InputStream input, MimeParserFactory factory) {
this.input = input ;
this.factory = factory;
}
}

View file

@ -0,0 +1,14 @@
// MimeParserException.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.mime;
public class MimeParserException extends Exception {
public MimeParserException(String msg) {
super(msg);
}
}

View file

@ -0,0 +1,24 @@
// MimeParserFactory.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.mime;
/**
* This class is used by the MimeParser, to create new MIME message holders.
* Each MIME parse instances is custmozied wit hits own factory, which it
* will use to create MIME header holders.
*/
public interface MimeParserFactory {
/**
* Create a new header holder to hold the parser's result.
* @param parser The parser that has something to parse.
* @return A MimeParserHandler compliant object.
*/
abstract public MimeHeaderHolder createHeaderHolder(MimeParser parser);
}

View file

@ -0,0 +1,374 @@
// MimeType.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.mime;
import java.util.*;
import java.io.*;
/**
* This class is used to represent parsed MIME types.
* It creates this representation from a string based representation of
* the MIME type, as defined in the RFC 1345.
*/
public class MimeType implements Serializable, Cloneable {
/**
* List of well known MIME types:
*/
public static MimeType TEXT_HTML = null ;
public static MimeType APPLICATION_POSTSCRIPT = null ;
public static MimeType TEXT_PLAIN = null ;
public static MimeType APPLICATION_X_WWW_FORM_URLENCODED = null ;
public static MimeType MULTIPART_FORM_DATA = null ;
public static MimeType APPLICATION_X_JAVA_AGENT = null ;
public static MimeType MESSAGE_HTTP = null ;
public static MimeType TEXT_CSS = null ;
public static MimeType TEXT = null ;
static {
try {
TEXT_HTML
= new MimeType("text/html");
APPLICATION_POSTSCRIPT
= new MimeType ("application/postscript") ;
TEXT_PLAIN
= new MimeType("text/plain") ;
APPLICATION_X_WWW_FORM_URLENCODED
= new MimeType("application/x-www-form-urlencoded") ;
MULTIPART_FORM_DATA
= new MimeType("multipart/form-data") ;
APPLICATION_X_JAVA_AGENT
= new MimeType("application/x-java-agent") ;
MESSAGE_HTTP
= new MimeType("message/http");
TEXT_CSS
= new MimeType("text/css");
TEXT
= new MimeType("text/*");
} catch (MimeTypeFormatException e) {
System.out.println ("httpd.MimeType: invalid static init.") ;
System.exit (1) ;
}
}
public final static int NO_MATCH = -1 ;
public final static int MATCH_TYPE = 1 ;
public final static int MATCH_SPECIFIC_TYPE = 2 ;
public final static int MATCH_SUBTYPE = 3 ;
public final static int MATCH_SPECIFIC_SUBTYPE = 4 ;
/**
* String representation of type
*
* @serial
*/
protected String type = null ;
/**
* String representation of subtype
*
* @serial
*/
protected String subtype = null ;
/**
* parameter names
*
* @serial
*/
protected String pnames[] = null;
/**
* parameter values
*
* @serial
*/
protected String pvalues[] = null;
/**
* external form of this mime type
*
* @serial
*/
protected String external = null ;
/**
* How good the given MimeType matches the receiver of the method ?
* This method returns a matching level among:
* <dl>
* <dt>NO_MATCH<dd>Types not matching,</dd>
* <dt>MATCH_TYPE<dd>Types match,</dd>
* <dt>MATCH_SPECIFIC_TYPE<dd>Types match exactly,</dd>
* <dt>MATCH_SUBTYPE<dd>Types match, subtypes matches too</dd>
* <dt>MATCH_SPECIFIC_SUBTYPE<dd>Types match, subtypes matches exactly</dd>
* </dl>
* The matches are ranked from worst match to best match, a simple
* Max ( match[i], matched) will give the best match.
* @param other The other MimeType to match against ourself.
*/
public int match (MimeType other) {
int match = NO_MATCH;
// match types:
if ( type.equals("*") || other.type.equals("*") ) {
return MATCH_TYPE;
} else if ( ! type.equals (other.type) ) {
return NO_MATCH ;
} else {
match = MATCH_SPECIFIC_TYPE;
}
// match subtypes:
if ( subtype.equals("*") || other.subtype.equals("*") ) {
match = MATCH_SUBTYPE ;
} else if ( ! subtype.equals (other.subtype) ) {
return NO_MATCH;
} else {
match = MATCH_SPECIFIC_SUBTYPE;
}
return match;
}
/**
* A printable representation of this MimeType.
* The printed representation is guaranteed to be parseable by the
* String constructor.
*/
public String toString () {
if ( external == null ) {
StringBuffer sb = new StringBuffer (type) ;
sb.append((char) '/') ;
sb.append (subtype) ;
if ( pnames != null ) {
for (int i = 0 ; i < pnames.length; i++) {
sb.append(';');
sb.append(pnames[i]);
if ( pvalues[i] != null ) {
sb.append('=');
sb.append(pvalues[i]);
}
}
}
external = sb.toString() ;
}
return external ;
}
/**
* Does this MIME type has some value for the given parameter ?
* @param name The parameter to check.
* @return <strong>True</strong> if this parameter has a value, false
* otherwise.
*/
public boolean hasParameter (String name) {
if ( pnames != null ) {
for (int i = 0 ; i < pnames.length ; i++) {
if ( pnames[i].equals(name) )
return true ;
}
}
return false ;
}
/**
* Get the major type of this mime type.
* @return The major type, encoded as a String.
*/
public String getType() {
return type;
}
/**
* Get the minor type (subtype) of this mime type.
* @return The minor or subtype encoded as a String.
*/
public String getSubtype() {
return subtype;
}
/**
* Get a mime type parameter value.
* @param name The parameter whose value is to be returned.
* @return The parameter value, or <b>null</b> if not found.
*/
public String getParameterValue (String name) {
if ( pnames != null ) {
for (int i = 0 ; i < pnames.length ; i++) {
if ( pnames[i].equals(name) )
return pvalues[i];
}
}
return null ;
}
/**
* adds some parameters to a MimeType
* @param param a String array of parameter names
* @param values the corresponding String array of values
*/
public void addParameters(String[] param, String[] values) {
// sanity check
if ((param == null) || (values == null) ||
(values.length != param.length))
return;
if (pnames == null) {
pnames = param;
pvalues = values;
} else {
String[] nparam = new String[pnames.length+param.length];
String[] nvalues = new String[pvalues.length+values.length];
System.arraycopy(pnames, 0, nparam, 0, pnames.length);
System.arraycopy(param, 0, nparam, pnames.length, param.length);
System.arraycopy(pvalues, 0, nvalues, 0, pvalues.length);
System.arraycopy(values,0, nvalues, pvalues.length, values.length);
pnames = nparam;
pvalues = nvalues;
}
external = null;
}
/**
* get a clone of this object
* @return another cloned instance of MimeType
*/
public MimeType getClone() {
try {
return (MimeType) clone();
} catch (CloneNotSupportedException ex) {
// should never happen as we are Cloneable!
}
// never reached
return null;
}
/**
* adds a parameterto a MimeType
* @param param the parameter name, as a String
* @param value the parameter value, as a Sting
*/
public void addParameter(String param, String value) {
String[] p = new String[1];
String[] v = new String[1];
p[0] = param;
v[0] = value;
addParameters(p, v);
}
/**
* Construct MimeType object for the given string.
* The string should be the representation of the type. This methods
* tries to be compliant with HTTP1.1, p 15, although it is not
* (because of quoted-text not being accepted).
* FIXME
* @parameter spec A string representing a MimeType
* @return A MimeType object
* @exception MimeTypeFormatException if the string couldn't be parsed.
*/
public MimeType (String spec)
throws MimeTypeFormatException
{
int strl = spec.length() ;
int start = 0, look = -1 ;
// skip leading/trailing blanks:
while ((start < strl) && (spec.charAt (start)) <= ' ')
start++ ;
while ((strl > start) && (spec.charAt (strl-1) <= ' '))
strl-- ;
// get the type:
StringBuffer sb = new StringBuffer () ;
while ((start < strl) && ((look = spec.charAt(start)) != '/')) {
sb.append ((char) look) ;
start++ ;
}
if ( look != '/' )
throw new MimeTypeFormatException (spec) ;
this.type = sb.toString() ;
// get the subtype:
start++ ;
sb.setLength(0) ;
while ((start < strl)
&& ((look = spec.charAt(start)) > ' ') && (look != ';')) {
sb.append ((char) look) ;
start++ ;
}
this.subtype = sb.toString() ;
// get parameters, if any:
while ((start < strl) && ((look = spec.charAt(start)) <= ' '))
start++ ;
if ( start < strl ) {
if (spec.charAt(start) != ';')
throw new MimeTypeFormatException (spec) ;
start++ ;
Vector vp = new Vector(4) ;
Vector vv = new Vector(4) ;
while ( start < strl ) {
while ((start < strl) && (spec.charAt(start) <= ' ')) start++ ;
// get parameter name:
sb.setLength (0) ;
while ((start < strl)
&& ((look=spec.charAt(start)) > ' ') && (look != '=')) {
sb.append (Character.toLowerCase((char) look)) ;
start++ ;
}
String name = sb.toString() ;
// get the value:
while ((start < strl) && (spec.charAt(start) <= ' ')) start++ ;
if (spec.charAt(start) != '=')
throw new MimeTypeFormatException (spec) ;
start++ ;
while ((start < strl) &&
((spec.charAt(start) == '"') ||
(spec.charAt(start) <= ' '))) start++ ;
sb.setLength(0) ;
while ((start < strl)
&& ((look=spec.charAt(start)) > ' ')
&& (look != ';')
&& (look != '"')) {
sb.append ((char) look) ;
start++ ;
}
while ((start < strl) && (spec.charAt(start) != ';')) start++ ;
start++ ;
String value = sb.toString() ;
vp.addElement(name);
vv.addElement(value);
}
this.pnames = new String[vp.size()];
vp.copyInto(pnames);
this.pvalues = new String[vv.size()];
vv.copyInto(pvalues);
}
}
public MimeType (String type, String subtype
, String pnames[], String pvalues[]) {
this.type = type ;
this.subtype = subtype ;
this.pnames = pnames;
this.pvalues = pvalues;
}
public MimeType (String type, String subtype) {
this.type = type;
this.subtype = subtype;
}
public static void main (String args[]) {
if ( args.length == 1) {
MimeType type = null ;
try {
type = new MimeType (args[0]) ;
} catch (MimeTypeFormatException e) {
}
if ( type != null )
System.out.println (type) ;
else
System.out.println ("Invalid mime type specification.") ;
} else {
System.out.println ("Usage: java MimeType <type-to-parse>") ;
}
}
}

View file

@ -0,0 +1,14 @@
// MimeType.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.mime;
public class MimeTypeFormatException extends Exception {
public MimeTypeFormatException(String msg) {
super(msg);
}
}

View file

@ -0,0 +1,214 @@
// MultipartInputStream.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.mime;
import java.io.* ;
/**
* A class to handle multipart MIME input streams. See RC 1521.
* This class handles multipart input streams, as defined by the RFC 1521.
* It prvides a sequential interface to all MIME parts, and for each part
* it delivers a suitable InputStream for getting its body.
*/
public class MultipartInputStream extends InputStream {
InputStream in = null;
byte boundary[] = null ;
byte buffer[] = null ;
boolean partEnd = false ;
boolean fileEnd = false ;
// Read boundary bytes of input in buffer
// Return true if enough bytes available, false otherwise.
private final boolean readBoundaryBytes()
throws IOException
{
int pos = 0;
while ( pos < buffer.length ) {
int got = in.read(buffer, pos, buffer.length-pos);
if ( got < 0 )
return false;
pos += got;
}
return true;
}
// Skip to next input boundary, set stream at begining of content:
// Returns true if boundary was found, false otherwise.
protected boolean skipToBoundary()
throws IOException
{
int ch = in.read() ;
skip:
while ( ch != -1 ) {
if ( ch != '-' ) {
ch = in.read() ;
continue ;
}
if ((ch = in.read()) != '-')
continue ;
in.mark(boundary.length) ;
if ( ! readBoundaryBytes() ) {
in.reset();
ch = in.read();
continue skip;
}
for (int i = 0 ; i < boundary.length ; i++) {
if ( buffer[i] != boundary[i] ) {
in.reset() ;
ch = in.read() ;
continue skip ;
}
}
// FIXME: should we check for a properly syntaxed part, which
// means that we should expect '\r\n'. For now, we just skip
// as much as we can.
if ( (ch = in.read()) == '\r' ) {
ch = in.read() ;
}
in.mark(3);
if( in.read() == '-' ) { // check fileEnd!
if( in.read() == '\r' && in.read() == '\n' ) {
fileEnd = true ;
return false ;
}
}
in.reset();
return true ;
}
fileEnd = true ;
return false ;
}
/**
* Read one byte of data from the current part.
* @return A byte of data, or <strong>-1</strong> if end of file.
* @exception IOException If some IO error occured.
*/
public int read()
throws IOException
{
int ch ;
if ( partEnd )
return -1 ;
switch (ch = in.read()) {
case '\r':
// check for a boundary
in.mark(boundary.length+3) ;
int c1 = in.read() ;
int c2 = in.read() ;
int c3 = in.read() ;
if ((c1 == '\n') && (c2 == '-') && (c3 == '-')) {
if ( ! readBoundaryBytes() ) {
in.reset();
return ch;
}
for (int i = 0 ; i < boundary.length ; i++) {
if ( buffer[i] != boundary[i] ) {
in.reset() ;
return ch ;
}
}
partEnd = true ;
if ( (ch = in.read()) == '\r' ) {
in.read() ;
} else if (ch == '-') {
// FIXME, check the second hyphen
if (in.read() == '-')
fileEnd = true ;
} else {
fileEnd = (ch == -1);
}
return -1 ;
} else {
in.reset () ;
return ch ;
}
// not reached
case -1:
fileEnd = true ;
return -1 ;
default:
return ch ;
}
}
/**
* Read n bytes of data from the current part.
* @return the number of bytes data, read or <strong>-1</strong>
* if end of file.
* @exception IOException If some IO error occured.
*/
public int read (byte b[], int off, int len)
throws IOException
{
int got = 0 ;
int ch ;
while ( got < len ) {
if ((ch = read()) == -1)
return (got == 0) ? -1 : got ;
b[off+(got++)] = (byte) (ch & 0xFF) ;
}
return got ;
}
public long skip (long n)
throws IOException
{
while ((--n >= 0) && (read() != -1))
;
return n ;
}
public int available ()
throws IOException
{
return in.available();
}
/**
* Switch to the next available part of data.
* One can interrupt the current part, and use this method to switch
* to next part before current part was totally read.
* @return A boolean <strong>true</strong> if there next partis ready,
* or <strong>false</strong> if this was the last part.
*/
public boolean nextInputStream()
throws IOException
{
if ( fileEnd ) {
return false ;
}
if ( ! partEnd ) {
return skipToBoundary() ;
} else {
partEnd = false ;
return true ;
}
}
/**
* Construct a new multipart input stream.
* @param in The initial (multipart) input stream.
* @param boundary The input stream MIME boundary.
*/
public MultipartInputStream (InputStream in, byte boundary[]) {
this.in = (in.markSupported()
? in
: new BufferedInputStream(in, boundary.length+4));
this.boundary = boundary ;
this.buffer = new byte[boundary.length] ;
this.partEnd = false ;
this.fileEnd = false ;
}
}

143
src/helma/mime/Utils.java Normal file
View file

@ -0,0 +1,143 @@
// Utils.java
// $Id$
// (c) COPYRIGHT MIT and INRIA, 1998.
// Please first read the full copyright statement in file COPYRIGHT.html
package helma.mime;
import java.util.Hashtable;
/**
* @version $Revision$
* @author Benoît Mahé (bmahe@w3.org)
*/
public class Utils {
private static Hashtable extension_map = new Hashtable();
private static void setSuffix(String ext, String ct) {
extension_map.put(ext, ct);
}
static {
setSuffix("", "content/unknown");
setSuffix(".uu", "application/octet-stream");
setSuffix(".saveme", "application/octet-stream");
setSuffix(".dump", "application/octet-stream");
setSuffix(".hqx", "application/octet-stream");
setSuffix(".arc", "application/octet-stream");
setSuffix(".o", "application/octet-stream");
setSuffix(".a", "application/octet-stream");
setSuffix(".bin", "application/octet-stream");
setSuffix(".exe", "application/octet-stream");
setSuffix(".z", "application/octet-stream");
setSuffix(".gz", "application/octet-stream");
setSuffix(".oda", "application/oda");
setSuffix(".pdf", "application/pdf");
setSuffix(".eps", "application/postscript");
setSuffix(".ai", "application/postscript");
setSuffix(".ps", "application/postscript");
setSuffix(".rtf", "application/rtf");
setSuffix(".dvi", "application/x-dvi");
setSuffix(".hdf", "application/x-hdf");
setSuffix(".latex", "application/x-latex");
setSuffix(".cdf", "application/x-netcdf");
setSuffix(".nc", "application/x-netcdf");
setSuffix(".tex", "application/x-tex");
setSuffix(".texinfo", "application/x-texinfo");
setSuffix(".texi", "application/x-texinfo");
setSuffix(".t", "application/x-troff");
setSuffix(".tr", "application/x-troff");
setSuffix(".roff", "application/x-troff");
setSuffix(".man", "application/x-troff-man");
setSuffix(".me", "application/x-troff-me");
setSuffix(".ms", "application/x-troff-ms");
setSuffix(".src", "application/x-wais-source");
setSuffix(".wsrc", "application/x-wais-source");
setSuffix(".zip", "application/zip");
setSuffix(".bcpio", "application/x-bcpio");
setSuffix(".cpio", "application/x-cpio");
setSuffix(".gtar", "application/x-gtar");
setSuffix(".shar", "application/x-shar");
setSuffix(".sh", "application/x-shar");
setSuffix(".sv4cpio", "application/x-sv4cpio");
setSuffix(".sv4crc", "application/x-sv4crc");
setSuffix(".tar", "application/x-tar");
setSuffix(".ustar", "application/x-ustar");
setSuffix(".snd", "audio/basic");
setSuffix(".au", "audio/basic");
setSuffix(".aifc", "audio/x-aiff");
setSuffix(".aif", "audio/x-aiff");
setSuffix(".aiff", "audio/x-aiff");
setSuffix(".wav", "audio/x-wav");
setSuffix(".gif", "image/gif");
setSuffix(".ief", "image/ief");
setSuffix(".jfif", "image/jpeg");
setSuffix(".jfif-tbnl", "image/jpeg");
setSuffix(".jpe", "image/jpeg");
setSuffix(".jpg", "image/jpeg");
setSuffix(".jpeg", "image/jpeg");
setSuffix(".tif", "image/tiff");
setSuffix(".tiff", "image/tiff");
setSuffix(".ras", "image/x-cmu-rast");
setSuffix(".pnm", "image/x-portable-anymap");
setSuffix(".pbm", "image/x-portable-bitmap");
setSuffix(".pgm", "image/x-portable-graymap");
setSuffix(".ppm", "image/x-portable-pixmap");
setSuffix(".rgb", "image/x-rgb");
setSuffix(".xbm", "image/x-xbitmap");
setSuffix(".xpm", "image/x-xpixmap");
setSuffix(".xwd", "image/x-xwindowdump");
setSuffix(".htm", "text/html");
setSuffix(".html", "text/html");
setSuffix(".text", "text/plain");
setSuffix(".c", "text/plain");
setSuffix(".cc", "text/plain");
setSuffix(".c++", "text/plain");
setSuffix(".h", "text/plain");
setSuffix(".pl", "text/plain");
setSuffix(".txt", "text/plain");
setSuffix(".java", "text/plain");
setSuffix(".rtx", "application/rtf");
setSuffix(".tsv", "texyt/tab-separated-values");
setSuffix(".etx", "text/x-setext");
setSuffix(".mpg", "video/mpeg");
setSuffix(".mpe", "video/mpeg");
setSuffix(".mpeg", "video/mpeg");
setSuffix(".mov", "video/quicktime");
setSuffix(".qt", "video/quicktime");
setSuffix(".avi", "application/x-troff-msvideo");
setSuffix(".movie", "video/x-sgi-movie");
setSuffix(".mv", "video/x-sgi-movie");
setSuffix(".mime", "message/rfc822");
}
/**
* A useful utility routine that tries to guess the content-type
* of an object based upon its extension.
*/
public static String guessContentTypeFromName(String fname) {
String ext = "";
int i = fname.lastIndexOf('#');
if (i != -1)
fname = fname.substring(0, i - 1);
i = fname.lastIndexOf('.');
i = Math.max(i, fname.lastIndexOf('/'));
i = Math.max(i, fname.lastIndexOf('?'));
if (i != -1 && fname.charAt(i) == '.') {
ext = fname.substring(i).toLowerCase();
}
return (String) extension_map.get(ext);
}
public static MimeType getMimeType(String filename) {
try {
return new MimeType(guessContentTypeFromName(filename));
} catch (MimeTypeFormatException ex) {
return null;
}
}
}

View file

@ -0,0 +1,51 @@
// ConcurrencyException.java
// Copyright (c) Hannes Wallnöfer 1998-2000
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 RuntimeException {
public ConcurrencyException (String msg) {
super (msg);
}
}

View file

@ -0,0 +1,457 @@
// DbMapping.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel;
import helma.framework.core.Application;
import helma.objectmodel.db.WrappedNodeManager;
import java.util.*;
import java.sql.*;
import com.workingdogs.village.*;
/**
* A DbMapping describes how a certain type of Nodes is to mapped to a
* relational database table. Basically it consists of a set of JavaScript property-to-
* Database row bindings which are represented by instances of the Relation class.
*/
public class DbMapping {
Application app;
String typename;
SystemProperties props;
DbSource source;
String table;
DbMapping parent;
DbMapping subnodes;
DbMapping properties;
private Relation parentRel;
private Relation subnodesRel;
private Relation propertiesRel;
// Map of property names to Relations objects
public Hashtable prop2db;
// Map of db columns to Relations objects
public Hashtable db2prop;
String idField;
String nameField;
private String idgen;
Schema schema = null;
KeyDef keydef = null;
private long lastTypeChange;
public long lastDataChange;
public DbMapping () {
prop2db = new Hashtable ();
db2prop = new Hashtable ();
parent = null;
subnodes = null;
properties = null;
idField = "id";
}
public DbMapping (Application app, String typename, SystemProperties props) {
this.app = app;
this.typename = typename;
prop2db = new Hashtable ();
db2prop = new Hashtable ();
parent = null;
subnodes = null;
properties = null;
idField = "id";
this.props = props;
read ();
app.putDbMapping (typename, this);
}
/**
* Read the mapping from the Properties. Return true if the properties were changed.
*/
public boolean read () {
long lastmod = props.lastModified ();
if (lastmod == lastTypeChange)
return false;
this.table = props.getProperty ("_tablename");
this.idgen = props.getProperty ("_idgen");
String sourceName = props.getProperty ("_datasource");
if (sourceName != null)
source = (DbSource) IServer.dbSources.get (sourceName.toLowerCase ());
lastTypeChange = lastmod;
// set the cached schema & keydef to null so it's rebuilt the next time around
schema = null;
keydef = null;
return true;
}
public void rewire () {
// if (table != null && source != null) {
// IServer.getLogger().log ("set data source for "+typename+" to "+source);
Hashtable p2d = new Hashtable ();
Hashtable d2p = new Hashtable ();
for (Enumeration e=props.keys(); e.hasMoreElements(); ) {
String propName = (String) e.nextElement ();
if (!propName.startsWith ("_") && propName.indexOf (".") < 0) {
String dbField = props.getProperty (propName);
Relation rel = new Relation (dbField, propName, this, props);
p2d.put (propName, rel);
if (rel.localField != null)
d2p.put (rel.localField, rel);
// IServer.getLogger().log ("Mapping "+propName+" -> "+dbField);
} else if ("_id".equalsIgnoreCase (propName)) {
idField = props.getProperty (propName);
} else if ("_name".equalsIgnoreCase (propName)) {
nameField = props.getProperty (propName);
}
}
prop2db = p2d;
db2prop = d2p;
String parentMapping = props.getProperty ("_parent");
if (parentMapping != null) {
parentRel = new Relation (parentMapping, "_parent", this, props);
if (parentRel.isReference ())
parent = parentRel.other;
else
parent = (DbMapping) app.getDbMapping (parentMapping);
}
String subnodeMapping = props.getProperty ("_subnodes");
if (subnodeMapping != null) {
subnodesRel = new Relation (subnodeMapping, "_subnodes", this, props);
if (subnodesRel.isReference ())
subnodes = subnodesRel.other;
else
subnodes = (DbMapping) app.getDbMapping (subnodeMapping);
}
String propertiesMapping = props.getProperty ("_properties");
if (propertiesMapping != null) {
propertiesRel = new Relation (propertiesMapping, "_properties", this, props);
if (propertiesRel.isReference ())
properties = propertiesRel.other;
else
properties = (DbMapping) app.getDbMapping (propertiesMapping);
// take over groupby flag from subnodes, if properties are subnodes
if (propertiesRel.subnodesAreProperties && subnodesRel != null)
propertiesRel.groupby = subnodesRel.groupby;
}
IServer.getLogger().log ("rewiring: "+parent+" -> "+this+" -> "+subnodes);
}
public Connection getConnection () throws ClassNotFoundException, SQLException {
if (source == null)
throw new SQLException ("Tried to get Connection from non-relational embedded data source.");
return source.getConnection ();
}
public DbSource getDbSource () {
return source;
}
public String getSourceID () {
return source == null ? "" : source.url;
}
public String getTableName () {
return table;
}
public Application getApplication () {
return app;
}
public String getAppName () {
return app.getName();
}
public String getTypeName () {
return typename;
}
/**
* Get the primary key column name for objects using this mapping.
*/
public String getIDField () {
return idField;
}
/**
* Get the column used for (internal) names of objects of this type.
*/
public String getNameField () {
return nameField;
}
/**
* Translate a database column name to a JavaScript property name according to this mapping.
*/
public Relation columnNameToProperty (String columnName) {
return (Relation) db2prop.get (columnName);
}
/**
* Translate a JavaScript property name to a database column name according to this mapping.
*/
public Relation propertyToColumnName (String propName) {
return (Relation) prop2db.get (propName);
}
public DbMapping getParentMapping () {
return parent;
}
public DbMapping getSubnodeMapping () {
return subnodes;
}
public void setSubnodeMapping (DbMapping sm) {
subnodes = sm;
}
public DbMapping getExactPropertyMapping (String propname) {
if (propname == null)
return null;
Relation rel = (Relation) prop2db.get (propname.toLowerCase());
return rel != null ? rel.other : null;
}
public DbMapping getPropertyMapping (String propname) {
if (propname == null)
return properties;
Relation rel = (Relation) prop2db.get (propname.toLowerCase());
return rel != null && !rel.virtual ? rel.other : properties;
}
public void setPropertyMapping (DbMapping pm) {
properties = pm;
}
public void setSubnodeRelation (Relation rel) {
subnodesRel = rel;
}
public void setPropertyRelation (Relation rel) {
propertiesRel = rel;
}
public Relation getSubnodeRelation () {
return subnodesRel;
}
public Relation getPropertyRelation () {
return propertiesRel;
}
public Relation getPropertyRelation (String propname) {
if (propname == null)
return propertiesRel;
Relation rel = (Relation) prop2db.get (propname.toLowerCase());
return rel != null ? rel : propertiesRel;
}
public String getIDgen () {
return idgen;
}
public WrappedNodeManager getWrappedNodeManager () {
if (app == null)
throw new RuntimeException ("Can't get node manager from internal db mapping");
return app.getWrappedNodeManager ();
}
public boolean isRelational () {
return source != null;
}
/**
* Return a Village Schema object for this DbMapping.
*/
public Schema getSchema () throws ClassNotFoundException, SQLException, DataSetException {
if (!isRelational ())
throw new SQLException ("Can't get Schema for non-relational data mapping");
// Use local variable s to avoid synchronization (schema may be nulled elsewhere)
Schema s = schema;
if (s != null)
return s;
schema = new Schema ().schema (getConnection (), table, "*");
return schema;
}
/**
* Return a Village Schema object for this DbMapping.
*/
public KeyDef getKeyDef () {
if (!isRelational ())
throw new RuntimeException ("Can't get KeyDef for non-relational data mapping");
// Use local variable s to avoid synchronization (keydef may be nulled elsewhere)
KeyDef k = keydef;
if (k != null)
return k;
keydef = new KeyDef ().addAttrib (idField);
return keydef;
}
public String toString () {
if (app == null)
return "[unspecified internal DbMapping]";
else
return ("["+app.getName()+"."+typename+"]");
}
public long getLastTypeChange () {
return lastTypeChange;
}
}

View file

@ -0,0 +1,95 @@
// DbSource.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.objectmodel;
import java.sql.*;
import java.util.Hashtable;
import helma.objectmodel.db.Transactor;
/**
* This class describes a releational data source (URL, driver, user and password).
*/
public class DbSource {
private String name;
protected String url;
private String driver;
protected String user;
private String password;
public DbSource (String name) throws ClassNotFoundException {
this.name = name;
url = IServer.dbProps.getProperty (name+".url");
driver = IServer.dbProps.getProperty (name+".driver");
Class.forName (driver);
user = IServer.dbProps.getProperty (name+".user");
password = IServer.dbProps.getProperty (name+".password");
IServer.getLogger().log ("created db source ["+name+", "+url+", "+driver+", "+user+"]");
IServer.dbSources.put (name.toLowerCase (), this);
}
public Connection getConnection () throws ClassNotFoundException, SQLException {
Transactor tx = (Transactor) Thread.currentThread ();
Connection con = tx.getConnection (this);
if (con == null || con.isClosed ()) {
Class.forName (driver);
con = DriverManager.getConnection (url, user, password);
// If we wanted to use SQL transactions, we'd set autoCommit to
// false here and make commit/rollback invocations in Transactor methods;
IServer.getLogger().log ("Created new Connection to "+url);
tx.registerConnection (this, con);
}
return con;
}
public String getDriverName () {
return driver;
}
public String getName () {
return name;
}
}

View file

@ -0,0 +1,114 @@
// INode.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel;
import java.util.*;
import java.io.*;
/**
* 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 {
public final static String webTypes = "image/jpeg, image/gif, image/png";
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;
/**
* id-related methods
*/
public String getID ();
public String getName ();
public String getNameOrID (); // get name or id depending if it's a named or an anonymous node.
public void setDbMapping (DbMapping dbmap);
public DbMapping getDbMapping ();
public int getState ();
public void setState (int s);
public String getFullName ();
public String getFullName (INode root);
public INode[] getPath ();
public void setName (String name);
public long lastModified ();
public long created ();
public boolean isAnonymous (); // is this a named node, or an anonymous node in a collection?
// public void setPrototype (String prototype);
// public String getPrototype ();
public INode getCacheNode ();
/**
* node-related methods
*/
public INode getParent ();
public void setSubnodeRelation (String rel);
public String getSubnodeRelation ();
public int numberOfNodes ();
public INode addNode (INode node);
public INode addNode (INode node, int where);
public INode createNode (String name);
public INode createNode (String name, int where);
public Enumeration getSubnodes ();
public INode getSubnode (String name);
public INode getSubnodeAt (int index);
public int contains (INode node);
public boolean remove ();
public void removeNode (INode node);
/**
* property-related methods
*/
public Enumeration properties ();
public IProperty get (String name, boolean inherit);
public String getString (String name, boolean inherit);
public String getString (String name, String defaultValue, boolean inherit);
public boolean getBoolean (String name, boolean inherit);
public Date getDate (String name, boolean inherit);
public long getInteger (String name, boolean inherit);
public double getFloat (String name, boolean inherit);
public INode getNode (String name, boolean inherit);
public Object getJavaObject (String name, boolean inherit);
public void setString (String name, String value);
public void setBoolean (String name, boolean value);
public void setDate (String name, Date value);
public void setInteger (String name, long value);
public void setFloat (String name, double value);
public void setNode (String name, INode value);
public void setJavaObject (String name, Object value);
public void unset (String name);
/**
* content-related methods
*/
public String getContentType ();
public void setContentType (String type);
public int getContentLength ();
public void setContent (byte content[], String type);
public void setContent (String content);
public byte[] getContent ();
public String getText ();
public String getUrl (INode root, INode userroot, String tmpname);
public String getHref (INode root, INode userroot, String tmpname, String prefix);
}

View file

@ -0,0 +1,15 @@
// INodeListener.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel;
/**
* An interface for objects that wish to be notified when certain nodes are
* modified. This is not used in the HOP as much as it used to be.
*/
public interface INodeListener {
public void nodeChanged (NodeEvent event);
}

View file

@ -0,0 +1,21 @@
// INode.java
// Copyright (c) Hannes Wallnöfer 2000
package helma.objectmodel;
/**
* Minimal Interface for Nodes that build a hierarchic tree
*/
public interface IPathElement {
public INode getSubnode (String name);
public INode getNode (String name, boolean inherit);
}

View file

@ -0,0 +1,34 @@
// IProperty.java
// Copyright (c) Hannes Wallnöfer 1997-2000
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;
public String getName ();
public int getType ();
public Object getValue ();
public INode getNodeValue ();
public String getStringValue ();
public boolean getBooleanValue ();
public long getIntegerValue ();
public double getFloatValue ();
public Date getDateValue ();
public Object getJavaObjectValue ();
}

View file

@ -0,0 +1,79 @@
// IServer.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel;
import helma.util.*;
import helma.xmlrpc.WebServer;
import java.util.*;
import java.io.*;
/**
* Abstract Server class. Defines the methods all servers have to implement.
*/
public abstract class IServer {
// public static final Object sync = new Object ();
public static SystemProperties sysProps, dbProps;
public static Hashtable dbSources;
protected static String hopHome = null;
private static Logger logger;
protected static WebServer xmlrpc;
/* public abstract INode getAppRoot (String appID);
public abstract INode getAppNode (String appID, Vector path, String name);
public abstract INode getSubnode (String path); */
public static void throwNodeEvent (NodeEvent evt) {
// noop
}
public static void addNodeListener (String id, INodeListener listener) {
// noop
}
public static void removeNodeListener (String node, INodeListener listener) {
// noop
}
public static Logger getLogger () {
if (logger == null) {
String logDir = sysProps.getProperty ("logdir");
if (logDir == null) {
logger = new Logger (System.out);
} else {
try {
File helper = new File (logDir);
if (hopHome != null && !helper.isAbsolute ())
helper = new File (hopHome, logDir);
logDir = helper.getAbsolutePath ();
logger = new Logger (logDir, "hop");
} catch (IOException iox) {
System.err.println ("Could not create Logger for log/hop: "+iox);
// fallback to System.out
logger = new Logger (System.out);
}
}
}
return logger;
}
public static String getHopHome () {
return hopHome;
}
public static WebServer getXmlRpcServer() {
return xmlrpc;
}
}

View file

@ -0,0 +1,140 @@
// Key.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel;
import Acme.LruHashtable;
import java.io.Serializable;
/**
* This is the internal representation of a database key. It is constructed
* out of the database URL, the table name, the user name and the database
* key of the node and unique within each HOP application. Currently only
* single keys are supported.
*/
public class Key implements Serializable {
protected String type;
protected String id;
private int hash;
private static LruHashtable keycache;
public synchronized static Key makeKey (DbMapping dbmap, String id) {
String _type = dbmap == null ? "" : dbmap.typename;
String _id = id.trim (); // removed .toLowerCase() - hw
return makeKey (_type, _id);
}
private synchronized static Key makeKey (String _type, String _id) {
if (keycache == null)
keycache = new LruHashtable (1000, 0.9f);
Key k = (Key) keycache.get (_type+"#"+_id);
if (k == null) {
k = new Key (_type, _id);
keycache.put (_type+"#"+_id, k);
}
return k;
}
private Key (String type, String id) {
this.type = type;
this.id = id;
hash = this.id.hashCode ();
}
public boolean equals (Object what) {
if (what == this)
return true;
if (what == null || !(what instanceof Key))
return false;
Key other = (Key) what;
if (type == null)
return (id.equals (other.id) && other.type == null);
else
return (id.equals (other.id) && type.equals (other.type));
}
public int hashCode () {
return hash;
}
/**
* Get the Key for a virtual node contained by this node, that is, a node that does
* not represent a record in the database. The main objective here is to generate
* a key that can't be mistaken for a relational db key.
*/
public Key getVirtualKey (String sid) {
Key virtkey = makeKey ("", getVirtualID (type, id, sid));
return virtkey;
}
public static String getVirtualID (DbMapping pmap, String pid, String sid) {
String ptype = pmap == null ? "" : pmap.typename;
return ptype+"/"+pid + "*h~v*" + sid;
}
public static String getVirtualID (String ptype, String pid, String sid) {
return ptype+"/"+pid + "*h~v*" + sid;
}
public String toString () {
return type+"["+id+"]";
}
}

View file

@ -0,0 +1,765 @@
// Node.java
// Copyright (c) Hannes Wallnöfer 1997-2000
package helma.objectmodel;
import java.util.*;
import java.io.*;
import helma.util.*;
/**
* A transient implementation of INode. If a transient node is stored in a
* database, it is automatically (along with all reachable subnodes) rebuilt
* as a persistent node.
*/
public class Node implements INode, Serializable {
protected Hashtable propMap, nodeMap;
protected Vector nodes;
protected Node parent;
protected Vector links; // links to this node
protected Vector proplinks; // nodes using this node as property
protected String contentType;
protected byte content[];
protected long created;
protected long lastmodified;
protected String id, name;
// is the main identity a named property or an anonymous node in a collection?
protected boolean anonymous = false;
transient DbMapping dbmap;
transient boolean adoptName = true; // little helper to know if this node is being converted
private static long idgen = 0;
public String generateID () {
return "t"+idgen++; // make transient ids differ from persistent ones
}
public Node () {
id = generateID ();
name = id;
created = lastmodified = System.currentTimeMillis ();
}
/**
* Erstellt einen neuen Node.
*/
public Node (String n) {
id = generateID ();
name = n == null || "".equals (n) ? id : n;
created = lastmodified = System.currentTimeMillis ();
}
/**
* Erstellt einen Clone eines Nodes in der "lokalen" Implementierung von INode.
*/
public Node (INode node, Hashtable ntable, boolean conversionRoot) {
this.id = generateID ();
this.name = node.getName ();
created = lastmodified = System.currentTimeMillis ();
setContent (node.getContent (), node.getContentType ());
created = lastmodified = System.currentTimeMillis ();
ntable.put (node, this);
adoptName = !conversionRoot; // only take over name from property if this is not the transient root
for (Enumeration e = node.getSubnodes (); e.hasMoreElements (); ) {
INode next = (INode) e.nextElement ();
Node nextc = (Node) ntable.get (next);
if (nextc == null)
nextc = new Node (next, ntable, true);
addNode (nextc);
}
for (Enumeration e = node.properties (); e.hasMoreElements (); ) {
IProperty next = (IProperty) e.nextElement ();
int t = next.getType ();
if (t == IProperty.NODE) {
INode n = next.getNodeValue ();
Node nextc = (Node) ntable.get (n);
if (nextc == null)
nextc = new Node (n, ntable, true);
setNode (next.getName (), nextc);
} else if (t == IProperty.STRING) {
setString (next.getName (), next.getStringValue ());
} else if (t == IProperty.INTEGER) {
setInteger (next.getName (), next.getIntegerValue ());
} else if (t == IProperty.FLOAT) {
setFloat (next.getName (), next.getFloatValue ());
} else if (t == IProperty.BOOLEAN) {
setBoolean (next.getName (), next.getBooleanValue ());
} else if (t == IProperty.DATE) {
setDate (next.getName (), next.getDateValue ());
}
}
adoptName = true; // switch back to normal name adoption behaviour
}
public void setDbMapping (DbMapping dbmap) {
this.dbmap = dbmap;
}
public DbMapping getDbMapping () {
return dbmap;
}
/**
* navigation-related
*/
public String getID () {
return id;
}
public boolean isAnonymous () {
return anonymous;
}
public String getName () {
return name;
}
public String getNameOrID () {
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 getFullName () {
return getFullName (null);
}
public String getFullName (INode root) {
String fullname = "";
String divider = null;
StringBuffer b = new StringBuffer ();
Node p = this;
while (p != null && p.parent != null && p != root) {
if (divider != null)
b.insert (0, divider);
else
divider = "/";
b.insert (0, p.getNameOrID ());
p = p.parent;
}
return b.toString ();
}
public INode[] getPath () {
int pathSize = 1;
INode p = getParent ();
while (p != null) {
pathSize +=1;
p = p.getParent ();
}
INode path[] = new INode[pathSize];
p = this;
for (int i = pathSize-1; i>=0; i--) {
path[i] = p;
p = p.getParent ();
}
return path;
}
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 INode getParent () {
return parent;
}
/**
* INode-related
*/
public void setSubnodeRelation (String rel) {
throw new RuntimeException ("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).");
// IServer.getLogger().log ("adding: "+node+" -- "+node.getContentLength ());
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 Node) {
Node node = (Node) elem;
if (node.parent == null) {
node.parent = this;
node.anonymous = true;
}
if (node.parent != null && (node.parent != this || !node.anonymous)) {
node.registerLink (this);
}
}
lastmodified = System.currentTimeMillis ();
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.SUBNODE_ADDED, node));
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 Node (nm);
if (anon)
addNode (n, where);
else
setNode (nm, n);
return n;
}
/**
* register a node that links to this node.
*/
protected void registerLink (Node from) {
if (links == null)
links = new Vector ();
if (!links.contains (from))
links.addElement (from);
}
public INode getSubnode (String name) {
return getSubnode (name, false);
}
public INode getSubnode (String name, boolean inherit) {
StringTokenizer st = new StringTokenizer (name, "/");
Node retval = this, 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 : (Node) runner.nodeMap.get (next);
if (retval == null)
retval = (Node) runner.getNode (next, inherit);
if (retval == null && inherit && runner == this && parent != null)
retval = (Node) parent.getSubnode (next, inherit);
}
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);
Node n = (Node) node;
if (n.getParent () == this && n.anonymous) {
int l = n.links == null ? 0 : n.links.size (); // notify nodes that link to n that n is going down.
for (int i = 0; i < l; i++) {
Node link = (Node) n.links.elementAt (i);
link.releaseNode (n);
}
if (n.proplinks != null) {
// clean up all nodes that use n as a property
for (Enumeration e1 = n.proplinks.elements (); e1.hasMoreElements (); ) try {
Property p = (Property) e1.nextElement ();
p.node.propMap.remove (p.propname.toLowerCase ());
} catch (Exception ignore) {}
}
for (Enumeration e2 = n.properties (); e2.hasMoreElements (); ) {
// tell all nodes that are properties of n that they are no longer used as such
Property p = (Property) e2.nextElement ();
if (p != null && p.type == Property.NODE)
p.unregisterNode ();
}
// 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 ((Node) v.elementAt (i));
}
if (n.content != null) {
// IServer.getLogger().log ("destroying content of node "+n.getName ());
// ObjectStore.destroy (n.content);
}
} else {
//
n.links.removeElement (this);
}
}
/**
* "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);
// nodes.remove (node);
Object what = nodeMap.remove (node.getName ().toLowerCase ());
// Server.throwNodeEvent (new NodeEvent (node, NodeEvent.NODE_REMOVED));
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.SUBNODE_REMOVED, node));
lastmodified = System.currentTimeMillis ();
// IServer.getLogger().log ("released node "+node +" from "+this+" oldobj = "+what);
}
public Enumeration getSubnodes () {
return nodes == null ? new Vector ().elements () : nodes.elements ();
}
/**
* property-related
*/
public Enumeration properties () {
return propMap == null ? new Vector ().elements () : propMap.elements ();
}
private Property getProperty (String propname, boolean inherit) {
Property prop = propMap == null ? null : (Property) propMap.get (propname);
if (prop == null && inherit && parent != null) {
prop = parent.getProperty (propname, inherit);
}
// check if we have to create a virtual node
if (prop == null && dbmap != null) {
Relation rel = dbmap.getPropertyRelation (propname);
if (rel != null && rel.virtual) {
prop = makeVirtualNode (propname, rel);
}
}
return prop;
}
private Property makeVirtualNode (String propname, Relation rel) {
INode node = new helma.objectmodel.db.Node (rel.propname, dbmap.getWrappedNodeManager());
// node.setState (TRANSIENT);
// make a db mapping good enough that the virtual node finds its subnodes
DbMapping dbm = new DbMapping ();
dbm.setSubnodeMapping (rel.other);
dbm.setSubnodeRelation (rel);
dbm.setPropertyMapping (rel.other);
dbm.setPropertyRelation (rel);
node.setDbMapping (dbm);
setNode (propname, node);
return (Property) propMap.get (propname);
}
public IProperty get (String propname, boolean inherit) {
propname = propname.toLowerCase ();
return getProperty (propname, inherit);
}
public String getString (String propname, String defaultValue, boolean inherit) {
String propValue = getString (propname, inherit);
return propValue == null ? defaultValue : propValue;
}
public String getString (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getStringValue ();
} catch (Exception ignore) {}
return null;
}
public long getInteger (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getIntegerValue ();
} catch (Exception ignore) {}
return 0;
}
public double getFloat (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getFloatValue ();
} catch (Exception ignore) {}
return 0.0;
}
public Date getDate (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getDateValue ();
} catch (Exception ignore) {}
return null;
}
public boolean getBoolean (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getBooleanValue ();
} catch (Exception ignore) {}
return false;
}
public INode getNode (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getNodeValue ();
} catch (Exception ignore) {}
return null;
}
public Object getJavaObject (String propname, boolean inherit) {
propname = propname.toLowerCase ();
Property prop = getProperty (propname, inherit);
try {
return prop.getJavaObjectValue ();
} catch (Exception ignore) {}
return null;
}
// create a property if it doesn't exist for this name
private Property initProperty (String propname) {
if (propMap == null)
propMap = new Hashtable ();
propname = propname.trim ();
String p2 = propname.toLowerCase ();
Property prop = (Property) propMap.get (p2);
if (prop == null) {
prop = new Property (propname, this);
propMap.put (p2, prop);
}
return prop;
}
public void setString (String propname, String value) {
// IServer.getLogger().log ("setting String prop");
Property prop = initProperty (propname);
try {
prop.setStringValue (value);
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
} catch (java.text.ParseException x) {
throw new RuntimeException ("Fehler beim Parsen des Datum-Strings");
}
lastmodified = System.currentTimeMillis ();
}
public void setInteger (String propname, long value) {
// IServer.getLogger().log ("setting bool prop");
Property prop = initProperty (propname);
prop.setIntegerValue (value);
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
}
public void setFloat (String propname, double value) {
// IServer.getLogger().log ("setting bool prop");
Property prop = initProperty (propname);
prop.setFloatValue (value);
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
}
public void setBoolean (String propname, boolean value) {
// IServer.getLogger().log ("setting bool prop");
Property prop = initProperty (propname);
prop.setBooleanValue (value);
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
}
public void setDate (String propname, Date value) {
// IServer.getLogger().log ("setting date prop");
Property prop = initProperty (propname);
prop.setDateValue (value);
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
}
public void setJavaObject (String propname, Object value) {
// IServer.getLogger().log ("setting date prop");
Property prop = initProperty (propname);
prop.setJavaObjectValue (value);
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
}
public void setNode (String propname, INode value) {
// IServer.getLogger().log ("setting date prop");
Property 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 Node) {
Node n = (Node) value;
if (n.parent == null && n.adoptName) {
n.name = propname;
n.parent = this;
n.anonymous = false;
}
}
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.SUBNODE_ADDED, n));
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
}
public void unset (String propname) {
if (propMap == null)
return;
try {
Property p = (Property) propMap.remove (propname.toLowerCase ());
if (p != null && p.type == Property.NODE)
p.unregisterNode ();
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.PROPERTIES_CHANGED));
lastmodified = System.currentTimeMillis ();
} catch (Exception ignore) {}
}
protected void registerPropLink (Property p) {
if (proplinks == null)
proplinks = new Vector ();
proplinks.addElement (p);
// IServer.getLogger().log ("registered proplink from "+p.node.getFullName ());
// the NodeEvent is thrown later, since the node is not yet in the prop table
}
protected void unregisterPropLink (Property p) {
if (proplinks != null)
proplinks.removeElement (p);
// IServer.getLogger().log ("unregistered proplink from "+p.node.getFullName ());
// Server.throwNodeEvent (new NodeEvent (this, NodeEvent.NODE_REMOVED));
// Server.throwNodeEvent (new NodeEvent (p.node, NodeEvent.SUBNODE_REMOVED, this));
}
/**
* content-related
*/
public boolean isText () throws IOException {
return getContentType().indexOf ("text/") == 0;
}
public boolean isBinary () throws IOException {
return getContentType().indexOf ("text/") != 0;
}
public String getContentType () {
if (contentType == null)
return "text/plain";
return contentType;
}
public void setContentType (String type) {
contentType = type;
lastmodified = System.currentTimeMillis ();
}
public int getContentLength () {
if (content == null)
return 0;
return content.length;
}
public void setContent (byte cnt[], String type) {
if (type != null)
contentType = type;
content = cnt;
lastmodified = System.currentTimeMillis ();
}
public void setContent (String cstr) {
content = cstr == null ? null : cstr.getBytes ();
lastmodified = System.currentTimeMillis ();
}
public byte[] getContent () {
if (content == null || content.length == 0)
return "".getBytes ();
byte retval[] = new byte[content.length];
System.arraycopy (content, 0, retval, 0, content.length);
return retval;
}
public String writeToFile (String dir) {
return writeToFile (dir, null);
}
public String writeToFile (String dir, String fname) {
try {
File base = new File (dir);
// make directories if they don't exist
if (!base.exists ())
base.mkdirs ();
String filename = name;
if (fname != null) {
if (fname.indexOf (".") < 0) {
// check if we can use extension from name
int ndot = name == null ? -1 : name.lastIndexOf (".");
if (ndot > -1)
filename = fname + name.substring (ndot);
else
filename = fname;
} else {
filename = fname;
}
}
File file = new File (base, filename);
FileOutputStream fout = new FileOutputStream (file);
fout.write (getContent ());
fout.close ();
return filename;
} catch (Exception x) {
return null;
}
}
public String getText () {
if (content != null) {
if (getContentType ().indexOf ("text/") == 0) {
return new String (content);
} else {
return null;
}
}
return null;
}
public String getHref (INode root, INode userroot, String tmpname, String prefix) {
return prefix + getUrl (root, userroot, tmpname);
}
public String getUrl (INode root, INode userroot, String tmpname) {
throw new RuntimeException ("HREFs on transient (non-db based) Nodes not supported");
}
public long lastModified () {
return lastmodified;
}
public long created () {
return created;
}
public String toString () {
return "Node " + name;
}
protected Node convert (INode n) {
Hashtable ntable = new Hashtable ();
Node converted = new Node (n, ntable, false);
return converted;
}
Node cacheNode;
/**
* 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 Node();
return cacheNode;
}
}

View file

@ -0,0 +1,46 @@
// NodeDataSource.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.objectmodel;
import javax.activation.*;
import java.io.*;
/**
* Makes Nodes usable as Datasources in the Java Activation Framework (JAF)
*/
public class NodeDataSource implements DataSource {
private INode node;
private String name;
public NodeDataSource (INode node) {
this.node = node;
this.name = node.getName ();
}
public NodeDataSource (INode node, String name) {
this.node = node;
this.name = name;
}
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(node.getContent ());
}
public OutputStream getOutputStream () throws IOException {
throw new IOException ("Can't write to Node object.");
}
public String getContentType() {
return node.getContentType ();
}
public String getName()
{
return name;
}
}

View file

@ -0,0 +1,59 @@
// NodeEvent.java
// Copyright (c) Hannes Wallnöfer 1998-2000
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;
public NodeEvent (INode node, int type) {
super ();
this.node = node;
this.id = node.getID ();
this.type = type;
}
public NodeEvent (INode node, int type, Object arg) {
super ();
this.node = node;
this.id = node.getID ();
this.type = type;
this.arg = arg;
}
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,49 @@
// ObjectNotFoundException.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel;
/**
* Thrown when an object could not found in the database where
* it was expected.
*/
public class ObjectNotFoundException extends Exception {
public ObjectNotFoundException (String msg) {
super (msg);
}
}

View file

@ -0,0 +1,332 @@
// Property.java
// Copyright (c) Hannes Wallnöfer 1997-2000
package helma.objectmodel;
import helma.util.*;
import java.util.*;
import java.io.*;
import java.text.*;
/**
* A property implementation for Nodes stored inside a database.
*/
public final class Property implements IProperty, Serializable, Cloneable {
protected String propname;
protected Node node;
public String svalue;
public boolean bvalue;
public long lvalue;
public double dvalue;
public INode nvalue;
public Object jvalue;
public int type;
public Property (Node node) {
this.node = node;
}
public Property (String propname, Node node) {
this.propname = propname;
this.node = node;
}
public String getName () {
return propname;
}
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;
}
public void setStringValue (String value) throws ParseException {
if (type == NODE)
unregisterNode ();
// IServer.getLogger().log ("setting string value of property "+propname + " to "+value);
if (type == DATE) {
SimpleDateFormat dateformat = new SimpleDateFormat ();
dateformat.setLenient (true);
Date date = dateformat.parse (value);
this.lvalue = date.getTime ();
return;
}
if (type == BOOLEAN) {
if ("true".equalsIgnoreCase (value))
this.bvalue = true;
else if ("false".equalsIgnoreCase (value))
this.bvalue = false;
return;
}
if (type == INTEGER) {
this.lvalue = Long.parseLong (value);
return;
}
if (type == FLOAT) {
this.dvalue = new Double (value).doubleValue ();
return;
}
if (type == JAVAOBJECT)
this.jvalue = null;
this.svalue = value;
type = STRING;
}
public void setIntegerValue (long value) {
if (type == NODE)
unregisterNode ();
if (type == JAVAOBJECT)
this.jvalue = null;
type = INTEGER;
this.lvalue = value;
}
public void setFloatValue (double value) {
if (type == NODE)
unregisterNode ();
if (type == JAVAOBJECT)
this.jvalue = null;
type = FLOAT;
this.dvalue = value;
}
public void setDateValue (Date value) {
if (type == NODE)
unregisterNode ();
if (type == JAVAOBJECT)
this.jvalue = null;
type = DATE;
this.lvalue = value.getTime();
}
public void setBooleanValue (boolean value) {
if (type == NODE)
unregisterNode ();
if (type == JAVAOBJECT)
this.jvalue = null;
type = BOOLEAN;
this.bvalue = value;
}
public void setNodeValue (INode value) {
if (type == JAVAOBJECT)
this.jvalue = null;
if (type == NODE && nvalue != value) {
unregisterNode ();
registerNode (value);
} else if (type != NODE)
registerNode (value);
type = NODE;
this.nvalue = value;
}
public void setJavaObjectValue (Object value) {
if (type == NODE)
unregisterNode ();
type = JAVAOBJECT;
this.jvalue = value;
}
/**
* tell a the value node that it is no longer used as a property.
*/
protected void unregisterNode () {
if (nvalue != null && nvalue instanceof Node) {
Node n = (Node) nvalue;
if (!n.anonymous && propname.equals (n.getName()) && this.node == n.getParent()) {
// this is the "main" property of a named node, so handle this as a total delete.
IServer.getLogger().log ("deleting named property");
if (n.proplinks != null) {
for (Enumeration e = n.proplinks.elements (); e.hasMoreElements (); ) {
Property p = (Property) e.nextElement ();
p.node.propMap.remove (p.propname.toLowerCase ());
}
}
if (n.links != null) {
for (Enumeration e = n.links.elements (); e.hasMoreElements (); ) {
Node n2 = (Node) e.nextElement ();
n2.releaseNode (n);
}
}
} else {
n.unregisterPropLink (this);
}
}
}
/**
* tell the value node that it is being used as a property value.
*/
protected void registerNode (INode n) {
if (n != null && n instanceof Node)
((Node) n).registerPropLink (this);
}
public String getStringValue () {
switch (type) {
case STRING:
return svalue;
case BOOLEAN:
return "" + bvalue;
case DATE:
SimpleDateFormat format = new SimpleDateFormat ("dd.MM.yy hh:mm");
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.toString ();
}
return "";
}
public String toString () {
return getStringValue ();
}
public long getIntegerValue () {
if (type == INTEGER)
return lvalue;
return 0;
}
public double getFloatValue () {
if (type == FLOAT)
return dvalue;
return 0.0;
}
public Date getDateValue () {
if (type == DATE)
return new Date (lvalue);
return null;
}
public boolean getBooleanValue () {
if (type == BOOLEAN)
return bvalue;
return false;
}
public INode getNodeValue () {
if (type == NODE)
return nvalue;
return null;
}
public Object getJavaObjectValue () {
if (type == JAVAOBJECT)
return jvalue;
return null;
}
public String getEditor () {
switch (type) {
case STRING:
return "password".equalsIgnoreCase (propname) ?
"<input type=password name=\""+propname+"\" value='"+ svalue.replace ('\'', '"') +"'>" :
"<input type=text name=\""+propname+"\" value='"+ svalue.replace ('\'', '"') +"'>" ;
case BOOLEAN:
return "<select name=\""+propname+"\"><option selected value="+bvalue+">"+bvalue+"</option><option value="+!bvalue+">"+!bvalue+"</option></select>";
case INTEGER:
return "<input type=text name=\""+propname+"\" value=\""+lvalue+"\">" ;
case FLOAT:
return "<input type=text name=\""+propname+"\" value=\""+dvalue+"\">" ;
case DATE:
SimpleDateFormat format = new SimpleDateFormat ("dd.MM.yy hh:mm");
String date = format.format (new Date (lvalue));
return "<input type=text name=\""+propname+"\" value=\""+date+"\">";
case NODE:
return "<input type=text size=25 name="+propname+" value='"+ nvalue.getFullName () +"'>";
}
return "";
}
private String escape (String s) {
char c[] = new char[s.length()];
s.getChars (0, c.length, c, 0);
StringBuffer b = new StringBuffer ();
int copyfrom = 0;
for (int i = 0; i < c.length; i++) {
switch (c[i]) {
case '\\':
case '"':
if (i-copyfrom > 0)
b.append (c, copyfrom, i-copyfrom);
b.append ('\\');
b.append (c[i]);
copyfrom = i+1;
}
}
if (c.length-copyfrom > 0)
b.append (c, copyfrom, c.length-copyfrom);
return b.toString ();
}
public int getType () {
return type;
}
public String getTypeString () {
switch (type) {
case STRING:
return "string";
case BOOLEAN:
return "boolean";
case DATE:
return "date";
case INTEGER:
return "integer";
case FLOAT:
return "float";
case NODE:
return "node";
}
return "";
}
public Object clone () {
try {
Property c = (Property) super.clone();
c.propname = this.propname;
c.svalue = this.svalue;
c.bvalue = this.bvalue;
c.lvalue = this.lvalue;
c.dvalue = this.dvalue;
c.type = this.type;
return c;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError ();
}
}
}

View file

@ -0,0 +1,308 @@
// Relation.java
// Copyright (c) Hannes Wallnöfer 1997-2000
package helma.objectmodel;
import helma.framework.core.Application;
import java.util.Properties;
/**
* This describes how a property of a persistent Object is stored in a
* relational database table. This can be either a scalar property (string, date, number etc.)
* or a reference to one or more other objects.
*/
public class Relation {
// TODO: explain hop mapping types
public final static int INVALID = -1;
public final static int PRIMITIVE = 0;
public final static int FORWARD = 1;
public final static int BACKWARD = 2;
public final static int DIRECT = 3;
public DbMapping home;
public DbMapping other;
public String propname;
public String localField, remoteField;
public int direction;
public boolean virtual;
public boolean readonly;
public boolean aggressiveLoading;
public boolean aggressiveCaching;
public boolean subnodesAreProperties;
public String order;
public String groupby;
public String prototype;
Relation filter = null; // additional relation used to filter subnodes
/**
* This constructor is used to directly construct a Relation, as opposed to reading it from a proerty file
*/
public Relation (DbMapping other, String localField, String remoteField, int direction, boolean subnodesAreProperties) {
this.other = other;
this.localField = localField;
this.remoteField = remoteField;
this.direction = direction;
this.subnodesAreProperties = subnodesAreProperties;
}
/**
* Reads a relation entry from a line in a properties file.
*/
public Relation (String desc, String propname, DbMapping home, Properties props) {
this.home = home;
this.propname = propname;
other = null;
Application app = home.getApplication ();
boolean mountpoint = false;
if (desc == null || "".equals (desc.trim ())) {
if (propname != null) {
direction = PRIMITIVE;
localField = propname;
} else {
direction = INVALID;
localField = propname;
}
} else {
desc = desc.trim ();
String descLower = desc.toLowerCase ();
if (descLower.startsWith ("[virtual]")) {
desc = desc.substring (9).trim ();
virtual = true;
} else if (descLower.startsWith ("[collection]")) {
desc = desc.substring (12).trim ();
virtual = true;
} else if (descLower.startsWith ("[mountpoint]")) {
desc = desc.substring (12).trim ();
virtual = true;
mountpoint = true;
} else {
virtual = false;
}
if (descLower.startsWith ("[readonly]")) {
desc = desc.substring (10).trim ();
readonly = true;
} else {
readonly = false;
}
if (desc.indexOf ("<") > -1) {
direction = BACKWARD;
int lt = desc.indexOf ("<");
int dot = desc.indexOf (".");
String otherType = dot < 0 ? desc.substring (1).trim () : desc.substring (1, dot).trim ();
other = app.getDbMapping (otherType);
if (other == null)
throw new RuntimeException ("DbMapping for "+otherType+" not found from "+home.typename);
remoteField = dot < 0 ? null : desc.substring (dot+1).trim ();
localField = lt < 0 ? null : desc.substring (0, lt).trim ();
if (mountpoint) prototype = otherType;
} else if (desc.indexOf (">") > -1) {
direction = FORWARD;
int bt = desc.indexOf (">");
int dot = desc.indexOf (".");
String otherType = dot > -1 ? desc.substring (bt+1, dot).trim () : desc.substring (bt+1).trim ();
other = app.getDbMapping (otherType);
if (other == null)
throw new RuntimeException ("DbMapping for "+otherType+" not found from "+home.typename);
localField = desc.substring (0, bt).trim ();
remoteField = dot < 0 ? null : desc.substring (dot+1).trim ();
if (mountpoint) prototype = otherType;
} else if (desc.indexOf (".") > -1) {
direction = DIRECT;
int dot = desc.indexOf (".");
String otherType = desc.substring (0, dot).trim ();
other = app.getDbMapping (otherType);
if (other == null)
throw new RuntimeException ("DbMapping for "+otherType+" not found from "+home.typename);
remoteField = desc.substring (dot+1).trim ();
localField = null;
if (mountpoint) prototype = otherType;
} else {
if (virtual) {
direction = DIRECT;
other = app.getDbMapping (desc);
if (other == null)
throw new RuntimeException ("DbMapping for "+desc+" not found from "+home.typename);
remoteField = localField = null;
if (mountpoint) prototype = desc;
} else {
direction = PRIMITIVE;
localField = desc.trim ();
}
}
}
String loading = props.getProperty (propname+".loadmode");
aggressiveLoading = loading != null && "aggressive".equalsIgnoreCase (loading.trim());
String caching = props.getProperty (propname+".cachemode");
aggressiveCaching = caching != null && "aggressive".equalsIgnoreCase (caching.trim());
// get order property
order = props.getProperty (propname+".order");
if (order != null && order.trim().length() == 0) order = null;
// get group by property
groupby = props.getProperty (propname+".groupby");
if (groupby != null && groupby.trim().length() == 0) groupby = null;
// check if subnode condition should be applied for property relations
if ("_properties".equalsIgnoreCase (propname) || virtual) {
String subnodes2props = props.getProperty (propname+".aresubnodes");
subnodesAreProperties = "true".equalsIgnoreCase (subnodes2props);
if (virtual) {
String subnodefilter = props.getProperty (propname+".subnoderelation");
if (subnodefilter != null) {
filter = new Relation (subnodefilter, propname+".subnoderelation", home, props);
filter.groupby = groupby;
}
}
}
}
public boolean isReference () {
return direction > PRIMITIVE;
}
public boolean usesPrimaryKey () {
if (remoteField == null || other == null)
return false;
return remoteField.equalsIgnoreCase (other.getIDField());
}
public Relation getFilter () {
return filter;
}
/**
* Gets a key string to cache a node with a specific value for this relation. If the
* Relation uses the primary key return just the key value, otherwise include info on the
* used column or even the base node to avoid collisions.
*/
public String getKeyID (INode home, String kval) {
// if the column is not the primary key, we add the column name to the key
if ((direction == DIRECT || direction == FORWARD) && !usesPrimaryKey ()) {
// check if the subnode relation also has to be considered
if (subnodesAreProperties)
return "["+home.getID()+"]"+remoteField+"="+kval; // HACK
else
return remoteField+"="+kval;
} else {
return kval;
}
}
/**
* Get the local column name for this relation. Uses the home node's id as fallback if local field is not specified.
*/
public String getLocalField () {
if (localField == null)
return home.getIDField ();
return localField;
}
/**
* Get the "remote" column name for this relation. Uses the remote node's id as fallback if the remote field is not specified.
*/
public String getRemoteField () {
if (remoteField == null)
return other.getIDField ();
return remoteField;
}
/**
* Return a Relation that defines the subnodes of a virtual node.
*/
public Relation getVirtualSubnodeRelation () {
if (!virtual)
throw new RuntimeException ("getVirtualSubnodeRelation called on non-virtual relation");
if (filter != null)
return filter;
return getVirtualPropertyRelation ();
}
/**
* Return a Relation that defines the properties of a virtual node.
*/
public Relation getVirtualPropertyRelation () {
if (!virtual)
throw new RuntimeException ("getVirtualPropertyRelation called on non-virtual relation");
Relation vr = new Relation (other, localField, remoteField, direction, subnodesAreProperties);
vr.groupby = groupby;
return vr;
}
/**
* Return a Relation that defines the subnodes of a group-by node.
*/
public Relation getGroupbySubnodeRelation () {
if (groupby == null)
throw new RuntimeException ("getGroupbySubnodeRelation called on non-group-by relation");
if (filter != null)
return filter;
return getGroupbyPropertyRelation ();
}
/**
* Return a Relation that defines the properties of a group-by node.
*/
public Relation getGroupbyPropertyRelation () {
if (groupby == null)
throw new RuntimeException ("getGroupbyPropertyRelation called on non-group-by relation");
return new Relation (other, localField, remoteField, direction, subnodesAreProperties);
}
}

View file

@ -0,0 +1,133 @@
// Property.java
// Copyright (c) Hannes Wallnöfer 1997-2000
package helma.objectmodel;
import helma.util.*;
import java.util.*;
import java.io.*;
/**
* A property dictionary that is updated from a property file each time the
* file is modified. It is also case insensitive.
*/
public final class SystemProperties extends Properties {
private Properties props; // wrapped properties
private Properties newProps; // used while building up props
private Properties defaultProps;
private File file;
private long lastread, lastcheck;
public SystemProperties (String filename) {
this (filename, null);
}
public SystemProperties (Properties defaultProps) {
this (null, defaultProps);
}
public SystemProperties (String filename, Properties defaultProps) {
// IServer.getLogger().log ("building sysprops with file "+filename+" and node "+node);
this.defaultProps = defaultProps;
props = defaultProps == null ?
new Properties () : new Properties (defaultProps);
if (filename != null) {
file = new File (filename);
checkFile ();
}
}
public boolean wasModified () {
return file != null && file.exists() && file.lastModified () > lastread;
}
public long lastModified () {
return file == null || !file.exists () ? 0 : file.lastModified ();
}
private synchronized void checkFile () {
if (wasModified ()) {
// IServer.getLogger().log ("Reading properties from file "+file);
newProps = defaultProps == null ?
new Properties () : new Properties (defaultProps);
try {
FileInputStream bpin = new FileInputStream (file);
load (bpin);
bpin.close ();
} catch (Exception x) {
IServer.getLogger().log ("Error reading properties from file "+file+": "+x);
}
lastread = System.currentTimeMillis ();
props = newProps;
newProps = null;
}
lastcheck = System.currentTimeMillis ();
}
/*
* This should not be used directly if properties are read from file,
* otherwise changes will be lost whe the file is next modified.
*/
public Object put (Object key, Object value) {
if (newProps == null)
return props.put (key.toString().toLowerCase(), value);
else
return newProps.put (key.toString().toLowerCase(), value);
}
public Object get (Object key) {
return props.get (key);
}
public Object remove (Object key) {
return props.remove (key);
}
public void clear () {
props.clear ();
}
public boolean contains (Object obj) {
return props.contains (obj);
}
public boolean containsKey (Object key) {
return props.containsKey (key);
}
public boolean isEmpty () {
return props.isEmpty ();
}
public String getProperty (String name) {
if (System.currentTimeMillis () - lastcheck > 1500l)
checkFile ();
return props.getProperty (name.toLowerCase());
}
public String getProperty (String name, String defaultValue) {
if (System.currentTimeMillis () - lastcheck > 1500l)
checkFile ();
return props.getProperty (name.toLowerCase(), defaultValue);
}
public Enumeration keys () {
if (System.currentTimeMillis () - lastcheck > 1500l)
checkFile ();
return props.keys();
}
public Enumeration elements () {
if (System.currentTimeMillis () - lastcheck > 1500l)
checkFile ();
return props.elements();
}
}

View file

@ -0,0 +1,139 @@
// ApplicationManager.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel.db;
import java.util.*;
import java.io.*;
import java.lang.reflect.*;
import java.rmi.*;
import java.rmi.server.*;
import helma.framework.*;
import helma.framework.core.*;
import helma.objectmodel.*;
import helma.servlet.*;
import Acme.Serve.*;
import javax.servlet.Servlet;
/**
* This class is responsible for starting and stopping HOP applications.
*/
public class ApplicationManager {
private Hashtable applications;
private int port;
private File appHome, dbHome;
private SystemProperties props;
private Server server;
private long lastModified;
public ApplicationManager (int port, File appHome, File dbHome, SystemProperties props, Server server) {
this.port = port;
this.appHome = appHome;
this.dbHome = dbHome;
this.props = props;
this.server = server;
applications = new Hashtable ();
lastModified = 0;
}
// regularely check applications property file to create and start new applications
protected void checkForChanges () {
if (props.lastModified () > lastModified) {
try {
for (Enumeration e = props.keys(); e.hasMoreElements (); ) {
String appName = (String) e.nextElement ();
if (applications.get (appName) == null) {
start (appName);
register (appName);
}
}
// then stop deleted ones
for (Enumeration e = applications.keys(); e.hasMoreElements (); ) {
String appName = (String) e.nextElement ();
if (!props.containsKey (appName)) {
stop (appName);
}
}
} catch (Exception mx) {
IServer.getLogger().log ("Error starting applications: "+mx);
}
lastModified = System.currentTimeMillis ();
}
}
private void start (String appName) {
IServer.getLogger().log ("Building application "+appName);
try {
Application app = new Application (appName, dbHome, appHome);
applications.put (appName, app);
app.start ();
} catch (Exception x) {
IServer.getLogger().log ("Error creating application "+appName+": "+x);
}
}
private void stop (String appName) {
IServer.getLogger().log ("Stopping application "+appName);
try {
Application app = (Application) applications.get (appName);
if (server.websrv == null) {
Naming.unbind ("//:"+port+"/"+appName);
} else {
server.websrv.removeServlet ("/"+appName+"/");
server.websrv.removeServlet ("/"+appName+"/*");
}
app.stop ();
IServer.getLogger().log ("Unregistered application "+appName);
} catch (Exception x) {
IServer.getLogger().log ("Couldn't unregister app: "+x);
}
applications.remove (appName);
}
private void register (String appName) {
try {
IServer.getLogger().log ("Binding application "+appName);
Application app = (Application) applications.get (appName);
if (server.websrv == null) {
Naming.rebind ("//:"+port+"/"+appName, app);
} else {
AcmeServletClient servlet = new AcmeServletClient (app);
server.websrv.addServlet ("/"+appName+"/", servlet);
server.websrv.addServlet ("/"+appName+"/*", servlet);
}
} catch (Exception x) {
IServer.getLogger().log ("Couldn't register app: "+x);
}
}
public void startAll () {
try {
for (Enumeration e = props.keys(); e.hasMoreElements (); ) {
String appName = (String) e.nextElement ();
start (appName);
}
for (Enumeration e = props.keys(); e.hasMoreElements (); ) {
String appName = (String) e.nextElement ();
register (appName);
}
if (server.websrv != null) {
File staticContent = new File (server.getHopHome(), "static");
IServer.getLogger().log("Serving static content from "+staticContent.getAbsolutePath());
AcmeFileServlet fsrv = new AcmeFileServlet (staticContent);
server.websrv.addServlet ("/static/", fsrv);
server.websrv.addServlet ("/static/*", fsrv);
}
lastModified = System.currentTimeMillis ();
} catch (Exception mx) {
IServer.getLogger().log ("Error starting applications: "+mx);
mx.printStackTrace ();
}
}
}

View file

@ -0,0 +1,281 @@
// DbWrapper.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.objectmodel.db;
import com.sleepycat.db.*;
import helma.objectmodel.ObjectNotFoundException;
import java.io.*;
/**
* A wrapper around a Berkeley embedded database. Used to gracefully handle the case
* when the native library can not be loaded.
*/
public class DbWrapper {
private boolean loaded, useTransactions;
private Db db;
DbEnv dbenv;
final int checkpointPause = 600000; // min. 10 minutes between checkpoints
volatile long lastCheckpoint = 0;
volatile long txncount=0;
private File dbBaseDir;
public DbWrapper (String dbHome, String dbFilename, boolean useTx) throws DbException {
try {
dbBaseDir = new File (dbHome);
if (!dbBaseDir.exists())
dbBaseDir.mkdirs();
useTransactions = useTx;
int dbInitFlags = Db.DB_CREATE | Db.DB_THREAD | Db.DB_INIT_MPOOL;
if (useTransactions) {
dbInitFlags = dbInitFlags | Db.DB_INIT_TXN;
}
dbenv = new DbEnv (0);
try {
dbenv.open (dbHome, dbInitFlags, 0); // for berkeley 3.0, add second parameter (null)
} catch (FileNotFoundException fnf) {
// we just created the dirs, so this shouldn't happen
}
try {
dbenv.set_error_stream(System.err);
dbenv.set_errpfx("HOP: ");
} catch (Exception e) {
System.err.println("Error in DbWrapper: "+e.toString());
}
db = new Db (dbenv, 0);
try {
db.upgrade (dbFilename, 0);
} catch (Exception ignore) {
// nothing to upgrade, db doesn't exist
}
try {
db.open (dbFilename, null, Db.DB_BTREE, Db.DB_CREATE, 0644);
} catch (FileNotFoundException fnf) {
// we just created the dirs, so this shouldn't happen
}
loaded = true;
} catch (NoClassDefFoundError noclass) {
Server.getLogger().log ("Warning: Using file based db as fallback. Reason: "+noclass);
loaded = false;
} catch (UnsatisfiedLinkError nolib) {
Server.getLogger().log ("Warning: Using file based db as fallback. Reason: "+nolib);
loaded = false;
}
}
public void shutdown () throws DbException {
if (loaded) {
db.close (0);
dbenv.close (0);
}
}
public DbTxn beginTransaction () throws DbException {
if (loaded && useTransactions)
return dbenv.txn_begin (null, 0);
else
return null;
}
public void commitTransaction (DbTxn txn) throws DbException {
if (txn == null || !loaded || !useTransactions)
return;
txn.commit (0);
if (++txncount%100 == 0 && System.currentTimeMillis()-checkpointPause > lastCheckpoint) {
// checkpoint transaction logs in time interval specified by server.checkpointPause
// if there are more then 100 transactions to checkpoint.
checkpoint ();
}
}
public void abortTransaction (DbTxn txn) throws DbException {
if (txn == null || !loaded || !useTransactions)
return;
txn.abort ();
}
protected void checkpoint () throws DbException {
if (!loaded || !useTransactions || txncount == 0)
return;
long now = System.currentTimeMillis();
if (now - lastCheckpoint < checkpointPause)
return;
dbenv.txn_checkpoint (0, 0, 0); // for berkeley 3.0, remove third 0 parameter
txncount = 0;
lastCheckpoint = now;
Server.getLogger().log ("Spent "+(System.currentTimeMillis()-now)+" in checkpoint");
}
public IDGenerator getIDGenerator (DbTxn txn, String kstr) throws Exception {
if (loaded)
return getIDGenFromDB (txn, kstr);
else
return getIDGenFromFile (kstr);
}
public Node getNode (DbTxn txn, String kstr) throws Exception {
if (loaded)
return getNodeFromDB (txn, kstr);
else
return getNodeFromFile (kstr);
}
public void save (DbTxn txn, String kstr, Object obj) throws Exception {
if (loaded)
saveToDB (txn, kstr, obj);
else
saveToFile (kstr, obj);
}
public void delete (DbTxn txn, String kstr) throws Exception {
if (loaded)
deleteFromDB (txn, kstr);
else
deleteFromFile (kstr);
}
private IDGenerator getIDGenFromDB (DbTxn txn, String kstr) throws Exception {
long now = System.currentTimeMillis ();
byte[] kbuf = kstr.getBytes ();
Dbt key = new Dbt (kbuf);
key.set_size (kbuf.length);
Dbt data = new Dbt ();
data.set_flags (Db.DB_DBT_MALLOC);
db.get (txn, key, data, 0);
byte[] b = data.get_data ();
if (b == null)
throw new ObjectNotFoundException ("Object not found for key "+kstr+".");
IDGenerator idgen = null;
ByteArrayInputStream bin = new ByteArrayInputStream (b);
ObjectInputStream oin = new ObjectInputStream (bin);
idgen = (IDGenerator) oin.readObject ();
oin.close ();
return idgen;
}
private Node getNodeFromDB (DbTxn txn, String kstr) throws Exception {
long now = System.currentTimeMillis ();
byte[] kbuf = kstr.getBytes ();
Dbt key = new Dbt (kbuf);
key.set_size (kbuf.length);
Dbt data = new Dbt ();
data.set_flags (Db.DB_DBT_MALLOC);
db.get (txn, key, data, 0);
byte[] b = data.get_data ();
if (b == null)
throw new ObjectNotFoundException ("Object not found for key "+kstr+".");
Node node = null;
ByteArrayInputStream bin = new ByteArrayInputStream (b);
ObjectInputStream oin = new ObjectInputStream (bin);
node = (Node) oin.readObject ();
oin.close ();
return node;
}
private void saveToDB (DbTxn txn, String kstr, Object obj) throws Exception {
long now = System.currentTimeMillis ();
byte kbuf[] = kstr.getBytes();
ByteArrayOutputStream bout = new ByteArrayOutputStream ();
ObjectOutputStream oout = new ObjectOutputStream (bout);
oout.writeObject (obj);
oout.close ();
byte vbuf[] = bout.toByteArray ();
Dbt key = new Dbt (kbuf);
key.set_size (kbuf.length);
Dbt value = new Dbt (vbuf);
value.set_size (vbuf.length);
db.put (txn, key, value, 0);
// IServer.getLogger().log ("saved "+obj+", size = "+vbuf.length);
}
private void deleteFromDB (DbTxn txn, String kstr) throws Exception {
byte kbuf[] = kstr.getBytes();
Dbt key = new Dbt (kbuf);
key.set_size (kbuf.length);
db.del (txn, key, 0);
}
////////////////////////////////////////////////////////////////////////////////
// File based fallback methods
///////////////////////////////////////////////////////////////////////////////
private IDGenerator getIDGenFromFile (String kstr) throws Exception {
File f = new File (dbBaseDir, kstr);
if ( ! f.exists() )
throw new ObjectNotFoundException ("Object not found for key "+kstr+".");
IDGenerator idgen = null;
FileInputStream bin = new FileInputStream (f);
ObjectInputStream oin = new ObjectInputStream (bin);
idgen = (IDGenerator) oin.readObject ();
oin.close ();
return idgen;
}
private Node getNodeFromFile (String kstr) throws Exception {
File f = new File (dbBaseDir, kstr);
if ( ! f.exists() )
throw new ObjectNotFoundException ("Object not found for key "+kstr+".");
Node node = null;
FileInputStream bin = new FileInputStream (f);
ObjectInputStream oin = new ObjectInputStream (bin);
node = (Node) oin.readObject ();
oin.close ();
return node;
}
private void saveToFile (String kstr, Object obj) throws Exception {
File f = new File (dbBaseDir, kstr);
FileOutputStream bout = new FileOutputStream (f);
ObjectOutputStream oout = new ObjectOutputStream (bout);
oout.writeObject (obj);
oout.close ();
}
private void deleteFromFile (String kstr) throws Exception {
File f = new File (dbBaseDir, kstr);
f.delete();
}
}

View file

@ -0,0 +1,39 @@
// ExternalizableVector.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.objectmodel.db;
import java.io.*;
import java.util.*;
/**
* A subclass of Vector that implements the Externalizable interface in order
* to be able to control how it is serialized and deserialized.
*/
public class ExternalizableVector extends Vector implements Externalizable {
static final long serialVersionUID = 2316243615310540423L;
public synchronized void readExternal (ObjectInput in) throws IOException {
try {
int size = in.readInt ();
for (int i=0; i<size; i++)
addElement (in.readObject ());
} catch (ClassNotFoundException x) {
throw new IOException (x.toString ());
}
}
public synchronized void writeExternal (ObjectOutput out) throws IOException {
int size = size ();
out.writeInt (size);
for (int i=0; i<size; i++)
out.writeObject (elementAt (i));
}
}

View file

@ -0,0 +1,43 @@
// HopSocketFactory.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.objectmodel.db;
import helma.objectmodel.IServer;
import helma.util.*;
import java.net.*;
import java.rmi.server.*;
import java.io.IOException;
/**
* An RMI socket factory that has a "paranoid" option to filter clients.
* We only do direct connections, no HTTP proxy stuff, since this is
* server-to-server.
*/
public class HopSocketFactory extends RMISocketFactory {
private InetAddressFilter filter;
public HopSocketFactory () {
filter = new InetAddressFilter ();
}
public void addAddress (String address) {
try {
filter.addAddress (address);
} catch (IOException x) {
IServer.getLogger().log ("Could not add "+address+" to Socket Filter: invalid address.");
}
}
public Socket createSocket(String host, int port) throws IOException {
return new Socket (host, port);
}
public ServerSocket createServerSocket(int port) throws IOException {
return new ParanoidServerSocket (port, filter);
}
}

View file

@ -0,0 +1,73 @@
// IDGenerator.java
// Copyright (c) Hannes Wallnöfer 1997-2000
package helma.objectmodel.db;
import java.io.Serializable;
/**
* An object that generates IDs (Strings) that are unique across the whole system.
* It does this keeping a simple long value which is incremented for each new ID.
* This is the key generation for nodes stored in the internal database, but it can
* also be used for relational nodes if no other mechanism is available. (Sequences
* in Oracle are supported, while auto-IDs are not, since the HOP has to know
* the keys of new objects.)
*/
public final class IDGenerator implements Serializable {
private long counter;
transient volatile boolean dirty;
static final long serialVersionUID = 753408631669789263L;
/**
* Builds a new IDGenerator starting with 0.
*/
public IDGenerator () {
this.counter = 0l;
dirty = false;
}
/**
* Builds a new IDGenerator starting with value.
*/
public IDGenerator (long value) {
this.counter = value;
dirty = false;
}
/**
* Delivers a unique id and increases counter by 1.
*/
public synchronized String newID () {
counter += 1l;
dirty = true;
return Long.toString (counter);
}
/**
* Set the counter to a new value
*/
protected synchronized void setValue (long value) {
counter = value;
dirty = true;
}
/**
* Get the current counter value
*/
protected long getValue () {
return counter;
}
public String toString () {
return "helma.objectmodel.db.IDGenerator[counter="+counter+",dirty="+dirty+"]";
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,959 @@
// NodeManager.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel.db;
import java.io.*;
import java.util.Vector;
import java.util.Properties;
import Acme.LruHashtable;
import helma.objectmodel.*;
import helma.framework.core.Application;
import com.sleepycat.db.*;
import java.sql.*;
import java.util.Vector;
import java.util.Enumeration;
import com.workingdogs.village.*;
/**
* The NodeManager is responsible for fetching Nodes from the internal or
* external data sources, caching them in a least-recently-used Hashtable,
* and writing changes back to the databases.
*/
public final class NodeManager {
private Application app;
private LruHashtable cache;
protected DbWrapper db;
protected IDGenerator idgen;
private long idBaseValue = 1l;
private boolean logSql;
// a wrapper that catches some Exceptions while accessing this NM
public final WrappedNodeManager safe;
public NodeManager (Application app, String dbHome, Properties props) throws DbException {
this.app = app;
int cacheSize = Integer.parseInt (props.getProperty ("cachesize", "1000"));
cache = new LruHashtable (cacheSize, 0.9f);
IServer.getLogger().log ("set up node cache ("+cacheSize+")");
safe = new WrappedNodeManager (this);
// get the initial id generator value
String idb = props.getProperty ("idBaseValue");
if (idb != null) try {
idBaseValue = Long.parseLong (idb);
idBaseValue = Math.max (1l, idBaseValue); // 0 and 1 are reserved for root nodes
} catch (NumberFormatException ignore) {}
db = new DbWrapper (dbHome, Server.dbFilename, Server.useTransactions);
initDb ();
logSql = "true".equalsIgnoreCase(props.getProperty ("logsql"));
}
/**
* Method used to create the root node and id-generator, if they don't exist already.
*/
public void initDb () throws DbException {
DbTxn txn = null;
try {
txn = db.beginTransaction ();
try {
idgen = db.getIDGenerator (txn, "idgen");
if (idgen.getValue() < idBaseValue) {
idgen.setValue (idBaseValue);
db.save (txn, "idgen", idgen);
}
} catch (ObjectNotFoundException notfound) {
// will start with idBaseValue+1
idgen = new IDGenerator (idBaseValue);
db.save (txn, "idgen", idgen);
}
// check if we need to set the id generator to a base value
Node node = null;
try {
node = db.getNode (txn, "0");
node.nmgr = safe;
} catch (ObjectNotFoundException notfound) {
node = new Node ("root", "0", "root", safe);
db.save (txn, node.getID (), node);
registerNode (node); // register node with nodemanager cache
}
try {
node = db.getNode (txn, "1");
node.nmgr = safe;
} catch (ObjectNotFoundException notfound) {
node = new Node ("users", "1", null, safe);
db.save (txn, node.getID (), node);
registerNode (node); // register node with nodemanager cache
}
db.commitTransaction (txn);
} catch (Exception x) {
System.err.println (">> "+x);
x.printStackTrace ();
try {
db.abortTransaction (txn);
} catch (Exception ignore) {}
throw (new DbException ("Error initializing db"));
}
}
public void shutdown () throws DbException {
db.shutdown ();
this.cache = null;
}
public void deleteNode (Node node) throws Exception {
if (node != null) {
String id = node.getID ();
synchronized (this) {
Transactor tx = (Transactor) Thread.currentThread ();
node.setState (Node.INVALID);
deleteNode (db, tx.txn, node);
}
}
}
public Node getNode (String kstr, DbMapping dbmap) throws Exception {
if (kstr == null)
return null;
Key key = Key.makeKey (dbmap, kstr);
Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("getNode "+kstr);
// See if Transactor has already come across this node
Node node = tx.getVisitedNode (key);
if (node != null && node.getState() != Node.INVALID) {
// tx.timer.endEvent ("getNode "+kstr);
return node;
}
// try to get the node from the shared cache
node = (Node) cache.get (key);
if (node == null || node.getState() == Node.INVALID) {
// The requested node isn't in the shared cache. Synchronize with key to make sure only one
// version is fetched from the database.
synchronized (key) {
// check again because only in the synchronized section can we be sure that
// another thread hasn't fetched the node in the meantime.
node = (Node) cache.get (key);
if (node == null || node.getState() == Node.INVALID) {
node = getNodeByKey (db, tx.txn, kstr, dbmap);
if (node != null) {
cache.put (node.getKey (), node);
}
}
} // synchronized
}
if (node != null)
tx.visitCleanNode (node.getKey (), node);
// tx.timer.endEvent ("getNode "+kstr);
return node;
}
public Node getNode (Node home, String kstr, Relation rel) throws Exception {
if (kstr == null)
return null;
Key key;
// If what we want is a virtual node create a "synthetic" key
if (rel.virtual /*&& home.getState() != INode.VIRTUAL */ || rel.groupby != null)
key = home.getKey ().getVirtualKey (kstr);
// if a key for a node from within the DB
else
key = Key.makeKey (rel.other, rel.getKeyID (home, kstr));
Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("getNode "+kstr);
// See if Transactor has already come across this node
Node node = tx.getVisitedNode (key);
if (node != null && node.getState() != Node.INVALID) {
// tx.timer.endEvent ("getNode "+kstr);
return node;
}
// try to get the node from the shared cache
node = (Node) cache.get (key);
if (node == null || node.getState() == Node.INVALID) {
// The requested node isn't in the shared cache. Synchronize with key to make sure only one
// version is fetched from the database.
synchronized (key) {
// check again because only in the synchronized section can we be sure that
// another thread hasn't fetched the node in the meantime.
node = (Node) cache.get (key);
if (node == null || node.getState() == Node.INVALID) {
node = getNodeByRelation (db, tx.txn, home, kstr, rel);
if (node != null) {
Key primKey = node.getKey ();
cache.put (primKey, node);
if (!key.equals (primKey)) {
// cache node with extra (non-primary but unique) key
cache.put (key, node);
}
}
}
} // synchronized
}
if (node != null)
tx.visitCleanNode (node.getKey (), node);
// tx.timer.endEvent ("getNode "+kstr);
return node;
}
public void registerNode (Node node) {
cache.put (node.getKey (), node);
}
public void evictNode (Node node) {
node.setState (INode.INVALID);
cache.remove (node.getKey ());
}
/**
* Used when a key stops being valid for a node.
*/
public void evictKey (Key key) {
cache.remove (key);
}
////////////////////////////////////////////////////////////////////////
// methods to do the actual db work
////////////////////////////////////////////////////////////////////////
public void insertNode (DbWrapper db, DbTxn txn, Node node) throws Exception {
Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("insertNode "+node);
DbMapping dbm = node.getDbMapping ();
if (dbm == null || !dbm.isRelational ()) {
db.save (txn, node.getID (), node);
} else {
IServer.getLogger().log ("inserting relational node: "+node.getID ());
TableDataSet tds = null;
try {
tds = new TableDataSet (dbm.getConnection (), dbm.getSchema (), dbm.getKeyDef ());
Record rec = tds.addRecord ();
rec.setValue (dbm.getIDField (), node.getID ());
String nameField = dbm.getNameField ();
if (nameField != null)
rec.setValue (nameField, node.getName ());
for (Enumeration e=dbm.prop2db.keys(); e.hasMoreElements(); ) {
String propname = (String) e.nextElement ();
Property p = node.getProperty (propname, false);
Relation rel = dbm.propertyToColumnName (propname);
if (p != null && rel != null) {
switch (p.getType ()) {
case IProperty.STRING:
rec.setValue (rel.localField, p.getStringValue ());
break;
case IProperty.BOOLEAN:
rec.setValue (rel.localField, p.getBooleanValue ());
break;
case IProperty.DATE:
Timestamp t = new Timestamp (p.getDateValue ().getTime ());
rec.setValue (rel.localField, t);
break;
case IProperty.INTEGER:
rec.setValue (rel.localField, p.getIntegerValue ());
break;
case IProperty.FLOAT:
rec.setValue (rel.localField, p.getFloatValue ());
break;
case IProperty.NODE:
if (rel.direction == Relation.FORWARD) {
// INode n = p.getNodeValue ();
// String foreignID = n == null ? null : n.getID ();
rec.setValue (rel.localField, p.getStringValue ());
}
break;
}
p.dirty = false;
} else if (rel != null && rel.localField != null) {
rec.setValueNull (rel.localField);
}
}
rec.markForInsert ();
tds.save ();
} finally {
if (tds != null) {
tds.close ();
}
}
dbm.lastDataChange = System.currentTimeMillis ();
}
// tx.timer.endEvent ("insertNode "+node);
}
public void updateNode (DbWrapper db, DbTxn txn, Node node) throws Exception {
Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("updateNode "+node);
DbMapping dbm = node.getDbMapping ();
if (dbm == null || !dbm.isRelational ()) {
db.save (txn, node.getID (), node);
} else {
TableDataSet tds = null;
try {
tds = new TableDataSet (dbm.getConnection (), dbm.getSchema (), dbm.getKeyDef ());
Record rec = tds.addRecord ();
rec.setValue (dbm.getIDField (), node.getID ());
int updated = 0;
for (Enumeration e=dbm.prop2db.keys(); e.hasMoreElements(); ) {
String propname = (String) e.nextElement ();
Property p = node.getProperty (propname, false);
Relation rel = dbm.propertyToColumnName (propname);
if (rel != null && rel.readonly)
continue;
if (p != null && rel != null) {
if (p.dirty) {
switch (p.getType ()) {
case IProperty.STRING:
rec.setValue (rel.localField, p.getStringValue ());
break;
case IProperty.BOOLEAN:
rec.setValue (rel.localField, p.getBooleanValue ());
break;
case IProperty.DATE:
Timestamp t = new Timestamp (p.getDateValue ().getTime ());
rec.setValue (rel.localField, t);
break;
case IProperty.INTEGER:
rec.setValue (rel.localField, p.getIntegerValue ());
break;
case IProperty.FLOAT:
rec.setValue (rel.localField, p.getFloatValue ());
break;
case IProperty.NODE:
if (rel.direction == Relation.FORWARD) {
// INode n = p.getNodeValue ();
// String foreignID = n == null ? null : n.getID ();
rec.setValue (rel.localField, p.getStringValue ());
}
break;
}
updated++;
p.dirty = false;
}
} else if (rel != null && rel.localField != null) {
updated++;
rec.setValueNull (rel.localField);
}
}
if (updated > 0) {
// mark the key value as clean so no try is made to update it
rec.markValueClean (dbm.getIDField ());
rec.markForUpdate ();
tds.save ();
}
} finally {
if (tds != null) {
tds.close ();
}
}
dbm.lastDataChange = System.currentTimeMillis ();
}
// update may cause changes in the node's parent subnode array
if (node.isAnonymous()) {
Node parent = (Node) node.getParent ();
if (parent != null)
parent.lastSubnodeChange = System.currentTimeMillis ();
}
// tx.timer.endEvent ("updateNode "+node);
}
public void deleteNode (DbWrapper db, DbTxn txn, Node node) throws Exception {
Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("deleteNode "+node);
DbMapping dbm = node.getDbMapping ();
if (dbm == null || !dbm.isRelational ()) {
db.delete (txn, node.getID ());
} else {
Statement st = null;
try {
Connection con = dbm.getConnection ();
st = con.createStatement ();
st.executeUpdate ("DELETE FROM "+dbm.getTableName ()+" WHERE "+dbm.getIDField ()+" = "+node.getID ());
} finally {
if (st != null)
st.close ();
}
dbm.lastDataChange = System.currentTimeMillis ();
}
// node may still be cached via non-primary keys. mark as invalid
node.setState (Node.INVALID);
// tx.timer.endEvent ("deleteNode "+node);
}
public String generateID (DbMapping map) throws Exception {
Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("generateID "+map);
QueryDataSet qds = null;
String retval = null;
try {
Connection con = map.getConnection ();
String q = "SELECT "+map.getIDgen()+".nextval FROM dual";
qds = new QueryDataSet (con, q);
qds.fetchRecords ();
retval = qds.getRecord (0).getValue (1).asString ();
} finally {
// tx.timer.endEvent ("generateID "+map);
if (qds != null) {
qds.close ();
}
}
return retval;
}
/**
* Loades subnodes via subnode relation. Only the ID index is loaded, the nodes are
* loaded later on demand.
*/
public Vector getNodeIDs (Node home, Relation rel) throws Exception {
Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("getNodeIDs "+home);
if (rel == null || rel.other == null || !rel.other.isRelational ()) {
// this should never be called for embedded nodes
throw new RuntimeException ("NodeMgr.countNodes called for non-relational node "+home);
} else {
Vector retval = new Vector ();
// if we do a groupby query (creating an intermediate layer of groupby nodes),
// retrieve the value of that field instead of the primary key
String idfield = rel.groupby == null ? rel.other.getIDField () : rel.groupby;
Connection con = rel.other.getConnection ();
String table = rel.other.getTableName ();
QueryDataSet qds = null;
try {
Relation subrel = rel;
if (subrel.getFilter () != null)
subrel = subrel.getFilter ();
if (home.getSubnodeRelation() != null) {
// subnode relation was explicitly set
qds = new QueryDataSet (con, "SELECT "+idfield+" FROM "+table+" "+home.getSubnodeRelation());
} else {
String q = "SELECT "+idfield+" FROM "+table;
if (subrel.direction == Relation.BACKWARD) {
String homeid = home.getState() == Node.VIRTUAL ? home.parentID : home.getID ();
q += " WHERE "+subrel.remoteField+" = '"+homeid+"'";
}
// set order, if specified and if not using subnode's relation
if (rel.groupby != null)
q += " GROUP BY "+rel.groupby+" ORDER BY "+rel.groupby;
else if (rel.order != null)
q += " ORDER BY "+rel.order;
qds = new QueryDataSet (con, q);
}
if (logSql)
IServer.getLogger().log ("### getNodeIDs: "+qds.getSelectString());
qds.fetchRecords ();
for (int i=0; i<qds.size (); i++) {
Record rec = qds.getRecord (i);
retval.addElement (rec.getValue (1).asString ());
}
} finally {
// tx.timer.endEvent ("getNodeIDs "+home);
if (qds != null) {
qds.close ();
}
}
return retval;
}
}
/**
* Loades subnodes via subnode relation. This is similar to getNodeIDs, but it
* actually loades all nodes in one go, which is better for small node collections.
* This method is used when xxx.loadmode=aggressive is specified.
*/
public Vector getNodes (Node home, Relation rel) throws Exception {
Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("getNodes "+home);
if (rel == null || rel.other == null || !rel.other.isRelational ()) {
// this should never be called for embedded nodes
throw new RuntimeException ("NodeMgr.countNodes called for non-relational node "+home);
} else {
Vector retval = new Vector ();
DbMapping dbm = rel.other;
TableDataSet tds = new TableDataSet (dbm.getConnection (), dbm.getSchema (), dbm.getKeyDef ());
try {
Relation subrel = rel;
if (subrel.getFilter () != null)
subrel = subrel.getFilter ();
if (home.getSubnodeRelation() != null) {
// HACK: subnodeRelation includes a "where", but we need it without
tds.where (home.getSubnodeRelation().trim().substring(5));
} else if (subrel.direction == Relation.BACKWARD) {
String homeid = home.getState() == Node.VIRTUAL ? home.parentID : home.getID ();
tds.where (subrel.remoteField+" = '"+homeid+"'");
// set order if specified
if (rel.order != null)
tds.order (rel.order);
} else {
// don't set where clause, but set order.
if (rel.order != null)
tds.order (rel.order);
}
if (logSql)
IServer.getLogger().log ("### getNodes: "+tds.getSelectString());
tds.fetchRecords ();
for (int i=0; i<tds.size (); i++) {
// create new Nodes.
Record rec = tds.getRecord (i);
Node node = new Node (rel.other, rec, safe);
retval.addElement (node.getID());
Key primKey = node.getKey ();
// do we need to synchronize on primKey here?
Node oldnode = (Node) cache.get (primKey);
if (oldnode == null || oldnode.getState() == INode.INVALID)
cache.put (primKey, node);
}
} finally {
// tx.timer.endEvent ("getNodes "+home);
if (tds != null) {
tds.close ();
}
}
return retval;
}
}
public int countNodes (Node home, Relation rel) throws Exception {
Transactor tx = (Transactor) Thread.currentThread ();
// tx.timer.beginEvent ("countNodes "+home);
if (rel == null || rel.other == null || !rel.other.isRelational ()) {
// this should never be called for embedded nodes
throw new RuntimeException ("NodeMgr.countNodes called for non-relational node "+home);
} else {
int retval = 0;
Connection con = rel.other.getConnection ();
String table = rel.other.getTableName ();
QueryDataSet qds = null;
try {
Relation subrel = rel;
if (subrel.getFilter () != null)
subrel = subrel.getFilter ();
if (home.getSubnodeRelation() != null) {
qds = new QueryDataSet (con, "SELECT count(*) FROM "+table+" "+home.getSubnodeRelation());
} else if (subrel.direction == Relation.BACKWARD) {
String homeid = home.getState() == Node.VIRTUAL ? home.parentID : home.getID ();
qds = new QueryDataSet (con, "SELECT count(*) FROM "+table+" WHERE "+subrel.remoteField+" = '"+homeid+"'");
} else {
qds = new QueryDataSet (con, "SELECT count(*) FROM "+table);
}
if (logSql)
IServer.getLogger().log ("### countNodes: "+qds.getSelectString());
qds.fetchRecords ();
if (qds.size () == 0)
retval = 0;
else
retval = qds.getRecord (0).getValue (1).asInt ();
} finally {
// tx.timer.endEvent ("countNodes "+home);
if (qds != null) {
qds.close ();
}
}
return retval;
}
}
///////////////////////////////////////////////////////////////////////////////////////
// private getNode methods
///////////////////////////////////////////////////////////////////////////////////////
private Node getNodeByKey (DbWrapper db, DbTxn txn, String kstr, DbMapping dbm) throws Exception {
Node node = null;
if (dbm == null || !dbm.isRelational ()) {
node = db.getNode (txn, kstr);
node.nmgr = safe;
if (node != null && dbm != null)
node.setDbMapping (dbm);
} else {
TableDataSet tds = null;
try {
tds = new TableDataSet (dbm.getConnection (), dbm.getSchema (), dbm.getKeyDef ());
tds.where (dbm.getIDField ()+" = '"+kstr+"'");
if (logSql)
IServer.getLogger().log ("### getNodeByKey: "+tds.getSelectString());
tds.fetchRecords ();
if (tds.size () == 0)
return null;
if (tds.size () > 1)
throw new RuntimeException ("More than one value returned by query.");
Record rec = tds.getRecord (0);
node = new Node (dbm, rec, safe);
} finally {
if (tds != null) {
tds.close ();
}
}
}
return node;
}
private Node getNodeByRelation (DbWrapper db, DbTxn txn, Node home, String kstr, Relation rel) throws Exception {
Node node = null;
if (rel != null && rel.virtual && home.getState() != INode.VIRTUAL) {
Key k = home.getKey ().getVirtualKey (kstr);
node = (Node) cache.get (k);
if (node != null && node.getState() != INode.INVALID) {
if (rel.prototype != null && !rel.prototype.equals (node.getString ("prototype", false)))
node.setString ("prototype", rel.prototype);
return node;
}
// if subnodes are stored in embedded db we have to actually assign it the virtual node,
// otherwise it and its subnodes will be lost across restarts.
if (rel.other == null || (!rel.other.isRelational() && !home.getDbMapping().isRelational())) {
node = (Node) home.createNode (rel.propname);
if (rel.prototype != null)
node.setString ("prototype", rel.prototype);
} else {
node = new Node (home, kstr, safe, rel.prototype);
}
if (rel.prototype != null) {
node.setDbMapping (app.getDbMapping (rel.prototype));
} else {
// make a db mapping good enough that the virtual node finds its subnodes
DbMapping dbm = new DbMapping ();
dbm.setSubnodeMapping (rel.other);
dbm.setSubnodeRelation (rel.getVirtualSubnodeRelation());
dbm.setPropertyMapping (rel.other);
dbm.setPropertyRelation (rel.getVirtualPropertyRelation());
node.setDbMapping (dbm);
}
} else if (rel != null && rel.groupby != null) {
node = new Node (home, kstr, safe, rel.prototype);
DbMapping dbm = new DbMapping ();
dbm.setSubnodeMapping (rel.other);
dbm.setSubnodeRelation (rel.getGroupbySubnodeRelation());
dbm.setPropertyMapping (rel.other);
dbm.setPropertyRelation (rel.getGroupbyPropertyRelation());
node.setDbMapping (dbm);
} else if (rel == null || rel.other == null || !rel.other.isRelational ()) {
node = db.getNode (txn, kstr);
node.nmgr = safe;
node.setDbMapping (rel.other);
return node;
} else {
TableDataSet tds = null;
try {
DbMapping dbm = rel.other;
tds = new TableDataSet (dbm.getConnection (), dbm.getSchema (), dbm.getKeyDef ());
StringBuffer where = new StringBuffer ();
where.append (rel.getRemoteField ());
where.append (" = '");
where.append (escape(kstr));
where.append ("'");
// Additionally filter properties through subnode relation?
if (rel.subnodesAreProperties) {
String homeid = home.getState() == Node.VIRTUAL ? home.parentID : home.getID ();
// first check for dynamic subrel from node
String nodesubrel = home.getSubnodeRelation();
if (nodesubrel != null && nodesubrel.trim().length() > 5) {
where.append (" and ");
where.append (nodesubrel.trim().substring(5).trim());
} else {
Relation subrel = home.getDbMapping().getSubnodeRelation ();
if (subrel != null) {
if (subrel.getFilter () != null)
subrel = subrel.getFilter ();
if (subrel != null && subrel.direction == Relation.BACKWARD) {
where.append (" and ");
where.append (subrel.remoteField);
where.append (" = '");
where.append (homeid);
where.append ("'");
}
}
}
}
tds.where (where.toString ());
if (logSql)
IServer.getLogger().log ("### getNodeByRelation: "+tds.getSelectString());
tds.fetchRecords ();
if (tds.size () == 0)
return null;
if (tds.size () > 1)
throw new RuntimeException ("More than one value returned by query.");
Record rec = tds.getRecord (0);
node = new Node (rel.other, rec, safe);
// Check if node is already cached with primary Key.
if (!rel.usesPrimaryKey()) {
Key pk = node.getKey();
Node existing = (Node) cache.get (pk);
if (existing != null && existing.getState() != Node.INVALID) {
node = existing;
}
}
} finally {
if (tds != null) {
tds.close ();
}
}
}
return node;
}
// a utility method to escape single quotes
private String escape (String str) {
if (str == null)
return null;
if (str.indexOf ("'") < 0)
return str;
int l = str.length();
StringBuffer sbuf = new StringBuffer (l + 10);
for (int i=0; i<l; i++) {
char c = str.charAt (i);
if (c == '\'')
sbuf.append ("\\");
sbuf.append (c);
}
return sbuf.toString ();
}
}

View file

@ -0,0 +1,561 @@
// Property.java
// Copyright (c) Hannes Wallnöfer 1997-2000
package helma.objectmodel.db;
import helma.util.*;
import java.util.*;
import java.io.*;
import java.text.*;
import helma.objectmodel.*;
/**
* 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 {
protected String propname;
protected Node node;
protected String svalue;
protected boolean bvalue;
protected long lvalue;
protected double dvalue;
protected String nvalueID;
private transient DbMapping dbm;
protected Object jvalue;
protected int type;
transient boolean dirty;
static final long serialVersionUID = -1022221688349192379L;
private void readObject (ObjectInputStream in) throws IOException {
try {
propname = in.readUTF ();
node = (Node) in.readObject ();
type = in.readInt ();
switch (type) {
case STRING:
svalue = in.readUTF ();
break;
case BOOLEAN:
bvalue = in.readBoolean ();
break;
case INTEGER:
case DATE:
lvalue = in.readLong ();
break;
case FLOAT:
dvalue = in.readDouble ();
break;
case NODE:
nvalueID = in.readUTF ();
break;
case JAVAOBJECT:
jvalue = in.readObject ();
break;
}
} catch (ClassNotFoundException x) {
throw new IOException (x.toString ());
}
}
private void writeObject (ObjectOutputStream out) throws IOException {
// don't even start if this is a non-serializable Java object
if (type == JAVAOBJECT && jvalue != null && !(jvalue instanceof Serializable))
return;
out.writeUTF (propname);
out.writeObject (node);
out.writeInt (type);
switch (type) {
case STRING:
out.writeUTF (svalue);
break;
case BOOLEAN:
out.writeBoolean (bvalue);
break;
case INTEGER:
case DATE:
out.writeLong (lvalue);
break;
case FLOAT:
out.writeDouble (dvalue);
break;
case NODE:
out.writeUTF (nvalueID);
break;
case JAVAOBJECT:
out.writeObject (jvalue);
break;
}
}
public Property (Node node) {
this.node = node;
dirty = true;
}
public Property (String propname, Node node) {
this.propname = propname;
this.node = node;
dirty = true;
}
public Property (String propname, Node node, Node value) {
this (propname, node);
type = NODE;
nvalueID = value == null ? null : value.getID ();
dirty = true;
}
public String getName () {
return propname;
}
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 null;
case JAVAOBJECT:
return jvalue;
}
return null;
}
public void setStringValue (String value) {
if (type == NODE)
unregisterNode ();
// IServer.getLogger().log ("setting string value of property "+propname + " to "+value);
// mark property as dirty
dirty = true;
// if this is not a string property, try to parse a value out of it
if (type == DATE) {
try {
SimpleDateFormat dateformat = new SimpleDateFormat ();
dateformat.setLenient (true);
Date date = dateformat.parse (value);
this.lvalue = date.getTime ();
return;
} catch (ParseException nodate) {
// store as plain string
}
}
if (type == BOOLEAN) {
if ("true".equalsIgnoreCase (value))
this.bvalue = true;
else if ("false".equalsIgnoreCase (value))
this.bvalue = false;
return;
}
if (type == INTEGER) {
this.lvalue = Long.parseLong (value);
return;
}
if (type == FLOAT) {
this.dvalue = new Double (value).doubleValue ();
return;
}
if (type == JAVAOBJECT)
this.jvalue = null;
this.svalue = value;
type = STRING;
}
public void setIntegerValue (long value) {
if (type == NODE)
unregisterNode ();
if (type == JAVAOBJECT)
this.jvalue = null;
type = INTEGER;
this.lvalue = value;
dirty = true;
}
public void setFloatValue (double value) {
if (type == NODE)
unregisterNode ();
if (type == JAVAOBJECT)
this.jvalue = null;
type = FLOAT;
this.dvalue = value;
dirty = true;
}
public void setDateValue (Date value) {
if (type == NODE)
unregisterNode ();
if (type == JAVAOBJECT)
this.jvalue = null;
type = DATE;
this.lvalue = value == null ? 0 : value.getTime();
dirty = true;
}
public void setBooleanValue (boolean value) {
if (type == NODE)
unregisterNode ();
if (type == JAVAOBJECT)
this.jvalue = null;
type = BOOLEAN;
this.bvalue = value;
dirty = true;
}
public void setNodeValue (Node value) {
value.checkWriteLock ();
if (type == NODE)
unregisterNode ();
if (type == JAVAOBJECT)
this.jvalue = null;
registerNode (value);
type = NODE;
if (node.dbmap != null) {
Relation rel = node.dbmap.getPropertyRelation (propname);
if (rel != null && rel.other != null) {
DbMapping vmap = value.getDbMapping ();
// check if actual type matches expected type
if (rel.other != vmap)
throw new RuntimeException ("Can't assign property: expected prototype "+rel.other+", got "+vmap);
// check if this is a forward relation, i.e. if we point to a field in the value object
// if so, we may use something else than the object's id to refer to it.
if (!rel.virtual && rel.direction == Relation.FORWARD) {
if (rel.remoteField == null || vmap.getIDField().equals (rel.remoteField)) {
this.nvalueID = value.getID ();
} else try {
this.nvalueID = value.getString (vmap.columnNameToProperty (rel.remoteField).propname, false);
} catch (Exception x) {
throw new RuntimeException ("Can't set "+propname+" to "+value+": error retrieving target property");
}
this.dbm = null;
dirty = true;
return;
}
}
}
this.nvalueID = value == null ? null : value.getID ();
this.dbm = value == null ? null : value.getDbMapping ();
dirty = true;
}
public void setJavaObjectValue (Object value) {
if (type == NODE)
unregisterNode ();
type = JAVAOBJECT;
this.jvalue = value;
}
/**
* tell a the value node that it is no longer used as a property.
* If this was the "main" property for the node, also remove all other references.
*/
protected void unregisterNode () {
if (nvalueID != null) {
DbMapping nvmap = null;
Relation nvrel = null;
if (node.dbmap != null) {
nvmap = node.dbmap.getPropertyMapping (propname);
nvrel = node.dbmap.getPropertyRelation (propname);
}
Node nvalue = node.nmgr.getNode (nvalueID, nvmap);
if (nvalue == null)
return;
nvalue.checkWriteLock ();
// check if the property node is also a subnode
// BUG: this doesn't work because properties for subnode/properties are never stored and therefore
// never reused.
if (nvrel != null && nvrel.subnodesAreProperties) {
node.removeNode (nvalue);
}
// only need to call unregisterPropLink if the value node is not stored in a relational db
// also, getParent is heuristical/implicit for relational nodes, so we don't do deepRemoveNode
// based on that for relational nodes.
if (nvmap == null || !nvmap.isRelational()) {
if (!nvalue.isAnonymous() && propname.equals (nvalue.getName()) && this.node == nvalue.getParent()) {
// this is the "main" property of a named node, so handle this as a cascading delete.
nvalue.deepRemoveNode ();
} else {
nvalue.unregisterPropLink (this.node);
}
}
}
}
/**
* Tell the value node that it is being used as a property value.
*/
protected void registerNode (Node n) {
// only need to call registerPropLink if the value node is not stored in a relational db
if (n != null && (n.dbmap == null || !n.dbmap.isRelational())) {
n.registerPropLink (this.node);
}
}
public String getStringValue () {
switch (type) {
case STRING:
return svalue;
case BOOLEAN:
return "" + bvalue;
case DATE:
SimpleDateFormat format = new SimpleDateFormat ("dd.MM.yy hh:mm");
return format.format (new Date (lvalue));
case INTEGER:
return Long.toString (lvalue);
case FLOAT:
return Double.toString (dvalue);
case NODE:
return nvalueID;
case JAVAOBJECT:
return jvalue.toString ();
}
return "";
}
public String toString () {
return getStringValue ();
}
public long getIntegerValue () {
if (type == INTEGER)
return lvalue;
return 0;
}
public double getFloatValue () {
if (type == FLOAT)
return dvalue;
return 0.0;
}
public Date getDateValue () {
if (type == DATE)
return new Date (lvalue);
return null;
}
public boolean getBooleanValue () {
if (type == BOOLEAN)
return bvalue;
return false;
}
public INode getNodeValue () {
if (type == NODE && nvalueID != null) {
Relation rel = null;
if (dbm == null && node.dbmap != null) {
// try to get DbMap for property, if it isn't known yet
rel = node.dbmap.getPropertyRelation (propname);
// figure out db mapping from relation
if (rel != null) {
// is the property a virtual node containing objects from relational db?
if (rel.virtual && rel.other.isRelational ())
return node.nmgr.getNode (node, propname, rel);
else if (!rel.virtual && rel.direction == Relation.FORWARD)
return node.nmgr.getNode (node, nvalueID, rel);
// avoid setting dbm for virtual relation
else if (!rel.virtual /* || node.getState() == INode.VIRTUAL*/ && rel.groupby == null)
dbm = rel.other;
}
}
Node retval = node.nmgr.getNode (nvalueID, dbm);
if (retval != null && retval.parentID == null) {
retval.setParent (node);
retval.setName (propname);
retval.anonymous = false;
}
if (retval != null && retval.getDbMapping () == null && rel != null && rel.virtual && rel.prototype == null) {
// a virtual node whose child nodes are not relational -
// set up dbmapping that describes subnodes and properties
DbMapping _dbm = new DbMapping ();
_dbm.setSubnodeMapping (rel.other);
_dbm.setPropertyMapping (rel.other);
_dbm.setSubnodeRelation (rel.getVirtualSubnodeRelation());
_dbm.setPropertyRelation (rel.getVirtualPropertyRelation());
retval.setDbMapping (_dbm);
}
return retval;
}
return null;
}
public Object getJavaObjectValue () {
if (type == JAVAOBJECT)
return jvalue;
return null;
}
public String getEditor () {
switch (type) {
case STRING:
return "password".equalsIgnoreCase (propname) ?
"<input type=password name=\""+propname+"\" value='"+ svalue.replace ('\'', '"') +"'>" :
"<input type=text name=\""+propname+"\" value='"+ svalue.replace ('\'', '"') +"'>" ;
case BOOLEAN:
return "<select name=\""+propname+"\"><option selected value="+bvalue+">"+bvalue+"</option><option value="+!bvalue+">"+!bvalue+"</option></select>";
case INTEGER:
return "<input type=text name=\""+propname+"\" value=\""+lvalue+"\">" ;
case FLOAT:
return "<input type=text name=\""+propname+"\" value=\""+dvalue+"\">" ;
case DATE:
SimpleDateFormat format = new SimpleDateFormat ("dd.MM.yy hh:mm");
String date = format.format (new Date (lvalue));
return "<input type=text name=\""+propname+"\" value=\""+date+"\">";
case NODE:
DbMapping nvmap = null;
if (node.dbmap != null)
nvmap = node.dbmap.getPropertyMapping (propname);
return "<input type=text size=25 name="+propname+" value='"+ node.nmgr.getNode (nvalueID, nvmap).getName () +"'>";
}
return "";
}
private String escape (String s) {
char c[] = new char[s.length()];
s.getChars (0, c.length, c, 0);
StringBuffer b = new StringBuffer ();
int copyfrom = 0;
for (int i = 0; i < c.length; i++) {
switch (c[i]) {
case '\\':
case '"':
if (i-copyfrom > 0)
b.append (c, copyfrom, i-copyfrom);
b.append ('\\');
b.append (c[i]);
copyfrom = i+1;
}
}
if (c.length-copyfrom > 0)
b.append (c, copyfrom, c.length-copyfrom);
return b.toString ();
}
public int getType () {
return type;
}
public String getTypeString () {
switch (type) {
case STRING:
return "string";
case BOOLEAN:
return "boolean";
case DATE:
return "date";
case INTEGER:
return "integer";
case FLOAT:
return "float";
case NODE:
return "node";
}
return "";
}
public Object clone () {
try {
Property c = (Property) super.clone();
c.propname = this.propname;
c.svalue = this.svalue;
c.bvalue = this.bvalue;
c.lvalue = this.lvalue;
c.dvalue = this.dvalue;
c.nvalueID = this.nvalueID;
c.type = this.type;
return c;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError ();
}
}
}

View file

@ -0,0 +1,355 @@
// Server.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel.db;
import java.util.*;
import java.io.*;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.net.*;
import helma.objectmodel.*;
import helma.framework.*;
import helma.framework.core.*;
import helma.xmlrpc.*;
import helma.util.*;
import com.sleepycat.db.*;
/**
* HOP main class.
*/
public class Server extends IServer implements Runnable {
static boolean useTransactions, paranoid;
private ApplicationManager appManager;
private Thread mainThread;
protected static ThreadGroup txgroup;
static String dbFilename = "hop.db";
static String propfile;
static String dbPropfile = "db.properties";
static String appsPropfile = "apps.properties";
static SystemProperties appsProps;
static String dbDir = null;
static int port = 5055;
static int webport = 0;
Acme.Serve.Serve websrv;
public static void main (String args[]) throws IOException {
boolean usageError = false;
useTransactions = true;
for (int i=0; i<args.length; i++) {
if (args[i].equals ("-h") && i+1<args.length)
hopHome = args[++i];
else if (args[i].equals ("-f") && i+1<args.length)
propfile = args[++i];
else if (args[i].equals ("-t"))
useTransactions = false;
else if (args[i].equals ("-p") && i+1<args.length) {
try {
port = Integer.parseInt (args[++i]);
} catch (Exception portx) {
usageError = true;
}
} else if (args[i].equals ("-w") && i+1<args.length) {
try {
webport = Integer.parseInt (args[++i]);
} catch (Exception portx) {
usageError = true;
}
} else
usageError = true;
}
// get main property file from home dir or vice versa, depending on what we have.
// get property file from hopHome
if (propfile == null) {
if (hopHome != null)
propfile = new File (hopHome, "server.properties").getAbsolutePath ();
else
propfile = new File ("server.properties").getAbsolutePath ();
}
sysProps = new SystemProperties (propfile);
getLogger().log ("propfile = "+propfile);
// get hopHome from property file
if (hopHome == null)
hopHome = sysProps.getProperty ("hophome");
if (hopHome == null)
hopHome = new File (propfile).getParent ();
getLogger().log ("hopHome = "+hopHome);
if (usageError ) {
System.out.println ("usage: java helma.objectmodel.db.Server [-h dir] [-f file] [-p port] [-w port] [-t]");
System.out.println (" -h dir Specify hop home directory");
System.out.println (" -f file Specify server.properties file");
System.out.println (" -p port Specify TCP port number");
System.out.println (" -w port Start embedded Web server on that port");
System.out.println (" -t Disable Berkeley DB Transactions");
getLogger().log ("Usage Error - exiting");
System.exit (0);
}
dbDir = sysProps.getProperty ("dbhome", "db");
File helper = new File (dbDir);
if (hopHome != null && !helper.isAbsolute ())
helper = new File (hopHome, dbDir);
dbDir = helper.getAbsolutePath ();
getLogger().log ("dbHome = "+dbDir);
dbPropfile = sysProps.getProperty ("dbpropfile", "db.properties");
helper = new File (dbPropfile);
if (hopHome != null && !helper.isAbsolute ())
helper = new File (hopHome, dbPropfile);
dbPropfile = helper.getAbsolutePath ();
getLogger().log ("dbPropfile = "+dbPropfile);
appsPropfile = sysProps.getProperty ("appspropfile", "apps.properties");
helper = new File (appsPropfile);
if (hopHome != null && !helper.isAbsolute ())
helper = new File (hopHome, appsPropfile);
appsPropfile = helper.getAbsolutePath ();
getLogger().log ("appsPropfile = "+appsPropfile);
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));
getLogger().log ("Locale = "+Locale.getDefault());
getLogger().log ("TimeZone = "+TimeZone.getDefault());
dbSources = new Hashtable ();
txgroup = new ThreadGroup ("Transactor");
new Server ();
}
public Server () {
try {
checkRunning (); // check if a server is already running with this db
} catch (Exception running) {
System.out.println (running.getMessage ());
System.exit (1);
}
// nmgr = new NodeManager (this, sysProps);
mainThread = new Thread (this);
mainThread.start ();
}
public void run () {
try {
// set up dbSources
try {
dbProps = new SystemProperties (dbPropfile);
String sources = dbProps.getProperty ("sources", "");
StringTokenizer st = new StringTokenizer (sources, ",; ");
String next = null;
while (st.hasMoreTokens ()) try {
next = st.nextToken ();
new DbSource (next);
} catch (Exception wrong) {
getLogger().log ("Error creating DbSource "+next);
getLogger().log ("Reason: "+wrong);
}
} catch (Exception x) {
getLogger().log ("Error loading data source properties: "+x);
}
// start embedded web server if port is specified
if (webport > 0) {
websrv = new Acme.Serve.Serve (webport, sysProps);
}
String xmlparser = sysProps.getProperty ("xmlparser");
if (xmlparser != null)
XmlRpc.setDriver (xmlparser);
// XmlRpc.setDebug (true);
xmlrpc = new WebServer (port+1);
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 ());
}
}
getLogger().log ("Starting XML-RPC server on port "+(port+1));
// the following seems not to be necessary after all ...
// System.setSecurityManager(new RMISecurityManager());
if (paranoid) {
HopSocketFactory factory = new HopSocketFactory ();
String rallow = sysProps.getProperty ("allowWeb");
if (rallow != null) {
StringTokenizer st = new StringTokenizer (rallow, " ,;");
while (st.hasMoreTokens ())
factory.addAddress (st.nextToken ());
}
RMISocketFactory.setSocketFactory (factory);
}
if (websrv == null) {
getLogger().log ("Starting server on port "+port);
LocateRegistry.createRegistry (port);
}
// start application framework
String appDir = sysProps.getProperty ("apphome", "apps");
File appHome = new File (appDir);
if (hopHome != null && !appHome.isAbsolute())
appHome = new File (hopHome, appDir);
appsProps = new SystemProperties (appsPropfile);
File dbHome = new File (dbDir);
appManager = new ApplicationManager (port, appHome, dbHome, appsProps, this);
} catch (Exception gx) {
getLogger().log ("Error initializing embedded database: "+gx);
gx.printStackTrace ();
/* try {
transactor.abort ();
} catch (Exception ignore) {} */
return;
}
// start applications
appManager.startAll ();
// start embedded web server
if (websrv != null) {
Thread webthread = new Thread (websrv, "WebServer");
webthread.start ();
}
int count = 0;
while (Thread.currentThread () == mainThread) {
try {
mainThread.sleep (3000l);
} catch (InterruptedException ie) {
return;
}
appManager.checkForChanges ();
// print some thread stats now and then
if (count++ > 20) {
printThreadStats ();
count = 0;
}
}
}
private void checkRunning () throws Exception {
try {
java.net.Socket socket = new java.net.Socket ("localhost", port);
} catch (Exception x) {
return;
}
// if we got so far, another server is already running on this port and db
throw new Exception ("Error: Server already running on this port");
}
public void printThreadStats () {
getLogger().log ("Thread Stats: "+txgroup.activeCount()+" active");
Runtime rt = Runtime.getRuntime ();
long free = rt.freeMemory ();
long total = rt.totalMemory ();
getLogger().log ("Free memory: "+(free/1024)+" kB");
getLogger().log ("Total memory: "+(total/1024)+" kB");
}
}

View file

@ -0,0 +1,338 @@
// Transactor.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel.db;
import java.io.*;
import java.util.*;
import java.sql.*;
import helma.objectmodel.*;
import helma.util.Timer;
import helma.framework.TimeoutException;
import com.sleepycat.db.*;
/**
* A subclass of thread that keeps track of changed nodes and triggers
* changes in the database when a transaction is commited.
*/
public class Transactor extends Thread {
NodeManager nmgr;
// List of nodes to be updated
private Hashtable nodes;
// List of visited clean nodes
private Hashtable cleannodes;
// Is a transaction in progress?
private volatile boolean active;
private volatile boolean killed;
// Transaction for the embedded database
protected DbTxn txn;
// Transactions for SQL data sources
protected Hashtable sqlCon;
public Timer timer;
// 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;
public Transactor (Runnable runnable, NodeManager nmgr) {
super (Server.txgroup, runnable, "Transactor");
this.nmgr = nmgr;
nodes = new Hashtable ();
cleannodes = new Hashtable ();
sqlCon = new Hashtable ();
active = false;
killed = false;
timer = new Timer();
}
public void visitNode (Node node) {
if (node != null) {
Key key = node.getKey ();
if (!nodes.containsKey (key)) {
nodes.put (key, node);
}
}
}
public void dropNode (Node node) {
if (node != null) {
Key key = node.getKey ();
nodes.remove (key);
}
}
public void visitCleanNode (Node node) {
if (node != null) {
Key key = node.getKey ();
if (!cleannodes.containsKey (key)) {
cleannodes.put (key, node);
}
}
}
public void visitCleanNode (Key key, Node node) {
if (node != null) {
if (!cleannodes.containsKey (key)) {
cleannodes.put (key, node);
}
}
}
public Node getVisitedNode (Object key) {
return key == null ? null : (Node) cleannodes.get (key);
}
public boolean isActive () {
return active;
}
public void registerConnection (DbSource src, Connection con) {
sqlCon.put (src, con);
}
public Connection getConnection (DbSource src) {
return (Connection) sqlCon.get (src);
}
public synchronized void begin (String tnm) throws Exception {
if (active) {
abort ();
}
nodes.clear ();
cleannodes.clear ();
txn = nmgr.db.beginTransaction ();
active = true;
tstart = System.currentTimeMillis ();
tname = tnm;
}
public synchronized void commit () throws Exception {
if (killed) {
abort ();
return;
}
int ins = 0, upd = 0, dlt = 0;
int l = nodes.size ();
for (Enumeration e=nodes.elements (); e.hasMoreElements (); ) {
Node node = (Node) e.nextElement ();
// update nodes in db
int nstate = node.getState ();
if (nstate == Node.NEW) {
nmgr.registerNode (node); // register node with nodemanager cache
nmgr.insertNode (nmgr.db, txn, node);
node.setState (Node.CLEAN);
ins++;
// IServer.getLogger().log ("inserted: "+node.getFullName ());
} else if (nstate == Node.MODIFIED) {
nmgr.updateNode (nmgr.db, txn, node);
node.setState (Node.CLEAN);
upd++;
IServer.getLogger().log ("updated: "+node.getFullName ());
} else if (nstate == Node.DELETED) {
// IServer.getLogger().log ("deleted: "+node.getFullName ()+" ("+node.getName ()+")");
nmgr.deleteNode (nmgr.db, txn, node);
nmgr.evictNode (node);
dlt++;
} else {
// IServer.getLogger().log ("noop: "+node.getFullName ());
}
node.clearWriteLock ();
}
nodes.clear ();
cleannodes.clear ();
// sqlCon.clear ();
if (nmgr.idgen.dirty) {
nmgr.db.save (txn, "idgen", nmgr.idgen);
nmgr.idgen.dirty = false;
}
if (active) {
active = false;
nmgr.db.commitTransaction (txn);
txn = null;
}
IServer.getLogger().log (tname+" "+l+" marked, "+ins+" inserted, "+upd+" updated, "+dlt+" deleted in "+(System.currentTimeMillis()-tstart)+" millis");
}
public synchronized void abort () throws Exception {
int l = nodes.size ();
for (Enumeration e=nodes.elements(); e.hasMoreElements(); ) {
Node node = (Node) e.nextElement ();
// 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 ();
}
nodes.clear ();
cleannodes.clear ();
for (Enumeration e=sqlCon.elements(); e.hasMoreElements(); ) {
try {
Connection con = (Connection) e.nextElement ();
con.close ();
} catch (Exception ignore) {}
}
sqlCon.clear ();
if (active) {
active = false;
nmgr.db.abortTransaction (txn);
txn = null;
}
IServer.getLogger().log (tname+" aborted after "+(System.currentTimeMillis()-tstart)+" millis");
}
public synchronized void kill () {
killed = true;
// The thread is told to stop by setting the thread flag in the EcmaScript
// evaluator, so we can hope that it stops without doing anything else.
try {
join (1000);
} catch (InterruptedException ir) {
Thread.currentThread().interrupt();
}
// Interrupt the thread if it has not noticed the flag (e.g. because it is busy
// reading from a network socket).
if (isAlive()) {
interrupt ();
try {
join (3000);
} catch (InterruptedException ignore) {
Thread.currentThread().interrupt();
}
if (isAlive())
// Sorry to be so rude...
stop (new TimeoutException());
}
}
public void cleanup () {
if (sqlCon != null) {
for (Enumeration e=sqlCon.elements(); e.hasMoreElements(); ) {
try {
Connection con = (Connection) e.nextElement ();
con.close ();
} catch (Exception ignore) {}
}
sqlCon.clear ();
sqlCon = null;
}
}
}

View file

@ -0,0 +1,186 @@
// WrappedNodeManager.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.objectmodel.db;
import helma.objectmodel.*;
import java.util.Vector;
/**
* 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 class WrappedNodeManager {
NodeManager nmgr;
public WrappedNodeManager (NodeManager nmgr) {
this.nmgr = nmgr;
}
public Node getNode (String id, DbMapping dbmap) {
try {
return nmgr.getNode (id, dbmap);
} catch (ObjectNotFoundException x) {
return null;
} catch (Exception x) {
Server.getLogger().log ("Error retrieving Node via DbMapping: "+x.getMessage ());
if ("true".equalsIgnoreCase (Server.sysProps.getProperty("debug")))
x.printStackTrace();
throw new RuntimeException ("Error retrieving Node: "+x.getMessage ());
}
}
public Node getNode (Node home, String id, Relation rel) {
try {
return nmgr.getNode (home, id, rel);
} catch (ObjectNotFoundException x) {
return null;
} catch (Exception x) {
Server.getLogger().log ("Error retrieving Node \""+id+"\" from "+home+": "+x.getMessage ());
if ("true".equalsIgnoreCase (Server.sysProps.getProperty("debug")))
x.printStackTrace();
throw new RuntimeException ("Error retrieving Node: "+x.getMessage ());
}
}
public Vector getNodes (Node home, Relation rel) {
try {
return nmgr.getNodes (home, rel);
} catch (Exception x) {
if ("true".equalsIgnoreCase (Server.sysProps.getProperty("debug")))
x.printStackTrace();
throw new RuntimeException ("Error retrieving Nodes: "+x.getMessage ());
}
}
public Vector getNodeIDs (Node home, Relation rel) {
try {
return nmgr.getNodeIDs (home, rel);
} catch (Exception x) {
if ("true".equalsIgnoreCase (Server.sysProps.getProperty("debug")))
x.printStackTrace();
throw new RuntimeException ("Error retrieving NodeIDs: "+x.getMessage ());
}
}
public int countNodes (Node home, Relation rel) {
try {
return nmgr.countNodes (home, rel);
} catch (Exception x) {
if ("true".equalsIgnoreCase (Server.sysProps.getProperty("debug")))
x.printStackTrace();
throw new RuntimeException ("Error counting Node: "+x.getMessage ());
}
}
public void deleteNode (Node node) {
try {
nmgr.deleteNode (node);
} catch (Exception x) {
if ("true".equalsIgnoreCase (Server.sysProps.getProperty("debug")))
x.printStackTrace();
throw new RuntimeException ("Error deleting Node: "+x.getMessage ());
}
}
public void registerNode (Node node) {
nmgr.registerNode (node);
}
public void evictNode (Node node) {
nmgr.evictNode (node);
}
public void evictKey (Key key) {
nmgr.evictKey (key);
}
public String generateID () {
return nmgr.idgen.newID ();
}
public String generateID (DbMapping map) {
try {
if (map == null || map.getIDgen() == null)
return nmgr.idgen.newID ();
else
return nmgr.generateID (map);
} catch (Exception x) {
throw new RuntimeException (x.toString ());
}
}
}

View file

@ -0,0 +1,208 @@
// FileServlet - servlet similar to a standard httpd
//
// 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/
package helma.servlet;
import java.io.*;
import java.util.*;
import java.text.*;
import Acme.Serve.*;
import javax.servlet.*;
import javax.servlet.http.*;
/// Servlet similar to a standard httpd.
// <P>
// Implements the "GET" and "HEAD" methods for files and directories.
// Handles index.html.
// Redirects directory URLs that lack a trailing /.
// Handles If-Modified-Since and Range.
// <P>
// <A HREF="/resources/classes/Acme/Serve/FileServlet.java">Fetch the software.</A><BR>
// <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
// <P>
// @see Acme.Serve.Serve
public class AcmeFileServlet extends FileServlet
{
private File root;
/// Constructor.
public AcmeFileServlet(File root)
{
super ();
this.root = root;
}
/// Services a single request from the client.
// @param req the servlet request
// @param req the servlet response
// @exception ServletException when an exception has occurred
public void service( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException
{
boolean headOnly;
if ( req.getMethod().equalsIgnoreCase( "get" ) )
headOnly = false;
else if ( ! req.getMethod().equalsIgnoreCase( "head" ) )
headOnly = true;
else
{
res.sendError( HttpServletResponse.SC_NOT_IMPLEMENTED );
return;
}
String path = req.getServletPath();
if ( path == null || path.charAt( 0 ) != '/' )
{
res.sendError( HttpServletResponse.SC_BAD_REQUEST );
return;
}
if ( path.indexOf( "/../" ) != -1 || path.endsWith( "/.." ) )
{
res.sendError( HttpServletResponse.SC_FORBIDDEN );
return;
}
// Make a version without the leading /.
String pathname = path.substring( 1 );
if ( pathname.length() == 0 )
pathname = "./";
dispatchPathname( req, res, headOnly, path, pathname );
}
protected void dispatchPathname( HttpServletRequest req, HttpServletResponse res, boolean headOnly, String path, String pathname ) throws IOException
{
String filename = pathname.replace( '/', File.separatorChar );
if ( filename.charAt( filename.length() - 1 ) == File.separatorChar )
filename = filename.substring( 0, filename.length() - 1 );
if (filename.startsWith ("static"))
filename = filename.substring ( Math.min (7, filename.length()) );
File file = new File( root, filename );
if ( file.exists() )
{
if ( ! file.isDirectory() )
serveFile( req, res, headOnly, path, filename, file );
else
{
if ( pathname.charAt( pathname.length() - 1 ) != '/' )
redirectDirectory( req, res, path, file );
else
{
String indexFilename =
filename + File.separatorChar + "index.html";
File indexFile = new File( indexFilename );
if ( indexFile.exists() )
serveFile(
req, res, headOnly, path, indexFilename,
indexFile );
else
serveDirectory(
req, res, headOnly, path, filename, file );
}
}
}
else
{
if ( pathname.endsWith( "/index.html" ) )
dispatchPathname(
req, res, headOnly, path,
pathname.substring( 0, pathname.length() - 10 ) );
else if ( pathname.equals( "index.html" ) )
dispatchPathname( req, res, headOnly, path, "./" );
else
res.sendError( HttpServletResponse.SC_NOT_FOUND );
}
}
private void serveDirectory( HttpServletRequest req, HttpServletResponse res, boolean headOnly, String path, String filename, File file ) throws IOException
{
log( "indexing " + path );
if ( ! file.canRead() )
{
res.sendError( HttpServletResponse.SC_FORBIDDEN );
return;
}
res.setStatus( HttpServletResponse.SC_OK );
res.setContentType( "text/html" );
OutputStream out = res.getOutputStream();
if ( ! headOnly )
{
PrintStream p = new PrintStream( new BufferedOutputStream( out ) );
p.println( "<HTML><HEAD>" );
p.println( "<TITLE>Index of " + path + "</TITLE>" );
p.println( "</HEAD><BODY BGCOLOR=\"#ffffff\">" );
p.println( "<H2>Index of " + path + "</H2>" );
p.println( "<PRE>" );
p.println( "mode bytes last-changed name" );
p.println( "<HR>" );
String[] names = file.list();
Acme.Utils.sortStrings( names );
for ( int i = 0; i < names.length; ++i )
{
String aFilename = filename + File.separatorChar + names[i];
File aFile = new File( aFilename );
String aFileType;
if ( aFile.isDirectory() )
aFileType = "d";
else if ( aFile.isFile() )
aFileType = "-";
else
aFileType = "?";
String aFileRead = ( aFile.canRead() ? "r" : "-" );
String aFileWrite = ( aFile.canWrite() ? "w" : "-" );
String aFileExe = "-";
String aFileSize = Acme.Fmt.fmt( aFile.length(), 8 );
String aFileDate =
Acme.Utils.lsDateStr( new Date( aFile.lastModified() ) );
String aFileDirsuf = ( aFile.isDirectory() ? "/" : "" );
String aFileSuf = ( aFile.isDirectory() ? "/" : "" );
p.println(
aFileType + aFileRead + aFileWrite + aFileExe +
" " + aFileSize + " " + aFileDate + " " +
"<A HREF=\"" + names[i] + aFileDirsuf + "\">" +
names[i] + aFileSuf + "</A>" );
}
p.println( "</PRE>" );
p.println( "<HR>" );
ServeUtils.writeAddress( p );
p.println( "</BODY></HTML>" );
p.flush();
}
out.close();
}
}

View file

@ -0,0 +1,254 @@
// ServletClient.java
// Copyright (c) Hannes Wallnoefer, Raphael Spannocchi 1998-2000
/* Portierung von helma.asp.AspClient auf Servlets */
/* Author: Raphael Spannocchi Datum: 27.11.1998 */
package helma.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import helma.framework.*;
import helma.framework.core.Application;
import helma.objectmodel.Node;
import helma.util.Uploader;
/**
* This is the HOP servlet adapter that uses the Acme servlet API clone and communicates
* directly with hop applications instead of using RMI.
*/
public class AcmeServletClient extends HttpServlet{
private int uploadLimit; // limit to HTTP uploads in kB
private Hashtable apps;
private Application app;
private String cookieDomain;
private boolean caching;
private boolean debug;
public AcmeServletClient (Application app) {
this.app = app;
this.uploadLimit = 1024; // generous 1mb upload limit
}
public void service (HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
execute (request, response);
}
public void doGet (HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
execute (request, response);
}
public void doPost (HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
execute (request, response);
}
private void execute (HttpServletRequest request, HttpServletResponse response) {
String protocol = request.getProtocol ();
Cookie[] cookies = request.getCookies();
try {
RequestTrans reqtrans = new RequestTrans ();
// HACK - sessions not fully supported in Acme.Serve
// Thats ok, we dont need the session object, just the id.
reqtrans.session = request.getRequestedSessionId();
if (cookies != null) {
for (int i=0; i < cookies.length;i++) try { // get Cookies
String nextKey = cookies[i].getName ();
String nextPart = cookies[i].getValue ();
reqtrans.set (nextKey, nextPart);
} catch (Exception badCookie) {}
}
// get optional path info
String pathInfo = request.getServletPath ();
if (pathInfo != null) {
if (pathInfo.indexOf (app.getName()) == 1)
pathInfo = pathInfo.substring (app.getName().length()+1);
reqtrans.path = trim (pathInfo);
} else
reqtrans.path = "";
String host = request.getHeader ("Host");
if (host != null) {
host = host.toLowerCase();
reqtrans.set ("http_host", host);
}
String referer = request.getHeader ("Referer");
if (referer != null)
reqtrans.set ("http_referer", referer);
String remotehost = request.getRemoteAddr ();
if (remotehost != null)
reqtrans.set ("http_remotehost", remotehost);
String browser = request.getHeader ("User-Agent");
if (browser != null)
reqtrans.set ("http_browser", browser);
for (Enumeration e = request.getParameterNames(); e.hasMoreElements(); ) {
// Params parsen
String nextKey = (String)e.nextElement();
String[] paramValues = request.getParameterValues(nextKey);
String nextValue = paramValues[0]; // Only take first value
reqtrans.set (nextKey, nextValue); // generic Header, Parameter
}
String contentType = request.getContentType();
if (contentType != null && contentType.indexOf("multipart/form-data")==0) {
// File Upload
Uploader up;
try {
if ((up = getUpload (uploadLimit, request)) != null) {
Hashtable upload = up.getParts ();
for (Enumeration e = upload.keys(); e.hasMoreElements(); ) {
String nextKey = (String) e.nextElement ();
Object nextPart = upload.get (nextKey);
reqtrans.set (nextKey, nextPart);
}
}
} catch (Exception upx) {
String uploadErr = upx.getMessage ();
if (uploadErr == null || uploadErr.length () == 0)
uploadErr = upx.toString ();
reqtrans.set ("uploadError", uploadErr);
}
}
ResponseTrans restrans = null;
restrans = app.execute (reqtrans);
writeResponse (response, restrans, cookies, protocol);
} catch (Exception x) {
x.printStackTrace ();
try {
response.setContentType ("text/html");
Writer out = response.getWriter ();
if (debug)
out.write ("<b>Error:</b><br>" +x);
else
out.write ("This server is temporarily unavailable. Please check back later.");
out.flush ();
} catch (Exception io_e) {}
}
}
private void writeResponse (HttpServletResponse res, ResponseTrans trans, Cookie[] cookies, String protocol) {
for (int i = 0; i < trans.countCookies(); i++) try {
Cookie c = new Cookie(trans.getKeyAt(i), trans.getValueAt(i));
c.setPath ("/");
if (cookieDomain != null)
c.setDomain (cookieDomain);
int expires = trans.getDaysAt(i);
if (expires > 0)
c.setMaxAge(expires * 60*60*24); // Cookie time to live, days -> seconds
res.addCookie(c);
} catch (Exception ign) {}
if (trans.redirect != null) {
try {
res.sendRedirect(trans.redirect);
} catch(Exception io_e) {}
} else {
if (!trans.cache || ! caching) {
// Disable caching of response.
if (protocol == null || !protocol.endsWith ("1.1"))
res.setHeader ("Pragma", "no-cache"); // for HTTP 1.0
else
res.setHeader ("Cache-Control", "no-cache"); // for HTTP 1.1
}
res.setStatus( HttpServletResponse.SC_OK );
res.setContentLength (trans.getContentLength ());
res.setContentType (trans.contentType);
try {
Writer writer = res.getWriter ();
writer.write (trans.getContentString ());
writer.flush ();
} catch(Exception io_e) { System.out.println ("Error in writeResponse: "+io_e); }
}
}
private void redirectResponse (HttpServletRequest request, HttpServletResponse res, ResponseTrans trans, String url) {
try {
res.sendRedirect(url);
} catch (Exception e) {
System.err.println ("Exception bei redirect: " + e + e.getMessage());
}
}
public Uploader getUpload (HttpServletRequest request) throws Exception {
return getUpload (500, request);
}
public Uploader getUpload (int maxKbytes, HttpServletRequest request) throws Exception {
int contentLength = request.getContentLength ();
BufferedInputStream in = new BufferedInputStream (request.getInputStream ());
Uploader up = null;
if (contentLength > maxKbytes*1024) {
throw new RuntimeException ("Upload exceeds limit of "+maxKbytes+" kb.");
}
String contentType = request.getContentType ();
up = new Uploader(maxKbytes);
up.load (in, contentType, contentLength);
return up;
}
public Object getUploadPart(Uploader up, String name) {
return up.getParts().get(name);
}
public String getServletInfo(){
return new String("Helma ServletClient");
}
private String trim (String str) {
if (str == null)
return null;
char[] val = str.toCharArray ();
int len = val.length;
int st = 0;
while ((st < len) && (val[st] <= ' ' || val[st] == '/')) {
st++;
}
while ((st < len) && (val[len - 1] <= ' ' || val[len - 1] == '/')) {
len--;
}
return ((st > 0) || (len < val.length)) ? new String (val, st, len-st) : str;
}
}

View file

@ -0,0 +1,312 @@
// ServletClient.java
// Copyright (c) Hannes Wallnöfer, Raphael Spannocchi 1998-2000
/* Portierung von helma.asp.AspClient auf Servlets */
/* Author: Raphael Spannocchi Datum: 27.11.1998 */
package helma.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.util.*;
import helma.framework.*;
import helma.objectmodel.Node;
import helma.util.*;
/**
* This is the HOP servlet adapter. This class communicates with hop applications
* via RMI.
*/
public class ServletClient extends HttpServlet{
private String host = null;
private int port = 0;
private int uploadLimit; // limit to HTTP uploads in kB
private Hashtable apps;
private String appName;
private String appUrl;
private String cookieDomain;
private boolean caching;
private boolean debug;
public void init (ServletConfig init) {
apps = new Hashtable();
appName = init.getInitParameter ("application");
if (appName == null) appName = "base";
host = init.getInitParameter ("host");
if (host == null) host = "localhost";
String portstr = init.getInitParameter ("port");
port = portstr == null ? 5055 : Integer.parseInt (portstr);
String upstr = init.getInitParameter ("uploadLimit");
uploadLimit = upstr == null ? 500 : Integer.parseInt (upstr);
cookieDomain = init.getInitParameter ("cookieDomain");
appUrl = "//" + host + ":" + port + "/";
debug = ("true".equalsIgnoreCase (init.getInitParameter ("debug")));
caching = ! ("false".equalsIgnoreCase (init.getInitParameter ("caching")));
}
private IRemoteApp getApp (String appID) throws Exception {
IRemoteApp retval = (IRemoteApp) apps.get (appID);
if (retval != null) {
return retval;
}
retval = (IRemoteApp) Naming.lookup (appUrl + appID);
apps.put (appID, retval);
return retval;
}
public void doGet (HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
execute (appName, request, response);
}
public void doPost (HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
execute (appName, request, response);
}
// not used anymore
private void get(String appID, HttpServletRequest request, HttpServletResponse response) {
}
private void execute (String appID, HttpServletRequest request, HttpServletResponse response) {
String protocol = request.getProtocol ();
Cookie[] cookies = request.getCookies();
try {
RequestTrans reqtrans = new RequestTrans ();
if (cookies != null) {
for (int i=0; i < cookies.length;i++) try { // get Cookies
String nextKey = cookies[i].getName ();
String nextPart = cookies[i].getValue ();
if ("HopSession".equals (nextKey))
reqtrans.session = nextPart;
else
reqtrans.set (nextKey, nextPart);
} catch (Exception badCookie) {}
}
// check if we need to create a session id
if (reqtrans.session == null) {
reqtrans.session = Long.toString (Math.round (Math.random ()*Long.MAX_VALUE), 16);
reqtrans.session += "@"+Long.toString (System.currentTimeMillis (), 16);
Cookie c = new Cookie("HopSession", reqtrans.session);
c.setPath ("/");
if (cookieDomain != null)
c.setDomain (cookieDomain);
response.addCookie(c);
}
// get optional path info
String pathInfo = request.getPathInfo ();
if (pathInfo != null)
reqtrans.path = trim (pathInfo);
else
reqtrans.path = "";
String host = request.getHeader ("Host");
if (host != null) {
host = host.toLowerCase();
reqtrans.set ("http_host", host);
}
String referer = request.getHeader ("Referer");
if (referer != null)
reqtrans.set ("http_referer", referer);
String remotehost = request.getRemoteAddr ();
if (remotehost != null)
reqtrans.set ("http_remotehost", remotehost);
String browser = request.getHeader ("User-Agent");
if (browser != null)
reqtrans.set ("http_browser", browser);
for (Enumeration e = request.getParameterNames(); e.hasMoreElements(); ) {
// Params parsen
String nextKey = (String)e.nextElement();
String[] paramValues = request.getParameterValues(nextKey);
String nextValue = paramValues[0]; // Only take first value
reqtrans.set (nextKey, nextValue); // generic Header, Parameter
}
String contentType = request.getContentType();
if (contentType != null && contentType.indexOf("multipart/form-data")==0) {
// File Upload
Uploader up;
try {
if ((up = getUpload (uploadLimit, request)) != null) {
Hashtable upload = up.getParts ();
for (Enumeration e = upload.keys(); e.hasMoreElements(); ) {
String nextKey = (String) e.nextElement ();
Object nextPart = upload.get (nextKey);
reqtrans.set (nextKey, nextPart);
}
}
} catch (Exception upx) {
String uploadErr = upx.getMessage ();
if (uploadErr == null || uploadErr.length () == 0)
uploadErr = upx.toString ();
reqtrans.set ("uploadError", uploadErr);
}
}
// get RMI ref to application and execute request
IRemoteApp app = getApp (appID);
ResponseTrans restrans = null;
try {
restrans = app.execute (reqtrans);
} catch (RemoteException cnx) {
apps.remove (appID);
app = getApp (appID);
app.ping ();
restrans = app.execute (reqtrans);
}
writeResponse (response, restrans, cookies, protocol);
} catch (Exception x) {
apps.remove (appID);
try {
response.setContentType ("text/html");
Writer out = response.getWriter ();
if (debug)
out.write ("<b>Error:</b><br>" +x);
else
out.write ("This server is temporarily unavailable. Please check back later.");
out.flush ();
} catch (Exception io_e) {}
}
}
private void writeResponse (HttpServletResponse res, ResponseTrans trans, Cookie[] cookies, String protocol) {
for (int i = 0; i < trans.countCookies(); i++) try {
Cookie c = new Cookie(trans.getKeyAt(i), trans.getValueAt(i));
c.setPath ("/");
if (cookieDomain != null)
c.setDomain (cookieDomain);
int expires = trans.getDaysAt(i);
if (expires > 0)
c.setMaxAge(expires * 60*60*24); // Cookie time to live, days -> seconds
res.addCookie(c);
} catch (Exception ign) {}
if (trans.redirect != null) {
try {
res.sendRedirect(trans.redirect);
} catch(Exception io_e) {}
} else {
if (!trans.cache || ! caching) {
// Disable caching of response.
if (protocol == null || !protocol.endsWith ("1.1"))
res.setHeader ("Pragma", "no-cache"); // for HTTP 1.0
else
res.setHeader ("Cache-Control", "no-cache"); // for HTTP 1.1
}
res.setContentLength (trans.getContentLength ());
res.setContentType (trans.contentType);
try {
Writer writer = res.getWriter ();
writer.write (trans.getContentString ());
writer.flush ();
} catch(Exception io_e) {}
}
}
private void redirectResponse (HttpServletRequest request, HttpServletResponse res, ResponseTrans trans, String url) {
try {
res.sendRedirect(url);
} catch (Exception e) {
System.err.println ("Exception at redirect: " + e + e.getMessage());
}
}
public Uploader getUpload (HttpServletRequest request) throws Exception {
return getUpload (500, request);
}
public Uploader getUpload (int maxKbytes, HttpServletRequest request) throws Exception {
int contentLength = request.getContentLength ();
BufferedInputStream in = new BufferedInputStream (request.getInputStream ());
Uploader up = null;
try {
if (contentLength > maxKbytes*1024) {
// consume all input to make Apache happy
byte b[] = new byte[1024];
int read = 0;
while (read > -1)
read = in.read (b, 0, 1024);
throw new RuntimeException ("Upload exceeds limit of "+maxKbytes+" kb.");
}
String contentType = request.getContentType ();
up = new Uploader(maxKbytes);
up.load (in, contentType, contentLength);
} finally {
try { in.close (); } catch (Exception ignore) {}
}
return up;
}
public Object getUploadPart(Uploader up, String name) {
return up.getParts().get(name);
}
public String getServletInfo(){
return new String("Helma ServletClient");
}
private String trim (String str) {
if (str == null)
return null;
char[] val = str.toCharArray ();
int len = val.length;
int st = 0;
while ((st < len) && (val[st] <= ' ' || val[st] == '/')) {
st++;
}
while ((st < len) && (val[len - 1] <= ' ' || val[len - 1] == '/')) {
len--;
}
return ((st > 0) || (len < val.length)) ? new String (val, st, len-st) : str;
}
}

View file

@ -0,0 +1,365 @@
// HtmlEncoder.java
// Copyright (c) Hannes Wallnöfer 1997-2000
package helma.util;
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.image.*;
import java.text.*;
/**
* This is a utility class to encode special characters and do formatting
* for HTML output.
*/
public final class HtmlEncoder {
static final Hashtable convertor = new Hashtable (128);
// conversion table
static {
convertor.put(new Integer(160), "&nbsp;");
convertor.put(new Integer(161), "&iexcl;");
convertor.put(new Integer(162), "&cent;");
convertor.put(new Integer(163), "&pound;");
convertor.put(new Integer(164), "&curren;");
convertor.put(new Integer(165), "&yen;");
convertor.put(new Integer(166), "&brvbar;");
convertor.put(new Integer(167), "&sect;");
convertor.put(new Integer(168), "&uml;");
convertor.put(new Integer(169), "&copy;");
convertor.put(new Integer(170), "&ordf;");
convertor.put(new Integer(171), "&laquo;");
convertor.put(new Integer(172), "&not;");
convertor.put(new Integer(173), "&shy;");
convertor.put(new Integer(174), "&reg;");
convertor.put(new Integer(175), "&macr;");
convertor.put(new Integer(176), "&deg;");
convertor.put(new Integer(177), "&plusmn;");
convertor.put(new Integer(178), "&sup2;");
convertor.put(new Integer(179), "&sup3;");
convertor.put(new Integer(180), "&acute;");
convertor.put(new Integer(181), "&micro;");
convertor.put(new Integer(182), "&para;");
convertor.put(new Integer(183), "&middot;");
convertor.put(new Integer(184), "&cedil;");
convertor.put(new Integer(185), "&sup1;");
convertor.put(new Integer(186), "&ordm;");
convertor.put(new Integer(187), "&raquo;");
convertor.put(new Integer(188), "&frac14;");
convertor.put(new Integer(189), "&frac12;");
convertor.put(new Integer(190), "&frac34;");
convertor.put(new Integer(191), "&iquest;");
convertor.put(new Integer(192), "&Agrave;");
convertor.put(new Integer(193), "&Aacute;");
convertor.put(new Integer(194), "&Acirc;");
convertor.put(new Integer(195), "&Atilde;");
convertor.put(new Integer(196), "&Auml;");
convertor.put(new Integer(197), "&Aring;");
convertor.put(new Integer(198), "&AElig;");
convertor.put(new Integer(199), "&Ccedil;");
convertor.put(new Integer(200), "&Egrave;");
convertor.put(new Integer(201), "&Eacute;");
convertor.put(new Integer(202), "&Ecirc;");
convertor.put(new Integer(203), "&Euml;");
convertor.put(new Integer(204), "&Igrave;");
convertor.put(new Integer(205), "&Iacute;");
convertor.put(new Integer(206), "&Icirc;");
convertor.put(new Integer(207), "&Iuml;");
convertor.put(new Integer(208), "&ETH;");
convertor.put(new Integer(209), "&Ntilde;");
convertor.put(new Integer(210), "&Ograve;");
convertor.put(new Integer(211), "&Oacute;");
convertor.put(new Integer(212), "&Ocirc;");
convertor.put(new Integer(213), "&Otilde;");
convertor.put(new Integer(214), "&Ouml;");
convertor.put(new Integer(215), "&times;");
convertor.put(new Integer(216), "&Oslash;");
convertor.put(new Integer(217), "&Ugrave;");
convertor.put(new Integer(218), "&Uacute;");
convertor.put(new Integer(219), "&Ucirc;");
convertor.put(new Integer(220), "&Uuml;");
convertor.put(new Integer(221), "&Yacute;");
convertor.put(new Integer(222), "&THORN;");
convertor.put(new Integer(223), "&szlig;");
convertor.put(new Integer(224), "&agrave;");
convertor.put(new Integer(225), "&aacute;");
convertor.put(new Integer(226), "&acirc;");
convertor.put(new Integer(227), "&atilde;");
convertor.put(new Integer(228), "&auml;");
convertor.put(new Integer(229), "&aring;");
convertor.put(new Integer(230), "&aelig;");
convertor.put(new Integer(231), "&ccedil;");
convertor.put(new Integer(232), "&egrave;");
convertor.put(new Integer(233), "&eacute;");
convertor.put(new Integer(234), "&ecirc;");
convertor.put(new Integer(235), "&euml;");
convertor.put(new Integer(236), "&igrave;");
convertor.put(new Integer(237), "&iacute;");
convertor.put(new Integer(238), "&icirc;");
convertor.put(new Integer(239), "&iuml;");
convertor.put(new Integer(240), "&eth;");
convertor.put(new Integer(241), "&ntilde;");
convertor.put(new Integer(242), "&ograve;");
convertor.put(new Integer(243), "&oacute;");
convertor.put(new Integer(244), "&ocirc;");
convertor.put(new Integer(245), "&otilde;");
convertor.put(new Integer(246), "&ouml;");
convertor.put(new Integer(247), "&divide;");
convertor.put(new Integer(248), "&oslash;");
convertor.put(new Integer(249), "&ugrave;");
convertor.put(new Integer(250), "&uacute;");
convertor.put(new Integer(251), "&ucirc;");
convertor.put(new Integer(252), "&uuml;");
convertor.put(new Integer(253), "&yacute;");
convertor.put(new Integer(254), "&thorn;");
convertor.put(new Integer(255), "&yuml;");
}
/**
*
*/
public final static String encode (String what) {
// try to make stringbuffer large enough from the start
StringBuffer ret = new StringBuffer (Math.round (what.length()*1.4f));
encode (what, ret);
return ret.toString();
}
/**
*
*/
public final static void encode (String what, StringBuffer ret) {
if (what == null || what.length() == 0) {
return;
}
StringReader in = new StringReader (what);
int c;
boolean closeTag=false, readTag=false, tagOpen=false;
boolean ignoreNewline = false, swallow = false;
StringBuffer tag = new StringBuffer ();
try {
while ((c = in.read()) != -1) {
if (readTag) {
if (Character.isLetterOrDigit ((char) c))
tag.append ((char) c);
else if ('/' == c)
closeTag = true;
else {
String t = tag.toString ();
// set ignoreNewline on some tags, depending on wheather they're
// being opened or closed.
if ("td".equalsIgnoreCase (t)) {
ignoreNewline = closeTag;
swallow = true; // for some reason, it's a good idea to swallow (ignore) newlines after some tags
} else if ("th".equalsIgnoreCase (t)) {
ignoreNewline = closeTag;
swallow = true;
} else if ("table".equalsIgnoreCase (t)) {
ignoreNewline = !closeTag;
swallow = true;
} else if ("ul".equalsIgnoreCase (t)) {
ignoreNewline = !closeTag;
swallow = true;
} else if ("ol".equalsIgnoreCase (t)) {
ignoreNewline = !closeTag;
swallow = true;
} else if ("li".equalsIgnoreCase (t)) {
swallow = true;
ignoreNewline = closeTag;
} else if ("p".equalsIgnoreCase (t)) {
swallow = true;
}
readTag = false;
closeTag = false;
tag.setLength (0);
}
} // if (readTag)
switch (c) {
// case '&':
// ret.append ("&amp;");
// break;
case '\n':
if (!ignoreNewline && !swallow)
ret.append ("<br>");
ret.append ('\n');
if (!tagOpen)
swallow = false;
break;
case '<':
closeTag = false;
readTag = true;
tagOpen = true;
ret.append ('<');
break;
case '>':
tagOpen = false;
ret.append ('>');
break;
default:
if (c < 160)
ret.append ((char) c);
else if (c >= 160 && c <= 255)
ret.append (convertor.get(new Integer(c)));
else {
ret.append ("&#");
ret.append (c);
ret.append (";");
}
if (!tagOpen && !Character.isWhitespace ((char)c))
swallow = false;
}
}
} catch (IOException e) {}
}
/**
*
*/
public final static String encodeFormValue (String what) {
StringBuffer ret = new StringBuffer (Math.round (what.length()*1.4f));
encodeAll (what, ret, false);
return ret.toString();
}
/**
*
*/
public final static String encodeAll (String what) {
StringBuffer ret = new StringBuffer (Math.round (what.length()*1.4f));
encodeAll (what, ret, true);
return ret.toString();
}
/**
*
*/
public final static String encodeAll (String what, StringBuffer ret) {
encodeAll (what, ret, true);
return ret.toString();
}
/**
*
*/
public final static void encodeAll (String what, StringBuffer ret, boolean encodeNewline) {
if (what == null || what.length() == 0) {
return;
}
StringReader in = new StringReader (what);
int c;
try {
while ((c = in.read()) != -1) {
switch (c) {
case '<' :
ret.append ("&lt;");
break;
case '>':
ret.append ("&gt;");
break;
case '&':
ret.append ("&amp;");
break;
case '"':
ret.append ("&quot;");
break;
case '\n':
if (encodeNewline) {
ret.append ("<br>");
break;
}
default:
if (c < 160)
ret.append ((char) c);
else if (c >= 160 && c <= 255)
ret.append (convertor.get(new Integer(c)));
else {
ret.append ("&#");
ret.append (c);
ret.append (";");
}
}
}
} catch (IOException e) {}
}
public final static String encodeSoft (String what) {
StringBuffer ret = new StringBuffer (Math.round (what.length()*1.4f));
encodeSoft (what, ret);
return ret.toString();
}
public final static void encodeSoft (String what, StringBuffer ret) {
if (what == null || what.length() == 0) {
return;
}
StringReader in = new StringReader (what);
int c;
try {
while ((c = in.read()) != -1) {
switch (c) {
case 128: // Euro-Symbol. This is for missing Unicode support in TowerJ.
ret.append ("&#8364;");
break;
default:
if (c < 160)
ret.append ((char) c);
else if (c >= 160 && c <= 255)
ret.append (convertor.get(new Integer(c)));
else {
ret.append ("&#");
ret.append (c);
ret.append (";");
}
}
}
} catch (IOException e) {}
}
public final static String encodeXml (String what) {
StringBuffer ret = new StringBuffer (Math.round (what.length()*1.4f));
encodeXml (what, ret);
return ret.toString();
}
public final static void encodeXml (String what, StringBuffer ret) {
if (what == null || what.length() == 0) {
return;
}
StringReader in = new StringReader (what);
int c;
try {
while ((c = in.read()) != -1) {
switch (c) {
case '<' :
ret.append ("&lt;");
break;
case '>':
ret.append ("&gt;");
break;
case '&':
ret.append ("&amp;");
break;
default:
ret.append ((char) c);
}
}
} catch (IOException e) {}
}
}

View file

@ -0,0 +1,56 @@
// InetAddressFilter.java
// Copyright (c) Hannes Wallnöfer 1998-2000
package helma.util;
import java.io.*;
import java.net.*;
import java.util.*;
/**
* A class for paranoid servers to filter IP addresses.
*/
public class InetAddressFilter {
Vector patterns;
public InetAddressFilter () {
patterns = new Vector ();
}
public void addAddress (String address) throws IOException {
int pattern[] = new int[4];
StringTokenizer st = new StringTokenizer (address, ".");
if (st.countTokens () != 4)
throw new IOException ("\""+address+"\" does not represent a valid IP address");
for (int i=0; i<4; i++) {
String next = st.nextToken ();
if ("*".equals (next))
pattern[i] = 256;
else
pattern[i] = (byte) Integer.parseInt (next);
}
patterns.addElement (pattern);
}
public boolean matches (InetAddress address) {
if (address == null)
return false;
byte add[] = address.getAddress ();
if (add == null)
return false;
int l = patterns.size();
for (int k=0; k<l; k++) {
int pattern[] = (int[]) patterns.elementAt (k);
for (int i=0; i<4; i++) {
if (pattern[i] < 255 && pattern[i] != add[i]) // not wildcard and doesn't match
break;
if (i == 3)
return true;
}
}
return false;
}
}

103
src/helma/util/Logger.java Normal file
View file

@ -0,0 +1,103 @@
// Logger.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.util;
import java.io.*;
import java.util.*;
import java.text.*;
/**
* Utility class for asynchronous logging.
*/
public class Logger implements Runnable {
private Thread logger;
private Vector entries;
private String filename;
private String dirname;
private File dir;
private File currentFile;
private PrintWriter currentWriter;
private int fileindex = 0;
private DecimalFormat nformat;
private DateFormat dformat;
private long dateLastRendered;
private String dateCache;
private PrintStream out = null;
public Logger (PrintStream out) {
dformat = DateFormat.getInstance ();
this.out = out;
}
public Logger (String dirname, String filename) throws IOException {
if (filename == null || dirname == null)
throw new IOException ("Logger can't use null as file or directory name");
this.filename = filename;
this.dirname = dirname;
nformat = new DecimalFormat ("00000");
dformat = DateFormat.getInstance ();
dir = new File (dirname);
if (!dir.exists())
dir.mkdirs ();
currentFile = new File (dir, filename+nformat.format(++fileindex)+".log");
while (currentFile.exists())
currentFile = new File (dir, filename+nformat.format(++fileindex)+".log");
currentWriter = new PrintWriter (new FileWriter (currentFile), false);
entries = new Vector ();
logger = new Thread (this);
// logger.setPriority (Thread.MIN_PRIORITY+2);
logger.start ();
}
public void log (String msg) {
// it's enough to render the date every 15 seconds
if (System.currentTimeMillis () - 15000 > dateLastRendered)
renderDate ();
// log directly to printstream or to buffer?
if (out == null)
entries.addElement (dateCache + " " + msg);
else
out.println (dateCache + " " + msg);
}
private void renderDate () {
dateCache = dformat.format (new Date());
dateLastRendered = System.currentTimeMillis ();
}
public void run () {
while (Thread.currentThread () == logger) {
try {
if (currentFile.length() > 10000000) {
// rotate log files each 10 megs
swapFile ();
}
int l = entries.size();
for (int i=0; i<l; i++) {
Object entry = entries.elementAt (0);
entries.removeElementAt (0);
currentWriter.println (entry.toString());
}
currentWriter.flush ();
logger.sleep (1000l);
} catch (InterruptedException ir) {
Thread.currentThread().interrupt ();
}
}
}
private void swapFile () {
try {
currentWriter.close();
currentFile = new File (dir, filename+nformat.format(++fileindex)+".log");
currentWriter = new PrintWriter (new FileWriter (currentFile), false);
} catch (IOException iox) {
System.err.println ("Error swapping Log files: "+iox);
}
}
}

View file

@ -0,0 +1,50 @@
// ParanoidServerSocket.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.util;
import java.net.*;
import java.io.IOException;
/**
* A server socket that can allow connections to only a few selected hosts.
*/
public class ParanoidServerSocket extends ServerSocket {
private InetAddressFilter filter;
public ParanoidServerSocket (int port) throws IOException {
super (port);
}
public ParanoidServerSocket (int port, InetAddressFilter filter) throws IOException {
super (port);
this.filter = filter;
}
public Socket accept () throws IOException {
Socket s = null;
while (s == null) {
s = super.accept ();
if (filter != null && !filter.matches (s.getInetAddress ())) {
System.err.println ("Refusing connection from "+s.getInetAddress ());
try {
s.close();
} catch (IOException ignore) {}
s = null;
}
}
return s;
}
public void setFilter (InetAddressFilter filter) {
this.filter = filter;
}
public InetAddressFilter getFilter () {
return this.filter;
}
}

70
src/helma/util/Timer.java Normal file
View file

@ -0,0 +1,70 @@
// Timer.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.util;
import java.util.*;
import java.io.PrintStream;
/**
* Utility class for timing a series of events
*/
public class Timer {
private Vector timeline;
private Hashtable events;
public Timer () {
timeline = new Vector ();
events = new Hashtable ();
}
public void reset () {
timeline.setSize (0);
events.clear ();
}
public void beginEvent (String name) {
timeline.addElement (name);
events.put (name, new Event (name));
}
public void endEvent (String name) {
Event event = (Event) events.get (name);
if (event != null)
event.terminate ();
}
public void dump (PrintStream out) {
for (int i=0; i<timeline.size(); i++) {
String name = (String) timeline.elementAt (i);
Event event = (Event) events.get (name);
out.println (event);
}
}
class Event {
String name;
long start, end;
Event (String name) {
this.name = name;
start = System.currentTimeMillis ();
}
void terminate () {
end = System.currentTimeMillis ();
}
public String toString () {
long now = System.currentTimeMillis ();
if (end == 0l)
return (" + "+(now-start)+" "+name);
else
return (" "+(end-start)+" "+name);
}
}
}

View file

@ -0,0 +1,110 @@
// Uploader.java
// Copyright (c) Hannes Wallnöfer 1996-2000
package helma.util;
import helma.mime.*;
import helma.objectmodel.*;
import java.io.*;
import java.util.*;
/**
* Utility class for file uploads via HTTP POST.
*/
public class Uploader {
public Hashtable parts;
int maxKbytes;
public Uploader () {
maxKbytes = 500;
}
public Uploader (int max) {
maxKbytes = max;
}
public Hashtable getParts () {
return parts;
}
public void load (InputStream is, String contentType, int contentLength) throws Exception {
parts = new Hashtable ();
String boundary = getSubHeader (contentType, "boundary");
if (boundary == null)
throw new MimeParserException ("Error parsing MIME input stream.");
if (maxKbytes > -1 && contentLength > maxKbytes*1024)
throw new IOException ("Size of upload exceeds limit of " + maxKbytes + " kB.");
byte b[] = new byte[contentLength];
MultipartInputStream in = new MultipartInputStream (new BufferedInputStream (is), boundary.getBytes ());
while (in.nextInputStream ()) {
MimeParser parser = new MimeParser (in, new MimeHeadersFactory ());
MimeHeaders headers = (MimeHeaders) parser.parse ();
InputStream bodystream = parser.getInputStream ();
int read, count = 0;
while ((read = bodystream.read (b, count, 4096)) > -1) {
count += read;
if (count == b.length) {
byte newb[] = new byte[count+4096];
System.arraycopy (b, 0, newb, 0, count);
b = newb;
}
}
byte newb[] = new byte[count];
System.arraycopy (b, 0, newb, 0, count);
String type = headers.getValue("Content-Type");
String disposition = headers.getValue ("Content-Disposition");
String name = getSubHeader (disposition, "name");
String filename = getSubHeader (disposition, "filename");
if (filename != null) {
int sep = filename.lastIndexOf ("\\");
if (sep > -1)
filename = filename.substring (sep+1);
sep = filename.lastIndexOf ("/");
if (sep > -1)
filename = filename.substring (sep+1);
}
if (filename != null) {
Node node = new Node (filename);
node.setContent (newb, type);
parts.put (name, node);
} else {
parts.put (name, new String (newb));
}
}
}
private String getSubHeader (String header, String subHeaderName) {
if (header == null)
return null;
String retval = null;
StringTokenizer headerTokenizer = new StringTokenizer(header, ";");
while (headerTokenizer.hasMoreTokens()) {
String token = headerTokenizer.nextToken().trim ();
int i = token.indexOf ("=");
if (i > 0) {
String hname = token.substring (0, i).trim ();
if (hname.equalsIgnoreCase (subHeaderName))
retval = token.substring (i+1).replace ('"', ' ').trim ();
}
}
return retval;
}
}

View file

@ -0,0 +1,126 @@
/*
* @(#)URLEncoder.java 1.12 98/07/01
*
* Copyright 1995-1998 by Sun Microsystems, Inc.,
* 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
* All rights reserved.
*
* This software is the confidential and proprietary information
* of Sun Microsystems, Inc. ("Confidential Information"). You
* shall not disclose such Confidential Information and shall use
* it only in accordance with the terms of the license agreement
* you entered into with Sun.
*/
// Repackaged by Hannes Wallnoefer because this is the only way to
// encode space as %20 (no, subclassing doesn't work here).
package helma.util;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.util.BitSet;
/**
* The class contains a utility method for converting a
* <code>String</code> into a MIME format called
* "<code>x-www-form-urlencoded</code>" format.
* <p>
* To convert a <code>String</code>, each character is examined in turn:
* <ul>
* <li>The ASCII characters '<code>a</code>' through '<code>z</code>',
* '<code>A</code>' through '<code>Z</code>', and '<code>0</code>'
* through '<code>9</code>' remain the same.
* <li>All other characters are converted into the 3-character string
* "<code>%<i>xy</i></code>", where <i>xy</i> is the two-digit
* hexadecimal representation of the lower 8-bits of the character.
* </ul>
*
* @author Herb Jellinek
* @version 1.12, 07/01/98
* @since JDK1.0
*/
public class UrlEncoder {
static BitSet dontNeedEncoding;
static final int caseDiff = ('a' - 'A');
/* The list of characters that are not encoded have been determined by
referencing O'Reilly's "HTML: The Definitive Guide" (page 164). */
static {
dontNeedEncoding = new BitSet(256);
int i;
for (i = 'a'; i <= 'z'; i++) {
dontNeedEncoding.set(i);
}
for (i = 'A'; i <= 'Z'; i++) {
dontNeedEncoding.set(i);
}
for (i = '0'; i <= '9'; i++) {
dontNeedEncoding.set(i);
}
// dontNeedEncoding.set(' '); /* removed to encode space as %20 */
dontNeedEncoding.set('-');
dontNeedEncoding.set('_');
dontNeedEncoding.set('.');
dontNeedEncoding.set('*');
}
/**
* You can't call the constructor.
*/
// private URLEncoder() { }
/**
* Translates a string into <code>x-www-form-urlencoded</code> format.
*
* @param s <code>String</code> to be translated.
* @return the translated <code>String</code>.
* @since JDK1.0
*/
public static String encode(String s) {
int maxBytesPerChar = 10;
ByteArrayOutputStream out = new ByteArrayOutputStream(s.length());
ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
OutputStreamWriter writer = new OutputStreamWriter(buf);
for (int i = 0; i < s.length(); i++) {
int c = (int)s.charAt(i);
if (dontNeedEncoding.get(c)) {
if (c == ' ') {
c = '+';
}
out.write(c);
} else {
// convert to external encoding before hex conversion
try {
writer.write(c);
writer.flush();
} catch(IOException e) {
buf.reset();
continue;
}
byte[] ba = buf.toByteArray();
for (int j = 0; j < ba.length; j++) {
out.write('%');
char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
// converting to use uppercase letter as part of
// the hex value if ch is a letter.
if (Character.isLetter(ch)) {
ch -= caseDiff;
}
out.write(ch);
ch = Character.forDigit(ba[j] & 0xF, 16);
if (Character.isLetter(ch)) {
ch -= caseDiff;
}
out.write(ch);
}
buf.reset();
}
}
return out.toString();
}
}

View file

@ -0,0 +1,350 @@
// WebBroadcaster.java
// Copyright (c) Hannes Wallnöfer 1999-2000
package helma.util;
import java.io.*;
import java.net.*;
import java.util.*;
/**
* A utility hack to do "html web broadcasts".
*/
public class WebBroadcaster implements Runnable {
private Vector connections;
private ServerSocket serverSocket;
private Thread listener;
private boolean paranoid;
static String lastResult = "";
private Vector accept, deny;
static String reloadJS = "<html>\r\n<head><SCRIPT language=JavaScript>\r\n<!--\r\nfunction reload (url) {\r\n window.location.href=url;\r\n}\r\n//-->\r\n</SCRIPT>\r\n";
static String scrollJS = "<SCRIPT language=JavaScript>\r\n<!--\r\nfunction scroller () {\r\n window.scroll(1, 500000);\r\n window.setTimeout(\"scroller()\", 100);\r\n}\r\nscroller();\r\n//-->\r\n</SCRIPT>\r\n</head>\r\n<body>\r\n";
long time;
int last;
/**
*
*/
public static void main (String args[]) {
System.out.println ("Usage: java helma.util.WebBroadcaster [port]");
int p = 8080;
if (args.length > 0) try {
p = Integer.parseInt (args[0]);
} catch (NumberFormatException nfx) {
System.out.println ("Error parsing port number: "+args[0]);
}
try {
WebBroadcaster server = new WebBroadcaster (p);
// webserver.setParanoid (false);
// webserver.acceptClient ("192.168.*.*");
System.out.println ("started web broadcast server on port "+p);
} catch (IOException x) {
System.out.println ("Error creating web broadcast server: "+x);
}
}
/**
* Creates a Web server at the specified port number.
*/
public WebBroadcaster (int port) throws IOException {
super();
connections = new Vector ();
accept = new Vector ();
deny = new Vector ();
// make a new server socket with extra large queue size
this.serverSocket = new ServerSocket (port, 2000);
listener = new Thread (this);
listener.start ();
}
public void broadcast (String message) {
long start = System.currentTimeMillis ();
int l = connections.size ();
synchronized (this) {
if (l != last) {
System.out.println ("broadcasting to "+l+" clients in "+time+" millis.");
last = l;
}
}
for (int i=l-1; i>=0; i--) {
try {
Connection c = (Connection) connections.elementAt (i);
c.send (message);
} catch (Exception ignore) {}
}
time = System.currentTimeMillis () - start;
}
/**
* Switch client filtering on/off.
* @see acceptClient(java.lang.String)
* @see denyClient(java.lang.String)
*/
public void setParanoid (boolean p) {
paranoid = p;
}
/**
* Add an IP address to the list of accepted clients. The parameter can contain '*' as wildcard
* character, e.g. "192.168.*.*". You must call setParanoid(true) in order for this to have any
* effect.
*
* @see denyClient(java.lang.String)
* @see setParanoid(boolean)
*/
public void acceptClient (String address) throws IllegalArgumentException {
try {
AddressMatcher m = new AddressMatcher (address);
accept.addElement (m);
} catch (Exception x) {
throw new IllegalArgumentException ("\""+address+"\" does not represent a valid IP address");
}
}
/**
* Add an IP address to the list of denied clients. The parameter can contain '*' as wildcard
* character, e.g. "192.168.*.*". You must call setParanoid(true) in order for this to have any
* effect.
*
* @see acceptClient(java.lang.String)
* @see setParanoid(boolean)
*/
public void denyClient (String address) throws IllegalArgumentException {
try {
AddressMatcher m = new AddressMatcher (address);
deny.addElement (m);
} catch (Exception x) {
throw new IllegalArgumentException ("\""+address+"\" does not represent a valid IP address");
}
}
private boolean checkSocket (Socket s) {
int l = deny.size ();
byte address[] = s.getInetAddress ().getAddress ();
for (int i=0; i<l; i++) {
AddressMatcher match = (AddressMatcher) deny.elementAt (i);
if (match.matches (address))
return false;
}
l = accept.size ();
for (int i=0; i<l; i++) {
AddressMatcher match = (AddressMatcher) accept.elementAt (i);
if (match.matches (address))
return true;
}
return false;
}
/**
* Listens for client requests until stopped.
*/
public void run() {
Thread current = Thread.currentThread ();
try {
while (listener == current) {
Socket socket = serverSocket.accept();
try {
// if (!paranoid || checkSocket (socket))
new Connection (socket);
// else
// socket.close ();
} catch (Exception x) {
System.out.println ("Error in listener: "+x);
}
}
}
catch (Exception exception) {
System.err.println("Error accepting Web connections (" + exception + ").");
}
finally {
try {
serverSocket.close();
serverSocket = null;
}
catch (IOException ignore) {}
}
}
class Connection implements Runnable {
private Socket socket;
private InputStream input;
private BufferedWriter output;
private Thread responder;
public Connection (Socket socket) throws IOException {
super();
this.socket = socket;
socket.setSoTimeout (30000);
input = new BufferedInputStream (socket.getInputStream());
output = new BufferedWriter (new OutputStreamWriter(socket.getOutputStream()));
responder = new Thread (this);
responder.start();
}
/* public void run () {
Thread current = Thread.currentThread ();
if (current == cleaner) {
cleanup ();
} else if (current == responder) {
respond ();
}
} */
public void run () {
boolean newConnection = false;
try {
DataInputStream reader = new DataInputStream (input);
boolean keepalive = false;
int cycle = 0;
// implement keep-alive connections
do {
// if (cycle > 0) System.out.println ("Reusing connection: "+cycle);
String line = reader.readLine();
if (line == null) throw new IOException ("connection reset");
int contentLength = 0;
StringTokenizer tokens = new StringTokenizer(line);
String method = tokens.nextToken();
String uri = tokens.nextToken ();
String httpversion = tokens.nextToken ();
keepalive = "HTTP/1.1".equals (httpversion);
do {
// System.out.println (line);
line = reader.readLine();
if (line != null) {
line = line.toLowerCase ();
if (line.startsWith ("content-length:"))
contentLength = Integer.parseInt (line.substring (15).trim ());
if (line.startsWith ("connection:"))
keepalive = line.indexOf ("keep-alive") > -1;
}
} while (line != null && ! line.equals(""));
// System.out.println ("");
if ("GET".equals (method)) {
output.write (httpversion+" 200 OK\r\n");
output.write ("Server: helma.WebBroadcast\r\n");
output.write ("Content-Type: text/html\r\n");
newConnection = uri.startsWith ("/NEW");
if (!newConnection) {
output.write ("Content-Length: 5\r\n");
if (keepalive)
output.write ("Connection: keep-alive\r\n");
output.write ("\r\n");
output.write ("done.");
output.flush ();
cycle += 1;
if (uri.startsWith ("/MSG"))
broadcast (uri+"<br>\r\n");
continue;
}
output.write ("Connection: close\r\n");
output.write ("\r\n");
output.write (reloadJS);
output.write (scrollJS);
output.flush ();
connections.addElement (this);
} else {
output.write ("HTTP/1.0 400 Bad Request\r\n");
output.write ("Server: helma.WebBroadcast\r\n\r\n");
output.write ("Bad Request.");
// output.write (lastResult);
output.flush ();
keepalive = false;
}
} while (keepalive && !newConnection);
} catch (Exception x) {
System.out.print (".");
} finally {
if (newConnection) // leave connection open
return;
try {
output.close();
} catch (IOException ignore) {}
try {
input.close();
} catch (IOException ignore) {}
try {
socket.close();
} catch (IOException ignore) {}
}
}
public void cleanup () {
}
public synchronized void send (String message) {
try {
output.write (message);
output.flush ();
} catch (Exception exception) {
try {
connections.removeElement (this);
} catch (Exception ignore) {}
try {
output.close();
} catch (IOException ignore) {}
try {
input.close();
} catch (IOException ignore) {}
try {
socket.close();
} catch (IOException ignore) {}
}
}
public String toString () {
return socket.getInetAddress ().getHostName ();
}
}
class AddressMatcher {
int pattern[];
public AddressMatcher (String address) throws Exception {
pattern = new int[4];
StringTokenizer st = new StringTokenizer (address, ".");
if (st.countTokens () != 4)
throw new Exception ("\""+address+"\" does not represent a valid IP address");
for (int i=0; i<4; i++) {
String next = st.nextToken ();
if ("*".equals (next))
pattern[i] = 256;
else
pattern[i] = (byte) Integer.parseInt (next);
}
}
public boolean matches (byte address[]) {
for (int i=0; i<4; i++) {
if (pattern[i] > 255) // wildcard
continue;
if (pattern[i] != address[i])
return false;
}
return true;
}
}
}

View file

@ -0,0 +1,20 @@
/*
* Copyright 2000 Hannes Wallnoefer
*/
package helma.xmlrpc;
import java.util.Vector;
/**
* An XML-RPC handler that also handles user authentication.
*/
public interface AuthenticatedXmlRpcHandler {
/**
* Return the result, or throw an Exception if something went wrong.
*/
public Object execute (String method, Vector params, String user, String password) throws Exception;
}

View file

@ -0,0 +1,130 @@
package helma.xmlrpc;
/**
* Provides encoding of raw bytes to base64-encoded characters, and
* decoding of base64 characters to raw bytes.
*
* @author Kevin Kelley (kelley@iguana.ruralnet.net)
* @version 1.0
* @date 06 August 1998
*/
public class Base64
{
/**
* returns an array of base64-encoded characters to represent the
* passed data array.
*
* @param data the array of bytes to encode
* @return base64-coded character array.
*/
static public char[] encode(byte[] data) {
char[] out = new char[((data.length + 2) / 3) * 4];
//
// 3 bytes encode to 4 chars. Output is always an even
// multiple of 4 characters.
//
for (int i = 0, index = 0; i < data.length; i += 3,
index += 4) {
boolean quad = false;
boolean trip = false;
int val = (0xFF & (int) data[i]);
val <<= 8;
if ((i + 1) < data.length) {
val |= (0xFF & (int) data[i + 1]);
trip = true;
}
val <<= 8;
if ((i + 2) < data.length) {
val |= (0xFF & (int) data[i + 2]);
quad = true;
}
out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)];
val >>= 6;
out[index + 2] = alphabet[(trip ? (val & 0x3F) : 64)];
val >>= 6;
out[index + 1] = alphabet[val & 0x3F];
val >>= 6;
out[index + 0] = alphabet[val & 0x3F];
}
return out;
}
/**
* Returns an array of bytes which were encoded in the passed
* character array.
*
* @param data the array of base64-encoded characters
* @return decoded data array
*/
static public byte[] decode(byte[] data) {
int len = ((data.length + 3) / 4) * 3;
if (data.length > 0 && data[data.length - 1] == '=')
--len;
if (data.length > 1 && data[data.length - 2] == '=')
--len;
byte[] out = new byte[len];
int shift = 0; // # of excess bits stored in accum
int accum = 0; // excess bits
int index = 0;
for (int ix = 0; ix < data.length; ix++) {
int value = codes[data[ix] & 0xFF]; // ignore high byte of char
if (value >= 0) {
// skip over non-code
accum <<= 6; // bits shift up by 6 each time thru
shift += 6; // loop, with new bits being put in
accum |= value; // at the bottom.
if (shift >= 8) {
// whenever there are 8 or more shifted in,
shift -= 8; // write them out (from the top, leaving any
// excess at the bottom for next iteration.
out[index++] = (byte)((accum >> shift) & 0xff);
}
}
}
if (index != out.length) throw new RuntimeException("Error decoding BASE64 element: miscalculated data length!");
return out;
}
//
// code characters for values 0..63
//
static private char[] alphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
.toCharArray();
//
// lookup table for converting base64 characters to value in range 0..63
//
static private byte[] codes = new byte[256];
static {
for (int i = 0; i < 256; i++)
codes[i] = -1;
for (int i = 'A'; i <= 'Z'; i++)
codes[i] = (byte)(i - 'A');
for (int i = 'a'; i <= 'z'; i++)
codes[i] = (byte)(26 + i - 'a');
for (int i = '0'; i <= '9'; i++)
codes[i] = (byte)(52 + i - '0');
codes['+'] = 62;
codes['/'] = 63;
}
}

View file

@ -0,0 +1,104 @@
/**
* Copyright 1999 Hannes Wallnoefer
*/
package helma.xmlrpc;
import java.util.*;
import java.io.IOException;
public class Benchmark implements Runnable {
XmlRpcClient client;
static String url;
static int clients = 8;
int gCalls = 0, gErrors = 0;
Date date;
public Benchmark () throws Exception {
client = new XmlRpcClientLite (url);
Vector args = new Vector ();
// Some JITs (Symantec, IBM) have problems with several Threads
// starting all at the same time.
// This initial XML-RPC call seems to pacify them.
args.addElement (new Integer (123));
client.execute ("math.abs", args);
date = new Date ();
date = new Date ((date.getTime()/1000)*1000);
for (int i=0; i<clients; i++)
new Thread (this).start ();
}
public void run () {
int errors = 0;
int calls = 0;
long start = System.currentTimeMillis ();
try {
int val = (int) (-100 * Math.random ());
Vector args = new Vector ();
// ECHO STRING
// args.addElement (Integer.toString (val));
// ABS INT
args.addElement (new Integer (val));
// ECHO DATE
// args.addElement (date);
for (int i=0; i<100; i++) {
// ABS INT
Integer ret = (Integer) client.execute ("math.abs", args);
// ECHO
// Vector v = (Vector) client.execute ("echo", args);
// ECHO DATE
// Date d = (Date) v.elementAt (0);
// ABS INT
if (ret.intValue () != Math.abs (val)) {
// ECHO DATE
// if (date.getTime() != d.getTime()) {
// ECHO STRING
// if (!Integer.toString(val).equals (v.elementAt (0))) {
errors += 1;
}
calls += 1;
}
} catch (IOException x) {
System.err.println ("Exception in client: "+x);
x.printStackTrace ();
} catch (XmlRpcException x) {
System.err.println ("Server reported error: "+x);
} catch (Exception other) {
System.err.println ("Exception in Benchmark client: "+other);
}
int millis = (int) (System.currentTimeMillis () - start);
checkout (calls, errors, millis);
}
private synchronized void checkout (int calls, int errors, int millis) {
clients--;
gCalls += calls;
gErrors += errors;
System.err.println ("Benchmark thread finished: "+calls+" calls, "+errors+" errors in "+millis+" milliseconds.");
if (clients == 0) {
System.err.println ("");
System.err.println ("Benchmark result: "+(1000*gCalls/millis)+" calls per second.");
}
}
public static void main (String args[]) throws Exception {
if (args.length > 0 && args.length < 3) {
url = args[0];
XmlRpc.setKeepAlive (true);
if (args.length == 2)
XmlRpc.setDriver (args[1]);
new Benchmark ();
} else {
System.err.println ("Usage: java helma.xmlrpc.Benchmark URL [SAXDriver]");
}
}
}

View file

@ -0,0 +1,61 @@
package helma.xmlrpc;
import java.io.InputStream;
import java.io.IOException;
// This class is borrowed from Apache JServ
class ServerInputStream extends InputStream {
// bytes remaining to be read from the input stream. This is
// initialized from CONTENT_LENGTH (or getContentLength()).
// This is used in order to correctly return a -1 when all the
// data POSTed was read. If this is left to -1, content length is
// assumed as unknown and the standard InputStream methods will be used
long available = -1;
private InputStream in;
public ServerInputStream(InputStream in, int available) {
this.in = in;
this.available = available;
}
public int read() throws IOException {
if (available > 0) {
available--;
return in.read();
} else if (available == -1)
return in.read ();
return -1;
}
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
if (available > 0) {
if (len > available) {
// shrink len
len = (int) available;
}
int read = in.read(b, off, len);
if (read != -1) {
available -= read;
} else {
available = -1;
}
return read;
} else if (available == -1)
return in.read (b, off, len);
return -1;
}
public long skip(long n) throws IOException {
long skip = in.skip(n);
if (available > 0)
available -= skip;
return skip;
}
}

View file

@ -0,0 +1,448 @@
package helma.xmlrpc;
import java.io.*;
import java.net.*;
import java.util.*;
/**
* A minimal web server that exclusively handles XML-RPC requests.
*/
public class WebServer implements Runnable {
XmlRpcServer xmlrpc;
private ServerSocket serverSocket;
private int port;
private Thread listener;
private boolean paranoid;
private Vector accept, deny;
private Stack threadpool;
private ThreadGroup runners;
static final byte[] ctype = "Content-Type: text/xml\r\n".getBytes();
static final byte[] clength = "Content-Length: ".getBytes();
static final byte[] newline = "\r\n".getBytes();
static final byte[] doubleNewline = "\r\n\r\n".getBytes();
static final byte[] conkeep = "Connection: Keep-Alive\r\n".getBytes();
static final byte[] conclose = "Connection: close\r\n".getBytes();
static final byte[] ok = " 200 OK\r\n".getBytes();
static final byte[] server = "Server: Helma XML-RPC 1.0\r\n".getBytes();
/**
* This <em>can</em> be called from command line, but you'll have to edit and recompile
* to change the server port or handler objects. By default, it sets up the following responders:
* <ul><li> A java.lang.String object
* <li> The java.lang.Math class (making its static methods callable via XML-RPC)
* <li> An Echo handler that returns the argument array
* </ul>
*/
public static void main (String args[]) {
System.err.println ("Usage: java helma.xmlrpc.WebServer [port]");
int p = 8080;
if (args.length > 0) try {
p = Integer.parseInt (args[0]);
} catch (NumberFormatException nfx) {
System.err.println ("Error parsing port number: "+args[0]);
}
// XmlRpc.setDebug (true);
XmlRpc.setKeepAlive (true);
// XmlRpc.setEncoding ("UTF-8");
try {
WebServer webserver = new WebServer (p);
// webserver.setParanoid (true);
// webserver.acceptClient ("192.168.*.*");
webserver.addHandler ("string", "Welcome to XML-RPC!");
webserver.addHandler ("math", Math.class);
webserver.addHandler ("auth", new AuthDemo());
webserver.addHandler ("$default", new Echo());
// XmlRpcClients can be used as Proxies in XmlRpcServers which is a cool feature for applets.
webserver.addHandler ("mttf", new XmlRpcClient ("http://www.mailtothefuture.com:80/RPC2"));
System.err.println ("started web server on port "+p);
} catch (IOException x) {
System.err.println ("Error creating web server: "+x);
}
}
/**
* Creates a Web server at the specified port number.
*/
public WebServer (int port) throws IOException {
this (port, null);
}
/**
* Creates a Web server at the specified port number and IP address.
*/
public WebServer (int port, InetAddress add) throws IOException {
this.port = port;
xmlrpc = new XmlRpcServer ();
accept = new Vector ();
deny = new Vector ();
threadpool = new Stack ();
runners = new ThreadGroup ("XML-RPC Runner");
this.serverSocket = new ServerSocket (port, 50, add);
listener = new Thread (this, "XML-RPC Weblistener");
listener.start();
}
/**
* Register a handler object with this name. Methods of this objects will be
* callable over XML-RPC as "name.method".
*/
public void addHandler (String name, Object target) {
xmlrpc.addHandler (name, target);
}
/**
* Remove a handler object that was previously registered with this server.
*/
public void removeHandler (String name) {
xmlrpc.removeHandler (name);
}
/**
* Switch client filtering on/off.
* @see acceptClient(java.lang.String)
* @see denyClient(java.lang.String)
*/
public void setParanoid (boolean p) {
paranoid = p;
}
/**
* Add an IP address to the list of accepted clients. The parameter can contain '*' as wildcard
* character, e.g. "192.168.*.*". You must call setParanoid(true) in order for this to have any
* effect.
*
* @see denyClient(java.lang.String)
* @see setParanoid(boolean)
*/
public void acceptClient (String address) throws IllegalArgumentException {
try {
AddressMatcher m = new AddressMatcher (address);
accept.addElement (m);
} catch (Exception x) {
throw new IllegalArgumentException ("\""+address+"\" does not represent a valid IP address");
}
}
/**
* Add an IP address to the list of denied clients. The parameter can contain '*' as wildcard
* character, e.g. "192.168.*.*". You must call setParanoid(true) in order for this to have any
* effect.
*
* @see acceptClient(java.lang.String)
* @see setParanoid(boolean)
*/
public void denyClient (String address) throws IllegalArgumentException {
try {
AddressMatcher m = new AddressMatcher (address);
deny.addElement (m);
} catch (Exception x) {
throw new IllegalArgumentException ("\""+address+"\" does not represent a valid IP address");
}
}
private boolean checkSocket (Socket s) {
int l = deny.size ();
byte address[] = s.getInetAddress ().getAddress ();
for (int i=0; i<l; i++) {
AddressMatcher match = (AddressMatcher) deny.elementAt (i);
if (match.matches (address))
return false;
}
l = accept.size ();
for (int i=0; i<l; i++) {
AddressMatcher match = (AddressMatcher) accept.elementAt (i);
if (match.matches (address))
return true;
}
return false;
}
/**
* Listens for client requests until stopped.
*/
public void run() {
try {
while (listener != null) {
try {
Socket socket = serverSocket.accept();
if (!paranoid || checkSocket (socket)) {
Runner runner = getRunner ();
runner.handle (socket);
// new Connection (socket);
} else
socket.close ();
} catch (Exception ex) {
System.err.println("Exception in XML-RPC listener loop (" + ex + ").");
} catch (Error err) {
System.err.println("Error in XML-RPC listener loop (" + err + ").");
}
}
} catch (Exception exception) {
System.err.println("Error accepting XML-RPC connections (" + exception + ").");
}
finally {
System.err.println("Closing XML-RPC server socket.");
try {
serverSocket.close();
serverSocket = null;
}
catch (IOException ignore) {}
}
}
/**
* Stop listening on the server port.
*/
public void shutdown () {
if (listener != null) {
Thread l = listener;
listener = null;
l.interrupt ();
}
}
private Runner getRunner () {
try {
return (Runner) threadpool.pop ();
} catch (EmptyStackException empty) {
if (runners.activeCount () > 255)
throw new RuntimeException ("System overload");
return new Runner ();
}
}
void releaseRunner (Runner runner) {
threadpool.push (runner);
}
class Runner implements Runnable {
Thread thread;
Connection con;
int count;
public void handle (Socket socket) throws IOException {
con = new Connection (socket);
count = 0;
if (thread == null || !thread.isAlive()) {
thread = new Thread (runners, this);
thread.start ();
} else {
synchronized (this) {
notify ();
}
}
}
public void run () {
while (Thread.currentThread () == thread) {
con.run ();
count++;
con = null;
if (count > 200 || threadpool.size() > 20)
return;
synchronized (this) {
releaseRunner (this);
try {
wait ();
} catch (InterruptedException ir) {
Thread.currentThread().interrupt();
}
}
}
}
} // end class Runner
class Connection implements Runnable {
private Socket socket;
private BufferedInputStream input;
private BufferedOutputStream output;
// private Thread responder;
private long lastRequest;
private String user, password;
public Connection (Socket socket) throws IOException {
// set read timeout to 30 seconds
socket.setSoTimeout (30000);
this.socket = socket;
input = new BufferedInputStream (socket.getInputStream());
output = new BufferedOutputStream (socket.getOutputStream());
// responder = new Thread (this, "xmlrpc-worker");
// responder.start();
}
public void run () {
try {
boolean keepalive = false;
do {
// reset user authentication
user = password = null;
String line = readLine ();
// Netscape sends an extra \n\r after bodypart, swallow it
if ("".equals (line))
line = readLine();
if (XmlRpc.debug)
System.err.println (line);
// get time of last request
lastRequest = System.currentTimeMillis ();
int contentLength = -1;
// tokenize first line of HTTP request
StringTokenizer tokens = new StringTokenizer(line);
String method = tokens.nextToken();
String uri = tokens.nextToken ();
String httpversion = tokens.nextToken ();
keepalive = XmlRpc.getKeepAlive() && "HTTP/1.1".equals (httpversion);
do {
line = readLine();
if (line != null) {
if (XmlRpc.debug)
System.err.println (line);
String lineLower = line.toLowerCase ();
if (lineLower.startsWith ("content-length:"))
contentLength = Integer.parseInt (line.substring (15).trim ());
if (lineLower.startsWith ("connection:"))
keepalive = XmlRpc.getKeepAlive() && lineLower.indexOf ("keep-alive") > -1;
if (lineLower.startsWith ("authorization: basic "))
parseAuth (line);
}
} while (line != null && ! line.equals(""));
if ("POST".equalsIgnoreCase (method)) {
ServerInputStream sin = new ServerInputStream (input, contentLength);
byte result[] = xmlrpc.execute (sin, user, password);
output.write (httpversion.getBytes());
output.write (ok);
output.write (server);
if (keepalive)
output.write (conkeep);
else
output.write (conclose);
output.write (ctype);
output.write (clength);
output.write (Integer.toString (result.length).getBytes());
output.write (doubleNewline);
output.write (result);
output.flush ();
} else {
output.write (httpversion.getBytes());
output.write (" 400 Bad Request\r\n".getBytes());
output.write ("Server: helma.XML-RPC\r\n\r\n".getBytes());
output.write (("Method "+method+" not implemented (try POST)").getBytes());
output.flush ();
keepalive = false;
}
} while (keepalive);
} catch (Exception exception) {
if (XmlRpc.debug) {
System.err.println (exception);
exception.printStackTrace ();
}
} finally {
try {
socket.close();
} catch (IOException ignore) {}
}
}
byte[] buffer;
private String readLine () throws IOException {
if (buffer == null) {
buffer = new byte[512];
}
int next;
int count = 0;
for (;;) {
next = input.read();
if (next < 0 || next == '\n')
break;
if (next != '\r') {
buffer[count++] = (byte) next;
}
if (count >= 512)
throw new IOException ("HTTP Header too long");
}
return new String (buffer, 0, count);
}
private void parseAuth (String line) {
try {
byte[] c = Base64.decode (line.substring (21).getBytes());
String str = new String (c);
int col = str.indexOf (":");
user = str.substring (0, col);
password = str.substring (col+1);
} catch (Throwable ignore) {}
}
}
class AddressMatcher {
int pattern[];
public AddressMatcher (String address) throws Exception {
pattern = new int[4];
StringTokenizer st = new StringTokenizer (address, ".");
if (st.countTokens () != 4)
throw new Exception ("\""+address+"\" does not represent a valid IP address");
for (int i=0; i<4; i++) {
String next = st.nextToken ();
if ("*".equals (next))
pattern[i] = 256;
else
pattern[i] = (byte) Integer.parseInt (next);
}
}
public boolean matches (byte address[]) {
for (int i=0; i<4; i++) {
if (pattern[i] > 255) // wildcard
continue;
if (pattern[i] != address[i])
return false;
}
return true;
}
}
}
// An echo handler for debugging purposes
class Echo implements XmlRpcHandler {
public Object execute (String method, Vector v) throws Exception {
return (v);
}
}
// An simple class that implements authentication
class AuthDemo implements AuthenticatedXmlRpcHandler {
public Object execute (String method, Vector v, String user, String password) throws Exception {
// our simplistic authentication guidelines never fail ;)
if (user == null || user.startsWith ("bad"))
throw new XmlRpcException (5, "Sorry, you're not allowed in here!");
return ("Hello "+user);
}
}

View file

@ -0,0 +1,583 @@
/**
* Copyright 1999 Hannes Wallnoefer
* XML-RPC base class. See http://www.xmlrpc.com/
*/
package helma.xmlrpc;
import java.io.*;
import java.util.*;
import java.text.*;
import org.xml.sax.*;
/**
* This abstract base class provides basic capabilities for XML-RPC, like parsing of parameters
* or encoding Java objects into XML-RPC format. Any XML parser with a <a href=http://www.megginson.com/SAX/>
* SAX</a> interface can be used.<p>
* XmlRpcServer and XmlRpcClient are the classes that actually implement an XML-RCP server and client.
* @see XmlRpcServer
* @see XmlRpcClient
*/
public abstract class XmlRpc extends HandlerBase {
public static final String version = "helma XML-RPC 1.0";
String methodName;
// class name of SAX parser to use
private static Class parserClass;
private static Hashtable saxDrivers = new Hashtable ();
static {
saxDrivers.put ("xp", "com.jclark.xml.sax.Driver");
saxDrivers.put ("ibm1", "com.ibm.xml.parser.SAXDriver");
saxDrivers.put ("ibm2", "com.ibm.xml.parsers.SAXParser");
saxDrivers.put ("aelfred", "com.microstar.xml.SAXDriver");
saxDrivers.put ("oracle1", "oracle.xml.parser.XMLParser");
saxDrivers.put ("oracle2", "oracle.xml.parser.v2.SAXParser");
saxDrivers.put ("openxml", "org.openxml.parser.XMLSAXParser");
}
// the stack we're parsing our values into.
Stack values;
Value currentValue;
// formats for parsing and generating dateTime values
// DateFormat datetime;
// now comes wapped into a synchronized class because dateFormat is not threadsafe
static Formatter dateformat = new Formatter ();
// used to collect character data of parameter values
StringBuffer cdata;
boolean readCdata;
// XML RPC parameter types used for dataMode
static final int STRING = 0;
static final int INTEGER = 1;
static final int BOOLEAN = 2;
static final int DOUBLE = 3;
static final int DATE = 4;
static final int BASE64 = 5;
static final int STRUCT = 6;
static final int ARRAY = 7;
static final int NIL = 8;
// Error level + message
int errorLevel;
String errorMsg;
static final int NONE = 0;
static final int RECOVERABLE = 1;
static final int FATAL = 2;
// use HTTP keepalive?
static boolean keepalive = false;
// for debugging output
public static boolean debug = false;
final static String types[] = {"String", "Integer", "Boolean", "Double", "Date", "Base64", "Struct", "Array", "Nil"};
// mapping between java encoding names and "real" names used in XML prolog.
// if you use an encoding not listed here send feedback to xmlrpc@helma.org
static String encoding = "ISO8859_1";
static Properties encodings = new Properties ();
static {
encodings.put ("UTF8", "UTF-8");
encodings.put ("ISO8859_1", "ISO-8859-1");
}
/**
* Set the SAX Parser to be used. The argument can either be the full class name or
* a user friendly shortcut if the parser is known to this class. The parsers that can
* currently be set by shortcut are listed in the main documentation page. If you are using
* another parser please send me the name of the SAX driver and I'll include it in a future release.
* If setDriver() is never called then the System property "sax.driver" is consulted. If that is not defined
* the driver defaults to OpenXML.
*/
public static void setDriver (String driver) throws ClassNotFoundException {
String parserClassName = null;
try {
parserClassName = (String) saxDrivers.get (driver);
if (parserClassName == null)
parserClassName = driver;
parserClass = Class.forName (parserClassName);
} catch (ClassNotFoundException x) {
throw new ClassNotFoundException ("SAX driver not found: "+parserClassName);
}
}
/**
* Set the SAX Parser to be used by directly passing the Class object.
*/
public static void setDriver (Class driver) {
parserClass = driver;
}
/**
* Set the encoding of the XML. This should be the name of a Java encoding
* contained in the encodings Hashtable.
*/
public static void setEncoding (String enc) {
encoding = enc;
}
public String getEncoding () {
return encodings.getProperty (encoding, encoding);
}
/**
* Switch debugging output on/off.
*/
public static void setDebug (boolean val) {
debug = val;
}
/**
* Switch HTTP keepalive on/off.
*/
public static void setKeepAlive (boolean val) {
keepalive = val;
}
/**
* get current HTTP keepalive mode.
*/
public static boolean getKeepAlive () {
return keepalive;
}
/**
* Parse the input stream. For each root level object, method <code>objectParsed</code>
* is called.
*/
synchronized void parse (InputStream is) throws Exception {
// reset values (XmlRpc objects are reusable)
errorLevel = NONE;
errorMsg = null;
values = new Stack ();
if (cdata == null)
cdata = new StringBuffer (128);
else
cdata.setLength (0);
readCdata = false;
currentValue = null;
long now = System.currentTimeMillis ();
if (parserClass == null) {
// try to get the name of the SAX driver from the System properties
setDriver (System.getProperty ("sax.driver", "org.openxml.parser.XMLSAXParser"));
}
Parser parser = null;
try {
parser = (Parser) parserClass.newInstance ();
} catch (NoSuchMethodError nsm) {
// This is thrown if no constructor exists for the parser class
// and is transformed into a regular exception.
throw new Exception ("Can't create Parser: "+parserClass);
}
parser.setDocumentHandler (this);
parser.setErrorHandler (this);
parser.parse (new InputSource (is));
if (debug)
System.err.println ("Spent "+(System.currentTimeMillis () - now)+" millis parsing");
}
/**
* Writes the XML representation of a supported Java object to the XML writer.
*/
void writeObject (Object what, XmlWriter writer) {
writer.startElement ("value");
if (what == null) {
// try sending experimental <ni/> element
writer.emptyElement ("nil");
} else if (what instanceof String) {
writer.chardata (what.toString ());
} else if (what instanceof Integer) {
writer.startElement ("int");
writer.write (what.toString ());
writer.endElement ("int");
} else if (what instanceof Boolean) {
writer.startElement ("boolean");
writer.write (((Boolean) what).booleanValue () ? "1" : "0");
writer.endElement ("boolean");
} else if (what instanceof Double || what instanceof Float) {
writer.startElement ("double");
writer.write (what.toString ());
writer.endElement ("double");
} else if (what instanceof Date) {
writer.startElement ("dateTime.iso8601");
Date d = (Date) what;
writer.write (dateformat.format (d));
writer.endElement ("dateTime.iso8601");
} else if (what instanceof byte[]) {
writer.startElement ("base64");
writer.write (Base64.encode ((byte[]) what));
writer.endElement ("base64");
} else if (what instanceof Vector) {
writer.startElement ("array");
writer.startElement ("data");
Vector v = (Vector) what;
int l2 = v.size ();
for (int i2=0; i2<l2; i2++)
writeObject (v.elementAt (i2), writer);
writer.endElement ("data");
writer.endElement ("array");
} else if (what instanceof Hashtable) {
writer.startElement ("struct");
Hashtable h = (Hashtable) what;
for (Enumeration e = h.keys (); e.hasMoreElements (); ) {
try {
String nextkey = (String) e.nextElement ();
Object nextval = h.get (nextkey);
writer.startElement ("member");
writer.startElement ("name");
writer.write (nextkey);
writer.endElement ("name");
writeObject (nextval, writer);
writer.endElement ("member");
} catch (ClassCastException cce) {
throw new ClassCastException ("Only Strings may be used as keys in XML-RPC structs, but Hashtable contained "+cce.getMessage());
}
}
writer.endElement ("struct");
} else
throw new RuntimeException ("unsupported Java type: " + what.getClass ());
writer.endElement ("value");
}
/**
* This method is called when a root level object has been parsed.
*/
abstract void objectParsed (Object what);
////////////////////////////////////////////////////////////////
// methods called by XML parser
/**
* Method called by SAX driver.
*/
public void characters (char ch[], int start, int length) throws SAXException {
if (!readCdata)
return;
cdata.append (ch, start, length);
}
/**
* Method called by SAX driver.
*/
public void endElement (String name) throws SAXException {
if (debug)
System.err.println ("endElement: "+name);
// finalize character data, if appropriate
if (currentValue != null && readCdata) {
currentValue.characterData (cdata.toString ());
cdata.setLength (0);
readCdata = false;
}
if ("value".equals (name)) {
int depth = values.size ();
// Only handle top level objects or objects contained in arrays here.
// For objects contained in structs, wait for </member> (see code below).
if (depth == 0 || values.peek ().hashCode () != STRUCT) {
Value v = currentValue;
if (depth == 0) {
// This is a top-level object
objectParsed (v.value);
currentValue = null;
} else {
// add object to sub-array; if current container is a struct, add later (at </member>)
currentValue = (Value) values.pop ();
currentValue.endElement (v);
}
}
}
// Handle objects contained in structs.
if ("member".equals (name)) {
Value v = currentValue;
currentValue = (Value) values.pop ();
currentValue.endElement (v);
}
else if ("methodName".equals (name)) {
methodName = cdata.toString ();
cdata.setLength (0);
readCdata = false;
}
}
/**
* Method called by SAX driver.
*/
public void startElement (String name, AttributeList atts) throws SAXException {
if (debug)
System.err.println ("startElement: "+name);
if ("value".equals (name)) {
// System.err.println ("starting value");
if (currentValue != null)
values.push (currentValue);
currentValue = new Value ();
// cdata object is reused
cdata.setLength(0);
readCdata = true;
}
else if ("methodName".equals (name)) {
cdata.setLength(0);
readCdata = true;
}
else if ("name".equals (name)) {
cdata.setLength(0);
readCdata = true;
}
else if ("string".equals (name)) {
cdata.setLength(0);
readCdata = true;
} else if ("i4".equals (name) || "int".equals (name)) {
currentValue.setType (INTEGER);
cdata.setLength(0);
readCdata = true;
} else if ("boolean".equals (name)) {
currentValue.setType (BOOLEAN);
cdata.setLength(0);
readCdata = true;
} else if ("double".equals (name)) {
currentValue.setType (DOUBLE);
cdata.setLength(0);
readCdata = true;
} else if ("dateTime.iso8601".equals (name)) {
currentValue.setType (DATE);
cdata.setLength(0);
readCdata = true;
} else if ("base64".equals (name)) {
currentValue.setType (BASE64);
cdata.setLength(0);
readCdata = true;
} else if ("struct".equals (name))
currentValue.setType (STRUCT);
else if ("array".equals (name))
currentValue.setType (ARRAY);
else if ("nil".equals (name))
currentValue.setType (NIL);
}
public void error (SAXParseException e) throws SAXException {
System.err.println ("Error parsing XML: "+e);
errorLevel = RECOVERABLE;
errorMsg = e.toString ();
}
public void fatalError(SAXParseException e) throws SAXException {
System.err.println ("Fatal error parsing XML: "+e);
errorLevel = FATAL;
errorMsg = e.toString ();
}
/**
* This represents an XML-RPC Value while the request is being parsed.
*/
class Value {
int type;
Object value;
// the name to use for the next member of struct values
String nextMemberName;
Hashtable struct;
Vector array;
/**
* Constructor.
*/
public Value () {
this.type = STRING;
}
/**
* Notification that a new child element has been parsed.
*/
public void endElement (Value child) {
if (type == ARRAY)
array.addElement (child.value);
else if (type == STRUCT)
struct.put (nextMemberName, child.value);
}
/**
* Set the type of this value. If it's a container, create the corresponding java container.
*/
public void setType (int type) {
// System.err.println ("setting type to "+types[type]);
this.type = type;
if (type == ARRAY)
value = array = new Vector ();
if (type == STRUCT)
value = struct = new Hashtable ();
}
/**
* Set the character data for the element and interpret it according to the
* element type
*/
public void characterData (String cdata) {
switch (type) {
case INTEGER:
value = new Integer (cdata.trim ());
break;
case BOOLEAN:
value = "1".equals (cdata.trim ()) ? Boolean.TRUE : Boolean.FALSE;
break;
case DOUBLE:
value = new Double (cdata.trim ());
break;
case DATE:
try {
value = dateformat.parse (cdata.trim ());
} catch (ParseException p) {
// System.err.println ("Exception while parsing date: "+p);
throw new RuntimeException (p.getMessage ());
}
break;
case BASE64:
value = Base64.decode (cdata.getBytes());
break;
case STRING:
value = cdata;
break;
case STRUCT:
// this is the name to use for the next member of this struct
nextMemberName = cdata;
break;
}
}
// This is a performance hack to get the type of a value without casting the Object.
// It breaks the contract of method hashCode, but it doesn't matter since
// Value objects are never used as keys in Hashtables.
public int hashCode () {
return type;
}
public String toString () {
return (types[type]+" element "+value);
}
}
// A quick and dirty XML writer.
class XmlWriter {
StringBuffer buf;
String enc;
public XmlWriter (StringBuffer buf) {
// The encoding used for XML-RPC is ISO-8859-1 for pragmatical reasons (Frontier/Win).
this (buf, encoding);
}
public XmlWriter (StringBuffer buf, String enc) {
this.buf = buf;
this.enc = enc;
// get name of encoding for XML prolog
String encName = encodings.getProperty (enc, enc);
buf.append ("<?xml version=\"1.0\" encoding=\""+encName+"\"?>");
}
public void startElement (String elem) {
buf.append ("<");
buf.append (elem);
buf.append (">");
}
public void endElement (String elem) {
buf.append ("</");
buf.append (elem);
buf.append (">");
}
public void emptyElement (String elem) {
buf.append ("<");
buf.append (elem);
buf.append ("/>");
}
public void chardata (String text) {
int l = text.length ();
for (int i=0; i<l; i++) {
char c = text.charAt (i);
switch (c) {
case '<' :
buf.append ("&lt;");
break;
case '&' :
buf.append ("&amp;");
break;
default :
buf.append (c);
}
}
}
public void write (char[] text) {
buf.append (text);
}
public void write (String text) {
buf.append (text);
}
public String toString () {
return buf.toString ();
}
public byte[] getBytes () throws UnsupportedEncodingException {
return buf.toString ().getBytes (enc);
}
}
}
// wraps a DateFormat because it's not threadsafe
class Formatter {
private DateFormat f;
public Formatter () {
f = new SimpleDateFormat ("yyyyMMdd'T'HH:mm:ss");
}
public synchronized String format (Date d) {
return f.format (d);
}
public synchronized Date parse (String s) throws ParseException {
return f.parse (s);
}
}

View file

@ -0,0 +1,226 @@
/**
* Copyright 1999 Hannes Wallnoefer
* Implements a XML-RPC client. See http://www.xmlrpc.com/
*/
package helma.xmlrpc;
import java.net.*;
import java.io.*;
import java.util.*;
import org.xml.sax.*;
/**
* A multithreaded, reusable XML-RPC client object. Use this if you need a full-grown
* HTTP client (e.g. for Proxy and Cookies support). If you don't need that, <code>XmlRpcClientLite</code>
* may work better for you.
*/
public class XmlRpcClient implements XmlRpcHandler {
URL url;
String auth;
/**
* Construct a XML-RPC client with this URL.
*/
public XmlRpcClient (URL url) {
this.url = url;
}
/**
* Construct a XML-RPC client for the URL represented by this String.
*/
public XmlRpcClient (String url) throws MalformedURLException {
this.url = new URL (url);
}
/**
* Construct a XML-RPC client for the specified hostname and port.
*/
public XmlRpcClient (String hostname, int port) throws MalformedURLException {
this.url = new URL ("http://"+hostname+":"+port+"/RPC2");
}
/**
* Sets Authentication for this client. This will be sent as Basic Authentication header
* to the server as described in <a href="http://www.ietf.org/rfc/rfc2617.txt">http://www.ietf.org/rfc/rfc2617.txt</a>.
*/
public void setBasicAuthentication (String user, String password) {
if (user == null || password == null)
auth = null;
else {
char[] basicAuth = Base64.encode ((user+":"+password).getBytes());
auth = new String (basicAuth).trim();
}
}
/**
* Generate an XML-RPC request and send it to the server. Parse the result and
* return the corresponding Java object.
*
* @exception XmlRpcException: If the remote host returned a fault message.
* @exception IOException: If the call could not be made because of lower level problems.
*/
public Object execute (String method, Vector params) throws XmlRpcException, IOException {
Worker worker = getWorker ();
try {
Object retval = worker.execute (method, params);
return retval;
} finally {
if (workers < 50 && !worker.fault)
pool.push (worker);
else
workers -= 1;
}
}
Stack pool = new Stack ();
int workers = 0;
private final Worker getWorker () throws IOException {
try {
return (Worker) pool.pop ();
} catch (EmptyStackException x) {
if (workers < 100) {
workers += 1;
return new Worker ();
}
throw new IOException ("XML-RPC System overload");
}
}
class Worker extends XmlRpc {
boolean fault;
Object result = null;
StringBuffer strbuf;
public Worker () throws IOException {
super ();
}
public Object execute (String method, Vector params) throws XmlRpcException, IOException {
fault = false;
long now = System.currentTimeMillis ();
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream ();
if (strbuf == null)
strbuf = new StringBuffer ();
else
strbuf.setLength (0);
XmlWriter writer = new XmlWriter (strbuf);
writeRequest (writer, method, params);
byte[] request = writer.getBytes();
URLConnection con = url.openConnection ();
con.setDoInput (true);
con.setDoOutput (true);
con.setUseCaches (false);
con.setAllowUserInteraction(false);
con.setRequestProperty ("Content-Length", Integer.toString (request.length));
con.setRequestProperty ("Content-Type", "text/xml");
if (auth != null)
con.setRequestProperty ("Authorization", "Basic "+auth);
// con.connect ();
OutputStream out = con.getOutputStream ();
out.write (request);
out.flush ();
InputStream in = con.getInputStream ();
parse (in);
} catch (Exception x) {
x.printStackTrace ();
throw new IOException (x.getMessage ());
}
if (fault) { // generate an XmlRpcException
XmlRpcException exception = null;
try {
Hashtable f = (Hashtable) result;
String faultString = (String) f.get ("faultString");
int faultCode = Integer.parseInt (f.get ("faultCode").toString ());
exception = new XmlRpcException (faultCode, faultString.trim ());
} catch (Exception x) {
throw new XmlRpcException (0, "Invalid fault response");
}
throw exception;
}
if (debug)
System.err.println ("Spent "+(System.currentTimeMillis () - now)+" in request");
return result;
}
/**
* Called when the return value has been parsed.
*/
void objectParsed (Object what) {
result = what;
}
/**
* Generate an XML-RPC request from a method name and a parameter vector.
*/
void writeRequest (XmlWriter writer, String method, Vector params) throws IOException {
writer.startElement ("methodCall");
writer.startElement ("methodName");
writer.write (method);
writer.endElement ("methodName");
writer.startElement ("params");
int l = params.size ();
for (int i=0; i<l; i++) {
writer.startElement ("param");
writeObject (params.elementAt (i), writer);
writer.endElement ("param");
}
writer.endElement ("params");
writer.endElement ("methodCall");
}
/**
* Overrides method in XmlRpc to handle fault repsonses.
*/
public void startElement (String name, AttributeList atts) throws SAXException {
if ("fault".equals (name))
fault = true;
else
super.startElement (name, atts);
}
} // end of inner class Worker
/**
* Just for testing.
*/
public static void main (String args[]) throws Exception {
// XmlRpc.setDebug (true);
try {
String url = args[0];
String method = args[1];
Vector v = new Vector ();
for (int i=2; i<args.length; i++) try {
v.addElement (new Integer (Integer.parseInt (args[i])));
} catch (NumberFormatException nfx) {
v.addElement (args[i]);
}
XmlRpcClient client = new XmlRpcClient (url);
try {
System.err.println (client.execute (method, v));
} catch (Exception ex) {
System.err.println ("Error: "+ex.getMessage());
}
} catch (Exception x) {
System.err.println (x);
System.err.println ("Usage: java helma.xmlrpc.XmlRpcClient <url> <method> <arg> ....");
System.err.println ("Arguments are sent as integers or strings.");
}
}
}

View file

@ -0,0 +1,358 @@
/**
* Copyright 1999 Hannes Wallnoefer
* Implements a XML-RPC client. See http://www.xmlrpc.com/
*/
package helma.xmlrpc;
import java.net.*;
import java.io.*;
import java.util.*;
import org.xml.sax.*;
/**
* A multithreaded, reusable XML-RPC client object. This version uses a homegrown
* HTTP client which can be quite a bit faster than java.net.URLConnection, especially
* when used with XmlRpc.setKeepAlive(true).
*/
public class XmlRpcClientLite extends XmlRpcClient {
/**
* Construct a XML-RPC client with this URL.
*/
public XmlRpcClientLite (URL url) {
super (url);
}
/**
* Construct a XML-RPC client for the URL represented by this String.
*/
public XmlRpcClientLite (String url) throws MalformedURLException {
super (url);
}
/**
* Construct a XML-RPC client for the specified hostname and port.
*/
public XmlRpcClientLite (String hostname, int port) throws MalformedURLException {
super (hostname, port);
}
/**
* Generate an XML-RPC request and send it to the server. Parse the result and
* return the corresponding Java object.
*
* @exception XmlRpcException: If the remote host returned a fault message.
* @exception IOException: If the call could not be made because of lower level problems.
*/
public Object execute (String method, Vector params) throws XmlRpcException, IOException {
Worker worker = getWorker ();
try {
Object retval = worker.execute (method, params);
return retval;
} finally {
if (workers < 50 && !worker.fault)
pool.push (worker);
else
workers -= 1;
}
}
Stack pool = new Stack ();
int workers = 0;
private final Worker getWorker () throws IOException {
try {
return (Worker) pool.pop ();
} catch (EmptyStackException x) {
if (workers < 100) {
workers += 1;
return new Worker ();
}
throw new IOException ("XML-RPC System overload");
}
}
class Worker extends XmlRpc {
boolean fault;
Object result = null;
HttpClient client = null;
StringBuffer strbuf;
public Worker () {
super ();
}
public Object execute (String method, Vector params) throws XmlRpcException, IOException {
long now = System.currentTimeMillis ();
fault = false;
try {
if (strbuf == null)
strbuf = new StringBuffer ();
else
strbuf.setLength (0);
XmlWriter writer = new XmlWriter (strbuf);
writeRequest (writer, method, params);
byte[] request = writer.getBytes();
// and send it to the server
if (client == null)
client = new HttpClient (url);
client.write (request);
InputStream in = client.getInputStream ();
// parse the response
parse (in);
// client keepalive is always false if XmlRpc.keepalive is false
if (!client.keepalive)
client.closeConnection ();
if (debug)
System.err.println ("result = "+result);
// check for errors from the XML parser
if (errorLevel == FATAL)
throw new Exception (errorMsg);
} catch (IOException iox) {
// this is a lower level problem, client could not talk to server for some reason.
throw iox;
} catch (Exception x) {
// same as above, but exception has to be converted to IOException.
if (XmlRpc.debug)
x.printStackTrace ();
String msg = x.getMessage ();
if (msg == null || msg.length () == 0)
msg = x.toString ();
throw new IOException (msg);
}
if (fault) {
// this is an XML-RPC-level problem, i.e. the server reported an error.
// throw an XmlRpcException.
XmlRpcException exception = null;
try {
Hashtable f = (Hashtable) result;
String faultString = (String) f.get ("faultString");
int faultCode = Integer.parseInt (f.get ("faultCode").toString ());
exception = new XmlRpcException (faultCode, faultString.trim ());
} catch (Exception x) {
throw new XmlRpcException (0, "Server returned an invalid fault response.");
}
throw exception;
}
if (debug)
System.err.println ("Spent "+(System.currentTimeMillis () - now)+" millis in request");
return result;
}
/**
* Called when the return value has been parsed.
*/
void objectParsed (Object what) {
result = what;
}
/**
* Generate an XML-RPC request from a method name and a parameter vector.
*/
void writeRequest (XmlWriter writer, String method, Vector params) throws IOException {
writer.startElement ("methodCall");
writer.startElement ("methodName");
writer.write (method);
writer.endElement ("methodName");
writer.startElement ("params");
int l = params.size ();
for (int i=0; i<l; i++) {
writer.startElement ("param");
writeObject (params.elementAt (i), writer);
writer.endElement ("param");
}
writer.endElement ("params");
writer.endElement ("methodCall");
}
/**
* Overrides method in XmlRpc to handle fault repsonses.
*/
public void startElement (String name, AttributeList atts) throws SAXException {
if ("fault".equals (name))
fault = true;
else
super.startElement (name, atts);
}
} // end of class Worker
// A replacement for java.net.URLConnection, which seems very slow on MS Java.
class HttpClient {
String hostname;
String host;
int port;
String uri;
Socket socket = null;
BufferedOutputStream output;
BufferedInputStream input;
boolean keepalive;
boolean fresh;
public HttpClient (URL url) throws IOException {
hostname = url.getHost ();
port = url.getPort ();
if (port < 1) port = 80;
uri = url.getFile ();
if (uri == null || "".equals (uri))
uri = "/";
host = port == 80 ? hostname : hostname+":"+port;
initConnection ();
}
protected void initConnection () throws IOException {
fresh = true;
socket = new Socket (hostname, port);
output = new BufferedOutputStream (socket.getOutputStream());
input = new BufferedInputStream (socket.getInputStream ());
}
protected void closeConnection () {
try {
socket.close ();
} catch (Exception ignore) {}
}
public void write (byte[] request) throws IOException {
try {
output.write (("POST "+uri+" HTTP/1.0\r\n").getBytes());
output.write (("User-Agent: "+XmlRpc.version+"\r\n").getBytes());
output.write (("Host: "+host+"\r\n").getBytes());
if (XmlRpc.getKeepAlive())
output.write ("Connection: Keep-Alive\r\n".getBytes());
output.write ("Content-Type: text/xml\r\n".getBytes());
if (auth != null)
output.write (("Authorization: Basic "+auth+"\r\n").getBytes());
output.write (("Content-Length: "+request.length).getBytes());
output.write ("\r\n\r\n".getBytes());
output.write (request);
output.flush ();
fresh = false;
} catch (IOException iox) {
// if the connection is not "fresh" (unused), the exception may have occurred
// because the server timed the connection out. Give it another try.
if (!fresh) {
initConnection ();
write (request);
} else {
throw (iox);
}
}
}
public InputStream getInputStream () throws IOException {
String line = readLine ();
if (XmlRpc.debug)
System.err.println (line);
int contentLength = -1;
try {
StringTokenizer tokens = new StringTokenizer (line);
String httpversion = tokens.nextToken ();
String statusCode = tokens.nextToken();
String statusMsg = tokens.nextToken ("\n\r");
keepalive = XmlRpc.getKeepAlive() && "HTTP/1.1".equals (httpversion);
if (!"200".equals (statusCode))
throw new IOException ("Unexpected Response from Server: "+statusMsg);
} catch (IOException iox) {
throw iox;
} catch (Exception x) {
x.printStackTrace ();
throw new IOException ("Server returned invalid Response.");
}
do {
line = readLine ();
if (line != null) {
if (XmlRpc.debug)
System.err.println (line);
line = line.toLowerCase ();
if (line.startsWith ("content-length:"))
contentLength = Integer.parseInt (line.substring (15).trim ());
if (line.startsWith ("connection:"))
keepalive = XmlRpc.getKeepAlive() && line.indexOf ("keep-alive") > -1;
}
} while (line != null && ! line.equals(""));
return new ServerInputStream (input, contentLength);
}
byte[] buffer;
private String readLine () throws IOException {
if (buffer == null)
buffer = new byte[512];
int next;
int count = 0;
while (true) {
next = input.read();
if (next < 0 || next == '\n')
break;
if (next != '\r')
buffer[count++] = (byte) next;
if (count >= 512)
throw new IOException ("HTTP Header too long");
}
return new String (buffer, 0, count);
}
protected void finalize () throws Throwable {
closeConnection ();
}
}
/**
* Just for testing.
*/
public static void main (String args[]) throws Exception {
// XmlRpc.setDebug (true);
try {
String url = args[0];
String method = args[1];
Vector v = new Vector ();
for (int i=2; i<args.length; i++) try {
v.addElement (new Integer (Integer.parseInt (args[i])));
} catch (NumberFormatException nfx) {
v.addElement (args[i]);
}
XmlRpcClient client = new XmlRpcClientLite (url);
try {
System.err.println (client.execute (method, v));
} catch (Exception ex) {
System.err.println ("Error: "+ex.getMessage());
}
} catch (Exception x) {
System.err.println (x);
System.err.println ("Usage: java helma.xmlrpc.XmlRpcClient <url> <method> <arg> ....");
System.err.println ("Arguments are sent as integers or strings.");
}
}
}

View file

@ -0,0 +1,24 @@
/**
* Copyright 1999 Hannes Wallnoefer
*/
package helma.xmlrpc;
/**
* This is thrown by the XmlRpcClient if the remote server reported an error. If something
* went wrong at a lower level (e.g. no http connection) an IOException will be thrown instead.
*/
public class XmlRpcException extends Exception {
/**
* The fault code of the exception. For servers based on this library, this will always be 0.
* (If there are predefined error codes, they should be in the XML-RPC spec.)
*/
public final int code;
public XmlRpcException (int code, String message) {
super (message);
this.code = code;
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright 1999 Hannes Wallnoefer
*/
package helma.xmlrpc;
import java.util.Vector;
/**
* The XML-RPC server uses this interface to call a method of an RPC handler. This should
* be implemented by any class that wants to directly take control when it is called over RPC. Classes
* not implementing this interface will be wrapped into an Invoker
* object that tries to find the matching method for an XML-RPC request.
*/
public interface XmlRpcHandler {
/**
* Return the result, or throw an Exception if something went wrong.
*/
public Object execute (String method, Vector params) throws Exception;
}

View file

@ -0,0 +1,42 @@
// Copyright 2000 Hannes Wallnöfer
package helma.xmlrpc;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.Vector;
/**
* A Servlet that acts as a XML-RPC Proxy . <p>
*
* The URL of the server to connect to is taken from the init parameter <tt>url</tt>.
*/
public class XmlRpcProxyServlet extends HttpServlet {
private XmlRpcServer xmlrpc;
public void init (ServletConfig config) throws ServletException {
if ("true".equalsIgnoreCase (config.getInitParameter ("debug")))
XmlRpc.setDebug (true);
String url = config.getInitParameter ("url");
xmlrpc = new XmlRpcServer ();
try {
xmlrpc.addHandler ("$default", new XmlRpcClientLite (url));
} catch (Exception x) {
throw new ServletException ("Invalid URL: "+url+" ("+x.toString ()+")");
}
}
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
byte[] result = xmlrpc.execute (req.getInputStream ());
res.setContentType("text/xml");
res.setContentLength (result.length);
OutputStream output = res.getOutputStream();
output.write (result);
output.flush ();
}
}

View file

@ -0,0 +1,284 @@
/**
* Copyright 1999 Hannes Wallnoefer
* Implements an XML-RPC server. See http://www.xmlrpc.com/
*/
package helma.xmlrpc;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
/**
* A multithreaded, reusable XML-RPC server object. The name may be misleading because this does not open any
* server sockets. Instead it is fed by passing an XML-RPC input stream to the execute method.
* If you want to open a HTTP listener, use the WebServer class instead.
*/
public class XmlRpcServer {
/**
*
*/
Hashtable handlers;
/**
* Construct a new XML-RPC server. You have to register handlers to make it
* do something useful.
*/
public XmlRpcServer () {
handlers = new Hashtable ();
}
/**
* Register a handler object with this name. Methods of this objects will be
* callable over XML-RPC as "handlername.methodname". For more information
* about XML-RPC handlers see the <a href="../index.html#1a">main documentation page</a>.
*/
public void addHandler (String handlername, Object handler) {
if (handler instanceof XmlRpcHandler || handler instanceof AuthenticatedXmlRpcHandler)
handlers.put (handlername, handler);
else if (handler != null)
handlers.put (handlername, new Invoker (handler));
}
/**
* Remove a handler object that was previously registered with this server.
*/
public void removeHandler (String handlername) {
handlers.remove (handlername);
}
/**
* Parse the request and execute the handler method, if one is found. Returns the result as XML.
* The calling Java code doesn't need to know whether the call was successful or not since this is all
* packed into the response.
*/
public byte[] execute (InputStream is) {
return execute (is, null, null);
}
/**
* Parse the request and execute the handler method, if one is found. If the invoked handler is
* AuthenticatedXmlRpcHandler, use the credentials to authenticate the user.
*/
public byte[] execute (InputStream is, String user, String password) {
Worker worker = getWorker ();
byte[] retval = worker.execute (is, user, password);
pool.push (worker);
return retval;
}
Stack pool = new Stack ();
int workers = 0;
private final Worker getWorker () {
try {
return (Worker) pool.pop ();
} catch (EmptyStackException x) {
if (workers < 100) {
workers += 1;
return new Worker ();
}
throw new RuntimeException ("System overload");
}
}
class Worker extends XmlRpc {
Vector inParams;
Object outParam;
byte[] result;
StringBuffer strbuf;
public byte[] execute (InputStream is, String user, String password) {
inParams = new Vector ();
if (strbuf == null)
strbuf = new StringBuffer ();
else
strbuf.setLength (0);
long now = System.currentTimeMillis ();
try {
parse (is);
if (debug) {
System.err.println ("method name: "+methodName);
System.err.println ("inparams: "+inParams);
}
// check for errors from the XML parser
if (errorLevel > NONE)
throw new Exception (errorMsg);
Object handler = null;
String handlerName = null;
int dot = methodName.indexOf (".");
if (dot > -1) {
handlerName = methodName.substring (0, dot);
handler = handlers.get (handlerName);
if (handler != null)
methodName = methodName.substring (dot+1);
}
if (handler == null) {
handler = handlers.get ("$default");
}
if (handler == null) {
if (dot > -1)
throw new Exception ("RPC handler object \""+handlerName+"\" not found and no default handler registered.");
else
throw new Exception ("RPC handler object not found for \""+methodName+"\": no default handler registered.");
}
if (handler instanceof AuthenticatedXmlRpcHandler)
outParam = ((AuthenticatedXmlRpcHandler) handler).execute (methodName, inParams, user, password);
else
outParam = ((XmlRpcHandler) handler).execute (methodName, inParams);
if (debug)
System.err.println ("outparam = "+outParam);
XmlWriter writer = new XmlWriter (strbuf);
writeResponse (outParam, writer);
result = writer.getBytes ();
} catch (Exception x) {
if (debug)
x.printStackTrace ();
XmlWriter writer = new XmlWriter (strbuf);
String message = x.toString ();
// check if XmlRpcException was thrown so we can get an error code
int code = x instanceof XmlRpcException ? ((XmlRpcException) x).code : 0;
writeError (code, message, writer);
try {
result = writer.getBytes ();
} catch (UnsupportedEncodingException encx) {
System.err.println ("XmlRpcServer.execute: "+encx);
result = writer.toString().getBytes();
}
}
if (debug)
System.err.println ("Spent "+(System.currentTimeMillis () - now)+" millis in request");
return result;
}
/**
* Called when an object to be added to the argument list has been parsed.
*/
void objectParsed (Object what) {
inParams.addElement (what);
}
/**
* Writes an XML-RPC response to the XML writer.
*/
void writeResponse (Object param, XmlWriter writer) {
writer.startElement ("methodResponse");
// if (param == null) param = ""; // workaround for Frontier bug
writer.startElement ("params");
writer.startElement ("param");
writeObject (param, writer);
writer.endElement ("param");
writer.endElement ("params");
writer.endElement ("methodResponse");
}
/**
* Writes an XML-RPC error response to the XML writer.
*/
void writeError (int code, String message, XmlWriter writer) {
// System.err.println ("error: "+message);
Hashtable h = new Hashtable ();
h.put ("faultCode", new Integer (code));
h.put ("faultString", message);
writer.startElement ("methodResponse");
writer.startElement ("fault");
writeObject (h, writer);
writer.endElement ("fault");
writer.endElement ("methodResponse");
}
} // end of inner class Worker
} // XmlRpcServer
// This class uses Java Reflection to call methods matching an XML-RPC call
class Invoker implements XmlRpcHandler {
private Object invokeTarget;
private Class targetClass;
public Invoker(Object target) {
invokeTarget = target;
targetClass = invokeTarget instanceof Class ?
(Class) invokeTarget : invokeTarget.getClass();
if (XmlRpc.debug)
System.err.println("Target object is " + targetClass);
}
// main method, sucht methode in object, wenn gefunden dann aufrufen.
public Object execute (String methodName, Vector params) throws Exception {
// Array mit Classtype bilden, ObjectAry mit Values bilden
Class[] argClasses = null;
Object[] argValues = null;
if(params != null){
argClasses = new Class[params.size()];
argValues = new Object[params.size()];
for(int i = 0; i < params.size(); i++){
argValues[i] = params.elementAt(i);
if (argValues[i] instanceof Integer)
argClasses[i] = Integer.TYPE;
else if (argValues[i] instanceof Double)
argClasses[i] = Double.TYPE;
else if (argValues[i] instanceof Boolean)
argClasses[i] = Boolean.TYPE;
else
argClasses[i] = argValues[i].getClass();
}
}
// Methode da ?
Method method = null;
if (XmlRpc.debug) {
System.err.println("Searching for method: " + methodName);
for(int i = 0; i < argClasses.length; i++)
System.err.println("Parameter " + i + ": " + argClasses[i] + " = " + argValues[i]);
}
try {
method = targetClass.getMethod(methodName, argClasses);
}
// Wenn nicht da dann entsprechende Exception returnen
catch(NoSuchMethodException nsm_e){
throw nsm_e;
}
catch (SecurityException s_e){
throw s_e;
}
// invoke
Object returnValue = null;
try {
returnValue = method.invoke (invokeTarget, argValues);
}
catch (IllegalAccessException iacc_e){
throw iacc_e;
}
catch (IllegalArgumentException iarg_e){
throw iarg_e;
}
catch (InvocationTargetException it_e) {
if (XmlRpc.debug)
it_e.getTargetException ().printStackTrace ();
throw new Exception (it_e.getTargetException ().toString ());
}
return returnValue;
}
}

View file

@ -0,0 +1,46 @@
// Copyright 1999 Hannes Wallnöfer, Raphael Spannocchi
package helma.xmlrpc;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.Vector;
/**
* A prototype servlet to run XML-RPC. <p>
*
* Note that some clients like the one in Frontier 5 and the first version of XmlRpcApplet
* had XML-RPC requests hard-coded to URI /RPC2. To work with these clients, you have
* to configure your servlet environment to respond to /RPC2. This has been fixed in the
* new version of the XmlRpcApplet.
*
*/
public class XmlRpcServlet extends HttpServlet implements XmlRpcHandler {
public XmlRpcServer xmlrpc;
public void init(ServletConfig config) throws ServletException {
xmlrpc = new XmlRpcServer ();
xmlrpc.addHandler ("example", this);
}
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
byte[] result = xmlrpc.execute (req.getInputStream ());
res.setContentType("text/xml");
res.setContentLength (result.length);
OutputStream output = res.getOutputStream();
output.write (result);
output.flush ();
}
/**
* Callback method for XML-RPC server
*/
public Object execute (String methodname, Vector params) {
return params;
}
}

View file

@ -0,0 +1,170 @@
// RpcXtension.java
// Copyright (c) Hannes Wallnöfer, 1999 - All rights reserved
package helma.xmlrpc.fesi;
import helma.xmlrpc.*;
import FESI.Interpreter.*;
import FESI.Exceptions.*;
import FESI.Extensions.*;
import FESI.Data.*;
import java.io.*;
import java.util.*;
import java.net.*;
/**
* An extension to transparently call and serve XML-RPC from the
* <a href=http://home.worldcom.ch/jmlugrin/fesi/>FESI EcmaScript</a> interpreter.
* The extension adds constructors for XML-RPC clients and servers to the Global Object.
* For more information on how to use this please look at the files <tt>server.es</tt> and
* <tt>client.es</tt> in the src/fesi directory of the distribution.
*
* All argument conversion is done automatically. Currently the following argument and return
* types are supported:
* <ul>
* <li> plain objects (with all properties returned by ESObject.getProperties ())
* <li> arrays
* <li> strings
* <li> date objects
* <li> booleans
* <li> integer and float numbers (long values are not supported!)
* </ul>
*
*/
public class FesiRpcExtension extends Extension {
Evaluator evaluator;
ESObject op;
public void initializeExtension (Evaluator evaluator) throws EcmaScriptException {
// XmlRpc.setDebug (true);
this.evaluator = evaluator;
GlobalObject go = evaluator.getGlobalObject();
FunctionPrototype fp = (FunctionPrototype) evaluator.getFunctionPrototype();
op = evaluator.getObjectPrototype();
go.putHiddenProperty ("Remote", new GlobalObjectRemote ("Remote", evaluator, fp)); // the Remote constructor
go.putHiddenProperty ("RemoteServer", new GlobalObjectRemoteServer ("RemoteServer", evaluator, fp)); // the RemoteServer constructor
}
class GlobalObjectRemote extends BuiltinFunctionObject {
GlobalObjectRemote (String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
return doConstruct(thisObject, arguments);
}
public ESObject doConstruct(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
ESObject remote = null;
String url = null;
String robj = null;
if (arguments.length >= 1)
url = arguments[0].toString ();
if (arguments.length >= 2)
robj = arguments[1].toString ();
try {
remote = new ESRemote (op, this.evaluator, url, robj);
} catch (MalformedURLException x) {
throw new EcmaScriptException (x.toString ());
}
return remote;
}
}
class GlobalObjectRemoteServer extends BuiltinFunctionObject {
GlobalObjectRemoteServer (String name, Evaluator evaluator, FunctionPrototype fp) {
super(fp, evaluator, name, 1);
}
public ESValue callFunction(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
return doConstruct(thisObject, arguments);
}
public ESObject doConstruct(ESObject thisObject, ESValue[] arguments) throws EcmaScriptException {
ESObject remotesrv = null;
String globalname = null;
if (arguments.length < 1 || arguments.length > 2)
throw new EcmaScriptException ("Wrong number of arguments for constructor RemoteServer");
int port = arguments[0].toInt32 ();
if (arguments.length == 2)
globalname = arguments[1].toString ();
try {
remotesrv = new FesiRpcServer (port, op, this.evaluator);
if (globalname != null)
this.evaluator.getGlobalObject ().putProperty (globalname, remotesrv, globalname.hashCode ());
} catch (IOException x) {
throw new EcmaScriptException (x.toString ());
}
return remotesrv;
}
}
class ESRemote extends ObjectPrototype {
URL url;
String remoteObject;
public ESRemote (ESObject prototype, Evaluator evaluator, String urlstring, String robj) throws MalformedURLException {
super (prototype, evaluator);
this.url = new URL (urlstring);
remoteObject = robj;
}
public ESRemote (ESObject prototype, Evaluator evaluator, URL url, String robj) {
super (prototype, evaluator);
this.url = url;
remoteObject = robj;
}
public ESValue doIndirectCall(Evaluator evaluator, ESObject target, String functionName, ESValue arguments[])
throws EcmaScriptException, NoSuchMethodException {
// System.out.println ("doIndirectCall called with "+functionName);
XmlRpcClient client = new XmlRpcClient (url);
long now = System.currentTimeMillis ();
Object retval = null;
int l = arguments.length;
Vector v = new Vector ();
for (int i=0; i<l; i++) {
Object arg = FesiRpcUtil.convertE2J (arguments[i]);
// System.out.println ("converted to J: "+arg.getClass ());
v.addElement (arg);
}
// System.out.println ("spent "+(System.currentTimeMillis ()-now)+" millis in argument conversion");
ESObject esretval = ObjectObject.createObject (evaluator);
try {
String method = remoteObject == null ? functionName : remoteObject+"."+functionName;
retval = client.execute (method, v);
esretval.putProperty ("error", ESNull.theNull, "error".hashCode());
esretval.putProperty ("result", FesiRpcUtil.convertJ2E (retval, this.evaluator), "result".hashCode());
} catch (Exception x) {
String msg = x.getMessage();
if (msg == null || msg.length() == 0)
msg = x.toString ();
esretval.putProperty ("error", new ESString(msg), "error".hashCode());
esretval.putProperty ("result", ESNull.theNull, "result".hashCode());
}
return esretval;
}
public ESValue getProperty (String name, int hash) throws EcmaScriptException {
ESValue sprop = super.getProperty (name, hash);
if (sprop != ESUndefined.theUndefined && sprop != ESNull.theNull)
return sprop;
String newRemoteObject = remoteObject == null ? name : remoteObject+"."+name;
return new ESRemote (op, this.evaluator, url, newRemoteObject);
}
}
}

View file

@ -0,0 +1,97 @@
/*
* Copyright 1999 Hannes Wallnoefer
*/
package helma.xmlrpc.fesi;
import helma.xmlrpc.*;
import java.util.*;
import java.io.*;
import FESI.Data.*;
import FESI.Interpreter.*;
import FESI.Exceptions.*;
/**
* An ESObject that makes its properties (sub-objects) callable via XML-RPC.
* For example, if Server is an instance of FesiRpcServer, the following would make the
* functions defined for someObject available to XML-RPC clients:
* <pre>
* Server.someObject = new SomeObject ();
* </pre>
*
*/
public class FesiRpcServer extends ObjectPrototype {
// This is public (for now) to be able to set access restrictions from the outside.
public WebServer srv;
Evaluator evaluator;
/**
* Create an XML-RPC server with an already existing WebServer.
*/
public FesiRpcServer (WebServer srv, ESObject op, Evaluator eval) throws IOException, EcmaScriptException {
super (op, eval);
this.evaluator = eval;
this.srv = srv;
}
/**
* Create an XML-RPC server listening on a specific port.
*/
public FesiRpcServer (int port, ESObject op, Evaluator eval) throws IOException, EcmaScriptException {
super (op, eval);
this.evaluator = eval;
srv = new WebServer (port);
}
public void putProperty(String propertyName, ESValue propertyValue, int hash) throws EcmaScriptException {
if (propertyValue instanceof ESObject)
srv.addHandler (propertyName, new FesiInvoker ((ESObject) propertyValue));
super.putProperty (propertyName, propertyValue, hash);
}
public boolean deleteProperty (String propertyName, int hash) throws EcmaScriptException {
srv.removeHandler (propertyName);
super.deleteProperty (propertyName, hash);
return true;
}
class FesiInvoker implements XmlRpcHandler {
ESObject target;
public FesiInvoker (ESObject target) {
this.target = target;
}
public Object execute (String method, Vector argvec) throws Exception {
// convert arguments
int l = argvec.size ();
ESObject callTarget = target;
if (method.indexOf (".") > -1) {
StringTokenizer st = new StringTokenizer (method, ".");
int cnt = st.countTokens ();
for (int i=1; i<cnt; i++) {
String next = st.nextToken ();
try {
callTarget = (ESObject) callTarget.getProperty (next, next.hashCode ());
} catch (Exception x) {
throw new EcmaScriptException ("The property \""+next+"\" is not defined in the remote object.");
}
}
method = st.nextToken ();
}
ESValue args[] = new ESValue[l];
for (int i=0; i<l; i++) {
args[i] = FesiRpcUtil.convertJ2E (argvec.elementAt (i), evaluator);
}
Object retval = FesiRpcUtil.convertE2J (callTarget.doIndirectCall (evaluator, callTarget, method, args));
return retval;
}
}
}

View file

@ -0,0 +1,85 @@
// RpcXtension.java
// Copyright (c) Hannes Wallnöfer, 1999 - All rights reserved
package helma.xmlrpc.fesi;
import helma.xmlrpc.*;
import FESI.Exceptions.*;
import FESI.Data.*;
import FESI.Interpreter.*;
import java.util.*;
public class FesiRpcUtil {
// convert a generic Java object to a JavaScript Object.
public static ESValue convertJ2E (Object what, Evaluator evaluator) throws Exception {
if (what == null)
return ESNull.theNull;
if (what instanceof Vector) {
Vector v = (Vector) what;
ArrayPrototype retval = new ArrayPrototype (evaluator.getArrayPrototype (), evaluator);
int l = v.size ();
for (int i=0; i<l; i++)
retval.putProperty (i, convertJ2E (v.elementAt (i), evaluator));
return retval;
}
if (what instanceof Hashtable) {
Hashtable t = (Hashtable) what;
ESObject retval = new ObjectPrototype (evaluator.getObjectPrototype (), evaluator);
for (Enumeration e=t.keys(); e.hasMoreElements(); ) {
String next = (String) e.nextElement ();
retval.putProperty (next, convertJ2E (t.get (next), evaluator), next.hashCode ());
}
return retval;
}
if (what instanceof String)
return new ESString (what.toString ());
if (what instanceof Number)
return new ESNumber (new Double (what.toString ()).doubleValue ());
if (what instanceof Boolean)
return ESBoolean.makeBoolean (((Boolean) what).booleanValue ());
if (what instanceof Date)
return new DatePrototype (evaluator, (Date) what);
return ESLoader.normalizeValue (what, evaluator);
}
// convert a JavaScript Object object to a generic Java.
public static Object convertE2J (ESValue what) throws EcmaScriptException {
if (XmlRpc.debug)
System.out.println ("converting e-2-j: "+what.getClass ());
if (what instanceof ESNull)
return null;
if (what instanceof ArrayPrototype) {
ArrayPrototype a = (ArrayPrototype) what;
int l = a.size ();
Vector v = new Vector ();
for (int i=0; i<l; i++) {
Object nj = convertE2J (a.getProperty (i));
v.addElement (nj);
}
return v;
}
if (what instanceof ObjectPrototype) {
ObjectPrototype o = (ObjectPrototype) what;
Hashtable t = new Hashtable ();
for (Enumeration e=o.getProperties (); e.hasMoreElements (); ) {
String next = (String) e.nextElement ();
if (XmlRpc.debug) System.out.println ("converting object member "+next);
Object nj = convertE2J (o.getProperty (next, next.hashCode ()));
if (nj != null) // can't put null as value in hashtable
t.put (next, nj);
}
return t;
}
if (what instanceof ESUndefined || what instanceof ESNull)
return null;
Object jval = what.toJavaObject ();
if (jval instanceof Byte || jval instanceof Short)
jval = new Integer (jval.toString ());
return jval;
}
}