001: package net.xoetrope.optional.service;
002:
003: import java.util.Hashtable;
004:
005: /**
006: * Constructs service call routes from an XML descriptor. XRouteManager caches
007: * routes so that they can be resused once constructed.
008: *
009: * The startup properties file lists the datasets file in its ModelData entry.
010: *
011: * e.g.
012: * <PRE>
013: * UseWindow=false
014: * ClientWidth=800
015: * ClientHeight=600
016: * StartPackage=net.xoetrope.myPackage
017: * StartClass=myStartScreen
018: * Title=My Application
019: * StyleFile=myStyles.txt
020: * <B>ModelData=datasets.xml</B>
021: * CenterWin=false
022: * XDataSourceClass=net.xoetrope.xlib.data.XLibDataSource
023: * </PRE>
024: *
025: * The dataset then points to a datasource, usually called routes.xml.
026: *
027: * <PRE>
028: * <?xml version="1.0" encoding="UTF-8"?>
029: * <DataSources>
030: * <DataSource name="Tables" type="database" filename="tables.xml"/>
031: * <DataSource name="ListValues" filename="staticdata.xml"/>
032: * <DataSource name="Routes" type="routing" filename="routes.xml"/>
033: * <DataSource name="Services" type="service" filename="services.xml"/>
034: * </DataSources>
035: * </PRE>
036: *
037: * This XML
038: * file describes each named route as a series of layers with separate entries
039: * for the client (local) and server (remote) sides.
040: *
041: * <PRE>
042: * <?xml version="1.0" encoding="UTF-8"?>
043: * <Routes>
044: * <route name="myRemoteService">
045: * <layer class="net.xoetrope.service.XHttpClientServiceProxy" url="http://localhost:8080/xserviceservlet" urlEncode="false"/>
046: * </route>
047: * <route name="<B>myCalculationService</B>">
048: * <layer class="net.xoetrope.service.test.CalcTestService"/>
049: * </route>
050: * </Routes>
051: * </PRE>
052: *
053: * The route manager is used in conjunction with the XServiceModelNode as the
054: * following fragement of test code looking up and invoking shows:
055: *
056: * <PRE>
057: * XRouteManager routeMgr = XRouteManager.getInstance();
058: * XServiceModelNode node = new XServiceModelNode();
059: * try {
060: * node.setupService( "getName", routeMgr.getRoute( "<B>myCalculationService</B>" ), null );
061: * Object result = node.get();
062: * assertTrue( result.toString().compareTo( CalcTestService.class.getName() ) == 0 );
063: * ...
064: * </PRE>
065: *
066: * Normally this method of invoking a service would not be used as the services
067: * can more easily be invoked by looking up the XServiceModelNode in the overall
068: * XModel. The nodes in the XModel are configured via the <i>Services</i>
069: * datasource as follow:
070: *
071: * <PRE>
072: * <?xml version="1.0" encoding="UTF-8"?>
073: * <Services>
074: * <service name="<B>calcVolume</B>" route="remote" processor="net.xoetrope.service.test.CalcTestService">
075: * <arg name="roomWidth" type="double"/>
076: * <arg name="roomLength" type="double"/>
077: * <arg name="roomHeight" type="double"/>
078: * <return type="double"/>
079: * </service>
080: * <service name="calcRepayments" route="remote">
081: * <arg name="term" type="int" mandatory="true"/>
082: * <arg name="value" type="double" mandatory="true"/>
083: * <arg name="deposit" type="double" mandatory="false"/>
084: * </service>
085: * <service name="getName" route="calcRoute">
086: * <return type="String"/>
087: * </service>
088: * <service name="getNumber" route="calcRoute">
089: * <return type="int"/>
090: * </service>
091: * </Services>
092: * </PRE>
093: * <BR>
094: * The service can then be invoked via the model as in the following example.
095: * <PRE>
096: * try {
097: * XServiceModelNode node = (XServiceModelNode)XModel.getInstance().get( "<B>calcVolume</B>" );
098: * node.setAttribValue( 0, new Double( 3.9 ));
099: * node.setAttribValue( 1, new Double( 4.6 ));
100: * node.setAttribValue( 2, new Double( 2.5 ));
101: * Object result = node.get();
102: * assertTrue( result instanceof Double );
103: * assertTrue( ((Double)result).doubleValue() == 101.0 );
104: * }
105: * catch ( Exception ex ) {
106: * ex.printStackTrace();
107: * }
108: *</PRE>
109: *
110: * Note that on the server side of a remote call the services may need to be
111: * implemented in the reverse order and in a slightly different order i.e. the
112: * server side will have to provide an implementation of the service.
113: *
114: * <p>Copyright (c) Xoetrope Ltd. 2001-2003</p>
115: * $Revision: 1.1 $
116: */
117: public class XRouteManager {
118: public static final int CLIENT_SIDE = 0;
119: public static final int SERVER_SIDE = 1;
120:
121: protected static XRouteManager routeMgr;
122: protected static Hashtable routes;
123: private static int side = CLIENT_SIDE;
124:
125: protected XRouteManager() {
126: routes = new Hashtable();
127: }
128:
129: public static XRouteManager getInstance() {
130: if (routeMgr == null) {
131: routeMgr = new XRouteManager();
132: }
133:
134: return routeMgr;
135: }
136:
137: /**
138: * Flags the route manager as being on the client or server side
139: * @param side indicates client (0) or server(1) side
140: */
141: public static void setSide(int s) {
142: side = s;
143: }
144:
145: /**
146: * @param route the route name
147: * @return the service route
148: */
149: public static ServiceProxy getRoute(String route)
150: throws XServiceProxyNotFoundException {
151: return getRoute(route, null);
152: }
153:
154: /**
155: * @param route the route name
156: * @param serviceImplementationClass the service implementation class if this
157: * is a new route or if a route is being customized by an additional service
158: * class
159: * @return the service route
160: */
161: public static ServiceProxy getRoute(String route,
162: String serviceImplementationClass)
163: throws XServiceProxyNotFoundException {
164: ServiceProxy spParent = null;
165:
166: try {
167: Hashtable[] layers = (Hashtable[]) routes.get(route);
168: if (layers != null) {
169: // Reverse the iteration if on the server side
170: int inc = side == CLIENT_SIDE ? 1 : -1;
171: int start = side == CLIENT_SIDE ? 0 : layers.length - 1;
172: int next = side == CLIENT_SIDE ? 1 : layers.length - 2;
173: int last = (side == CLIENT_SIDE) ? layers.length : -1;
174:
175: // Build the first element in the stack
176: spParent = spParent = (ServiceProxy) Class.forName(
177: (String) layers[start].get("class"))
178: .newInstance();
179: ;
180: spParent.setRouteName(route);
181: spParent.setAttributes(layers[start]);
182:
183: ServiceProxy spNext = spParent;
184:
185: // Build the remainder of the stack
186: while (next != last) {
187: ServiceProxy sp = (ServiceProxy) Thread
188: .currentThread().getContextClassLoader()
189: .getClass().forName(
190: (String) layers[next].get("class"))
191: .newInstance();
192: sp.setRouteName(route);
193: sp.setAttributes(layers[next]);
194:
195: spNext.setNextProxy(sp);
196:
197: spNext = sp;
198: next += inc;
199: }
200:
201: // Set the implementation class as the last node as it should be the last
202: // thing invoked, after alll the stack elements have done their thin
203: // Load the implementation class
204: ServiceProxy implementationProxy = null;
205: try {
206: if (serviceImplementationClass != null) {
207: implementationProxy = (ServiceProxy) Class
208: .forName(serviceImplementationClass)
209: .newInstance();
210: implementationProxy.setRouteName(route);
211:
212: spNext.setNextProxy(implementationProxy);
213: }
214: } catch (Exception ex) {
215: }
216: }
217: } catch (Exception ex) {
218: System.err.println("Route not found: " + route);
219: throw new XServiceProxyNotFoundException();
220: }
221:
222: if (spParent == null) {
223: System.err.println("Route not found: " + route);
224: throw new XServiceProxyNotFoundException();
225: }
226:
227: return spParent;
228: }
229:
230: public static void addRoute(String name, Hashtable[] layers) {
231: routes.put(name, layers);
232: }
233: }
|