From: kenstir Date: Wed, 18 Nov 2015 17:12:48 +0000 (-0500) Subject: These are the OpenSRF sources exactly as I got them, checked into the Evergreen app... X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=f9d649fc42d295fc80d79e858df003926ed1789a;p=working%2FEvergreen.git These are the OpenSRF sources exactly as I got them, checked into the Evergreen app as Open-ILS/src/Android/libs/org.opensrf2_serialized_reg.jar I don't know the exact history of this copy but it is clear that the registry was made serializeable. For now I want these sources inside the app build so I can debug them. --- diff --git a/Open-ILS/src/Android/opensrf/libs/java_memcached-release_2.0.1.jar b/Open-ILS/src/Android/opensrf/libs/java_memcached-release_2.0.1.jar new file mode 100644 index 0000000000..9f62013fe4 Binary files /dev/null and b/Open-ILS/src/Android/opensrf/libs/java_memcached-release_2.0.1.jar differ diff --git a/Open-ILS/src/Android/opensrf/libs/json-20090211.jar b/Open-ILS/src/Android/opensrf/libs/json-20090211.jar new file mode 100644 index 0000000000..ef29094093 Binary files /dev/null and b/Open-ILS/src/Android/opensrf/libs/json-20090211.jar differ diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/ClientSession.java b/Open-ILS/src/Android/opensrf/org/opensrf/ClientSession.java new file mode 100644 index 0000000000..3ed4908296 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/ClientSession.java @@ -0,0 +1,175 @@ +package org.opensrf; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.Random; +import java.util.Arrays; + +import org.opensrf.util.*; +import org.opensrf.net.xmpp.*; + + +/** + * Models an OpenSRF client session. + */ +public class ClientSession extends Session { + + /** The remote service to communicate with */ + private String service; + /** OpenSRF domain */ + private String domain; + /** Router name */ + private String router; + + /** + * original remote node. The current remote node will change based + * on server responses. This is used to reset the remote node to + * its original state. + */ + private String origRemoteNode; + /** The next request id */ + private int nextId; + /** The requests this session has sent */ + private Map requests; + + /** + * Creates a new client session. Initializes the + * @param service The remote service. + */ + public ClientSession(String service) throws ConfigException { + this(service, null); + } + + /** + * Creates a new client session. Initializes the + * @param service The remote service. + * @param locale The locale for this session. + */ + public ClientSession(String service, String locale) throws ConfigException { + this.service = service; + if(locale != null) + setLocale(locale); + + /** generate the remote node string */ + domain = (String) Config.global().getFirst("/domain"); + router = Config.global().getString("/router_name"); + setRemoteNode(router + "@" + domain + "/" + service); + origRemoteNode = getRemoteNode(); + + + /** create a random thread */ + long time = new Date().getTime(); + Random rand = new Random(time); + setThread(rand.nextInt()+""+rand.nextInt()+""+time+Thread.currentThread().getId()); + + nextId = 0; + requests = new HashMap(); + cacheSession(); + } + + /** + * Creates a new request to send to our remote service. + * @param method The method API name + * @param params The list of method parameters + * @return The request object. + */ + public Request request(String method, List params) throws SessionException { + return request(new Request(this, nextId++, method, params)); + } + + /** + * Creates a new request to send to our remote service. + * @param method The method API name + * @param params The list of method parameters + * @return The request object. + */ + public Request request(String method, Object[] params) throws SessionException { + return request(new Request(this, nextId++, method, Arrays.asList(params))); + } + + + /** + * Creates a new request to send to our remote service. + * @param method The method API name + * @return The request object. + */ + public Request request(String method) throws SessionException { + return request(new Request(this, nextId++, method)); + } + + + private Request request(Request req) throws SessionException { + if(getConnectState() != ConnectState.CONNECTED) + resetRemoteId(); + requests.put(new Integer(req.getId()), req); + req.send(); + return req; + } + + + /** + * Resets the remoteNode to its original state. + */ + public void resetRemoteId() { + setRemoteNode(origRemoteNode); + } + + + /** + * Pushes a response onto the result queue of the appropriate request. + * @param msg The received RESULT Message + */ + public void pushResponse(Message msg) { + + Request req = findRequest(msg.getId()); + if(req == null) { + /** LOG that we've received a result to a non-existant request */ + System.err.println(msg.getId() +" has no corresponding request"); + return; + } + OSRFObject payload = (OSRFObject) msg.get("payload"); + + /** build a result and push it onto the request's result queue */ + req.pushResponse( + new Result( + payload.getString("status"), + payload.getInt("statusCode"), + payload.get("content") + ) + ); + } + + public Request findRequest(int reqId) { + return requests.get(new Integer(reqId)); + } + + /** + * Removes a request for this session's request set + */ + public void cleanupRequest(int reqId) { + requests.remove(new Integer(reqId)); + } + + public void setRequestComplete(int reqId) { + Request req = findRequest(reqId); + if(req == null) return; + req.setComplete(); + } + + public static Object atomicRequest(String service, String method, Object[] params) throws MethodException { + try { + ClientSession session = new ClientSession(service); + Request osrfRequest = session.request(method, params); + Result result = osrfRequest.recv(600000); + if(result.getStatusCode() != 200) + throw new MethodException( + "Request "+service+":"+method+":"+" failed with status code " + result.getStatusCode()); + return result.getContent(); + } catch(Exception e) { + throw new MethodException(e); + } + } +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/Message.java b/Open-ILS/src/Android/opensrf/org/opensrf/Message.java new file mode 100644 index 0000000000..6bfe1eaaa7 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/Message.java @@ -0,0 +1,110 @@ +package org.opensrf; +import org.opensrf.util.*; + + +public class Message implements OSRFSerializable { + + /** Message types */ + public static final String REQUEST = "REQUEST"; + public static final String STATUS = "STATUS"; + public static final String RESULT = "RESULT"; + public static final String CONNECT = "CONNECT"; + public static final String DISCONNECT = "DISCONNECT"; + + /** Message ID. This number is used to relate requests to responses */ + private int id; + /** type of message. */ + private String type; + /** message payload */ + private Object payload; + /** message locale */ + private String locale; + + /** Create a registry for the osrfMessage object */ + private static OSRFRegistry registry = + OSRFRegistry.registerObject( + "osrfMessage", + OSRFRegistry.WireProtocol.HASH, + new String[] {"threadTrace", "type", "payload", "locale"}); + + /** + * @param id This message's ID + * @param type The type of message + */ + public Message(int id, String type) { + setId(id); + setString(type); + } + + /** + * @param id This message's ID + * @param type The type of message + * @param payload The message payload + */ + public Message(int id, String type, Object payload) { + this(id, type); + setPayload(payload); + } + + /** + * @param id This message's ID + * @param type The type of message + * @param payload The message payload + * @param locale The message locale + */ + public Message(int id, String type, Object payload, String locale) { + this(id, type, payload); + setPayload(payload); + setLocale(locale); + } + + + public int getId() { + return id; + } + public String getType() { + return type; + } + public Object getPayload() { + return payload; + } + public String getLocale() { + return locale; + } + public void setId(int id) { + this.id = id; + } + public void setString(String type) { + this.type = type; + } + public void setPayload(Object p) { + payload = p; + } + public void setLocale(String l) { + locale = l; + } + + /** + * Implements the generic get() API required by OSRFSerializable + */ + public Object get(String field) { + if("threadTrace".equals(field)) + return getId(); + if("type".equals(field)) + return getType().toString(); + if("payload".equals(field)) + return getPayload(); + if("locale".equals(field)) + return getLocale(); + return null; + } + + /** + * @return The osrfMessage registry. + */ + public OSRFRegistry getRegistry() { + return registry; + } +} + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/Method.java b/Open-ILS/src/Android/opensrf/org/opensrf/Method.java new file mode 100644 index 0000000000..b708d4fa39 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/Method.java @@ -0,0 +1,78 @@ +package org.opensrf; +import java.util.List; +import java.util.ArrayList; +import org.opensrf.util.*; + + +public class Method extends OSRFObject { + + /** The method API name */ + private String name; + /** The ordered list of method params */ + private List params; + + /** Create a registry for the osrfMethod object */ + private static OSRFRegistry registry = + OSRFRegistry.registerObject( + "osrfMethod", + OSRFRegistry.WireProtocol.HASH, + new String[] {"method", "params"}); + + /** + * @param name The method API name + */ + public Method(String name) { + this.name = name; + this.params = new ArrayList(8); + } + + /** + * @param name The method API name + * @param params The ordered list of params + */ + public Method(String name, List params) { + this.name = name; + this.params = params; + } + + /** + * @return The method API name + */ + public String getName() { + return name; + } + /** + * @return The ordered list of params + */ + public List getParams() { + return params; + } + + /** + * Pushes a new param object onto the set of params + * @param p The new param to add to the method. + */ + public void addParam(Object p) { + this.params.add(p); + } + + /** + * Implements the generic get() API required by OSRFSerializable + */ + public Object get(String field) { + if("method".equals(field)) + return getName(); + if("params".equals(field)) + return getParams(); + return null; + } + + /** + * @return The osrfMethod registry. + */ + public OSRFRegistry getRegistry() { + return registry; + } + +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/MethodException.java b/Open-ILS/src/Android/opensrf/org/opensrf/MethodException.java new file mode 100644 index 0000000000..f87e638cf2 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/MethodException.java @@ -0,0 +1,14 @@ +package org.opensrf; + +/** + * Thrown when the server responds with a method exception. + */ +public class MethodException extends Exception { + public MethodException(String info) { + super(info); + } + public MethodException(Throwable cause) { + super(cause); + } +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/MultiSession.java b/Open-ILS/src/Android/opensrf/org/opensrf/MultiSession.java new file mode 100644 index 0000000000..a312aff9b8 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/MultiSession.java @@ -0,0 +1,123 @@ +package org.opensrf; +import java.util.List; +import java.util.ArrayList; +import org.opensrf.util.ConfigException; + +public class MultiSession { + + class RequestContainer { + Request request; + int id; + RequestContainer(Request r) { + request = r; + } + } + + private boolean complete; + private List requests; + private int lastId; + + public MultiSession() { + requests = new ArrayList(); + } + + public boolean isComplete() { + return complete; + } + + public int lastId() { + return lastId; + } + + /** + * Adds a new request to the set of requests. + * @param service The OpenSRF service + * @param method The OpenSRF method + * @param params The array of method params + * @return The request ID, which is used to map results from recv() to the original request. + */ + public int request(String service, String method, Object[] params) throws SessionException, ConfigException { + ClientSession ses = new ClientSession(service); + return request(ses.request(method, params)); + } + + + public int request(String service, String method) throws SessionException, ConfigException { + ClientSession ses = new ClientSession(service); + return request(ses.request(method)); + } + + private int request(Request req) { + RequestContainer c = new RequestContainer(req); + c.id = requests.size(); + requests.add(c); + return c.id; + } + + + /** + * Calls recv on all pending requests until there is data to return. The ID which + * maps the received object to the request can be retrieved by calling lastId(). + * @param millis Number of milliseconds to wait for some data to arrive. + * @return The object result or null if all requests are complete + * @throws MethodException Thrown if no response is received within + * the given timeout or the method fails. + */ + public Object recv(int millis) throws MethodException { + if(complete) return null; + + Request req = null; + Result res = null; + RequestContainer cont = null; + + long duration = 0; + long blockTime = 100; + + /* if there is only 1 outstanding request, don't poll */ + if(requests.size() == 1) + blockTime = millis; + + while(true) { + for(int i = 0; i < requests.size(); i++) { + + cont = requests.get(i); + req = cont.request; + + try { + if(i == 0) { + res = req.recv(blockTime); + } else { + res = req.recv(0); + } + } catch(SessionException e) { + throw new MethodException(e); + } + + if(res != null) break; + } + + if(res != null) break; + duration += blockTime; + + if(duration >= millis) { + System.out.println("duration = " + duration + " millis = " + millis); + throw new MethodException("No request received within " + millis + " milliseconds"); + } + } + + if(res.getStatusCode() != 200) { + throw new MethodException("Request " + cont.id + " failed with status code " + + res.getStatusCode() + " and status message " + res.getStatus()); + } + + if(req.isComplete()) + requests.remove(requests.indexOf(cont)); + + if(requests.size() == 0) + complete = true; + + lastId = cont.id; + return res.getContent(); + } +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/Request.java b/Open-ILS/src/Android/opensrf/org/opensrf/Request.java new file mode 100644 index 0000000000..2d72e2df65 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/Request.java @@ -0,0 +1,138 @@ +package org.opensrf; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.List; +import java.util.Date; +import org.opensrf.net.xmpp.XMPPException; +import org.opensrf.util.Logger; + +public class Request { + + /** This request's controlling session */ + private ClientSession session; + /** The method */ + private Method method; + /** The ID of this request */ + private int id; + /** Queue of Results */ + private Queue resultQueue; + /** If true, the receive timeout for this request should be reset */ + private boolean resetTimeout; + + /** If true, the server has indicated that this request is complete. */ + private boolean complete; + + /** + * @param ses The controlling session for this request. + * @param id This request's ID. + * @param method The requested method. + */ + public Request(ClientSession ses, int id, Method method) { + this.session = ses; + this.id = id; + this.method = method; + resultQueue = new ConcurrentLinkedQueue(); + complete = false; + resetTimeout = false; + } + + /** + * @param ses The controlling session for this request. + * @param id This request's ID. + * @param methodName The requested method's API name. + */ + public Request(ClientSession ses, int id, String methodName) { + this(ses, id, new Method(methodName)); + } + + /** + * @param ses The controlling session for this request. + * @param id This request's ID. + * @param methodName The requested method's API name. + * @param params The list of request params + */ + public Request(ClientSession ses, int id, String methodName, List params) { + this(ses, id, new Method(methodName, params)); + } + + /** + * Sends the request to the server. + */ + public void send() throws SessionException { + session.send(new Message(id, Message.REQUEST, method, session.getLocale())); + } + + /** + * Receives the next result for this request. This method + * will wait up to the specified number of milliseconds for + * a response. + * @param millis Number of milliseconds to wait for a result. If + * negative, this method will wait indefinitely. + * @return The result or null if none arrives in time + */ + public Result recv(long millis) throws SessionException, MethodException { + + Result result = null; + + if((result = resultQueue.poll()) != null) + return result; + + if(millis < 0 && !complete) { + /** wait potentially forever for a result to arrive */ + while(!complete) { + session.waitForMessage(millis); + if((result = resultQueue.poll()) != null) + return result; + } + + } else { + + while(millis >= 0 && !complete) { + + /** wait up to millis milliseconds for a result. waitForMessage() + * will return if a response to any request arrives, so we keep track + * of how long we've been waiting in total for a response to + * this request */ + + long start = new Date().getTime(); + session.waitForMessage(millis); + millis -= new Date().getTime() - start; + if((result = resultQueue.poll()) != null) + return result; + } + } + + return null; + } + + /** + * Pushes a result onto the result queue + * @param result The result to push + */ + public void pushResponse(Result result) { + resultQueue.offer(result); + } + + /** + * @return This request's ID + */ + public int getId() { + return id; + } + + /** + * Removes this request from the controlling session's request set + */ + public void cleanup() { + session.cleanupRequest(id); + } + + /** Sets this request as complete */ + public void setComplete() { + complete = true; + } + + public boolean isComplete() { + return complete; + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/Result.java b/Open-ILS/src/Android/opensrf/org/opensrf/Result.java new file mode 100644 index 0000000000..80b71dd138 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/Result.java @@ -0,0 +1,106 @@ +package org.opensrf; +import org.opensrf.util.*; + + +/** + * Models a single result from a method request. + */ +public class Result implements OSRFSerializable { + + /** Method result content */ + private Object content; + /** Name of the status */ + private String status; + /** Status code number */ + private int statusCode; + + + /** Register this object */ + private static OSRFRegistry registry = + OSRFRegistry.registerObject( + "osrfResult", + OSRFRegistry.WireProtocol.HASH, + new String[] {"status", "statusCode", "content"}); + + + /** + * @param status The status message for this result + * @param statusCode The status code + * @param content The content of the result + */ + public Result(String status, int statusCode, Object content) { + this.status = status; + this.statusCode = statusCode; + this.content = content; + } + + /** + * Get status. + * @return status as String. + */ + public String getStatus() { + return status; + } + + /** + * Set status. + * @param status the value to set. + */ + public void setStatus(String status) { + this.status = status; + } + + /** + * Get statusCode. + * @return statusCode as int. + */ + public int getStatusCode() { + return statusCode; + } + + /** + * Set statusCode. + * @param statusCode the value to set. + */ + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + /** + * Get content. + * @return content as Object. + */ + public Object getContent() { + return content; + } + + /** + * Set content. + * @param content the value to set. + */ + public void setContent(Object content) { + this.content = content; + } + + /** + * Implements the generic get() API required by OSRFSerializable + */ + public Object get(String field) { + if("status".equals(field)) + return getStatus(); + if("statusCode".equals(field)) + return getStatusCode(); + if("content".equals(field)) + return getContent(); + return null; + } + + /** + * @return The osrfMethod registry. + */ + public OSRFRegistry getRegistry() { + return registry; + } + +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/ServerSession.java b/Open-ILS/src/Android/opensrf/org/opensrf/ServerSession.java new file mode 100644 index 0000000000..62e5133058 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/ServerSession.java @@ -0,0 +1,8 @@ +package org.opensrf; + +/** + * Models an OpenSRF server session. + */ +public class ServerSession extends Session { +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/Session.java b/Open-ILS/src/Android/opensrf/org/opensrf/Session.java new file mode 100644 index 0000000000..15fd352d5f --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/Session.java @@ -0,0 +1,180 @@ +package org.opensrf; +import org.opensrf.util.JSONWriter; +import org.opensrf.net.xmpp.*; +import java.util.Map; +import java.util.HashMap; +import java.util.Arrays; + +public abstract class Session { + + /** Represents the different connection states for a session */ + public enum ConnectState { + DISCONNECTED, + CONNECTING, + CONNECTED + }; + + /** local cache of existing sessions */ + private static Map + sessionCache = new HashMap(); + + /** the current connection state */ + private ConnectState connectState; + + /** The address of the remote party we are communicating with */ + private String remoteNode; + + /** Session locale */ + protected String locale; + /** Default session locale */ + protected static String defaultLocale = "en-US"; + + /** + * The thread is used to link messages to a given session. + * In other words, each session has a unique thread, and all messages + * in that session will carry this thread around as an indicator. + */ + private String thread; + + public Session() { + connectState = ConnectState.DISCONNECTED; + } + + /** + * Sends a Message to our remoteNode. + */ + public void send(Message omsg) throws SessionException { + + /** construct the XMPP message */ + XMPPMessage xmsg = new XMPPMessage(); + xmsg.setTo(remoteNode); + xmsg.setThread(thread); + xmsg.setBody(new JSONWriter(Arrays.asList(new Message[] {omsg})).write()); + + try { + XMPPSession.getThreadSession().send(xmsg); + } catch(XMPPException e) { + connectState = ConnectState.DISCONNECTED; + throw new SessionException("Error sending message to " + remoteNode, e); + } + } + + /** + * Waits for a message to arrive over the network and passes + * all received messages to the stack for processing + * @param millis The number of milliseconds to wait for a message to arrive + */ + public static void waitForMessage(long millis) throws SessionException, MethodException { + try { + Stack.processXMPPMessage( + XMPPSession.getThreadSession().recv(millis)); + } catch(XMPPException e) { + throw new SessionException("Error waiting for message", e); + } + } + + /** + * Removes this session from the session cache. + */ + public void cleanup() { + sessionCache.remove(thread); + } + + /** + * Searches for the cached session with the given thread. + * @param thread The session thread. + * @return The found session or null. + */ + public static Session findCachedSession(String thread) { + return sessionCache.get(thread); + } + + /** + * Puts this session into session cache. + */ + protected void cacheSession() { + sessionCache.put(thread, this); + } + + /** + * Sets the remote address + * @param nodeName The name of the remote node. + */ + public void setRemoteNode(String nodeName) { + remoteNode = nodeName; + } + /** + * @return The remote node + */ + public String getRemoteNode() { + return remoteNode; + } + + + /** + * Get thread. + * @return thread as String. + */ + public String getThread() { + return thread; + } + + /** + * Set thread. + * @param thread the value to set. + */ + public void setThread(String thread) { + this.thread = thread; + } + + /** + * Get locale. + * @return locale as String. + */ + public String getLocale() { + if(locale == null) + return defaultLocale; + return locale; + } + + /** + * Set locale. + * @param locale the value to set. + */ + public void setLocale(String locale) { + this.locale = locale; + } + + /** + * Get defaultLocale. + * @return defaultLocale as String. + */ + public String getDefaultLocale() { + return defaultLocale; + } + + /** + * Set defaultLocale. + * @param defaultLocale the value to set. + */ + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } + + + /** + * Get connectState. + * @return connectState as ConnectState. + */ + public ConnectState getConnectState() { + return connectState; + } + + /** + * Set connectState. + * @param connectState the value to set. + */ + public void setConnectState(ConnectState connectState) { + this.connectState = connectState; + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/SessionException.java b/Open-ILS/src/Android/opensrf/org/opensrf/SessionException.java new file mode 100644 index 0000000000..bd90a7610d --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/SessionException.java @@ -0,0 +1,13 @@ +package org.opensrf; +/** + * Used by sessions to indicate communication errors + */ +public class SessionException extends Exception { + public SessionException(String info) { + super(info); + } + public SessionException(String info, Throwable cause) { + super(info, cause); + } +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/Stack.java b/Open-ILS/src/Android/opensrf/org/opensrf/Stack.java new file mode 100644 index 0000000000..3e7e6061fb --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/Stack.java @@ -0,0 +1,105 @@ +package org.opensrf; +import org.opensrf.net.xmpp.XMPPMessage; +import org.opensrf.util.*; +import java.util.Date; +import java.util.List; +import java.util.Iterator; + + +public class Stack { + + public static void processXMPPMessage(XMPPMessage msg) throws MethodException { + + if(msg == null) return; + + //System.out.println(msg.getBody()); + + /** fetch this session from the cache */ + Session ses = Session.findCachedSession(msg.getThread()); + + if(ses == null) { + /** inbound client request, create a new server session */ + return; + } + + /** parse the JSON message body, which should result in a list of OpenSRF messages */ + List msgList; + + try { + msgList = new JSONReader(msg.getBody()).readArray(); + } catch(JSONException e) { + /** XXX LOG error */ + e.printStackTrace(); + return; + } + + Iterator itr = msgList.iterator(); + + OSRFObject obj = null; + long start = new Date().getTime(); + + /** cycle through the messages and push them up the stack */ + while(itr.hasNext()) { + + /** Construct a Message object from the parsed generic OSRFObject */ + obj = (OSRFObject) itr.next(); + + processOSRFMessage( + ses, + new Message( + obj.getInt("threadTrace"), + obj.getString("type"), + obj.get("payload") + ) + ); + } + + /** LOG the duration */ + } + + private static void processOSRFMessage(Session ses, Message msg) throws MethodException { + + Logger.debug("received id=" + msg.getId() + + " type=" + msg.getType() + " payload=" + msg.getPayload()); + + if( ses instanceof ClientSession ) + processResponse((ClientSession) ses, msg); + else + processRequest((ServerSession) ses, msg); + } + + /** + * Process a server response + */ + private static void processResponse(ClientSession session, Message msg) throws MethodException { + String type = msg.getType(); + + if(msg.RESULT.equals(type)) { + session.pushResponse(msg); + return; + } + + if(msg.STATUS.equals(type)) { + + OSRFObject obj = (OSRFObject) msg.getPayload(); + Status stat = new Status(obj.getString("status"), obj.getInt("statusCode")); + int statusCode = stat.getStatusCode(); + String status = stat.getStatus(); + + switch(statusCode) { + case Status.COMPLETE: + session.setRequestComplete(msg.getId()); + break; + case Status.NOTFOUND: + session.setRequestComplete(msg.getId()); + throw new MethodException(status); + } + } + } + + /** + * Process a client request + */ + private static void processRequest(ServerSession session, Message msg) { + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/Status.java b/Open-ILS/src/Android/opensrf/org/opensrf/Status.java new file mode 100644 index 0000000000..8026c7b8cd --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/Status.java @@ -0,0 +1,63 @@ +package org.opensrf; +import org.opensrf.util.*; + +public class Status { + + public static final int CONTINUE = 100; + public static final int OK = 200; + public static final int ACCEPTED = 202; + public static final int COMPLETE = 205; + public static final int REDIRECTED = 307; + public static final int EST = 400; + public static final int STATUS_UNAUTHORIZED = 401; + public static final int FORBIDDEN = 403; + public static final int NOTFOUND = 404; + public static final int NOTALLOWED = 405; + public static final int TIMEOUT = 408; + public static final int EXPFAILED = 417; + public static final int INTERNALSERVERERROR = 500; + public static final int NOTIMPLEMENTED = 501; + public static final int VERSIONNOTSUPPORTED = 505; + + private OSRFRegistry registry = OSRFRegistry.registerObject( + "osrfConnectStatus", + OSRFRegistry.WireProtocol.HASH, + new String[] {"status", "statusCode"}); + + /** The name of the status */ + String status; + /** The status code */ + int statusCode; + + public Status(String status, int statusCode) { + this.status = status; + this.statusCode = statusCode; + } + + public int getStatusCode() { + return statusCode; + } + public String getStatus() { + return status; + } + + /** + * Implements the generic get() API required by OSRFSerializable + */ + public Object get(String field) { + if("status".equals(field)) + return getStatus(); + if("statusCode".equals(field)) + return new Integer(getStatusCode()); + return null; + } + + /** + * @return The osrfMessage registry. + */ + public OSRFRegistry getRegistry() { + return registry; + } +} + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/Sys.java b/Open-ILS/src/Android/opensrf/org/opensrf/Sys.java new file mode 100644 index 0000000000..d65f8a4ffa --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/Sys.java @@ -0,0 +1,86 @@ +package org.opensrf; + +import org.opensrf.util.*; +import org.opensrf.net.xmpp.*; +import java.util.Random; +import java.util.Date; +import java.net.InetAddress; + + +public class Sys { + + private static void initLogger(Config config) { + if(Logger.instance() == null) { + try { + String logFile = config.getString("/logfile"); + int logLevel = config.getInt("/loglevel"); + Logger.init( (short) config.getInt("/loglevel"), new FileLogger(logFile)); + /** add syslog support... */ + } catch(Exception e) { + /* by default, log to stderr at WARN level */ + Logger.init(Logger.WARN, new Logger()); + } + } + } + + /** + * Connects to the OpenSRF network so that client sessions may communicate. + * @param configFile The OpenSRF config file + * @param configContext Where in the XML document the config chunk lives. This + * allows an OpenSRF client config chunk to live in XML files where other config + * information lives. + */ + public static void bootstrapClient(String configFile, String configContext) + throws ConfigException, SessionException { + + + /** see if the current thread already has a connection */ + XMPPSession existing = XMPPSession.getThreadSession(); + if(existing != null && existing.connected()) + return; + + /** create the config parser */ + Config config = new Config(configContext); + config.parse(configFile); + Config.setConfig(config); /* set this as the global config */ + + initLogger(config); + + /** Collect the network connection info from the config */ + String username = config.getString("/username"); + String passwd = config.getString("/passwd"); + String host = (String) config.getFirst("/domain"); + int port = config.getInt("/port"); + + + /** Create a random login resource string */ + String res = "java_"; + try { + res += InetAddress.getLocalHost().getHostAddress(); + } catch(java.net.UnknownHostException e) {} + res += "_"+Math.abs(new Random(new Date().getTime()).nextInt()) + + "_t"+ Thread.currentThread().getId(); + + + + try { + + /** Connect to the Jabber network */ + Logger.info("attempting to create XMPP session "+username+"@"+host+"/"+res); + XMPPSession xses = new XMPPSession(host, port); + xses.connect(username, passwd, res); + XMPPSession.setThreadSession(xses); + + } catch(XMPPException e) { + throw new SessionException("Unable to bootstrap client", e); + } + } + + /** + * Shuts down the connection to the opensrf network + */ + public static void shutdown() { + XMPPSession.getThreadSession().disconnect(); + } +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/net/http/GatewayRequest.java b/Open-ILS/src/Android/opensrf/org/opensrf/net/http/GatewayRequest.java new file mode 100644 index 0000000000..ab3594612a --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/net/http/GatewayRequest.java @@ -0,0 +1,133 @@ +package org.opensrf.net.http; + +import org.opensrf.*; +import org.opensrf.util.*; + +import java.io.IOException; +import java.io.BufferedInputStream; +import java.io.OutputStreamWriter; +import java.io.InputStream; +import java.io.IOException; +import java.net.URL; +import java.net.URI; +import java.net.HttpURLConnection; +import java.lang.StringBuffer; +import java.util.List; +import java.util.Iterator; +import java.util.Map; +import java.util.HashMap; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class GatewayRequest extends HttpRequest { + + private boolean readComplete; + + public GatewayRequest(HttpConnection conn, String service, Method method) { + super(conn, service, method); + readComplete = false; + } + + public GatewayRequest send() { + try { + + String postData = compilePostData(service, method); + + urlConn = (HttpURLConnection) httpConn.url.openConnection(); + urlConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + urlConn.setDoInput(true); + urlConn.setDoOutput(true); + + + + OutputStreamWriter wr = new OutputStreamWriter(urlConn.getOutputStream()); + wr.write(postData); + wr.flush(); + wr.close(); + + } catch (java.io.IOException ex) { + failed = true; + failure = ex; + } + + return this; + } + + public Object recv() { + + if (readComplete) + return nextResponse(); + + try { + + InputStream netStream = new BufferedInputStream(urlConn.getInputStream()); + StringBuffer readBuf = new StringBuffer(); + + int bytesRead = 0; + byte[] buffer = new byte[1024]; + + while ((bytesRead = netStream.read(buffer)) != -1) { + readBuf.append(new String(buffer, 0, bytesRead)); + } + + netStream.close(); + urlConn = null; + + Map result = null; + + System.out.println("Received " + readBuf.toString()); + try { + result = (Map) new JSONReader(readBuf.toString()).readObject(); + } catch (org.opensrf.util.JSONException ex) { + ex.printStackTrace(); + return null; + } + System.out.println("Converted object " + result); + String status = result.get("status").toString(); + if (!"200".equals(status)) { + failed = true; + // failure = + } + + // gateway always returns a wrapper array with the full results set + responseList = (List) result.get("payload"); + + // System.out.println("Response list : " + responseList); + } catch (java.io.IOException ex) { + failed = true; + failure = ex; + } + + readComplete = true; + return nextResponse(); + } + + private String compilePostData(String service, Method method) { + URI uri = null; + StringBuffer postData = new StringBuffer(); + + postData.append("service="); + postData.append(service); + postData.append("&method="); + postData.append(method.getName()); + + List params = method.getParams(); + Iterator itr = params.iterator(); + + while (itr.hasNext()) { + postData.append("¶m="); + postData.append(new JSONWriter(itr.next()).write()); + } + + try { + // not using URLEncoder because it replaces ' ' with '+'. + uri = new URI("http", "", null, postData.toString(), null); + } catch (java.net.URISyntaxException ex) { + ex.printStackTrace(); + } + + return uri.getRawQuery(); + } +} + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/net/http/HttpConnection.java b/Open-ILS/src/Android/opensrf/org/opensrf/net/http/HttpConnection.java new file mode 100644 index 0000000000..32fdebc212 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/net/http/HttpConnection.java @@ -0,0 +1,97 @@ +package org.opensrf.net.http; + +import java.net.URL; +import java.net.MalformedURLException; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.opensrf.*; +import org.opensrf.util.*; + + +/** + * Manages connection parameters and thread limiting for opensrf json gateway connections. + */ + +public class HttpConnection { + + /** Compiled URL object */ + protected URL url; + /** Number of threads currently communicating with the server */ + protected int activeThreads; + /** Queue of pending async requests */ + protected Queue pendingThreadQueue; + /** maximum number of actively communicating threads allowed */ + protected int maxThreads = 10; + + public HttpConnection(String fullUrl) throws java.net.MalformedURLException { + activeThreads = 0; + pendingThreadQueue = new ConcurrentLinkedQueue(); + url = new URL(fullUrl); + } + + public int getMaxThreads() { + return maxThreads; + } + + /** + * Set the maximum number of actively communicating threads allowed + */ + public void setMaxThreads(int max) { + maxThreads = max; + } + + /** + * Launches or queues an asynchronous request. + * + * If the maximum active thread count has not been reached, + * start a new thread and use it to send and receive the request. + * The response is passed to the request's HttpRequestHandler + * onComplete(). After complete, if the number of active threads + * is still lower than the max, one request will be pulled (if + * present) from the async queue and fired. + * + * If there are too many active threads, the main request is + * pushed onto the async queue for later processing + */ + protected void manageAsyncRequest(final HttpRequest request) { + + if (activeThreads >= maxThreads) { + pendingThreadQueue.offer(request); + return; + } + + activeThreads++; + + //Send the request receive the response, fire off the next + //thread if necessary, then pass the result to the handler + Runnable r = new Runnable() { + public void run() { + + Object response; + request.send(); + + while ((response = request.recv()) != null) { + if (request.handler != null) + request.handler.onResponse(request, response); + } + + if (request.handler != null) + request.handler.onComplete(request); + + activeThreads--; + + if (activeThreads < maxThreads) { + try { + manageAsyncRequest(pendingThreadQueue.remove()); + } catch (java.util.NoSuchElementException ex) { + // may have been gobbled by another thread + } + } + } + }; + + new Thread(r).start(); + } +} + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/net/http/HttpRequest.java b/Open-ILS/src/Android/opensrf/org/opensrf/net/http/HttpRequest.java new file mode 100644 index 0000000000..8af4623451 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/net/http/HttpRequest.java @@ -0,0 +1,70 @@ +package org.opensrf.net.http; +import org.opensrf.*; +import org.opensrf.util.*; + +import java.util.List; +import java.util.LinkedList; +import java.net.HttpURLConnection; + +public abstract class HttpRequest { + + protected String service; + protected Method method; + protected HttpURLConnection urlConn; + protected HttpConnection httpConn; + protected HttpRequestHandler handler; + protected List responseList; + protected Exception failure; + protected boolean failed; + protected boolean complete; + + public HttpRequest() { + failed = false; + complete = false; + handler = null; + urlConn = null; + } + + public HttpRequest(HttpConnection conn, String service, Method method) { + this(); + this.httpConn = conn; + this.service = service; + this.method = method; + } + + public void sendAsync(final HttpRequestHandler handler) { + this.handler = handler; + httpConn.manageAsyncRequest(this); + } + + protected void pushResponse(Object response) { + if (responseList == null) + responseList = new LinkedList(); + responseList.add(response); + } + + protected List responses() { + return responseList; + } + + protected Object nextResponse() { + if (complete || failed) return null; + if (responseList.size() > 0) + return responseList.remove(0); + return null; + } + + public Exception getFailure() { + return failure; + } + + public abstract HttpRequest send(); + + public abstract Object recv(); + + public boolean failed(){ + return failed; + } +} + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/net/http/HttpRequestHandler.java b/Open-ILS/src/Android/opensrf/org/opensrf/net/http/HttpRequestHandler.java new file mode 100644 index 0000000000..9c0f9e5043 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/net/http/HttpRequestHandler.java @@ -0,0 +1,25 @@ +package org.opensrf.net.http; + +import java.util.List; + +/* + * Handler for async gateway responses. + */ +public abstract class HttpRequestHandler { + + /** + * Called when all responses have been received. + * + * If discardResponses() returns true, will be passed null. + */ + public void onComplete(HttpRequest request) { + } + + /** + * Called with each response received from the server. + * + * @param payload the value returned from the server. + */ + public void onResponse(HttpRequest request, Object response) { + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/net/xmpp/XMPPException.java b/Open-ILS/src/Android/opensrf/org/opensrf/net/xmpp/XMPPException.java new file mode 100644 index 0000000000..8c20ab7aee --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/net/xmpp/XMPPException.java @@ -0,0 +1,10 @@ +package org.opensrf.net.xmpp; + +/** + * Used for XMPP stream/authentication errors + */ +public class XMPPException extends Exception { + public XMPPException(String info) { + super(info); + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/net/xmpp/XMPPMessage.java b/Open-ILS/src/Android/opensrf/org/opensrf/net/xmpp/XMPPMessage.java new file mode 100644 index 0000000000..b6e2c76346 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/net/xmpp/XMPPMessage.java @@ -0,0 +1,101 @@ +package org.opensrf.net.xmpp; + +import java.io.*; + +/** + * Models a single XMPP message. + */ +public class XMPPMessage { + + /** Message body */ + private String body; + /** Message recipient */ + private String to; + /** Message sender */ + private String from; + /** Message thread */ + private String thread; + /** Message xid */ + private String xid; + + public XMPPMessage() { + } + + public String getBody() { + return body; + } + public String getTo() { + return to; + } + public String getFrom() { + return from; + } + public String getThread() { + return thread; + } + public String getXid() { + return xid; + } + public void setBody(String body) { + this.body = body; + } + public void setTo(String to) { + this.to = to; + } + public void setFrom(String from) { + this.from = from; + } + public void setThread(String thread) { + this.thread = thread; + } + public void setXid(String xid) { + this.xid = xid; + } + + + /** + * Generates the XML representation of this message. + */ + public String toXML() { + StringBuffer sb = new StringBuffer(""); + escapeXML(thread, sb); + sb.append(""); + escapeXML(body, sb); + sb.append(""); + return sb.toString(); + } + + + /** + * Escapes non-valid XML characters. + * @param s The string to escape. + * @param sb The StringBuffer to append new data to. + */ + private void escapeXML(String s, StringBuffer sb) { + if( s == null ) return; + char c; + int l = s.length(); + for( int i = 0; i < l; i++ ) { + c = s.charAt(i); + switch(c) { + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '&': + sb.append("&"); + break; + default: + sb.append(c); + } + } + } +} + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/net/xmpp/XMPPReader.java b/Open-ILS/src/Android/opensrf/org/opensrf/net/xmpp/XMPPReader.java new file mode 100644 index 0000000000..406298a730 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/net/xmpp/XMPPReader.java @@ -0,0 +1,293 @@ +package org.opensrf.net.xmpp; + +import javax.xml.stream.*; +import javax.xml.stream.events.* ; +import javax.xml.namespace.QName; +import java.util.Queue; +import java.io.InputStream; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.Date; +import org.opensrf.util.Logger; + +/** + * Slim XMPP Stream reader. This reader only understands enough XMPP + * to handle logins and recv messages. It's implemented as a StAX parser. + * @author Bill Erickson, Georgia Public Library Systems + */ +public class XMPPReader implements Runnable { + + /** Queue of received messages. */ + private Queue msgQueue; + /** Incoming XMPP XML stream */ + private InputStream inStream; + /** Current message body */ + private StringBuffer msgBody; + /** Current message thread */ + private StringBuffer msgThread; + /** Current message status */ + private StringBuffer msgStatus; + /** Current message error type */ + private StringBuffer msgErrType; + /** Current message sender */ + private String msgFrom; + /** Current message recipient */ + private String msgTo; + /** Current message error code */ + private int msgErrCode; + + /** Where this reader currently is in the document */ + private XMLState xmlState; + + /** The current connect state to the XMPP server */ + private XMPPStreamState streamState; + + + /** Used to represent out connection state to the XMPP server */ + public static enum XMPPStreamState { + DISCONNECTED, /* not connected to the server */ + CONNECT_SENT, /* we've sent the initial connect message */ + CONNECT_RECV, /* we've received a response to our connect message */ + AUTH_SENT, /* we've sent an authentication request */ + CONNECTED /* authentication is complete */ + }; + + + /** Used to represents where we are in the XML document stream. */ + public static enum XMLState { + IN_NOTHING, + IN_BODY, + IN_THREAD, + IN_STATUS + }; + + + /** + * Creates a new reader. Initializes the message queue. + * Sets the stream state to disconnected, and the xml + * state to in_nothing. + * @param inStream the inbound XML stream + */ + public XMPPReader(InputStream inStream) { + msgQueue = new ConcurrentLinkedQueue(); + this.inStream = inStream; + resetBuffers(); + xmlState = XMLState.IN_NOTHING; + streamState = XMPPStreamState.DISCONNECTED; + } + + /** + * Change the connect state and notify that a core + * event has occurred. + */ + protected void setXMPPStreamState(XMPPStreamState state) { + streamState = state; + notifyCoreEvent(); + } + + /** + * @return The current stream state of the reader + */ + public XMPPStreamState getXMPPStreamState() { + return streamState; + } + + + /** + * @return The next message in the queue, or null + */ + public XMPPMessage popMessageQueue() { + return (XMPPMessage) msgQueue.poll(); + } + + + /** + * Initializes the message buffers + */ + private void resetBuffers() { + msgBody = new StringBuffer(); + msgThread = new StringBuffer(); + msgStatus = new StringBuffer(); + msgErrType = new StringBuffer(); + msgFrom = ""; + msgTo = ""; + } + + + /** + * Notifies the waiting thread that a core event has occurred. + * Each reader should have exactly one dependent session thread. + */ + private synchronized void notifyCoreEvent() { + notifyAll(); + } + + + /** + * Waits up to timeout milliseconds for a core event to occur. + * Also, having a message already waiting in the queue + * constitutes a core event. + * @param timeout The number of milliseconds to wait. If + * timeout is negative, waits potentially forever. + * @return The number of milliseconds in wait + */ + public synchronized long waitCoreEvent(long timeout) { + + if(msgQueue.peek() != null || timeout == 0) return 0; + long start = new Date().getTime(); + + try{ + if(timeout < 0) + wait(); + else + wait(timeout); + } catch(InterruptedException ie) {} + + return new Date().getTime() - start; + } + + + + /** Kickoff the thread */ + public void run() { + read(); + } + + + /** + * Parses XML data from the provided XMPP stream. + */ + public void read() { + + try { + + XMLInputFactory factory = XMLInputFactory.newInstance(); + + /** disable as many unused features as possible to speed up the parsing */ + factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.FALSE); + factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); + factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE); + factory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.FALSE); + factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); + + /** create the stream reader */ + XMLStreamReader reader = factory.createXMLStreamReader(inStream); + int eventType; + + while(reader.hasNext()) { + /** cycle through the XML events */ + + eventType = reader.next(); + + switch(eventType) { + + case XMLEvent.START_ELEMENT: + handleStartElement(reader); + break; + + case XMLEvent.CHARACTERS: + switch(xmlState) { + case IN_BODY: + msgBody.append(reader.getText()); + break; + case IN_THREAD: + msgThread.append(reader.getText()); + break; + case IN_STATUS: + msgStatus.append(reader.getText()); + break; + } + break; + + case XMLEvent.END_ELEMENT: + xmlState = XMLState.IN_NOTHING; + if("message".equals(reader.getName().toString())) { + + /** build a message and add it to the message queue */ + XMPPMessage msg = new XMPPMessage(); + msg.setFrom(msgFrom); + msg.setTo(msgTo); + msg.setBody(msgBody.toString()); + msg.setThread(msgThread.toString()); + + Logger.internal("xmpp message from="+msgFrom+" " + msg.getBody()); + + msgQueue.offer(msg); + resetBuffers(); + notifyCoreEvent(); + } + break; + } + } + + } catch(javax.xml.stream.XMLStreamException se) { + /* XXX log an error */ + xmlState = XMLState.IN_NOTHING; + streamState = XMPPStreamState.DISCONNECTED; + notifyCoreEvent(); + } + } + + + /** + * Handles the start_element event. + */ + private void handleStartElement(XMLStreamReader reader) { + + String name = reader.getName().toString(); + + if("message".equals(name)) { + xmlState = XMLState.IN_BODY; + + /** add a special case for the opensrf "router_from" attribute */ + String rf = reader.getAttributeValue(null, "router_from"); + if( rf != null ) + msgFrom = rf; + else + msgFrom = reader.getAttributeValue(null, "from"); + msgTo = reader.getAttributeValue(null, "to"); + return; + } + + if("body".equals(name)) { + xmlState = XMLState.IN_BODY; + return; + } + + if("thread".equals(name)) { + xmlState = XMLState.IN_THREAD; + return; + } + + if("stream:stream".equals(name)) { + setXMPPStreamState(XMPPStreamState.CONNECT_RECV); + return; + } + + if("iq".equals(name)) { + if("result".equals(reader.getAttributeValue(null, "type"))) + setXMPPStreamState(XMPPStreamState.CONNECTED); + return; + } + + if("status".equals(name)) { + xmlState = XMLState.IN_STATUS; + return; + } + + if("stream:error".equals(name)) { + setXMPPStreamState(XMPPStreamState.DISCONNECTED); + return; + } + + if("error".equals(name)) { + msgErrType.append(reader.getAttributeValue(null, "type")); + msgErrCode = Integer.parseInt(reader.getAttributeValue(null, "code")); + setXMPPStreamState(XMPPStreamState.DISCONNECTED); + return; + } + } +} + + + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/net/xmpp/XMPPSession.java b/Open-ILS/src/Android/opensrf/org/opensrf/net/xmpp/XMPPSession.java new file mode 100644 index 0000000000..f9be7d2a63 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/net/xmpp/XMPPSession.java @@ -0,0 +1,263 @@ +package org.opensrf.net.xmpp; + +import java.io.*; +import java.net.Socket; +import java.util.Map; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * Represents a single XMPP session. Sessions are responsible for writing to + * the stream and for managing a stream reader. + */ +public class XMPPSession { + + /** Initial jabber message */ + public static final String JABBER_CONNECT = + ""; + + /** Basic auth message */ + public static final String JABBER_BASIC_AUTH = + "" + + "%s%s%s"; + + public static final String JABBER_DISCONNECT = ""; + + private static Map threadConnections = new ConcurrentHashMap(); + + /** jabber domain */ + private String host; + /** jabber port */ + private int port; + /** jabber username */ + private String username; + /** jabber password */ + private String password; + /** jabber resource */ + private String resource; + + /** XMPP stream reader */ + XMPPReader reader; + /** Fprint-capable socket writer */ + PrintWriter writer; + /** Raw socket output stream */ + OutputStream outStream; + /** The raw socket */ + Socket socket; + + /** The process-wide session. All communication occurs + * accross this single connection */ + private static XMPPSession globalSession; + + + /** + * Creates a new session. + * @param host The jabber domain + * @param port The jabber port + */ + public XMPPSession( String host, int port ) { + this.host = host; + this.port = port; + } + + /** + * Returns the global, process-wide session + */ + /* + public static XMPPSession getGlobalSession() { + return globalSession; + } + */ + + public static XMPPSession getThreadSession() { + return (XMPPSession) threadConnections.get(new Long(Thread.currentThread().getId())); + } + + /** + * Sets the given session as the global session for the current thread + * @param ses The session + */ + public static void setThreadSession(XMPPSession ses) { + /* every time we create a new connection, clean up any dead threads. + * this is cheaper than cleaning up the dead threads at every access. */ + cleanupThreadSessions(); + threadConnections.put(new Long(Thread.currentThread().getId()), ses); + } + + /** + * Analyzes the threadSession data to see if there are any sessions + * whose controlling thread has gone away. + */ + private static void cleanupThreadSessions() { + Thread threads[] = new Thread[Thread.activeCount()]; + Thread.enumerate(threads); + for(Iterator i = threadConnections.keySet().iterator(); i.hasNext(); ) { + boolean found = false; + Long id = (Long) i.next(); + for(Thread t : threads) { + if(t.getId() == id.longValue()) { + found = true; + break; + } + } + if(!found) + threadConnections.remove(id); + } + } + + /** + * Sets the global, process-wide section + */ + /* + public static void setGlobalSession(XMPPSession ses) { + globalSession = ses; + } + */ + + + /** true if this session is connected to the server */ + public boolean connected() { + return ( + reader != null && + reader.getXMPPStreamState() == XMPPReader.XMPPStreamState.CONNECTED && + !socket.isClosed() + ); + } + + + /** + * Connects to the network. + * @param username The jabber username + * @param password The jabber password + * @param resource The Jabber resource + */ + public void connect(String username, String password, String resource) throws XMPPException { + + this.username = username; + this.password = password; + this.resource = resource; + + try { + /* open the socket and associated streams */ + socket = new Socket(host, port); + + /** the session maintains control over the output stream */ + outStream = socket.getOutputStream(); + writer = new PrintWriter(outStream, true); + + /** pass the input stream to the reader */ + reader = new XMPPReader(socket.getInputStream()); + + } catch(IOException ioe) { + throw new + XMPPException("unable to communicate with host " + host + " on port " + port); + } + + /* build the reader thread */ + Thread thread = new Thread(reader); + thread.setDaemon(true); + thread.start(); + + synchronized(reader) { + /* send the initial jabber message */ + sendConnect(); + reader.waitCoreEvent(10000); + } + if( reader.getXMPPStreamState() != XMPPReader.XMPPStreamState.CONNECT_RECV ) + throw new XMPPException("unable to connect to jabber server"); + + synchronized(reader) { + /* send the basic auth message */ + sendBasicAuth(); + reader.waitCoreEvent(10000); + } + if(!connected()) + throw new XMPPException("Authentication failed"); + } + + /** Sends the initial jabber message */ + private void sendConnect() { + reader.setXMPPStreamState(XMPPReader.XMPPStreamState.CONNECT_SENT); + writer.printf(JABBER_CONNECT, host); + } + + /** Send the basic auth message */ + private void sendBasicAuth() { + reader.setXMPPStreamState(XMPPReader.XMPPStreamState.AUTH_SENT); + writer.printf(JABBER_BASIC_AUTH, username, password, resource); + } + + + /** + * Sends an XMPPMessage. + * @param msg The message to send. + */ + public synchronized void send(XMPPMessage msg) throws XMPPException { + checkConnected(); + try { + String xml = msg.toXML(); + outStream.write(xml.getBytes()); + } catch (Exception e) { + throw new XMPPException(e.toString()); + } + } + + + /** + * @throws XMPPException if we are no longer connected. + */ + private void checkConnected() throws XMPPException { + if(!connected()) + throw new XMPPException("Disconnected stream"); + } + + + /** + * Receives messages from the network. + * @param timeout Maximum number of milliseconds to wait for a message to arrive. + * If timeout is negative, this method will wait indefinitely. + * If timeout is 0, this method will not block at all, but will return a + * message if there is already a message available. + */ + public XMPPMessage recv(long timeout) throws XMPPException { + + XMPPMessage msg; + + if(timeout < 0) { + + while(true) { /* wait indefinitely for a message to arrive */ + reader.waitCoreEvent(timeout); + msg = reader.popMessageQueue(); + if( msg != null ) return msg; + checkConnected(); + } + + } else { + + while(timeout >= 0) { /* wait at most 'timeout' milleseconds for a message to arrive */ + msg = reader.popMessageQueue(); + if( msg != null ) return msg; + timeout -= reader.waitCoreEvent(timeout); + msg = reader.popMessageQueue(); + if( msg != null ) return msg; + checkConnected(); + if(timeout == 0) break; + } + } + + return reader.popMessageQueue(); + } + + + /** + * Disconnects from the jabber server and closes the socket + */ + public void disconnect() { + try { + outStream.write(JABBER_DISCONNECT.getBytes()); + socket.close(); + } catch(Exception e) {} + } +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/test/MathBench.java b/Open-ILS/src/Android/opensrf/org/opensrf/test/MathBench.java new file mode 100644 index 0000000000..b6e67f953c --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/test/MathBench.java @@ -0,0 +1,79 @@ +package org.opensrf.test; +import org.opensrf.*; +import org.opensrf.util.*; +import java.util.Date; +import java.util.List; +import java.util.ArrayList; +import java.io.PrintStream; + + +public class MathBench { + + public static void main(String args[]) throws Exception { + + PrintStream out = System.out; + + if(args.length < 2) { + out.println("usage: java org.opensrf.test.MathBench "); + return; + } + + /** connect to the opensrf network */ + Sys.bootstrapClient(args[0], "/config/opensrf"); + + /** how many iterations */ + int count = Integer.parseInt(args[1]); + + /** create the client session */ + ClientSession session = new ClientSession("opensrf.math"); + + /** params are 1,2 */ + List params = new ArrayList(); + params.add(new Integer(1)); + params.add(new Integer(2)); + + Request request; + Result result; + long start; + double total = 0; + + for(int i = 0; i < count; i++) { + + start = new Date().getTime(); + + /** create (and send) the request */ + request = session.request("add", params); + + /** wait up to 3 seconds for a response */ + result = request.recv(3000); + + /** collect the round-trip time */ + total += new Date().getTime() - start; + + if(result.getStatusCode() == Status.OK) { + out.print("+"); + } else { + out.println("\nrequest failed"); + out.println("status = " + result.getStatus()); + out.println("status code = " + result.getStatusCode()); + } + + /** remove this request from the session's request set */ + request.cleanup(); + + if((i+1) % 100 == 0) /** print 100 responses per line */ + out.println(" [" + (i+1) + "]"); + } + + out.println("\nAverage request time is " + (total/count) + " ms"); + + /** remove this session from the global session cache */ + session.cleanup(); + + /** disconnect from the opensrf network */ + Sys.shutdown(); + } +} + + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/test/TestCache.java b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestCache.java new file mode 100644 index 0000000000..d555444ccf --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestCache.java @@ -0,0 +1,24 @@ +package org.opensrf.test; +import org.opensrf.util.Cache; + +public class TestCache { + public static void main(String args[]) throws Exception { + + /** + * args is a list of string like so: server:port server2:port server3:port ... + */ + + Cache.initCache(args); + Cache cache = new Cache(); + + cache.set("key1", "HI, MA!"); + cache.set("key2", "HI, MA! 2"); + cache.set("key3", "HI, MA! 3"); + + System.out.println("got key1 = " + (String) cache.get("key1")); + System.out.println("got key2 = " + (String) cache.get("key2")); + System.out.println("got key3 = " + (String) cache.get("key3")); + } +} + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/test/TestClient.java b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestClient.java new file mode 100644 index 0000000000..a1136cd719 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestClient.java @@ -0,0 +1,80 @@ +package org.opensrf.test; +import org.opensrf.*; +import org.opensrf.util.*; +import java.util.Map; +import java.util.Date; +import java.util.List; +import java.util.ArrayList; +import java.io.PrintStream; + +public class TestClient { + + public static void main(String args[]) throws Exception { + + /** which opensrf service are we sending our request to */ + String service; + /** which opensrf method we're calling */ + String method; + /** method params, captures from command-line args */ + List params; + /** knows how to read JSON */ + JSONReader reader; + /** opensrf request */ + Request request; + /** request result */ + Result result; + /** start time for the request */ + long start; + /** for brevity */ + PrintStream out = System.out; + + if(args.length < 3) { + out.println( "usage: org.opensrf.test.TestClient "+ + " [, ]"); + return; + } + + /** connect to the opensrf network, default config context + * for opensrf_core.xml is /config/opensrf */ + Sys.bootstrapClient(args[0], "/config/opensrf"); + + /* grab the server, method, and any params from the command line */ + service = args[1]; + method = args[2]; + params = new ArrayList(); + for(int i = 3; i < args.length; i++) + params.add(new JSONReader(args[i]).read()); + + + /** build the client session */ + ClientSession session = new ClientSession(service); + + /** kick off the timer */ + start = new Date().getTime(); + + /** Create the request object from the session, method and params */ + request = session.request(method, params); + + while( (result = request.recv(60000)) != null ) { + /** loop over the results and print the JSON version of the content */ + + if(result.getStatusCode() != 200) { + /** make sure the request succeeded */ + out.println("status = " + result.getStatus()); + out.println("status code = " + result.getStatusCode()); + continue; + } + + /** JSON-ify the resulting object and print it */ + out.println("\nresult JSON: " + new JSONWriter(result.getContent()).write()); + } + + /** How long did the request take? */ + out.println("Request round trip took: " + (new Date().getTime() - start) + " ms."); + + Sys.shutdown(); + } +} + + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/test/TestConfig.java b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestConfig.java new file mode 100644 index 0000000000..f65a84f701 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestConfig.java @@ -0,0 +1,16 @@ +package org.opensrf.test; +import org.opensrf.*; +import org.opensrf.util.*; + +public class TestConfig { + public static void main(String args[]) throws Exception { + Config config = new Config(""); + config.parse(args[0]); + Config.setConfig(config); + System.out.println(config); + System.out.println(""); + + for(int i = 1; i < args.length; i++) + System.out.println("Found config value: " + args[i] + ": " + Config.global().get(args[i])); + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/test/TestJSON.java b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestJSON.java new file mode 100644 index 0000000000..b19d4088aa --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestJSON.java @@ -0,0 +1,51 @@ +package org.opensrf.test; + +import org.opensrf.*; +import org.opensrf.util.*; +import java.util.*; + +public class TestJSON { + + public static void main(String args[]) throws Exception { + + Map map = new HashMap(); + map.put("key1", "value1"); + map.put("key2", "value2"); + map.put("key3", "value3"); + map.put("key4", "athe\u0301s"); + map.put("key5", null); + + List list = new ArrayList(16); + list.add(new Integer(1)); + list.add(new Boolean(true)); + list.add("WATER"); + list.add(null); + map.put("key6", list); + + System.out.println(new JSONWriter(map).write() + "\n"); + + String[] fields = {"isnew", "name", "shortname", "ill_address"}; + OSRFRegistry.registerObject("aou", OSRFRegistry.WireProtocol.ARRAY, fields); + + OSRFObject obj = new OSRFObject(OSRFRegistry.getRegistry("aou")); + obj.put("name", "athens clarke county"); + obj.put("ill_address", new Integer(1)); + obj.put("shortname", "ARL-ATH"); + + map.put("key7", obj); + list.add(obj); + System.out.println(new JSONWriter(map).write() + "\n"); + + + Message m = new Message(1, Message.REQUEST); + Method method = new Method("opensrf.settings.host_config.get"); + method.addParam("app07.dev.gapines.org"); + m.setPayload(method); + + String s = new JSONWriter(m).write(); + System.out.println(s + "\n"); + + Object o = new JSONReader(s).read(); + System.out.println("Read+Wrote: " + new JSONWriter(o).write()); + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/test/TestLog.java b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestLog.java new file mode 100644 index 0000000000..1d60242969 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestLog.java @@ -0,0 +1,15 @@ +package org.opensrf.test; +import org.opensrf.util.Logger; +import org.opensrf.util.FileLogger; + + +/** Simple test class for tesing the logging functionality */ +public class TestLog { + public static void main(String args[]) { + Logger.init(Logger.DEBUG, new FileLogger("test.log")); + Logger.error("Hello, world"); + Logger.warn("Hello, world"); + Logger.info("Hello, world"); + Logger.debug("Hello, world"); + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/test/TestMultiSession.java b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestMultiSession.java new file mode 100644 index 0000000000..bb0f1a1c09 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestMultiSession.java @@ -0,0 +1,26 @@ +package org.opensrf.test; +import org.opensrf.*; +import org.opensrf.util.*; + +public class TestMultiSession { + public static void main(String[] args) { + try { + String config = args[0]; + + Sys.bootstrapClient(config, "/config/opensrf"); + MultiSession ses = new MultiSession(); + + for(int i = 0; i < 40; i++) { + ses.request("opensrf.settings", "opensrf.system.time"); + } + + while(!ses.isComplete()) + System.out.println("result = " + ses.recv(5000) + " and id = " + ses.lastId()); + + System.out.println("done"); + Sys.shutdown(); + } catch(Exception e) { + e.printStackTrace(); + } + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/test/TestSettings.java b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestSettings.java new file mode 100644 index 0000000000..116bbe1683 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestSettings.java @@ -0,0 +1,14 @@ +package org.opensrf.test; +import org.opensrf.*; +import org.opensrf.util.*; + +public class TestSettings { + public static void main(String args[]) throws Exception { + Sys.bootstrapClient(args[0], "/config/opensrf"); + SettingsClient client = SettingsClient.instance(); + String lang = client.getString("/apps/opensrf.settings/language"); + String impl = client.getString("/apps/opensrf.settings/implementation"); + System.out.println("opensrf.settings language = " + lang); + System.out.println("opensrf.settings implementation = " + impl); + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/test/TestThread.java b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestThread.java new file mode 100644 index 0000000000..bb4cf067e9 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestThread.java @@ -0,0 +1,68 @@ +package org.opensrf.test; +import org.opensrf.*; +import org.opensrf.util.*; +import java.util.Map; +import java.util.Date; +import java.util.List; +import java.util.ArrayList; +import java.io.PrintStream; + +/** + * Connects to the opensrf network once per thread and runs + * and runs a series of request acccross all launched threads. + * The purpose is to verify that the java threaded client api + * is functioning as expected + */ +public class TestThread implements Runnable { + + String args[]; + + public TestThread(String args[]) { + this.args = args; + } + + public void run() { + + try { + + Sys.bootstrapClient(args[0], "/config/opensrf"); + ClientSession session = new ClientSession(args[3]); + + List params = new ArrayList(); + for(int i = 5; i < args.length; i++) + params.add(new JSONReader(args[3]).read()); + + for(int i = 0; i < Integer.parseInt(args[2]); i++) { + System.out.println("thread " + Thread.currentThread().getId()+" sending request " + i); + Request request = session.request(args[4], params); + Result result = request.recv(3000); + if(result != null) { + System.out.println("thread " + Thread.currentThread().getId()+ + " got result JSON: " + new JSONWriter(result.getContent()).write()); + } else { + System.out.println("* thread " + Thread.currentThread().getId()+ " got NO result"); + } + } + + Sys.shutdown(); + } catch(Exception e) { + System.err.println(e); + } + } + + public static void main(String args[]) throws Exception { + + if(args.length < 5) { + System.out.println( "usage: org.opensrf.test.TestClient "+ + " [, ]"); + return; + } + + int numThreads = Integer.parseInt(args[1]); + for(int i = 0; i < numThreads; i++) + new Thread(new TestThread(args)).start(); + } +} + + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/test/TestXMLFlattener.java b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestXMLFlattener.java new file mode 100644 index 0000000000..c1fa394fef --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestXMLFlattener.java @@ -0,0 +1,11 @@ +package org.opensrf.test; +import org.opensrf.util.XMLFlattener; +import java.io.FileInputStream; + +public class TestXMLFlattener { + public static void main(String args[]) throws Exception { + FileInputStream fis = new FileInputStream(args[0]); + XMLFlattener f = new XMLFlattener(fis); + System.out.println(f.read()); + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/test/TestXMLTransformer.java b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestXMLTransformer.java new file mode 100644 index 0000000000..9768372f11 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestXMLTransformer.java @@ -0,0 +1,22 @@ +package org.opensrf.test; +import org.opensrf.util.XMLTransformer; +import java.io.File; + +public class TestXMLTransformer { + /** + * arg[0] path to an XML file + * arg[1] path to the XSL file to apply + */ + public static void main(String[] args) { + try { + File xmlFile = new File(args[0]); + File xslFile = new File(args[1]); + XMLTransformer t = new XMLTransformer(xmlFile, xslFile); + System.out.println(t.apply()); + } catch(Exception e) { + e.printStackTrace(); + } + } +} + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/test/TestXMPP.java b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestXMPP.java new file mode 100644 index 0000000000..2fba67fb8b --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/test/TestXMPP.java @@ -0,0 +1,63 @@ +package org.opensrf.test; + +import org.opensrf.net.xmpp.XMPPReader; +import org.opensrf.net.xmpp.XMPPMessage; +import org.opensrf.net.xmpp.XMPPSession; + +public class TestXMPP { + + /** + * Connects to the jabber server and waits for inbound messages. + * If a recipient is provided, a small message is sent to the recipient. + */ + public static void main(String args[]) throws Exception { + + String host; + int port; + String username; + String password; + String resource; + String recipient; + + try { + host = args[0]; + port = Integer.parseInt(args[1]); + username = args[2]; + password = args[3]; + resource = args[4]; + + } catch(ArrayIndexOutOfBoundsException e) { + System.err.println("usage: org.opensrf.test.TestXMPP []"); + return; + } + + XMPPSession session = new XMPPSession(host, port); + session.connect(username, password, resource); + + XMPPMessage msg; + + if( args.length == 6 ) { + + /** they specified a recipient */ + + recipient = args[5]; + msg = new XMPPMessage(); + msg.setTo(recipient); + msg.setThread("test-thread"); + msg.setBody("Hello, from java-xmpp"); + System.out.println("Sending message to " + recipient); + session.send(msg); + } + + while(true) { + System.out.println("waiting for message..."); + msg = session.recv(-1); /* wait forever for a message to arrive */ + System.out.println("got message: " + msg.toXML()); + } + } +} + + + + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/Cache.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/Cache.java new file mode 100644 index 0000000000..53036886cc --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/Cache.java @@ -0,0 +1,38 @@ +package org.opensrf.util; +import com.danga.MemCached.*; +import java.util.List; + +/** + * Memcache client + */ +public class Cache extends MemCachedClient { + + public Cache() { + super(); + setCompressThreshold(4096); /* ?? */ + } + + /** + * Initializes the cache client + * @param serverList Array of server:port strings specifying the + * set of memcache servers this client will talk to + */ + public static void initCache(String[] serverList) { + SockIOPool pool = SockIOPool.getInstance(); + pool.setServers(serverList); + pool.initialize(); + com.danga.MemCached.Logger logger = + com.danga.MemCached.Logger.getLogger(MemCachedClient.class.getName()); + logger.setLevel(logger.LEVEL_ERROR); + } + + /** + * Initializes the cache client + * @param serverList List of server:port strings specifying the + * set of memcache servers this client will talk to + */ + public static void initCache(List serverList) { + initCache(serverList.toArray(new String[]{})); + } +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/Config.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/Config.java new file mode 100644 index 0000000000..ddac9c0b71 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/Config.java @@ -0,0 +1,139 @@ +package org.opensrf.util; + +import org.json.*; +import java.util.Map; +import java.util.List; + + +/** + * Config reader and accesor module. This module reads an XML config file, + * then loads the file into an internal config, whose values may be accessed + * by xpath-style lookup paths. + */ +public class Config { + + /** The globl config instance */ + private static Config config; + /** The object form of the parsed config */ + private Map configObject; + /** + * The log parsing context. This is used as a prefix to the + * config item search path. This allows config XML chunks to + * be inserted into arbitrary XML files. + */ + private String context; + + public static Config global() { + return config; + } + + + /** + * @param context The config context + */ + public Config(String context) { + this.context = context; + } + + /** + * Sets the global config object. + * @param c The config object to use. + */ + public static void setGlobalConfig(Config c) { + config = c; + } + + /** + * Parses an XML config file. + * @param filename The path to the file to parse. + */ + public void parse(String filename) throws ConfigException { + try { + String xml = Utils.fileToString(filename); + JSONObject jobj = XML.toJSONObject(xml); + configObject = (Map) new JSONReader(jobj.toString()).readObject(); + } catch(Exception e) { + throw new ConfigException("Error parsing config", e); + } + } + + public static void setConfig(Config conf) { + config = conf; + } + + public void setConfigObject(Map config) { + this.configObject = config; + } + + protected Map getConfigObject() { + return this.configObject; + } + + + /** + * Returns the configuration value found at the requested path. + * @param path The search path + * @return The config value, or null if no value exists at the given path. + * @throws ConfigException thrown if nothing is found at the path + */ + public String getString(String path) throws ConfigException { + try { + return (String) get(path); + } catch(Exception e) { + throw new + ConfigException("No config string found at " + path); + } + } + + /** + * Gets the int value at the given path + * @param path The search path + */ + public int getInt(String path) throws ConfigException { + try { + return Integer.parseInt(getString(path)); + } catch(Exception e) { + throw new + ConfigException("No config int found at " + path); + } + } + + /** + * Returns the configuration object found at the requested path. + * @param path The search path + * @return The config value + * @throws ConfigException thrown if nothing is found at the path + */ + public Object get(String path) throws ConfigException { + try { + Object obj = Utils.findPath(configObject, context + path); + if(obj == null) + throw new ConfigException(""); + return obj; + } catch(Exception e) { + e.printStackTrace(); + throw new ConfigException("No config object found at " + path); + } + } + + /** + * Returns the first item in the list found at the given path. If + * no list is found, ConfigException is thrown. + * @param path The search path + */ + public Object getFirst(String path) throws ConfigException { + Object obj = get(path); + if(obj instanceof List) + return ((List) obj).get(0); + return obj; + } + + + /** + * Returns the config as a JSON string + */ + public String toString() { + return new JSONWriter(configObject).write(); + } +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/ConfigException.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/ConfigException.java new file mode 100644 index 0000000000..c1c491ec8d --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/ConfigException.java @@ -0,0 +1,14 @@ +package org.opensrf.util; + +/** + * Thrown by the Config module when a user requests a configuration + * item that does not exist + */ +public class ConfigException extends Exception { + public ConfigException(String info) { + super(info); + } + public ConfigException(String info, Throwable t) { + super(info, t); + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/FileLogger.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/FileLogger.java new file mode 100644 index 0000000000..9eb838df0c --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/FileLogger.java @@ -0,0 +1,44 @@ +package org.opensrf.util; +import java.io.BufferedWriter; +import java.io.FileWriter; + + +public class FileLogger extends Logger { + + /** File to log to */ + private String filename; + + /** + * FileLogger constructor + * @param filename The path to the log file + */ + public FileLogger(String filename) { + this.filename = filename; + } + + /** + * Logs the mesage to a file. + * @param level The log level + * @param msg The mesage to log + */ + protected synchronized void log(short level, String msg) { + if(level > logLevel) return; + + BufferedWriter out = null; + try { + out = new BufferedWriter(new FileWriter(this.filename, true)); + out.write(formatMessage(level, msg) + "\n"); + + } catch(Exception e) { + /** If we are unable to write our log message, go ahead and + * fall back to the default (stdout) logger */ + Logger.init(logLevel, new Logger()); + Logger.logByLevel(ERROR, "Unable to write to log file " + this.filename); + Logger.logByLevel(level, msg); + } + + try { + out.close(); + } catch(Exception e) {} + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/JSONException.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/JSONException.java new file mode 100644 index 0000000000..ec28e1d86d --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/JSONException.java @@ -0,0 +1,9 @@ +package org.opensrf.util; +/** + * Used to indicate JSON parsing errors + */ +public class JSONException extends Exception { + public JSONException(String s) { + super(s); + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/JSONReader.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/JSONReader.java new file mode 100644 index 0000000000..55eb5c03b4 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/JSONReader.java @@ -0,0 +1,176 @@ +package org.opensrf.util; + +import java.io.*; +import java.util.*; + +import org.json.JSONTokener; +import org.json.JSONObject; +import org.json.JSONArray; + + +/** + * JSON utilities. + */ +public class JSONReader { + + /** Special OpenSRF serializable object netClass key */ + public static final String JSON_CLASS_KEY = "__c"; + + /** Special OpenSRF serializable object payload key */ + public static final String JSON_PAYLOAD_KEY = "__p"; + + /** The JSON string to parser */ + private String json; + + /** + * @param json The JSON to parse + */ + public JSONReader(String json) { + this.json = json; + } + + /** + * Parses JSON and creates an object. + * @return The resulting object which may be a List, + * Map, Number, String, Boolean, or null + */ + public Object read() throws JSONException { + JSONTokener tk = new JSONTokener(json); + try { + return readSubObject(tk.nextValue()); + } catch(org.json.JSONException e) { + throw new JSONException(e.toString()); + } + } + + /** + * Assumes that a JSON array will be read. Returns + * the resulting array as a list. + */ + public List readArray() throws JSONException { + Object o = read(); + try { + return (List) o; + } catch(Exception e) { + throw new JSONException("readArray(): JSON cast exception"); + } + } + + /** + * Assumes that a JSON object will be read. Returns + * the resulting object as a map. + */ + public Map readObject() throws JSONException { + Object o = read(); + try { + return (Map) o; + } catch(Exception e) { + throw new JSONException("readObject(): JSON cast exception"); + } + } + + + /** + * Recurse through the object and turn items into maps, lists, etc. + */ + private Object readSubObject(Object obj) throws JSONException { + + if( obj == null || + obj instanceof String || + obj instanceof Number || + obj instanceof Boolean) + return obj; + + try { + + if( obj instanceof JSONObject ) { + + /* read objects */ + String key; + JSONObject jobj = (JSONObject) obj; + Map map = new HashMap(); + + for( Iterator e = jobj.keys(); e.hasNext(); ) { + key = (String) e.next(); + + /* we encoutered the special class key */ + if( JSON_CLASS_KEY.equals(key) ) + return buildRegisteredObject( + (String) jobj.get(key), jobj.get(JSON_PAYLOAD_KEY)); + + /* we encountered the data key */ + if( JSON_PAYLOAD_KEY.equals(key) ) + return buildRegisteredObject( + (String) jobj.get(JSON_CLASS_KEY), jobj.get(key)); + + map.put(key, readSubObject(jobj.get(key))); + } + return map; + } + + if ( obj instanceof JSONArray ) { + + JSONArray jarr = (JSONArray) obj; + int length = jarr.length(); + List list = new ArrayList(length); + + for( int i = 0; i < length; i++ ) + list.add(readSubObject(jarr.get(i))); + return list; + + } + + } catch(org.json.JSONException e) { + + throw new JSONException(e.toString()); + } + + return null; + } + + + + /** + * Builds an OSRFObject map registered OSRFHash object based on the JSON object data. + * @param netClass The network class hint for this object. + * @param paylaod The actual object on the wire. + */ + private OSRFObject buildRegisteredObject( + String netClass, Object payload) throws JSONException { + + OSRFRegistry registry = OSRFRegistry.getRegistry(netClass); + OSRFObject obj = new OSRFObject(registry); + + try { + if( payload instanceof JSONArray ) { + JSONArray jarr = (JSONArray) payload; + + /* for each array item, instert the item into the hash. the hash + * key is found by extracting the fields array from the registered + * object at the current array index */ + String fields[] = registry.getFields(); + for( int i = 0; i < jarr.length(); i++ ) { + obj.put(fields[i], readSubObject(jarr.get(i))); + } + + } else if( payload instanceof JSONObject ) { + + /* since this is a hash, simply copy the data over */ + JSONObject jobj = (JSONObject) payload; + String key; + for( Iterator e = jobj.keys(); e.hasNext(); ) { + key = (String) e.next(); + obj.put(key, readSubObject(jobj.get(key))); + } + } + + } catch(org.json.JSONException e) { + throw new JSONException(e.toString()); + } + + return obj; + } +} + + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/JSONWriter.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/JSONWriter.java new file mode 100644 index 0000000000..7cb2cca02d --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/JSONWriter.java @@ -0,0 +1,172 @@ +package org.opensrf.util; + +import java.io.*; +import java.util.*; + + +/** + * JSONWriter + */ +public class JSONWriter { + + /** The object to serialize to JSON */ + private Object obj; + + /** + * @param obj The object to encode + */ + public JSONWriter(Object obj) { + this.obj = obj; + } + + + /** + * Encodes a java object to JSON. + */ + public String write() { + StringBuffer sb = new StringBuffer(); + write(sb); + return sb.toString(); + } + + + + /** + * Encodes a java object to JSON. + * Maps (HashMaps, etc.) are encoded as JSON objects. + * Iterable's (Lists, etc.) are encoded as JSON arrays + */ + public void write(StringBuffer sb) { + write(obj, sb); + } + + /** + * Encodes the object as JSON into the provided buffer + */ + public void write(Object obj, StringBuffer sb) { + + /** JSON null */ + if(obj == null) { + sb.append("null"); + return; + } + + /** JSON string */ + if(obj instanceof String) { + sb.append('"'); + Utils.escape((String) obj, sb); + sb.append('"'); + return; + } + + /** JSON number */ + if(obj instanceof Number) { + sb.append(obj.toString()); + return; + } + + /** JSON array */ + if(obj instanceof Iterable) { + encodeJSONArray((Iterable) obj, sb); + return; + } + + /** OpenSRF serializable objects */ + if(obj instanceof OSRFSerializable) { + encodeOSRFSerializable((OSRFSerializable) obj, sb); + return; + } + + /** JSON object */ + if(obj instanceof Map) { + encodeJSONObject((Map) obj, sb); + return; + } + + /** JSON boolean */ + if(obj instanceof Boolean) { + sb.append((((Boolean) obj).booleanValue() ? "true" : "false")); + return; + } + } + + + /** + * Encodes a List as a JSON array + */ + private void encodeJSONArray(Iterable iterable, StringBuffer sb) { + Iterator itr = iterable.iterator(); + sb.append("["); + boolean some = false; + + while(itr.hasNext()) { + some = true; + write(itr.next(), sb); + sb.append(','); + } + + /* remove the trailing comma if the array has any items*/ + if(some) + sb.deleteCharAt(sb.length()-1); + sb.append("]"); + } + + + /** + * Encodes a Map as a JSON object + */ + private void encodeJSONObject(Map map, StringBuffer sb) { + Iterator itr = map.keySet().iterator(); + sb.append("{"); + Object key = null; + + while(itr.hasNext()) { + key = itr.next(); + write(key, sb); + sb.append(':'); + write(map.get(key), sb); + sb.append(','); + } + + /* remove the trailing comma if the object has any items*/ + if(key != null) + sb.deleteCharAt(sb.length()-1); + sb.append("}"); + } + + + /** + * Encodes a network-serializable OpenSRF object + */ + private void encodeOSRFSerializable(OSRFSerializable obj, StringBuffer sb) { + + OSRFRegistry reg = obj.getRegistry(); + String[] fields = reg.getFields(); + Map map = new HashMap(); + map.put(JSONReader.JSON_CLASS_KEY, reg.getNetClass()); + + if( reg.getWireProtocol() == OSRFRegistry.WireProtocol.ARRAY ) { + + /** encode arrays as lists */ + List list = new ArrayList(fields.length); + for(String s : fields) + list.add(obj.get(s)); + map.put(JSONReader.JSON_PAYLOAD_KEY, list); + + } else { + + /** encode hashes as maps */ + Map subMap = new HashMap(); + for(String s : fields) + subMap.put(s, obj.get(s)); + map.put(JSONReader.JSON_PAYLOAD_KEY, subMap); + + } + + /** now serialize the encoded object */ + write(map, sb); + } +} + + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/Logger.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/Logger.java new file mode 100644 index 0000000000..2923c23b9e --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/Logger.java @@ -0,0 +1,144 @@ +package org.opensrf.util; +import java.text.SimpleDateFormat; +import java.text.FieldPosition; +import java.util.Date; + +/** + * Basic OpenSRF logging API. This default implementation + * logs to stderr. + */ +public class Logger { + + /** Log levels */ + public static final short ERROR = 1; + public static final short WARN = 2; + public static final short INFO = 3; + public static final short DEBUG = 4; + public static final short INTERNAL = 5; + + /** The global log instance */ + private static Logger instance; + /** The global log level */ + protected static short logLevel; + + public Logger() {} + + /** Sets the global Logger instance + * @param level The global log level. + * @param l The Logger instance to use + */ + public static void init(short level, Logger l) { + instance = l; + logLevel = level; + } + + /** + * @return The global Logger instance + */ + public static Logger instance() { + return instance; + } + + /** + * Logs an error message + * @param msg The message to log + */ + public static void error(String msg) { + instance.log(ERROR, msg); + } + + /** + * Logs an warning message + * @param msg The message to log + */ + public static void warn(String msg) { + instance.log(WARN, msg); + } + + /** + * Logs an info message + * @param msg The message to log + */ + public static void info(String msg) { + instance.log(INFO, msg); + } + + /** + * Logs an debug message + * @param msg The message to log + */ + public static void debug(String msg) { + instance.log(DEBUG, msg); + } + + /** + * Logs an internal message + * @param msg The message to log + */ + public static void internal(String msg) { + instance.log(INTERNAL, msg); + } + + + /** + * Appends the text representation of the log level + * @param sb The stringbuffer to append to + * @param level The log level + */ + protected static void appendLevelString(StringBuffer sb, short level) { + switch(level) { + case DEBUG: + sb.append("DEBG"); break; + case INFO: + sb.append("INFO"); break; + case INTERNAL: + sb.append("INT "); break; + case WARN: + sb.append("WARN"); break; + case ERROR: + sb.append("ERR "); break; + } + } + + /** + * Formats a message for logging. Appends the current date+time + * and the log level string. + * @param level The log level + * @param msg The message to log + */ + protected static String formatMessage(short level, String msg) { + + StringBuffer sb = new StringBuffer(); + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format( + new Date(), sb, new FieldPosition(0)); + + sb.append(" ["); + appendLevelString(sb, level); + sb.append(":"); + sb.append(Thread.currentThread().getId()); + sb.append("] "); + sb.append(msg); + return sb.toString(); + } + + /** + * Logs a message by passing the log level explicitly + * @param level The log level + * @param msg The message to log + */ + public static void logByLevel(short level, String msg) { + instance.log(level, msg); + } + + /** + * Performs the actual logging. Subclasses should override + * this method. + * @param level The log level + * @param msg The message to log + */ + protected synchronized void log(short level, String msg) { + if(level > logLevel) return; + System.err.println(formatMessage(level, msg)); + } +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/OSRFObject.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/OSRFObject.java new file mode 100644 index 0000000000..af28f4a860 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/OSRFObject.java @@ -0,0 +1,63 @@ +package org.opensrf.util; + +import java.util.Map; +import java.util.HashMap; + + +/** + * Generic OpenSRF network-serializable object. This allows + * access to object fields. + */ +public class OSRFObject extends HashMap implements OSRFSerializable { + + /** This objects registry */ + private OSRFRegistry registry; + + public OSRFObject() { + } + + + /** + * Creates a new object with the provided registry + */ + public OSRFObject(OSRFRegistry reg) { + this(); + registry = reg; + } + + + /** + * Creates a new OpenSRF object based on the net class string + * */ + public OSRFObject(String netClass) { + this(OSRFRegistry.getRegistry(netClass)); + } + + + /** + * @return This object's registry + */ + public OSRFRegistry getRegistry() { + return registry; + } + + /** + * Implement get() to fulfill our contract with OSRFSerializable + */ + public Object get(String field) { + return super.get(field); + } + + /** Returns the string value found at the given field */ + public String getString(String field) { + return (String) get(field); + } + + /** Returns the int value found at the given field */ + public int getInt(String field) { + Object o = get(field); + if(o instanceof String) + return Integer.parseInt((String) o); + return ((Integer) get(field)).intValue(); + } +} diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/OSRFRegistry.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/OSRFRegistry.java new file mode 100644 index 0000000000..c6e6bc6540 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/OSRFRegistry.java @@ -0,0 +1,112 @@ +package org.opensrf.util; + +import java.io.Serializable; +import java.util.Map; +import java.util.HashMap; + + +/** + * Manages the registration of OpenSRF network-serializable objects. + * A serializable object has a class "hint" (called netClass within) which + * describes the type of object. Each object also has a set of field names + * for accessing/mutating object properties. Finally, objects have a + * serialization wire protocol. Currently supported protocols are HASH + * and ARRAY. + */ +public class OSRFRegistry implements Serializable{ + + + /** + * + */ + private static final long serialVersionUID = 1L; + /** + * Global collection of registered net objects. + * Maps netClass names to registries. + */ + private static HashMap + registry = new HashMap(); + + + /** Serialization types for registered objects */ + public enum WireProtocol { + ARRAY, HASH + }; + + + /** Array of field names for this registered object */ + String fields[]; + /** The wire protocol for this object */ + WireProtocol wireProtocol; + /** The network class for this object */ + String netClass; + + /** + * Returns the array of field names + */ + public String[] getFields() { + return this.fields; + } + + + /** + * Registers a new object. + * @param netClass The net class for this object + * @param wireProtocol The object's wire protocol + * @param fields An array of field names. For objects whose + * wire protocol is ARRAY, the positions of the field names + * will be used as the array indices for the fields at serialization time + */ + public static OSRFRegistry registerObject(String netClass, WireProtocol wireProtocol, String fields[]) { + OSRFRegistry r = new OSRFRegistry(netClass, wireProtocol, fields); + registry.put(netClass, r); + return r; + } + + /** + * Returns the registry for the given netclass + * @param netClass The network class to lookup + */ + public static OSRFRegistry getRegistry(String netClass) { + if( netClass == null ) return null; + return (OSRFRegistry) registry.get(netClass); + } + + + /** + * @param field The name of the field to lookup + * @return the index into the fields array of the given field name. + */ + public int getFieldIndex(String field) { + for( int i = 0; i < fields.length; i++ ) + if( fields[i].equals(field) ) + return i; + return -1; + } + + /** Returns the wire protocol of this object */ + public WireProtocol getWireProtocol() { + return this.wireProtocol; + } + + /** Returns the netClass ("hint") of this object */ + public String getNetClass() { + return this.netClass; + } + + /** + * Creates a new registry object. + * @param netClass The network class/hint + * @param wireProtocol The wire protocol + * @param fields The array of field names. For array-based objects, + * the fields array must be sorted in accordance with the sorting + * of the objects in the array. + */ + public OSRFRegistry(String netClass, WireProtocol wireProtocol, String fields[]) { + this.netClass = netClass; + this.wireProtocol = wireProtocol; + this.fields = fields; + } +} + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/OSRFSerializable.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/OSRFSerializable.java new file mode 100644 index 0000000000..64b5d6f4e5 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/OSRFSerializable.java @@ -0,0 +1,19 @@ +package org.opensrf.util; + +/** + * All network-serializable OpenSRF object must implement this interface. + */ +public interface OSRFSerializable { + + /** + * Returns the object registry object for the implementing class. + */ + public abstract OSRFRegistry getRegistry(); + + /** + * Returns the object found at the given field + */ + public abstract Object get(String field); +} + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/SettingsClient.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/SettingsClient.java new file mode 100644 index 0000000000..71e570a410 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/SettingsClient.java @@ -0,0 +1,53 @@ +package org.opensrf.util; +import org.opensrf.*; +import java.util.Map; + +/** + * Connects to the OpenSRF Settings server to fetch the settings config. + * Provides a Config interface for fetching settings via path + */ +public class SettingsClient extends Config { + + /** Singleton SettingsClient instance */ + private static SettingsClient client = new SettingsClient(); + + public SettingsClient() { + super(""); + } + + /** + * @return The global settings client instance + */ + public static SettingsClient instance() throws ConfigException { + if(client.getConfigObject() == null) + client.fetchConfig(); + return client; + } + + /** + * Fetches the settings object from the settings server + */ + private void fetchConfig() throws ConfigException { + + ClientSession ses = new ClientSession("opensrf.settings"); + try { + + Request req = ses.request( + "opensrf.settings.host_config.get", + new String[]{(String)Config.global().getFirst("/domain")}); + + Result res = req.recv(12000); + if(res == null) { + /** throw exception */ + } + setConfigObject((Map) res.getContent()); + + } catch(Exception e) { + throw new ConfigException("Error fetching settings config", e); + + } finally { + ses.cleanup(); + } + } +} + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/Utils.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/Utils.java new file mode 100644 index 0000000000..159d254706 --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/Utils.java @@ -0,0 +1,106 @@ +package org.opensrf.util; + +import java.io.*; +import java.util.*; + +/** + * Collection of general, static utility methods + */ +public class Utils { + + /** + * Returns the string representation of a given file. + * @param filename The file to turn into a string + */ + public static String fileToString(String filename) + throws FileNotFoundException, IOException { + + StringBuffer sb = new StringBuffer(); + BufferedReader in = new BufferedReader(new FileReader(filename)); + String str; + while ((str = in.readLine()) != null) + sb.append(str); + in.close(); + return sb.toString(); + } + + + /** + * Escapes a string. + */ + public static String escape(String string) { + StringBuffer sb = new StringBuffer(); + escape(string, sb); + return sb.toString(); + } + + /** + * Escapes a string. Turns bare newlines into \n, etc. + * Escapes \n, \r, \t, ", \f + * Encodes non-ascii characters as UTF-8: \u0000 + * @param string The string to escape + * @param sb The string buffer to write the escaped string into + */ + public static void escape(String string, StringBuffer sb) { + int len = string.length(); + String utf; + char c; + for( int i = 0; i < len; i++ ) { + c = string.charAt(i); + switch (c) { + case '\\': + sb.append("\\\\"); + break; + case '"': + sb.append("\\\""); + break; + case '\b': + sb.append("\\b"); + break; + case '\t': + sb.append("\\t"); + break; + case '\n': + sb.append("\\n"); + break; + case '\f': + sb.append("\\f"); + break; + case '\r': + sb.append("\\r"); + break; + default: + if (c < 32 || c > 126 ) { + /* escape all non-ascii or control characters as UTF-8 */ + utf = "000" + Integer.toHexString(c); + sb.append("\\u" + utf.substring(utf.length() - 4)); + } else { + sb.append(c); + } + } + } + } + + + /** + * Descends into the map along the given XPATH-style path + * and returns the object found there. + * @param path The XPATH-style path to search. Path + * components are separated by '/' characters. + * Example: /opensrf/loglevel + * @return The found object. + */ + + public static Object findPath(Map map, String path) { + String keys[] = path.split("/", -1); + int i = 0; + if(path.charAt(0) == '/') i++; + for(; i < keys.length - 1; i++ ) + map = (Map) map.get(keys[i]); + + return map.get(keys[i]); + } +} + + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/XMLFlattener.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/XMLFlattener.java new file mode 100644 index 0000000000..7abefe09fd --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/XMLFlattener.java @@ -0,0 +1,128 @@ +package org.opensrf.util; + +import javax.xml.stream.*; +import javax.xml.stream.events.* ; +import javax.xml.namespace.QName; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.ListIterator; +import java.io.InputStream; +import org.opensrf.util.JSONWriter; +import org.opensrf.util.JSONReader; + +/** + * Flattens an XML file into a properties map. Values are stored as JSON strings or arrays. + * An array is created if more than one value resides at the same key. + * e.g. html.head.script = "alert('hello');" + */ +public class XMLFlattener { + + /** Flattened properties map */ + private Map props; + /** Incoming XML stream */ + private InputStream inStream; + /** Runtime list of encountered elements */ + private List elementList; + + /** + * Creates a new reader. Initializes the message queue. + * Sets the stream state to disconnected, and the xml + * state to in_nothing. + * @param inStream the inbound XML stream + */ + public XMLFlattener(InputStream inStream) { + props = new HashMap(); + this.inStream = inStream; + elementList = new ArrayList(); + } + + /** Turns an array of strings into a dot-separated key string */ + private String listToString() { + ListIterator itr = elementList.listIterator(); + StringBuffer sb = new StringBuffer(); + while(itr.hasNext()) { + sb.append(itr.next()); + if(itr.hasNext()) + sb.append("."); + } + return sb.toString(); + } + + /** + * Parses XML data from the provided stream. + */ + public Map read() throws javax.xml.stream.XMLStreamException { + + XMLInputFactory factory = XMLInputFactory.newInstance(); + + /** disable as many unused features as possible to speed up the parsing */ + factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.TRUE); + factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); + factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE); + factory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.FALSE); + factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); + + /** create the stream reader */ + XMLStreamReader reader = factory.createXMLStreamReader(inStream); + int eventType; + + while(reader.hasNext()) { + /** cycle through the XML events */ + + eventType = reader.next(); + if(reader.isWhiteSpace()) continue; + + switch(eventType) { + + case XMLEvent.START_ELEMENT: + elementList.add(reader.getName().toString()); + break; + + case XMLEvent.CHARACTERS: + String text = reader.getText(); + String key = listToString(); + + if(props.containsKey(key)) { + + /* something in the map already has this key */ + + Object o = null; + try { + o = new JSONReader(props.get(key)).read(); + } catch(org.opensrf.util.JSONException e){} + + if(o instanceof List) { + /* if the map contains a list, append to the list and re-encode */ + ((List) o).add(text); + + } else { + /* if the map just contains a string, start building a new list + * with the old string and append the new string */ + List arr = new ArrayList(); + arr.add((String) o); + arr.add(text); + o = arr; + } + + props.put(key, new JSONWriter(o).write()); + + } else { + props.put(key, new JSONWriter(text).write()); + } + break; + + case XMLEvent.END_ELEMENT: + elementList.remove(elementList.size()-1); + break; + } + } + + return props; + } +} + + + + diff --git a/Open-ILS/src/Android/opensrf/org/opensrf/util/XMLTransformer.java b/Open-ILS/src/Android/opensrf/org/opensrf/util/XMLTransformer.java new file mode 100644 index 0000000000..f8bc0d3fac --- /dev/null +++ b/Open-ILS/src/Android/opensrf/org/opensrf/util/XMLTransformer.java @@ -0,0 +1,59 @@ +package org.opensrf.util; +import javax.xml.transform.*; +import javax.xml.transform.stream.*; +import javax.xml.parsers.*; +import java.io.File; +import java.io.ByteArrayInputStream; +import java.io.OutputStream; +import java.io.ByteArrayOutputStream; + + +/** + * Performs XSL transformations. + * TODO: Add ability to pass in XSL variables + */ +public class XMLTransformer { + + /** The XML to transform */ + private Source xmlSource; + /** The stylesheet to apply */ + private Source xslSource; + + public XMLTransformer(Source xmlSource, Source xslSource) { + this.xmlSource = xmlSource; + this.xslSource = xslSource; + } + + public XMLTransformer(String xmlString, File xslFile) { + this( + new StreamSource(new ByteArrayInputStream(xmlString.getBytes())), + new StreamSource(xslFile)); + } + + public XMLTransformer(File xmlFile, File xslFile) { + this( + new StreamSource(xmlFile), + new StreamSource(xslFile)); + } + + /** + * Applies the transformation and puts the result into the provided output stream + */ + public void apply(OutputStream outStream) throws TransformerException, TransformerConfigurationException { + Result result = new StreamResult(outStream); + Transformer trans = TransformerFactory.newInstance().newTransformer(xslSource); + trans.transform(xmlSource, result); + } + + /** + * Applies the transformation and return the resulting string + * @return The String created by the XSL transformation + */ + public String apply() throws TransformerException, TransformerConfigurationException { + OutputStream outStream = new ByteArrayOutputStream(); + this.apply(outStream); + return outStream.toString(); + } +} + +