001: /*
002: *
003: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
004: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU General Public License version
008: * 2 only, as published by the Free Software Foundation.
009: *
010: * This program is distributed in the hope that it will be useful, but
011: * WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * General Public License version 2 for more details (a copy is
014: * included at /legal/license.txt).
015: *
016: * You should have received a copy of the GNU General Public License
017: * version 2 along with this work; if not, write to the Free Software
018: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
019: * 02110-1301 USA
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
022: * Clara, CA 95054 or visit www.sun.com if you need additional
023: * information or have any questions.
024: */
025:
026: package com.sun.j2mews.xml.rpc;
027:
028: import javax.microedition.xml.rpc.*;
029: import javax.xml.namespace.QName;
030: import javax.xml.rpc.*;
031: import java.rmi.MarshalException;
032: import java.rmi.ServerException;
033:
034: import java.io.*;
035: import javax.microedition.io.*;
036:
037: // midp private base64 encoder
038: import com.sun.midp.io.Base64;
039:
040: /**
041: * The <code>OperationImpl</code> class is an implementation
042: * of the <code>javax.microedition.xml.rpc.Operation</code>
043: * class, corresponding to a wsdl:operation defined for a
044: * target service endpoint.
045: *
046: * @version 0.1
047: */
048: public class OperationImpl extends Operation {
049:
050: /**
051: * HTTP Sessions are implemented through the use of cookies.
052: * Since a "session" would involve likely more than one Operation,
053: * the set of cookies for all known sessions is made static across
054: * all Operations. The cookies array is implemented like the properties
055: * array - as a set of tuples. The first value being the endpoint
056: * address which started the session, the second value being the
057: * cookie for the session (i.e., cookie 0 is represented by the
058: * endpoint at index 0, and the cookie string at index 1).
059: */
060: private static String[] cookies;
061:
062: /**
063: * The index of the next available entry in the cookie set.
064: * Since cookies are tuples, the algorithm (cookieIndex / 2)
065: * will yield the number of properties stored in the set.
066: */
067: private static int cookieIndex;
068:
069: /**
070: * The set of properties set by the <code>setProperty</code>
071: * method. Each property is a tuple, represented by two sequential
072: * entries in the array (i.e., property 0 is represented by the
073: * key at index 0, and the value at index 1)
074: */
075: private String[] properties;
076:
077: /**
078: * The index of the next available entry in the property set.
079: * Since properties are tuples, the algorithm (propertyIndex / 2)
080: * will yield the number of properties stored in the set.
081: */
082: private int propertyIndex;
083:
084: /**
085: * The Encoder to use when encoding communications to
086: * Web Services
087: */
088: private SOAPEncoder encoder;
089:
090: /**
091: * The Decoder to use when decoding communications from
092: * Web Services
093: */
094: private SOAPDecoder decoder;
095:
096: /**
097: * The QName of this operation
098: */
099: private QName name;
100:
101: /**
102: * The Type of the input parameter to this Operation
103: */
104: private Element inputType;
105:
106: /**
107: * The Type of the output parameter to this Operation
108: */
109: private Element returnType;
110:
111: /**
112: * The handler to provide the decoding information for
113: * custom fault details
114: */
115: private FaultDetailHandler faultHandler;
116:
117: /**
118: * A flag indicating the the object was moved (i.e. 3xx HTTP response
119: * was received) and we have to retry the operation using a new location
120: */
121: private boolean resourceMoved = false;
122:
123: /**
124: * Default constructor matches that of Operation
125: *
126: * @param name the QName of this Operation
127: * @param input the input Type for this Operation
128: * @param output the output Type for this Operation
129: */
130: public OperationImpl(QName name, Element input, Element output)
131: throws IllegalArgumentException {
132: this .name = name;
133: this .inputType = input;
134: this .returnType = output;
135:
136: this .encoder = new SOAPEncoder();
137: this .decoder = new SOAPDecoder();
138: }
139:
140: /**
141: * Default constructor matches that of Operation
142: *
143: * @param name the QName of this Operation
144: * @param input the input Type for this Operation
145: * @param output the output Type for this Operation
146: * @param faultDetailHandler the handler which will return the type
147: * descriptor for a custom fault detail
148: * this operation may encounter
149: */
150: public OperationImpl(QName name, Element input, Element output,
151: FaultDetailHandler faultDetailHandler)
152: throws IllegalArgumentException {
153: this .name = name;
154: this .inputType = input;
155: this .returnType = output;
156: this .faultHandler = faultDetailHandler;
157:
158: this .encoder = new SOAPEncoder();
159: this .decoder = new SOAPDecoder();
160: }
161:
162: /**
163: * Sets the property <code>name</code> to the value,
164: * <code>value</code>.
165: *
166: * @param name the name of the property to be set
167: * @param value the value the property is to be set
168: *
169: * @throws IllegalArgumentException
170: * <UL>
171: * <LI>if an error occurs setting the property
172: * </UL>
173: */
174: public void setProperty(String name, String value)
175: throws IllegalArgumentException {
176: // disallow any null key or value data
177: if (name == null || value == null) {
178: throw new IllegalArgumentException();
179: } else if (!name.equals(Stub.ENDPOINT_ADDRESS_PROPERTY)
180: && !name.equals(Stub.PASSWORD_PROPERTY)
181: && !name.equals(Stub.USERNAME_PROPERTY)
182: && !name.equals(Stub.SESSION_MAINTAIN_PROPERTY)
183: && !name.equals(Operation.SOAPACTION_URI_PROPERTY)) {
184: throw new IllegalArgumentException();
185: }
186:
187: // Before appending, check to see if we are modifying
188: // a previous key/value pair
189: if (properties != null) {
190: for (int i = 0; i < propertyIndex; i += 2) {
191: if (properties[i].equals(name)) {
192: properties[i + 1] = value;
193: return;
194: }
195: }
196: }
197:
198: // If properties not instantiated yet, start out with '
199: // room to hold 5 properties (5 * 2 entries = 10)
200: if (properties == null) {
201: properties = new String[10];
202:
203: // If properties is full, re-size with room for another
204: // 5 properties
205: } else if (propertyIndex == properties.length) {
206: String[] newProps = new String[properties.length + 10];
207: System.arraycopy(properties, 0, newProps, 0,
208: properties.length);
209: properties = null;
210: properties = newProps;
211: }
212:
213: properties[propertyIndex++] = name;
214: properties[propertyIndex++] = value;
215:
216: // propertyIndex is left pointing at the next available
217: // entry in the property list
218: }
219:
220: /**
221: * Invokes the wsdl:operation defined by this
222: * <code>Operation</code> and returns the result.
223: *
224: * @param params a <code>ValueType</code> array representing the
225: * input parameters for this <code>Operation</code>.
226: * Can be <code>null</code> if this operation takes
227: * no parameters.
228: * @return a <code>ValueType</code> array representing the output
229: * value(s) for this operation. Can be <code>null</code>
230: * if this operation returns no value.
231: *
232: * @throws JAXRPCException
233: * <UL>
234: * <LI>if an error occurs while excuting the operation.
235: * </UL>
236: * @see javax.microedition.xml.rpc.Operation
237: */
238: public Object invoke(Object params) throws JAXRPCException {
239: HttpConnection http = null;
240: OutputStream ostream = null;
241: InputStream istream = null;
242: Object result = null;
243: int attempts = 0;
244: // Maximal number of "Object moved" http responses that we will handle
245: final int maxAttempts = Constants.MAX_REDIRECT_ATTEMPTS;
246:
247: try {
248: do {
249: // This flag will be set to 'true' by setupResStream() method
250: // if code 3xx is returned by the http connection.
251: resourceMoved = false;
252:
253: // open stream to service endpoint
254: http = (HttpConnection) Connector
255: .open(getProperty(Stub.ENDPOINT_ADDRESS_PROPERTY));
256:
257: ostream = setupReqStream(http);
258:
259: // IMPL NOTE: encoding should be either UTF-8 or UTF-16
260: encoder.encode(params, inputType, ostream, null);
261:
262: if (ostream != null) {
263: ostream.close();
264: }
265:
266: istream = setupResStream(http);
267:
268: if (returnType != null && istream != null) {
269: result = decoder.decode(returnType, istream, http
270: .getEncoding(), http.getLength());
271: }
272:
273: if (http != null) {
274: http.close();
275: }
276: if (istream != null) {
277: istream.close();
278: }
279:
280: } while (resourceMoved && (attempts++ < maxAttempts));
281:
282: if (resourceMoved) {
283: throw new JAXRPCException("Too many redirections");
284: }
285:
286: return result;
287:
288: } catch (Throwable t) {
289: // Debug Line
290:
291: if (ostream != null) {
292: try {
293: ostream.close();
294: } catch (Throwable t2) {
295: }
296: }
297: if (istream != null) {
298: try {
299: istream.close();
300: } catch (Throwable t3) {
301: }
302: }
303: if (http != null) {
304: try {
305: http.close();
306: } catch (Throwable t1) {
307: }
308: }
309: // Re-throw whatever error/exception occurs as a new
310: // JAXRPCException
311: if (t instanceof JAXRPCException) {
312: throw (JAXRPCException) t;
313: } else {
314: if (t instanceof MarshalException
315: || t instanceof ServerException
316: || t instanceof FaultDetailException) {
317: throw new JAXRPCException(t);
318: } else {
319: throw new JAXRPCException(t.toString());
320: }
321: }
322: }
323: }
324:
325: /**
326: * Method to configure the HTTP request stream.
327: * This method will do whatever mechanics are necessary to
328: * utilize the HTTP connection object to return an output
329: * stream to be utilized to send the HTTP request.
330: *
331: * @param http the HttpConnection object, unopened, with no
332: * headers or any configuration yet set
333: * @return an OutputStream which can be used to write the
334: * request data to this HTTP request.
335: */
336: protected OutputStream setupReqStream(HttpConnection http)
337: throws IOException {
338: http.setRequestMethod(HttpConnection.POST);
339: http.setRequestProperty("User-Agent",
340: "Profile/MIDP-1.0 Configuration/CLDC-1.0");
341: http.setRequestProperty("Content-Language", "en-US");
342: http.setRequestProperty("Content-Type", "text/xml");
343:
344: String soapAction = getProperty(Operation.SOAPACTION_URI_PROPERTY);
345: if (soapAction == null || "".equals(soapAction)) {
346: soapAction = "\"\"";
347: } else {
348: if (!soapAction.startsWith("\"")) {
349: soapAction = "\"" + soapAction;
350: }
351: if (!soapAction.endsWith("\"")) {
352: soapAction = soapAction + "\"";
353: }
354: }
355:
356: http.setRequestProperty("SOAPAction", soapAction);
357:
358: String useSession = getProperty(Stub.SESSION_MAINTAIN_PROPERTY);
359: if (useSession != null
360: && useSession.toLowerCase().equals("true")) {
361: String cookie = getSessionCookie(getProperty(Stub.ENDPOINT_ADDRESS_PROPERTY));
362: if (cookie != null) {
363: http.setRequestProperty("Cookie", cookie);
364: }
365: }
366:
367: String s1 = getProperty(Stub.USERNAME_PROPERTY);
368: String s2 = getProperty(Stub.PASSWORD_PROPERTY);
369: if (s1 != null && s2 != null) {
370: byte[] encodeData = (s1 + ":" + s2).getBytes();
371: http.setRequestProperty("Authorization", "Basic "
372: + Base64.encode(encodeData, 0, encodeData.length));
373: }
374: return http.openOutputStream();
375: }
376:
377: /**
378: * Method to configure the HTTP response stream.
379: * This method will do whatever mechanics are necessary to
380: * utilize the HTTP connection object to return an input
381: * stream to the HTTP response.
382: *
383: * @param http the HttpConnection object, already opened and
384: * presumably with response data waiting
385: * @return an InputStream corresponding to the response data
386: * from this HttpConnection or null if not available.
387: */
388: protected InputStream setupResStream(HttpConnection http)
389: throws IOException, ServerException {
390: int response = http.getResponseCode();
391:
392: if (response == HttpConnection.HTTP_MOVED_PERM
393: || response == HttpConnection.HTTP_MOVED_TEMP) {
394: // Resource was moved, get a new location and retry the operation
395: String newLocation = http.getHeaderField("Location");
396: setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, newLocation);
397: resourceMoved = true;
398: return null;
399: }
400:
401: InputStream input = http.openInputStream();
402:
403: if (response == HttpConnection.HTTP_OK) {
404:
405: // Catch any session cookie if one was set
406: String useSession = getProperty(Stub.SESSION_MAINTAIN_PROPERTY);
407: if (useSession != null
408: && useSession.toLowerCase().equals("true")) {
409: String cookie = http.getHeaderField("Set-Cookie");
410: if (cookie != null) {
411: addSessionCookie(
412: getProperty(Stub.ENDPOINT_ADDRESS_PROPERTY),
413: cookie);
414: }
415: }
416:
417: return input;
418: } else {
419: Object detail = decoder.decodeFault(faultHandler, input,
420: http.getEncoding(), http.getLength());
421:
422: if (detail instanceof String) {
423: if (((String) detail).indexOf("DataEncodingUnknown") != -1) {
424: throw new MarshalException((String) detail);
425: } else {
426: throw new ServerException((String) detail);
427: }
428: } else {
429: Object[] wrapper = (Object[]) detail;
430: String message = (String) wrapper[0];
431: QName name = (QName) wrapper[1];
432: detail = wrapper[2];
433: throw new JAXRPCException(message,
434: new FaultDetailException(name, detail));
435: }
436: }
437: }
438:
439: /**
440: * Internal utility method to retrieve a property value
441: * from the property set
442: *
443: * @param key the property identifier
444: * @return the string value of the property
445: */
446: private String getProperty(String key) {
447: if (properties != null) {
448: for (int i = 0; i < (properties.length - 2); i += 2) {
449: if (properties[i] == null) {
450: return null;
451: }
452: if (properties[i].equals(key)) {
453: return properties[i + 1];
454: }
455: }
456: }
457: return null;
458: }
459:
460: /**
461: * Adds a cookie to identify this session.
462: * Please refer to the section 13.2 of the JAX-RPC 1.1 spec.
463: *
464: * @param endpoint an address to associate with the given cookie
465: * @param cookie a cookie identifying the session
466: */
467: private static synchronized void addSessionCookie(String endpoint,
468: String cookie) {
469: if (endpoint == null || cookie == null) {
470: return;
471: }
472:
473: // We strip off everything after the name=value info
474: int i = cookie.indexOf(";");
475: if (i > 0) {
476: cookie = cookie.substring(0, i);
477: }
478:
479: // Before appending, check to see if we are modifying
480: // a previous key/value pair
481: if (cookies != null) {
482: for (i = 0; i < cookieIndex; i += 2) {
483: if (cookies[i].equals(endpoint)) {
484: cookies[i + 1] = cookie;
485: return;
486: }
487: }
488: }
489:
490: // If cookies not instantiated yet, start out with '
491: // room to hold 5 cookies (5 * 2 entries = 10)
492: if (cookies == null) {
493: cookies = new String[10];
494:
495: // If cookies is full, re-size with room for another
496: // 5 cookies
497: } else if (cookieIndex == cookies.length) {
498: String[] newCookies = new String[cookies.length + 10];
499: System.arraycopy(cookies, 0, newCookies, 0, cookies.length);
500: cookies = null;
501: cookies = newCookies;
502: }
503:
504: cookies[cookieIndex++] = endpoint;
505: cookies[cookieIndex++] = cookie;
506:
507: // cookieIndex is left pointing at the next available
508: // entry in the cookie list
509: }
510:
511: /**
512: * Look through the set of session cookies, and return the
513: * one that matches the current endpoint address of this
514: * Operation, if any.
515: *
516: * @param endpoint address of this Operation
517: * @return the session cookie for this Operation's endpoint address,
518: * or null if there is no session cookie
519: */
520: private static synchronized String getSessionCookie(String endpoint) {
521: if (cookies != null) {
522: for (int i = 0; i < (cookies.length - 2); i += 2) {
523: if (cookies[i] == null) {
524: return null;
525: }
526: if (cookies[i].equals(endpoint)) {
527: return cookies[i + 1];
528: }
529: }
530: }
531: return null;
532: }
533: }
|