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.
--- /dev/null
+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<Integer, Request> 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<Integer, Request>();
+ 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<Object> 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);
+ }
+ }
+}
+
--- /dev/null
+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;
+ }
+}
+
+
--- /dev/null
+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<Object> 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<Object>(8);
+ }
+
+ /**
+ * @param name The method API name
+ * @param params The ordered list of params
+ */
+ public Method(String name, List<Object> 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<Object> 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;
+ }
+
+}
+
--- /dev/null
+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);
+ }
+}
+
--- /dev/null
+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<RequestContainer> requests;
+ private int lastId;
+
+ public MultiSession() {
+ requests = new ArrayList<RequestContainer>();
+ }
+
+ 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();
+ }
+}
+
--- /dev/null
+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<Result> 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<Result>();
+ 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<Object> 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;
+ }
+}
--- /dev/null
+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;
+ }
+
+}
+
--- /dev/null
+package org.opensrf;
+
+/**
+ * Models an OpenSRF server session.
+ */
+public class ServerSession extends Session {
+}
+
--- /dev/null
+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<String, Session>
+ sessionCache = new HashMap<String, Session>();
+
+ /** 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;
+ }
+}
--- /dev/null
+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);
+ }
+}
+
--- /dev/null
+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) {
+ }
+}
--- /dev/null
+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;
+ }
+}
+
+
--- /dev/null
+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();
+ }
+}
+
--- /dev/null
+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<String,?> result = null;
+
+ System.out.println("Received " + readBuf.toString());
+ try {
+ result = (Map<String, ?>) 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 = <some new exception>
+ }
+
+ // 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();
+ }
+}
+
+
--- /dev/null
+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<HttpRequest> 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();
+ }
+}
+
+
--- /dev/null
+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<Object> 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<Object>();
+ 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;
+ }
+}
+
+
--- /dev/null
+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) {
+ }
+}
--- /dev/null
+package org.opensrf.net.xmpp;
+
+/**
+ * Used for XMPP stream/authentication errors
+ */
+public class XMPPException extends Exception {
+ public XMPPException(String info) {
+ super(info);
+ }
+}
--- /dev/null
+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("<message to='");
+ escapeXML(to, sb);
+ sb.append("' osrf_xid='");
+ escapeXML(xid, sb);
+ sb.append("'><thread>");
+ escapeXML(thread, sb);
+ sb.append("</thread><body>");
+ escapeXML(body, sb);
+ sb.append("</body></message>");
+ 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);
+ }
+ }
+ }
+}
+
+
--- /dev/null
+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<XMPPMessage> 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<XMPPMessage>();
+ 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;
+ }
+ }
+}
+
+
+
+
--- /dev/null
+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 =
+ "<stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>";
+
+ /** Basic auth message */
+ public static final String JABBER_BASIC_AUTH =
+ "<iq id='123' type='set'><query xmlns='jabber:iq:auth'>" +
+ "<username>%s</username><password>%s</password><resource>%s</resource></query></iq>";
+
+ public static final String JABBER_DISCONNECT = "</stream:stream>";
+
+ 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) {}
+ }
+}
+
--- /dev/null
+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 <osrfConfig> <numIterations>");
+ 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<Object> params = new ArrayList<Object>();
+ 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();
+ }
+}
+
+
+
--- /dev/null
+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"));
+ }
+}
+
+
--- /dev/null
+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<Object> 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 "+
+ "<osrfConfigFile> <service> <method> [<JSONparam1>, <JSONparam2>]");
+ 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<Object>();
+ 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();
+ }
+}
+
+
+
--- /dev/null
+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]));
+ }
+}
--- /dev/null
+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<String,Object> map = new HashMap<String,Object>();
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+ map.put("key3", "value3");
+ map.put("key4", "athe\u0301s");
+ map.put("key5", null);
+
+ List<Object> list = new ArrayList<Object>(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());
+ }
+}
--- /dev/null
+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");
+ }
+}
--- /dev/null
+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();
+ }
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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<Object>();
+ 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 "+
+ "<osrfConfigFile> <numthreads> <numiter> <service> <method> [<JSONparam1>, <JSONparam2>]");
+ return;
+ }
+
+ int numThreads = Integer.parseInt(args[1]);
+ for(int i = 0; i < numThreads; i++)
+ new Thread(new TestThread(args)).start();
+ }
+}
+
+
+
--- /dev/null
+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());
+ }
+}
--- /dev/null
+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();
+ }
+ }
+}
+
+
--- /dev/null
+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 <host> <port> <username> <password> <resource> [<recipient>]");
+ 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());
+ }
+ }
+}
+
+
+
+
+
--- /dev/null
+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<String> serverList) {
+ initCache(serverList.toArray(new String[]{}));
+ }
+}
+
--- /dev/null
+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();
+ }
+}
+
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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) {}
+ }
+}
--- /dev/null
+package org.opensrf.util;
+/**
+ * Used to indicate JSON parsing errors
+ */
+public class JSONException extends Exception {
+ public JSONException(String s) {
+ super(s);
+ }
+}
--- /dev/null
+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<String, Object> map = new HashMap<String, Object>();
+
+ 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<Object> list = new ArrayList<Object>(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;
+ }
+}
+
+
+
--- /dev/null
+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<String, Object> map = new HashMap<String, Object>();
+ map.put(JSONReader.JSON_CLASS_KEY, reg.getNetClass());
+
+ if( reg.getWireProtocol() == OSRFRegistry.WireProtocol.ARRAY ) {
+
+ /** encode arrays as lists */
+ List<Object> list = new ArrayList<Object>(fields.length);
+ for(String s : fields)
+ list.add(obj.get(s));
+ map.put(JSONReader.JSON_PAYLOAD_KEY, list);
+
+ } else {
+
+ /** encode hashes as maps */
+ Map<String, Object> subMap = new HashMap<String, Object>();
+ 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);
+ }
+}
+
+
+
--- /dev/null
+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));
+ }
+}
+
--- /dev/null
+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<String, Object> 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();
+ }
+}
--- /dev/null
+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<String, OSRFRegistry>
+ registry = new HashMap<String, OSRFRegistry>();
+
+
+ /** 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;
+ }
+}
+
+
--- /dev/null
+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);
+}
+
+
--- /dev/null
+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();
+ }
+ }
+}
+
--- /dev/null
+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]);
+ }
+}
+
+
+
--- /dev/null
+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<String, String> props;
+ /** Incoming XML stream */
+ private InputStream inStream;
+ /** Runtime list of encountered elements */
+ private List<String> 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<String, String>();
+ this.inStream = inStream;
+ elementList = new ArrayList<String>();
+ }
+
+ /** 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<String> arr = new ArrayList<String>();
+ 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;
+ }
+}
+
+
+
+
--- /dev/null
+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();
+ }
+}
+
+