/* * * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.j2mews.xml.rpc; import javax.microedition.io.Connector; import javax.microedition.io.HttpConnection; import javax.microedition.xml.rpc.*; import javax.xml.namespace.QName; import javax.xml.rpc.*; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.rmi.MarshalException; import java.rmi.ServerException; import com.sun.j2me.io.Base64; /** * The <code>OperationImpl</code> class is an implementation * of the <code>javax.microedition.xml.rpc.Operation</code> * class, corresponding to a wsdl:operation defined for a * target service endpoint. * * @version 0.1 */ public class OperationImpl extends Operation { /** * HTTP Sessions are implemented through the use of cookies. * Since a "session" would involve likely more than one Operation, * the set of cookies for all known sessions is made static across * all Operations. The cookies array is implemented like the properties * array - as a set of tuples. The first value being the endpoint * address which started the session, the second value being the * cookie for the session (i.e., cookie 0 is represented by the * endpoint at index 0, and the cookie string at index 1). */ private static String[] cookies; /** * The index of the next available entry in the cookie set. * Since cookies are tuples, the algorithm (cookieIndex / 2) * will yield the number of properties stored in the set. */ private static int cookieIndex; /** * The set of properties set by the <code>setProperty</code> * method. Each property is a tuple, represented by two sequential * entries in the array (i.e., property 0 is represented by the * key at index 0, and the value at index 1) */ private String[] properties; /** * The index of the next available entry in the property set. * Since properties are tuples, the algorithm (propertyIndex / 2) * will yield the number of properties stored in the set. */ private int propertyIndex; /** * The Encoder to use when encoding communications to * Web Services */ private SOAPEncoder encoder; /** * The Decoder to use when decoding communications from * Web Services */ private SOAPDecoder decoder; /** * The QName of this operation */ private QName name; /** * The Type of the input parameter to this Operation */ private Element inputType; /** * The Type of the output parameter to this Operation */ private Element returnType; /** * The handler to provide the decoding information for * custom fault details */ private FaultDetailHandler faultHandler; /** * A flag indicating the the object was moved (i.e. 3xx HTTP response * was received) and we have to retry the operation using a new location */ private boolean resourceMoved = false; /** * Default constructor matches that of Operation * * @param name the QName of this Operation * @param input the input Type for this Operation * @param output the output Type for this Operation */ public OperationImpl(QName name, Element input, Element output) throws IllegalArgumentException { this.name = name; this.inputType = input; this.returnType = output; this.encoder = new SOAPEncoder(); this.decoder = new SOAPDecoder(); } /** * Default constructor matches that of Operation * * @param name the QName of this Operation * @param input the input Type for this Operation * @param output the output Type for this Operation * @param faultDetailHandler the handler which will return the type * descriptor for a custom fault detail * this operation may encounter */ public OperationImpl(QName name, Element input, Element output, FaultDetailHandler faultDetailHandler) throws IllegalArgumentException { this.name = name; this.inputType = input; this.returnType = output; this.faultHandler = faultDetailHandler; this.encoder = new SOAPEncoder(); this.decoder = new SOAPDecoder(); } /** * Sets the property <code>name</code> to the value, * <code>value</code>. * * @param name the name of the property to be set * @param value the value the property is to be set * * @throws IllegalArgumentException * <UL> * <LI>if an error occurs setting the property * </UL> */ public void setProperty(String name, String value) throws IllegalArgumentException { // disallow any null key or value data if (name == null || value == null) { throw new IllegalArgumentException(); } else if ( !name.equals(Stub.ENDPOINT_ADDRESS_PROPERTY) && !name.equals(Stub.PASSWORD_PROPERTY) && !name.equals(Stub.USERNAME_PROPERTY) && !name.equals(Stub.SESSION_MAINTAIN_PROPERTY) && !name.equals(Operation.SOAPACTION_URI_PROPERTY)) { throw new IllegalArgumentException(); } // Before appending, check to see if we are modifying // a previous key/value pair if (properties != null) { for (int i = 0; i < propertyIndex; i += 2) { if (properties[i].equals(name)) { properties[i + 1] = value; return; } } } // If properties not instantiated yet, start out with ' // room to hold 5 properties (5 * 2 entries = 10) if (properties == null) { properties = new String[10]; // If properties is full, re-size with room for another // 5 properties } else if (propertyIndex == properties.length) { String[] newProps = new String[properties.length + 10]; System.arraycopy(properties, 0, newProps, 0, properties.length); properties = null; properties = newProps; } properties[propertyIndex++] = name; properties[propertyIndex++] = value; // propertyIndex is left pointing at the next available // entry in the property list } /** * Invokes the wsdl:operation defined by this * <code>Operation</code> and returns the result. * * @param params a <code>ValueType</code> array representing the * input parameters for this <code>Operation</code>. * Can be <code>null</code> if this operation takes * no parameters. * @return a <code>ValueType</code> array representing the output * value(s) for this operation. Can be <code>null</code> * if this operation returns no value. * * @throws JAXRPCException * <UL> * <LI>if an error occurs while executing the operation. * </UL> * @see javax.microedition.xml.rpc.Operation */ public Object invoke(Object params) throws JAXRPCException { HttpConnection http = null; OutputStream ostream = null; InputStream istream = null; Object result = null; int attempts = 0; // Maximal number of "Object moved" http responses that we will handle final int maxAttempts = Constants.MAX_REDIRECT_ATTEMPTS; try { do { // This flag will be set to 'true' by setupResStream() method // if code 3xx is returned by the http connection. resourceMoved = false; // open stream to service endpoint http = (HttpConnection)Connector.open( getProperty(Stub.ENDPOINT_ADDRESS_PROPERTY)); ostream = setupReqStream(http); // IMPL NOTE: encoding should be either UTF-8 or UTF-16 encoder.encode(params, inputType, ostream, null); if (ostream != null) { ostream.close(); ostream = null; } istream = setupResStream(http); if (returnType != null && istream != null) { result = decoder.decode(returnType, istream, http.getEncoding(), http.getLength()); } if (istream != null) { istream.close(); istream = null; } if (http != null) { http.close(); http = null; } } while (resourceMoved && (attempts++ < maxAttempts)); if (resourceMoved) { throw new JAXRPCException("Too many redirections"); } return result; } catch (Exception t) { // Debug Line if (ostream != null) { try { ostream.close(); } catch (Exception t1) {} ostream = null; } if (istream != null) { try { istream.close(); } catch (Exception t1) {} istream = null; } if (http != null) { try { http.close(); } catch (Exception t1) {} http = null; } // Re-throw whatever error/exception occurs as a new // JAXRPCException if (t instanceof RuntimeException) throw (RuntimeException)t; if (t instanceof MarshalException || t instanceof ServerException || t instanceof FaultDetailException) { throw new JAXRPCException(t); } throw new JAXRPCException(t.toString()); } } /** * Method to configure the HTTP request stream. * This method will do whatever mechanics are necessary to * utilize the HTTP connection object to return an output * stream to be utilized to send the HTTP request. * * @param http the HttpConnection object, unopened, with no * headers or any configuration yet set * @return an OutputStream which can be used to write the * request data to this HTTP request. */ protected OutputStream setupReqStream(HttpConnection http) throws IOException { http.setRequestMethod(HttpConnection.POST); http.setRequestProperty("User-Agent", "Profile/MIDP-1.0 Configuration/CLDC-1.0"); http.setRequestProperty("Content-Language", "en-US"); http.setRequestProperty("Content-Type", "text/xml"); String soapAction = getProperty(Operation.SOAPACTION_URI_PROPERTY); if (soapAction == null || "".equals(soapAction)) { soapAction = "\"\""; } else { if (!soapAction.startsWith("\"")) { soapAction = "\"" + soapAction; } if (!soapAction.endsWith("\"")) { soapAction = soapAction + "\""; } } http.setRequestProperty("SOAPAction", soapAction); String useSession = getProperty(Stub.SESSION_MAINTAIN_PROPERTY); if (useSession != null && useSession.toLowerCase().equals("true")) { String cookie = getSessionCookie( getProperty(Stub.ENDPOINT_ADDRESS_PROPERTY)); if (cookie != null) { http.setRequestProperty("Cookie", cookie); } } String s1 = getProperty(Stub.USERNAME_PROPERTY); String s2 = getProperty(Stub.PASSWORD_PROPERTY); if (s1 != null && s2 != null) { byte[] encodeData = (s1 + ":" + s2).getBytes(); http.setRequestProperty("Authorization", "Basic " + Base64.encode(encodeData, 0, encodeData.length)); } return http.openOutputStream(); } /** * Method to configure the HTTP response stream. * This method will do whatever mechanics are necessary to * utilize the HTTP connection object to return an input * stream to the HTTP response. * * @param http the HttpConnection object, already opened and * presumably with response data waiting * @return an InputStream corresponding to the response data * from this HttpConnection or null if not available. */ protected InputStream setupResStream(HttpConnection http) throws IOException { int response = http.getResponseCode(); if (response == HttpConnection.HTTP_MOVED_PERM || response == HttpConnection.HTTP_MOVED_TEMP) { // Resource was moved, get a new location and retry the operation String newLocation = http.getHeaderField("Location"); setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, newLocation); resourceMoved = true; return null; } InputStream input = http.openInputStream(); try { if (response == HttpConnection.HTTP_OK) { // Catch any session cookie if one was set String useSession = getProperty(Stub.SESSION_MAINTAIN_PROPERTY); if (useSession != null && useSession.toLowerCase().equals("true")) { String cookie = http.getHeaderField("Set-Cookie"); if (cookie != null) { addSessionCookie( getProperty(Stub.ENDPOINT_ADDRESS_PROPERTY), cookie); } } return input; } Object detail = decoder.decodeFault(faultHandler, input, http.getEncoding(), http.getLength()); if (detail instanceof String) { if (((String)detail).indexOf("DataEncodingUnknown") != -1) throw new MarshalException((String)detail); throw new ServerException((String)detail); } Object[] wrapper = (Object[])detail; String message = (String)wrapper[0]; QName name = (QName)wrapper[1]; detail = wrapper[2]; throw new JAXRPCException(message, new FaultDetailException(name, detail)); } catch( IOException t ){ input.close(); throw t; } catch( RuntimeException t ){ input.close(); throw t; } } /** * Internal utility method to retrieve a property value * from the property set * * @param key the property identifier * @return the string value of the property */ private String getProperty(String key) { if (properties != null) { for (int i = 0; i < (properties.length - 2); i += 2) { if (properties[i] == null) { return null; } if (properties[i].equals(key)) { return properties[i + 1]; } } } return null; } /** * Adds a cookie to identify this session. * Please refer to the section 13.2 of the JAX-RPC 1.1 spec. * * @param endpoint an address to associate with the given cookie * @param cookie a cookie identifying the session */ private static synchronized void addSessionCookie(String endpoint, String cookie) { if (endpoint == null || cookie == null) { return; } // We strip off everything after the name=value info int i = cookie.indexOf(";"); if (i > 0) { cookie = cookie.substring(0, i); } // Before appending, check to see if we are modifying // a previous key/value pair if (cookies != null) { for (i = 0; i < cookieIndex; i += 2) { if (cookies[i].equals(endpoint)) { cookies[i + 1] = cookie; return; } } } // If cookies not instantiated yet, start out with ' // room to hold 5 cookies (5 * 2 entries = 10) if (cookies == null) { cookies = new String[10]; // If cookies is full, re-size with room for another // 5 cookies } else if (cookieIndex == cookies.length) { String[] newCookies = new String[cookies.length + 10]; System.arraycopy(cookies, 0, newCookies, 0, cookies.length); cookies = null; cookies = newCookies; } cookies[cookieIndex++] = endpoint; cookies[cookieIndex++] = cookie; // cookieIndex is left pointing at the next available // entry in the cookie list } /** * Look through the set of session cookies, and return the * one that matches the current endpoint address of this * Operation, if any. * * @param endpoint address of this Operation * @return the session cookie for this Operation's endpoint address, * or null if there is no session cookie */ private static synchronized String getSessionCookie(String endpoint) { if (cookies != null) { for (int i = 0; i < (cookies.length - 2); i += 2) { if (cookies[i] == null) { return null; } if (cookies[i].equals(endpoint)) { return cookies[i + 1]; } } } return null; } }