001: package com.xoetrope.service;
002:
003: import java.io.File;
004: import java.io.FileReader;
005: import java.io.FileWriter;
006: import java.util.Date;
007: import java.util.Hashtable;
008: import java.util.Vector;
009:
010: import net.n3.nanoxml.IXMLElement;
011: import net.n3.nanoxml.XMLElement;
012: import net.n3.nanoxml.XMLWriter;
013: import net.xoetrope.optional.service.ServiceProxy;
014: import net.xoetrope.optional.service.ServiceProxyException;
015: import com.xoetrope.carousel.build.BuildProperties;
016: import java.util.Enumeration;
017: import net.xoetrope.optional.service.ServiceContext;
018: import net.xoetrope.optional.service.ServiceProxyArgs;
019: import net.xoetrope.xml.XmlElement;
020: import net.xoetrope.xml.XmlSource;
021: import net.xoetrope.xml.nanoxml.NanoXmlElement;
022: import net.xoetrope.xui.XProject;
023: import net.xoetrope.xui.XProjectManager;
024:
025: /**
026: * A simple caching proxy. Compares the request to cached objects and returns
027: * the cache result if the request matches.
028: *
029: * <p> Copyright (c) Xoetrope Ltd., 2001-2006, This software is licensed under
030: * the GNU Public License (GPL), please see license.txt for more details. If
031: * you make commercial use of this software you must purchase a commercial
032: * license from Xoetrope.</p>
033: * <p> $Revision: 1.8 $</p>
034: */
035: public class XCachingService extends ServiceProxy {
036: protected static Vector storedCalls = null;
037: protected long ttl = -1;
038: protected boolean cachable = true;
039:
040: /**
041: * The owner project and the context in which this object operates.
042: */
043: protected XProject currentProject = XProjectManager
044: .getCurrentProject();
045:
046: /**
047: * Public constructor
048: */
049: public XCachingService() {
050: loadCachedCalls();
051: }
052:
053: public Object call() throws ServiceProxyException {
054: try {
055: status = STARTED;
056: Object res = nextProxy.call();
057: status = COMPLETE;
058: return res;
059: } catch (ServiceProxyException e) {
060: status = FAILED;
061: throw (e);
062: }
063: }
064:
065: public Object call(String method, ServiceContext context)
066: throws ServiceProxyException {
067: return call(method, context, true);
068: }
069:
070: /**
071: * Call this proxy with the specified arguments
072: * @return the result of the call
073: * @param context The ServiceContext contain pass and return parameters
074: * @param method the name of the service being called
075: * @throws net.xoetrope.optional.service.ServiceProxyException Throw an exception if there is a problem with the call
076: */
077: public Object call(String method, ServiceContext context,
078: boolean store) throws ServiceProxyException {
079: try {
080: status = STARTED;
081: Object res = null;
082: if (cachable)
083: res = checkCache(method, context);
084: if (status == COMPLETE)
085: return res;
086:
087: // Store the new request in the cache
088: res = nextProxy.call(method, context);
089: if (cachable)
090: storeCall(method, context, res);
091: status = COMPLETE;
092: return res;
093: } catch (ServiceProxyException ex) {
094: if (BuildProperties.DEBUG)
095: ex.printStackTrace();
096: }
097: status = FAILED;
098: return null;
099: }
100:
101: public Object checkCache(String method, ServiceContext context) {
102: ServiceProxyArgs args = context.getArgs();
103: int numCalls = storedCalls.size();
104: for (int i = 0; i < numCalls; i++) {
105: XmlElement xml = (XmlElement) storedCalls.elementAt(i);
106:
107: if (ttl >= 0) {
108: // Check for expiry
109: long callTime = new Long(xml.getAttribute("time"))
110: .longValue();
111: System.out.println("now: " + new Date().getTime());
112: System.out.println("expires: " + new Date().getTime());
113: if ((callTime + ttl) < new Date().getTime()) {
114: // The cache element has expired
115: storedCalls.removeElementAt(i--);
116: numCalls--;
117: removeDeadFile(xml);
118: continue;
119: }
120: }
121: if (xml.getAttribute("method").compareTo(method) == 0) {
122: Vector vargs = xml.getChildren();
123: int numArgs = vargs.size();
124: if (numArgs == args.getNumPassArgs()) {
125: for (int j = 0; j < numArgs; j++) {
126: XmlElement argElement = (XmlElement) vargs
127: .elementAt(j);
128: String argName = argElement
129: .getAttribute("name");
130: //if ( ( ( argName.compareTo( "null" ) == 0 ) ) || ( argName.compareTo( args.getArgName( j ) ) == 0 ) ) {
131: String argValue = argElement
132: .getAttribute("value");
133: if (((argValue.compareTo("null") == 0) && (args
134: .getPassParam(argName) == null))
135: || (argValue.compareTo(args
136: .getPassParam(argName)
137: .toString()) == 0)) {
138: // So far so good!
139: } else
140: break;
141: //}
142: //else
143: // break;
144: }
145:
146: // All parameters and arguments match
147: status = COMPLETE;
148: return xml.getAttribute("result");
149: }
150: }
151: }
152: return null;
153: }
154:
155: public void storeCall(String method, ServiceContext context,
156: Object res) {
157: try {
158: String callTime = new Long(new Date().getTime()).toString();
159: String cachePath = currentProject
160: .getStartupParam("CachePath");
161: File file = new File(cachePath, callTime + ".cxml");
162: if (BuildProperties.DEBUG)
163: System.out.println("creating file: " + callTime
164: + ".cxml");
165: file.createNewFile();
166: FileWriter fw = new FileWriter(file);
167:
168: XMLElement xml = new XMLElement("Calls");
169: XmlElement nanoXml = new NanoXmlElement(xml);
170:
171: xml.setAttribute("method", method);
172: xml.setAttribute("name", method);
173: xml.setAttribute("time", callTime);
174: xml.setAttribute("route", routeName);
175: xml.setAttribute("service", serviceName);
176: xml.setAttribute("result", res.toString());
177:
178: /*for ( int i = 0; i < args.getNumArgs(); i++ ) {
179: IXMLElement attribXml = xml.createElement( "Arg" );
180: if ( args.getArgName( i ) != null )
181: attribXml.setAttribute( "name", args.getArgName( i ) );
182: else
183: attribXml.setAttribute( "name", "null" );
184:
185: if ( args.getParam( i ) != null )
186: attribXml.setAttribute( "value", args.getParam( i ).toString() );
187: else
188: attribXml.setAttribute( "value", "null" );
189: xml.addChild( attribXml );
190: }*/
191: Hashtable passArgs = context.getPassArgs();
192: Enumeration keys = passArgs.keys();
193: while (keys.hasMoreElements()) {
194: String name = (String) keys.nextElement();
195: String value = (String) passArgs.get(name);
196: IXMLElement attribXml = xml.createElement("Arg");
197: /*if ( args.getArgName( i ) != null )
198: attribXml.setAttribute( "name", args.getArgName( i ) );
199: else
200: attribXml.setAttribute( "name", "null" );
201: */
202:
203: if (value != null)
204: attribXml.setAttribute("value", value);
205: else
206: attribXml.setAttribute("value", "null");
207: xml.addChild(attribXml);
208: }
209:
210: XMLWriter xmlWriter = new XMLWriter(fw);
211: xmlWriter.write(xml, true, 4);
212: fw.flush();
213: fw.close();
214:
215: storedCalls.addElement(nanoXml);
216: } catch (Exception ex) {
217: ex.printStackTrace();
218: }
219: }
220:
221: /**
222: * Load any stored instances of calls from the cache.
223: */
224: protected void loadCachedCalls() {
225: if (storedCalls != null)
226: return;
227:
228: try {
229: storedCalls = new Vector();
230: File[] files = new File(currentProject
231: .getStartupParam("CachePath"), "").listFiles();
232: if (files != null) {
233:
234: int numFiles = files.length;
235: for (int i = 0; i < numFiles; i++) {
236: if (files[i].getName().indexOf(".cxml") > 0) {
237: FileReader reader = new FileReader(files[i]);
238: XmlElement callXml = XmlSource.read(reader);
239: storedCalls.addElement(callXml);
240: }
241: }
242: }
243: } catch (Exception ex) {
244: if (BuildProperties.DEBUG)
245: ex.printStackTrace();
246: }
247: }
248:
249: /**
250: * Get the number of stored calls.
251: * @return the number of calls awaiting completion
252: */
253: public int getNumStoredCalls() {
254: return storedCalls.size();
255: }
256:
257: /**
258: * Set the time to live for cache entries
259: * @param ms the ttl in milliseconds
260: */
261: public void setTTL(long ms) {
262: ttl = ms;
263: }
264:
265: /**
266: * Sets a flag to indicate if the method being called can be cached.
267: * @param b true to allow caching
268: */
269: public void setCachable(boolean b) {
270: cachable = b;
271: }
272:
273: /**
274: * Remove an unneeded cache file
275: * @param element
276: */
277: private void removeDeadFile(XmlElement element) {
278: try {
279: if (BuildProperties.DEBUG)
280: System.out.println("deleting:"
281: + element.getAttribute("time") + ".xml");
282: File file = new File(currentProject
283: .getStartupParam("CachePath"), element
284: .getAttribute("time")
285: + ".xml");
286: try {
287: if (!file.delete())
288: file.deleteOnExit();
289: } catch (Exception ex1) {
290: if (BuildProperties.DEBUG)
291: ex1.printStackTrace();
292: file.deleteOnExit();
293: }
294: } catch (Exception ex) {
295: if (BuildProperties.DEBUG)
296: ex.printStackTrace();
297: }
298: }
299:
300: public void setAttributes(Hashtable attribs) {
301: boolean useCache = true;
302: try {
303: String s = (String) attribs.get("cachable");
304: if (s != null)
305: useCache = new Boolean(s).booleanValue();
306: } catch (Exception ex) {
307: useCache = true;
308: }
309: setCachable(useCache);
310: }
311: }
|