Check in current Hop snapshot
This commit is contained in:
commit
09d1fdd4e3
85 changed files with 16603 additions and 0 deletions
21
src/helma/framework/ApplicationStoppedException.java
Normal file
21
src/helma/framework/ApplicationStoppedException.java
Normal 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
17
src/helma/framework/FrameworkException.java
Normal file
17
src/helma/framework/FrameworkException.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
src/helma/framework/IRemoteApp.java
Normal file
20
src/helma/framework/IRemoteApp.java
Normal 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;
|
||||||
|
|
||||||
|
}
|
34
src/helma/framework/RedirectException.java
Normal file
34
src/helma/framework/RedirectException.java
Normal 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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
52
src/helma/framework/RequestTrans.java
Normal file
52
src/helma/framework/RequestTrans.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
207
src/helma/framework/ResponseTrans.java
Normal file
207
src/helma/framework/ResponseTrans.java
Normal 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
18
src/helma/framework/TimeoutException.java
Normal file
18
src/helma/framework/TimeoutException.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
1066
src/helma/framework/extensions/Database.java
Normal file
1066
src/helma/framework/extensions/Database.java
Normal file
File diff suppressed because it is too large
Load diff
214
src/helma/framework/extensions/ESMail.java
Normal file
214
src/helma/framework/extensions/ESMail.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
419
src/helma/framework/extensions/FtpExtension.java
Normal file
419
src/helma/framework/extensions/FtpExtension.java
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
127
src/helma/framework/extensions/ImageExtension.java
Normal file
127
src/helma/framework/extensions/ImageExtension.java
Normal 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.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
255
src/helma/framework/extensions/MailExtension.java
Normal file
255
src/helma/framework/extensions/MailExtension.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
47
src/helma/image/ActivatedImageWrapper.java
Normal file
47
src/helma/image/ActivatedImageWrapper.java
Normal 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 ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
24
src/helma/image/IRemoteGenerator.java
Normal file
24
src/helma/image/IRemoteGenerator.java
Normal 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;
|
||||||
|
|
||||||
|
}
|
37
src/helma/image/IRemoteImage.java
Normal file
37
src/helma/image/IRemoteImage.java
Normal 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;
|
||||||
|
|
||||||
|
}
|
133
src/helma/image/ImageGenerator.java
Normal file
133
src/helma/image/ImageGenerator.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
245
src/helma/image/ImageWrapper.java
Normal file
245
src/helma/image/ImageWrapper.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
106
src/helma/image/RemoteImage.java
Normal file
106
src/helma/image/RemoteImage.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
90
src/helma/image/Server.java
Normal file
90
src/helma/image/Server.java
Normal 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
56
src/helma/image/SunImageWrapper.java
Normal file
56
src/helma/image/SunImageWrapper.java
Normal 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 ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
161
src/helma/mime/LanguageTag.java
Normal file
161
src/helma/mime/LanguageTag.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
51
src/helma/mime/MimeHeaderHolder.java
Normal file
51
src/helma/mime/MimeHeaderHolder.java
Normal 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;
|
||||||
|
|
||||||
|
}
|
147
src/helma/mime/MimeHeaders.java
Normal file
147
src/helma/mime/MimeHeaders.java
Normal 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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
25
src/helma/mime/MimeHeadersFactory.java
Normal file
25
src/helma/mime/MimeHeadersFactory.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
228
src/helma/mime/MimeParser.java
Normal file
228
src/helma/mime/MimeParser.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
14
src/helma/mime/MimeParserException.java
Normal file
14
src/helma/mime/MimeParserException.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
src/helma/mime/MimeParserFactory.java
Normal file
24
src/helma/mime/MimeParserFactory.java
Normal 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);
|
||||||
|
|
||||||
|
}
|
374
src/helma/mime/MimeType.java
Normal file
374
src/helma/mime/MimeType.java
Normal 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>") ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
src/helma/mime/MimeTypeFormatException.java
Normal file
14
src/helma/mime/MimeTypeFormatException.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
214
src/helma/mime/MultipartInputStream.java
Normal file
214
src/helma/mime/MultipartInputStream.java
Normal 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
143
src/helma/mime/Utils.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
51
src/helma/objectmodel/ConcurrencyException.java
Normal file
51
src/helma/objectmodel/ConcurrencyException.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
457
src/helma/objectmodel/DbMapping.java
Normal file
457
src/helma/objectmodel/DbMapping.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
95
src/helma/objectmodel/DbSource.java
Normal file
95
src/helma/objectmodel/DbSource.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
114
src/helma/objectmodel/INode.java
Normal file
114
src/helma/objectmodel/INode.java
Normal 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);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
15
src/helma/objectmodel/INodeListener.java
Normal file
15
src/helma/objectmodel/INodeListener.java
Normal 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);
|
||||||
|
|
||||||
|
}
|
21
src/helma/objectmodel/IPathElement.java
Normal file
21
src/helma/objectmodel/IPathElement.java
Normal 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);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
34
src/helma/objectmodel/IProperty.java
Normal file
34
src/helma/objectmodel/IProperty.java
Normal 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 ();
|
||||||
|
|
||||||
|
}
|
79
src/helma/objectmodel/IServer.java
Normal file
79
src/helma/objectmodel/IServer.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
140
src/helma/objectmodel/Key.java
Normal file
140
src/helma/objectmodel/Key.java
Normal 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+"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
765
src/helma/objectmodel/Node.java
Normal file
765
src/helma/objectmodel/Node.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
46
src/helma/objectmodel/NodeDataSource.java
Normal file
46
src/helma/objectmodel/NodeDataSource.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
59
src/helma/objectmodel/NodeEvent.java
Normal file
59
src/helma/objectmodel/NodeEvent.java
Normal 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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
49
src/helma/objectmodel/ObjectNotFoundException.java
Normal file
49
src/helma/objectmodel/ObjectNotFoundException.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
332
src/helma/objectmodel/Property.java
Normal file
332
src/helma/objectmodel/Property.java
Normal 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 ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
308
src/helma/objectmodel/Relation.java
Normal file
308
src/helma/objectmodel/Relation.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
133
src/helma/objectmodel/SystemProperties.java
Normal file
133
src/helma/objectmodel/SystemProperties.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
139
src/helma/objectmodel/db/ApplicationManager.java
Normal file
139
src/helma/objectmodel/db/ApplicationManager.java
Normal 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 ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
281
src/helma/objectmodel/db/DbWrapper.java
Normal file
281
src/helma/objectmodel/db/DbWrapper.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
39
src/helma/objectmodel/db/ExternalizableVector.java
Normal file
39
src/helma/objectmodel/db/ExternalizableVector.java
Normal 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
43
src/helma/objectmodel/db/HopSocketFactory.java
Normal file
43
src/helma/objectmodel/db/HopSocketFactory.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
73
src/helma/objectmodel/db/IDGenerator.java
Normal file
73
src/helma/objectmodel/db/IDGenerator.java
Normal 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+"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
1643
src/helma/objectmodel/db/Node.java
Normal file
1643
src/helma/objectmodel/db/Node.java
Normal file
File diff suppressed because it is too large
Load diff
959
src/helma/objectmodel/db/NodeManager.java
Normal file
959
src/helma/objectmodel/db/NodeManager.java
Normal 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 ();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
561
src/helma/objectmodel/db/Property.java
Normal file
561
src/helma/objectmodel/db/Property.java
Normal 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 ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
355
src/helma/objectmodel/db/Server.java
Normal file
355
src/helma/objectmodel/db/Server.java
Normal 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
338
src/helma/objectmodel/db/Transactor.java
Normal file
338
src/helma/objectmodel/db/Transactor.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
186
src/helma/objectmodel/db/WrappedNodeManager.java
Normal file
186
src/helma/objectmodel/db/WrappedNodeManager.java
Normal 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 ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
208
src/helma/servlet/AcmeFileServlet.java
Normal file
208
src/helma/servlet/AcmeFileServlet.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
254
src/helma/servlet/AcmeServletClient.java
Normal file
254
src/helma/servlet/AcmeServletClient.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
312
src/helma/servlet/ServletClient.java
Normal file
312
src/helma/servlet/ServletClient.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
365
src/helma/util/HtmlEncoder.java
Normal file
365
src/helma/util/HtmlEncoder.java
Normal 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), " ");
|
||||||
|
convertor.put(new Integer(161), "¡");
|
||||||
|
convertor.put(new Integer(162), "¢");
|
||||||
|
convertor.put(new Integer(163), "£");
|
||||||
|
convertor.put(new Integer(164), "¤");
|
||||||
|
convertor.put(new Integer(165), "¥");
|
||||||
|
convertor.put(new Integer(166), "¦");
|
||||||
|
convertor.put(new Integer(167), "§");
|
||||||
|
convertor.put(new Integer(168), "¨");
|
||||||
|
convertor.put(new Integer(169), "©");
|
||||||
|
convertor.put(new Integer(170), "ª");
|
||||||
|
convertor.put(new Integer(171), "«");
|
||||||
|
convertor.put(new Integer(172), "¬");
|
||||||
|
convertor.put(new Integer(173), "­");
|
||||||
|
convertor.put(new Integer(174), "®");
|
||||||
|
convertor.put(new Integer(175), "¯");
|
||||||
|
convertor.put(new Integer(176), "°");
|
||||||
|
convertor.put(new Integer(177), "±");
|
||||||
|
convertor.put(new Integer(178), "²");
|
||||||
|
convertor.put(new Integer(179), "³");
|
||||||
|
convertor.put(new Integer(180), "´");
|
||||||
|
convertor.put(new Integer(181), "µ");
|
||||||
|
convertor.put(new Integer(182), "¶");
|
||||||
|
convertor.put(new Integer(183), "·");
|
||||||
|
convertor.put(new Integer(184), "¸");
|
||||||
|
convertor.put(new Integer(185), "¹");
|
||||||
|
convertor.put(new Integer(186), "º");
|
||||||
|
convertor.put(new Integer(187), "»");
|
||||||
|
convertor.put(new Integer(188), "¼");
|
||||||
|
convertor.put(new Integer(189), "½");
|
||||||
|
convertor.put(new Integer(190), "¾");
|
||||||
|
convertor.put(new Integer(191), "¿");
|
||||||
|
convertor.put(new Integer(192), "À");
|
||||||
|
convertor.put(new Integer(193), "Á");
|
||||||
|
convertor.put(new Integer(194), "Â");
|
||||||
|
convertor.put(new Integer(195), "Ã");
|
||||||
|
convertor.put(new Integer(196), "Ä");
|
||||||
|
convertor.put(new Integer(197), "Å");
|
||||||
|
convertor.put(new Integer(198), "Æ");
|
||||||
|
convertor.put(new Integer(199), "Ç");
|
||||||
|
convertor.put(new Integer(200), "È");
|
||||||
|
convertor.put(new Integer(201), "É");
|
||||||
|
convertor.put(new Integer(202), "Ê");
|
||||||
|
convertor.put(new Integer(203), "Ë");
|
||||||
|
convertor.put(new Integer(204), "Ì");
|
||||||
|
convertor.put(new Integer(205), "Í");
|
||||||
|
convertor.put(new Integer(206), "Î");
|
||||||
|
convertor.put(new Integer(207), "Ï");
|
||||||
|
convertor.put(new Integer(208), "Ð");
|
||||||
|
convertor.put(new Integer(209), "Ñ");
|
||||||
|
convertor.put(new Integer(210), "Ò");
|
||||||
|
convertor.put(new Integer(211), "Ó");
|
||||||
|
convertor.put(new Integer(212), "Ô");
|
||||||
|
convertor.put(new Integer(213), "Õ");
|
||||||
|
convertor.put(new Integer(214), "Ö");
|
||||||
|
convertor.put(new Integer(215), "×");
|
||||||
|
convertor.put(new Integer(216), "Ø");
|
||||||
|
convertor.put(new Integer(217), "Ù");
|
||||||
|
convertor.put(new Integer(218), "Ú");
|
||||||
|
convertor.put(new Integer(219), "Û");
|
||||||
|
convertor.put(new Integer(220), "Ü");
|
||||||
|
convertor.put(new Integer(221), "Ý");
|
||||||
|
convertor.put(new Integer(222), "Þ");
|
||||||
|
convertor.put(new Integer(223), "ß");
|
||||||
|
convertor.put(new Integer(224), "à");
|
||||||
|
convertor.put(new Integer(225), "á");
|
||||||
|
convertor.put(new Integer(226), "â");
|
||||||
|
convertor.put(new Integer(227), "ã");
|
||||||
|
convertor.put(new Integer(228), "ä");
|
||||||
|
convertor.put(new Integer(229), "å");
|
||||||
|
convertor.put(new Integer(230), "æ");
|
||||||
|
convertor.put(new Integer(231), "ç");
|
||||||
|
convertor.put(new Integer(232), "è");
|
||||||
|
convertor.put(new Integer(233), "é");
|
||||||
|
convertor.put(new Integer(234), "ê");
|
||||||
|
convertor.put(new Integer(235), "ë");
|
||||||
|
convertor.put(new Integer(236), "ì");
|
||||||
|
convertor.put(new Integer(237), "í");
|
||||||
|
convertor.put(new Integer(238), "î");
|
||||||
|
convertor.put(new Integer(239), "ï");
|
||||||
|
convertor.put(new Integer(240), "ð");
|
||||||
|
convertor.put(new Integer(241), "ñ");
|
||||||
|
convertor.put(new Integer(242), "ò");
|
||||||
|
convertor.put(new Integer(243), "ó");
|
||||||
|
convertor.put(new Integer(244), "ô");
|
||||||
|
convertor.put(new Integer(245), "õ");
|
||||||
|
convertor.put(new Integer(246), "ö");
|
||||||
|
convertor.put(new Integer(247), "÷");
|
||||||
|
convertor.put(new Integer(248), "ø");
|
||||||
|
convertor.put(new Integer(249), "ù");
|
||||||
|
convertor.put(new Integer(250), "ú");
|
||||||
|
convertor.put(new Integer(251), "û");
|
||||||
|
convertor.put(new Integer(252), "ü");
|
||||||
|
convertor.put(new Integer(253), "ý");
|
||||||
|
convertor.put(new Integer(254), "þ");
|
||||||
|
convertor.put(new Integer(255), "ÿ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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 ("&");
|
||||||
|
// 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 ("<");
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
ret.append (">");
|
||||||
|
break;
|
||||||
|
case '&':
|
||||||
|
ret.append ("&");
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
ret.append (""");
|
||||||
|
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 ("€");
|
||||||
|
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 ("<");
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
ret.append (">");
|
||||||
|
break;
|
||||||
|
case '&':
|
||||||
|
ret.append ("&");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret.append ((char) c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
56
src/helma/util/InetAddressFilter.java
Normal file
56
src/helma/util/InetAddressFilter.java
Normal 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
103
src/helma/util/Logger.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
50
src/helma/util/ParanoidServerSocket.java
Normal file
50
src/helma/util/ParanoidServerSocket.java
Normal 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
70
src/helma/util/Timer.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
110
src/helma/util/Uploader.java
Normal file
110
src/helma/util/Uploader.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
126
src/helma/util/UrlEncoder.java
Normal file
126
src/helma/util/UrlEncoder.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
350
src/helma/util/WebBroadcaster.java
Normal file
350
src/helma/util/WebBroadcaster.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
src/helma/xmlrpc/AuthenticatedXmlRpcHandler.java
Normal file
20
src/helma/xmlrpc/AuthenticatedXmlRpcHandler.java
Normal 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;
|
||||||
|
|
||||||
|
}
|
130
src/helma/xmlrpc/Base64.java
Normal file
130
src/helma/xmlrpc/Base64.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
104
src/helma/xmlrpc/Benchmark.java
Normal file
104
src/helma/xmlrpc/Benchmark.java
Normal 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]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
61
src/helma/xmlrpc/ServerInputStream.java
Normal file
61
src/helma/xmlrpc/ServerInputStream.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
448
src/helma/xmlrpc/WebServer.java
Normal file
448
src/helma/xmlrpc/WebServer.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
583
src/helma/xmlrpc/XmlRpc.java
Normal file
583
src/helma/xmlrpc/XmlRpc.java
Normal 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 ("<");
|
||||||
|
break;
|
||||||
|
case '&' :
|
||||||
|
buf.append ("&");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
226
src/helma/xmlrpc/XmlRpcClient.java
Normal file
226
src/helma/xmlrpc/XmlRpcClient.java
Normal 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
358
src/helma/xmlrpc/XmlRpcClientLite.java
Normal file
358
src/helma/xmlrpc/XmlRpcClientLite.java
Normal 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
24
src/helma/xmlrpc/XmlRpcException.java
Normal file
24
src/helma/xmlrpc/XmlRpcException.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
src/helma/xmlrpc/XmlRpcHandler.java
Normal file
23
src/helma/xmlrpc/XmlRpcHandler.java
Normal 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;
|
||||||
|
|
||||||
|
}
|
42
src/helma/xmlrpc/XmlRpcProxyServlet.java
Normal file
42
src/helma/xmlrpc/XmlRpcProxyServlet.java
Normal 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 ();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
284
src/helma/xmlrpc/XmlRpcServer.java
Normal file
284
src/helma/xmlrpc/XmlRpcServer.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
src/helma/xmlrpc/XmlRpcServlet.java
Normal file
46
src/helma/xmlrpc/XmlRpcServlet.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
170
src/helma/xmlrpc/fesi/FesiRpcExtension.java
Normal file
170
src/helma/xmlrpc/fesi/FesiRpcExtension.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
97
src/helma/xmlrpc/fesi/FesiRpcServer.java
Normal file
97
src/helma/xmlrpc/fesi/FesiRpcServer.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
85
src/helma/xmlrpc/fesi/FesiRpcUtil.java
Normal file
85
src/helma/xmlrpc/fesi/FesiRpcUtil.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue