Java tutorial
/* * Copyright (c) 2007-2010 The Regents of the University of California. * All rights reserved. * * '$Author: crawl $' * '$Date: 2013-01-17 14:55:09 -0800 (Thu, 17 Jan 2013) $' * '$Revision: 31346 $' * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the above * copyright notice and the following two paragraphs appear in all copies * of this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, * ENHANCEMENTS, OR MODIFICATIONS. * */ package org.sdm.spa; import java.io.IOException; import java.io.StringWriter; import java.rmi.RemoteException; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Vector; import javax.wsdl.BindingOperation; import javax.wsdl.Operation; import javax.wsdl.Port; import javax.wsdl.Service; import javax.wsdl.extensions.soap.SOAPAddress; import javax.wsdl.extensions.soap.SOAPBody; import javax.xml.namespace.QName; import javax.xml.rpc.Call; import javax.xml.rpc.ServiceException; import javax.xml.soap.SOAPException; import org.apache.axis.Constants; import org.apache.axis.constants.Use; import org.apache.axis.message.MessageElement; import org.apache.axis.message.SOAPBodyElement; import org.apache.axis.utils.XMLUtils; import org.apache.axis.wsdl.gen.Parser; import org.apache.axis.wsdl.symbolTable.BindingEntry; import org.apache.axis.wsdl.symbolTable.ElementDecl; import org.apache.axis.wsdl.symbolTable.Parameter; import org.apache.axis.wsdl.symbolTable.Parameters; import org.apache.axis.wsdl.symbolTable.ServiceEntry; import org.apache.axis.wsdl.symbolTable.SymTabEntry; import org.apache.axis.wsdl.symbolTable.TypeEntry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.kepler.icon.ComponentEntityConfig; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import ptolemy.actor.IOPort; import ptolemy.actor.IORelation; import ptolemy.actor.TypedAtomicActor; import ptolemy.actor.TypedCompositeActor; import ptolemy.actor.TypedIOPort; import ptolemy.actor.TypedIORelation; import ptolemy.actor.parameters.PortParameter; import ptolemy.data.BooleanToken; import ptolemy.data.StringToken; import ptolemy.data.XMLToken; import ptolemy.data.expr.StringParameter; import ptolemy.data.type.ArrayType; import ptolemy.data.type.BaseType; import ptolemy.data.type.Type; import ptolemy.domains.sdf.lib.ArrayToSequence; import ptolemy.domains.sdf.lib.SequenceToArray; import ptolemy.kernel.ComponentEntity; import ptolemy.kernel.CompositeEntity; import ptolemy.kernel.util.Attribute; import ptolemy.kernel.util.IllegalActionException; import ptolemy.kernel.util.Location; import ptolemy.kernel.util.NameDuplicationException; import ptolemy.kernel.util.NamedObj; import ptolemy.moml.MoMLChangeRequest; import ptolemy.util.XSLTUtilities; ////////////////////////////////////////////////////////////////////////// //// WSWithComplexTypes /** * <p> * * This actor executes SOAP web services defined by WSDLs. Given a web service's URL * of a WSDL and an operation name, this actor specializes its input and output * ports to reflect the input and output parameters of the operation. For simple * web service types, e.g. string, int, or double, this actor's ports are * configured to the matching ptolemy type. Otherwise, the ports are set to * XMLTOKEN. When this actor fires, it reads each input port, invokes the web * service operation and sends the input data, and outputs the response to the * output ports. * * </p> * <p> * * The <i>inputMechanism</i> and <i>outputMechanism</i> parameters control the * creation of helper actors for complex/nested web service types. (These * parameters have no effect for simple web service types). By setting either to * 'composite', a composite actor is created for each parameter that is * complex/nested. Each composite actor is populated with necessary XML * Assembler or XML Disassembler actors needed to build the nested web service * type, and the composite actor ports are all simple ptolemy types. Changing * the mechanism back to 'simple' <b>deletes</b> the connected helper actors. If * you have made changes to the composite actors and don't want them lost, * disconnect them from this actor before changing the mechanism to 'simple'. * * </p> * <p> * * <b>Limitations:</b> * <ul> * <li>Unused input ports on composite actors must have the corresponding * internal links to XML Assembler actors removed. This is because the XML * Assembler actors will read a token from each input port whose width is * greater than 0. * <li>The layout of the composite actors on the canvas is not perfect; * sometimes a composite actor is placed over an existing actor. * <li>If the input to the web service contains an array of a nested type, the * generated composite actors will use SequenceToArray with <i>arrayLength</i> * defaulting to 1. For longer arrays, you must manually create them and send to * the appropriate XML Assembler actor. * <li>Web service responses containing multi-reference values (elements * refering to other elements for their content) are not handled. * <li>If the WSDL doesn't fully define the operation response, then the * corresponding output port is set to XMLTOKEN. You can access the values by * using XML Disassembler actor(s). * <li>A web service parameter with the WSDL type "any" sets the corresponding * actor port type to XMLTOKEN. * <li>Multidimensional arrays not handled and the corresponding port is set to * XMLTOKEN. You can get or set the values by using XML Assembler or * Disassembler actor(s). * </ul> * * </p> * * TODO: handle commit vs cancel? multiple created array conversion actors not * moved vertically * * @author Daniel Crawl * @version $Id: WSWithComplexTypes.java 31346 2013-01-17 22:55:09Z crawl $ */ public class WSWithComplexTypes extends TypedAtomicActor { /** * Construct a WSWithComplexTypes source with the given container and name. * * @param name * The name of this actor. * @exception IllegalActionException * If the entity cannot be contained by the proposed * container. * @exception NameDuplicationException * If the container already has an actor with this name. */ public WSWithComplexTypes(CompositeEntity container, String name) throws NameDuplicationException, IllegalActionException { super(container, name); wsdl = new PortParameter(this, "wsdl"); wsdl.setStringMode(true); wsdl.setTypeEquals(BaseType.STRING); wsdl.getPort().setTypeEquals(BaseType.STRING); new Attribute(wsdl, "_showName"); method = new StringParameter(this, "method"); inputMechanism = new StringParameter(this, "inputMechanism"); inputMechanism.setExpression(_ioTypes[0]); outputMechanism = new StringParameter(this, "outputMechanism"); outputMechanism.setExpression(_ioTypes[0]); for (int i = 0; i < _ioTypes.length; i++) { inputMechanism.addChoice(_ioTypes[i]); outputMechanism.addChoice(_ioTypes[i]); } outputNil = new ptolemy.data.expr.Parameter(this, "outputNil", new BooleanToken(_outputNilVal)); outputNil.setTypeEquals(BaseType.BOOLEAN); outputNil.setExpression("false"); // params for the call username = new StringParameter(this, "username"); password = new StringParameter(this, "password"); timeout = new StringParameter(this, "timeout"); timeout.setExpression("" + 600000); ignoreInvokeErrors = new ptolemy.data.expr.Parameter(this, "ignoreInvokeErrors"); ignoreInvokeErrors.setTypeEquals(BaseType.BOOLEAN); ignoreInvokeErrors.setToken(BooleanToken.FALSE); hadError = new TypedIOPort(this, "hadError", false, true); hadError.setTypeEquals(BaseType.BOOLEAN); new Attribute(hadError, "_showName"); _helper = new XMLHelper(this); _helper.setOutputNil(false); _helper.setArraysWrapped(true); // comparators to sort wsdl parameters and elementdecl names _paramComp = new ParameterComparator(); _elDeclComp = new ElementDeclComparator(); _attachText("_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" " + "width=\"60\" height=\"20\" " + "style=\"fill:white\"/>\n" + "</svg>\n"); } // ///////////////////////////////////////////////////////////////// // // ports and parameters //// /** * The web service WSDL address. */ public PortParameter wsdl = null; /** * The web service method name to run. */ public StringParameter method = null; /** * Setting to composite creates XML assembler and disassembler actors for * complex (nested) parameters. */ public StringParameter inputMechanism = null; /** * Setting to composite creates XML assembler and disassembler actors for * complex (nested) parameters. */ public StringParameter outputMechanism = null; /** * If true, then each output port whose name is not a child element of the * incoming XML document outputs a nil token. (See XMLDisassembler). */ public ptolemy.data.expr.Parameter outputNil = null; /** * The user name for authentication. */ public StringParameter username = null; /** * The password for authentication. */ public StringParameter password = null; /** * The timeout in milliseconds used by transport sender. */ public StringParameter timeout = null; /** If true, will not throw exception if error occurs invoking method. */ public ptolemy.data.expr.Parameter ignoreInvokeErrors; /** Outputs true if error was ignored invoking method. */ public TypedIOPort hadError; // ///////////////////////////////////////////////////////////////// // // public methods //// /** * React to a change in an attribute. * * @param attribute * The changed parameter. * @exception IllegalActionException * If the parameter set is not valid. */ public void attributeChanged(Attribute attribute) throws IllegalActionException { try { if (attribute == wsdl) { String str = ((StringToken) wsdl.getToken()).stringValue(); // System.out.println("wsdl is now " + str); if (!str.equals(_wsdlStr)) { _wsdlStr = str; _initWSDL(); } } else if (attribute == method) { String str = ((StringToken) method.getToken()).stringValue(); //System.out.println(getName() + "method is now " + str); if (!str.equals(_methodStr)) { _methodStr = str; _initMethod(); } } else if (attribute == inputMechanism) { String str = ((StringToken) inputMechanism.getToken()).stringValue(); _inputIO = -1; for (int i = 0; i < _ioTypes.length; i++) { if (_ioTypes[i].equals(str)) { _inputIO = i; break; } } if (_inputIO == -1) { throw new IllegalActionException(this, "Invalid input mechanism: " + str); } _genInputPortsAndActors(); } else if (attribute == outputMechanism) { String str = ((StringToken) outputMechanism.getToken()).stringValue(); _outputIO = -1; for (int i = 0; i < _ioTypes.length; i++) { if (_ioTypes[i].equals(str)) { _outputIO = i; break; } } if (_outputIO == -1) { throw new IllegalActionException(this, "Invalid input mechanism: " + str); } _genOutputPortsAndActors(); } else if (attribute == outputNil) { _outputNilVal = ((BooleanToken) outputNil.getToken()).booleanValue(); _helper.setOutputNil(_outputNilVal); } } catch (NameDuplicationException e) { throw new IllegalActionException(this, "NameDuplicationException: " + e.getMessage()); } super.attributeChanged(attribute); } /** * Initialize the actor by getting the input and output parameters from the * soap service. */ public void preinitialize() throws IllegalActionException { super.preinitialize(); try { _initWSDL(); _initMethod(); } catch (NameDuplicationException e) { throw new IllegalActionException(this, "NameDuplicationException: " + e.getMessage()); } } /** * Create and send the request, and send the response to the appropriate * output ports. */ public void fire() throws IllegalActionException { super.fire(); wsdl.update(); //System.out.println(getName()); SOAPBodyElement sbe; try { sbe = _buildSOAPBodyRoot(); } catch (SOAPException e) { throw new IllegalActionException(this, e, "Error build SOAP request."); } List<?> response = null; boolean ignoringError = false; try { //System.out.println(getFullName() + " invoking"); response = _invokeMethod(sbe); //System.out.println(getFullName() + " done"); } catch (Exception e) { boolean ignoreInvokeErrorsVal = ((BooleanToken) ignoreInvokeErrors.getToken()).booleanValue(); if (!ignoreInvokeErrorsVal) { throw new IllegalActionException(this, e, "Error invoking service."); } else { System.out.println("WARNING: Ignoring invocation error: " + e.getMessage()); ignoringError = true; } } if (!ignoringError) { _parseResponse(response); hadError.broadcast(BooleanToken.FALSE); } else { hadError.broadcast(BooleanToken.TRUE); } } /** Returns false unless there are connected input ports. */ public boolean postfire() throws IllegalActionException { final List<?> inputs = inputPortList(); if (inputs.size() > 0) { for (Object object : inputs) { final TypedIOPort port = (TypedIOPort) object; if (port.numberOfSources() > 0) { return super.postfire(); } } } return false; } // ///////////////////////////////////////////////////////////////// // // private methods //// /** Initialize the WSDL Parser and find the SOAP endpoint. */ private void _initWSDL() throws IllegalActionException, NameDuplicationException { int i; // parse the WSDL try { _wsdlParser = new Parser(); _wsdlParser.run(_wsdlStr); } catch (Exception e) { throw new IllegalActionException(this, "Could not parse WSDL: " + e.getMessage()); } // now find the entry for the ServiceEntry class in the symbol table. _wsdlService = null; HashMap<?, ?> map = _wsdlParser.getSymbolTable().getHashMap(); Iterator<?> entrySetIter = map.entrySet().iterator(); while (entrySetIter.hasNext()) { Map.Entry<?, ?> currentEntry = (Map.Entry<?, ?>) entrySetIter.next(); Vector<?> valueVector = (Vector<?>) currentEntry.getValue(); for (i = 0; i < valueVector.size(); i++) { SymTabEntry symTabEntryObj = (SymTabEntry) valueVector.get(i); if ((ServiceEntry.class).isInstance(symTabEntryObj)) { _wsdlService = ((ServiceEntry) symTabEntryObj).getService(); break; } } if (_wsdlService != null) { break; } } if (_wsdlService == null) { throw new IllegalActionException(this, "Could not find service"); } // find a port with a SOAPAddress extensibility element. _wsdlPort = null; Map<?, ?> ports = _wsdlService.getPorts(); Iterator<?> nameIter = ports.keySet().iterator(); while (nameIter.hasNext()) { String portName = (String) nameIter.next(); Port port = (Port) ports.get(portName); List<?> extElemList = port.getExtensibilityElements(); for (i = 0; extElemList != null && i < extElemList.size(); i++) { Object extEl = extElemList.get(i); if (extEl instanceof SOAPAddress) { _wsdlPort = port; break; } } if (_wsdlPort != null) { break; } } if (_wsdlPort == null) { throw new IllegalActionException(this, "Could not find port"); } // now add the valid choices to the method parameter method.removeAllChoices(); BindingEntry be = _wsdlParser.getSymbolTable().getBindingEntry(_wsdlPort.getBinding().getQName()); Iterator<?> iter = be.getParameters().keySet().iterator(); while (iter.hasNext()) { Operation oper = (Operation) iter.next(); method.addChoice(oper.getName()); } } /** Get the SOAP parameters for a method. */ private void _initMethod() throws IllegalActionException, NameDuplicationException { _wsdlParams = null; if (_wsdlParser == null) { throw new IllegalActionException(this, "wsdl parser is null"); } else if (_wsdlPort == null) { throw new IllegalActionException(this, "wsdl port is null"); } BindingEntry be = _wsdlParser.getSymbolTable().getBindingEntry(_wsdlPort.getBinding().getQName()); Iterator<?> iter = be.getParameters().keySet().iterator(); while (iter.hasNext()) { Operation oper = (Operation) iter.next(); if (oper.getName().equals(_methodStr)) { _wsdlParams = (Parameters) be.getParameters().get(oper); break; } } if (_wsdlParams == null) { throw new IllegalActionException(this, "Could not get parameters for method " + _methodStr); } // determine the use _methodUse = null; iter = _wsdlPort.getBinding().getBindingOperations().iterator(); while (iter.hasNext()) { BindingOperation bo = (BindingOperation) iter.next(); if (bo.getName().equals(_methodStr)) { List<?> ext = null; if ((ext = bo.getBindingInput().getExtensibilityElements()) != null && ext.size() > 0) { SOAPBody body = (SOAPBody) ext.get(0); _methodUse = body.getUse(); } else if ((ext = bo.getBindingOutput().getExtensibilityElements()) != null && ext.size() > 0) { SOAPBody body = (SOAPBody) ext.get(0); _methodUse = body.getUse(); } break; } } if (_methodUse == null) { throw new IllegalActionException(this, "Could not find binding operation for " + _methodStr); } // create the mapping between namespaces and types _nsTypeMap = createNSMapping(_wsdlParams.list); // NOTE: we remove unused ports here for both input and // output before creating the new ones since the old // method could have had a input parameter with the same // name as an output parameter in the current method // (or vice-versa). _removeUnusedInputPortsAndActors(); _removeUnusedOutputPortsAndActors(); _genInputPortsAndActors(); _genOutputPortsAndActors(); } /** Create input ports and actors. */ private void _genInputPortsAndActors() throws IllegalActionException, NameDuplicationException { if (_wsdlParams != null) { _removeUnusedInputPortsAndActors(); _linkedPortsMap = new LinkMap(this, true); // create the input ports Object[] paramArray = _wsdlParams.list.toArray(); Arrays.sort(paramArray, _paramComp); for (int i = 0; i < paramArray.length; i++) { Parameter p = (Parameter) paramArray[i]; _createOnePort(this, null, p.getName(), p.getType(), true); } } } /** * Remove input ports and connected actors that don't match the input * parameters of the current web service operation. */ private void _removeUnusedInputPortsAndActors() throws IllegalActionException, NameDuplicationException { int i; HashMap<String, String> inputNamesMap = new HashMap<String, String>(); if (_wsdlParams != null) { Object[] paramArray = _wsdlParams.list.toArray(); for (i = 0; paramArray != null && i < paramArray.length; i++) { Parameter p = (Parameter) paramArray[i]; inputNamesMap.put(p.getName(), "used"); } } // remove input ports not used by this method Object[] inputArray = inputPortList().toArray(); for (i = 0; i < inputArray.length; i++) { IOPort p = (IOPort) inputArray[i]; if (p != wsdl.getPort() && inputNamesMap.get(p.getName()) == null) { _findLinkedComposite(p, true); p.setContainer(null); } } } /** Create output ports and actors. */ private void _genOutputPortsAndActors() throws IllegalActionException, NameDuplicationException { if (_wsdlParams != null) { _removeUnusedOutputPortsAndActors(); // create the output port if service returns data if (_wsdlParams.returnParam != null) { /* * System.out.println("return param name is " + * _wsdlParams.returnParam.getName()); */ _linkedPortsMap = new LinkMap(this, false); //try //{ _createOnePort(this, null, _wsdlParams.returnParam.getName(), _wsdlParams.returnParam.getType(), false); //} //catch(Throwable t) //{ // t.printStackTrace(); //} } } } /** * Remove output ports and connected actors that don't match the output * parameters of the current web service operation. */ private void _removeUnusedOutputPortsAndActors() throws IllegalActionException, NameDuplicationException { String outName = null; // see if there is a return parameter. if (_wsdlParams != null && _wsdlParams.returnParam != null) { outName = _wsdlParams.returnParam.getName(); } Object[] outputArray = outputPortList().toArray(); for (int i = 0; i < outputArray.length; i++) { IOPort p = (IOPort) outputArray[i]; // if there is no return parameter, or the // current output port name isn't the same, // remove the port and actor. if (p != hadError && (outName == null || !p.getName().equals(_outputNamePrepend + outName))) { _findLinkedComposite(p, true); _debugMessage("removing output port " + p.getName()); p.setContainer(null); } } } /** * Convert a type string from a WSDL into the closest ptolemy type. * * For XML data types see: * http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#built-in-datatypes * * @param typeStr * the type string * @param name * the parameter name * @return the ptolemy type */ private static Type _getTypeFromWSDLStr(String typeStr, String name) { Type retval = null; if (typeStr.equals("string")) { retval = BaseType.STRING; } else if (typeStr.equals("boolean")) { retval = BaseType.BOOLEAN; } else if (typeStr.equals("double") || typeStr.equals("float")) { retval = BaseType.DOUBLE; } else if (typeStr.equals("long") || typeStr.equals("unsignedLong") // according to the specification, these don't have // a max or min range so set them to be long instead // of int. maybe basetype.scalar would be more appropriate? || typeStr.equals("integer") || typeStr.equals("nonPositiveInteger") || typeStr.equals("negativeInteger") || typeStr.equals("nonNegativeInteger") || typeStr.equals("positiveInteger")) { retval = BaseType.LONG; } else if (typeStr.equals("int") || typeStr.equals("short") || typeStr.equals("byte") || typeStr.equals("unsignedInt") || typeStr.equals("unsignedShort") || typeStr.equals("unsignedByte")) { retval = BaseType.INT; } else if (typeStr.equals("unsignedByte")) { retval = BaseType.UNSIGNED_BYTE; } else { // throw new IllegalActionException(this, "Unknown type: " // + typeStr); _log.warn("WARNING: " + name + " has unknown WSDL type: " + typeStr); } return retval; } /** Convenience routine to get the input or output mechanism setting. */ private int _getIOSetting(boolean isInput) { if (isInput) { return _inputIO; } else { return _outputIO; } } /** * Create an input or output port for an actor, set its type, and * (optionally) recursively create and link to XML assembler or disassembler * helper actors. * * @param beginActor * the actor on which to create the port * @param myContainer * the (possibly null) container in which to link this port and * actors. * @param name * the name of the port * @param te * the WSDL TypeEntry * @param isInput * whether this is an input port * @return the number of newly created and linked actors to this port. */ private void _createOnePort(TypedAtomicActor beginActor, TypedCompositeActor myContainer, String name, TypeEntry te, boolean isInput) throws IllegalActionException, NameDuplicationException { int i; TypedIOPort port = null; String localName = _getNestedLeafName(name); boolean alreadyConnected = false; boolean isMultiArray = false; _debugMessage("createOnePort for " + beginActor.getName() + " is input " + isInput + " port name " + name); // check to see if the port already exists if (beginActor == this) { // NOTE: we prepend a "> " to the output port name to prevent a // NameDuplicationException if an input parameter has the // same name. if (!isInput) { localName = _outputNamePrepend + localName; } Object[] portArray = null; if (isInput) { portArray = inputPortList().toArray(); } else { portArray = outputPortList().toArray(); } for (i = 0; portArray != null && i < portArray.length; i++) { TypedIOPort myPort = (TypedIOPort) portArray[i]; // NOTE: if it's the output port, we need to prepend // outputNamePrepend. if (myPort.getName().equals(localName)) { port = myPort; alreadyConnected = _findLinkedComposite(port, false); break; } } } // create the port if we're not operating on the web service // actor, or we didn't find it. if (port == null) { port = _createPort(beginActor, localName, isInput, null); new Attribute(port, "_showName"); } String wsdlTypeStr = null; boolean isArray = false; Type type = null; if (te.getDimensions().equals("[]")) { TypeEntry newTe = null; // determine the underlying type of the array. // see if single dimension QName qname = null; if ((qname = te.getComponentType()) != null) { wsdlTypeStr = qname.getLocalPart(); newTe = _wsdlParser.getSymbolTable().getTypeEntry(qname, false); // if the type entry is null, get the reference type // (e.g. http://www.ncbi.nlm.nih.gov/entrez/eutils/soap/v2.0/eutils.wsdl, // run_eSearch, output type IdListType). if (newTe == null) { newTe = te.getRefType(); } _debugMessage(name + " is 1d array, wsdlTypeStr: " + wsdlTypeStr); } else { // multidimensional newTe = te.getRefType(); wsdlTypeStr = newTe.getQName().getLocalPart(); isMultiArray = true; _debugMessage(name + " is multi-dim array, wsdlTypeStr: " + wsdlTypeStr); } wsdlTypeStr = _getNestedLeafName(wsdlTypeStr); te = newTe; isArray = true; } else { wsdlTypeStr = te.getQName().getLocalPart(); } // see if it's a primitive type if (te.isSimpleType() || te.isBaseType()) { if (te.isSimpleType()) { ElementDecl sub = (ElementDecl) te.getContainedElements().get(0); wsdlTypeStr = sub.getType().getQName().getLocalPart(); } type = _getTypeFromWSDLStr(wsdlTypeStr, localName); // see if type was unknown if (type == null) { type = BaseType.XMLTOKEN; } _debugMessage("noncomplex port type " + te.getQName().getLocalPart()); if (isArray) { port.setTypeEquals(new ArrayType(type)); } else { port.setTypeEquals(type); } // see if we are creating a port inside a composite actor // for nested types. if (myContainer != null) { _addPortForComposite(myContainer, name, port, true, myContainer); } // set the port type in the moml _updateVergilType(beginActor, localName, port.getType()); } else // is a nested type { _debugMessage("has nested port type"); if (isArray) { //_debugMessage("create " + name + " -> " + typeStr); type = new ArrayType(BaseType.XMLTOKEN); } else { type = BaseType.XMLTOKEN; } port.setTypeEquals(type); _updateVergilType(beginActor, localName, type); if (!alreadyConnected && _getIOSetting(isInput) == IO_COMPOSITE) { if (beginActor == this) { CompositeEntity top = (CompositeEntity) getContainer(); String compNameStr = _genNestedName(getName(), name); _debugMessage("creating composite " + compNameStr); // create the composite actor myContainer = new TypedCompositeActor(top, compNameStr); _updateVergilLocation(beginActor, myContainer, isInput); // create the connection between the web service actor // and the composite actor port = _addPortForComposite(myContainer, name, port, false, top); } // bail out since we can't handle these if (isMultiArray) { _debugMessage("is multiArray"); // add the xml port to the composite actor /*_addPortForComposite(myContainer, name, port, true, myContainer); return; */ } _debugMessage("creating actor for port " + name); if (isArray) { // create a new relation from beginActor to convActor TypedIORelation convRel = new TypedIORelation(myContainer, myContainer.uniqueName(localName)); port.link(convRel); // create the conversion actor TypedAtomicActor convActor = null; if (isInput) { convActor = new SequenceToArray(myContainer, myContainer.uniqueName("SequenceToArray")); // link the conversion actor convActor.getPort("output").link(convRel); // shift the port to the input of the conversion actor port = (TypedIOPort) convActor.getPort("input"); } else { convActor = new ArrayToSequence(myContainer, myContainer.uniqueName("ArrayToSequence")); ((ArrayToSequence) convActor).enforceArrayLength.setToken(new BooleanToken(false)); // link the conversion actor convActor.getPort("input").link(convRel); // shift the port to the output of the conversion actor port = (TypedIOPort) convActor.getPort("output"); } _updateVergilLocation(beginActor, convActor, isInput); // shift the pointers down so that we operate on the // conversion actor. beginActor = convActor; type = BaseType.XMLTOKEN; localName = wsdlTypeStr; } TypedAtomicActor xmlActor = null; if (isInput) { _debugMessage("creating assembler for " + name); xmlActor = new XMLAssembler(myContainer, name); } else { _debugMessage("creating disassembler for " + name); XMLDisassembler dis = new XMLDisassembler(myContainer, name); // set the disassembler's output nil value based on ours. dis.outputNil.setToken(new BooleanToken(_outputNilVal)); dis.arraysWrapped.setToken(new BooleanToken(true)); xmlActor = dis; } _debugMessage( "createOnePort for " + xmlActor.getName() + " is input " + !isInput + " port name " + name); // remove the prepended output name. if (beginActor == this && !isInput) { localName = localName.substring(_outputNamePrepend.length()); } TypedIOPort xmlPort = _createPort(xmlActor, localName, !isInput, type); _updateVergilLocation(beginActor, xmlActor, isInput); _debugMessage(beginActor.getName() + " -> " + xmlActor.getName()); String relName = myContainer.uniqueName("rel " + xmlActor.getName()); //_debugMessage("creating relation " + relName); TypedIORelation relation = new TypedIORelation(myContainer, relName); // System.out.println("linking to " // + beginActor.getName() + " : " + port.getName()); // System.out.println("and to " // + xmlActor.getName() + " : " + xmlPort.getName()); port.link(relation); xmlPort.link(relation); _debugMessage("going to recurse down nested types for " + name); // recursive on nested types List<?> parts = te.getContainedElements(); // see if there are any parts if (parts == null) { // check for reference type te = te.getRefType(); if (te != null) { // get parts of reference type parts = te.getContainedElements(); } } if (parts != null) { Object[] partsArray = parts.toArray(); Arrays.sort(partsArray, _elDeclComp); _debugMessage("has " + partsArray.length + " contained elements"); for (i = 0; i < partsArray.length; i++) { ElementDecl decl = (ElementDecl) partsArray[i]; String nestedName = _genNestedName(name, decl.getQName().getLocalPart()); _debugMessage("nested name " + nestedName); _createOnePort(xmlActor, myContainer, nestedName, decl.getType(), isInput); } } else { _debugMessage("WARNING: no contained elements!"); _debugMessage(" name = " + name); _debugMessage(" type = " + te); } } } _debugMessage( "createOnePort end for " + beginActor.getName() + " is input " + isInput + " port name " + name); } /** * See if a port is connected to a helper composite actor. A match is found * if the actor is a TypedCompositeActor, and the name of the actor is a * nested name of the web service actor name plus the port name. If a match * is found, portWillBeDeleted is true, and the IO setting is simple, the * matching composite actor, and all its contents, is deleted. * * @param port * the port * @param portWillBeDeleted * if the port will soon be deleted * @return if a composite actor found */ private boolean _findLinkedComposite(IOPort port, boolean portWillBeDeleted) throws IllegalActionException, NameDuplicationException { boolean retval = false; TypedCompositeActor composite = null; String portName = port.getName(); if (port.isOutput() && portName.startsWith(_outputNamePrepend)) { portName = portName.substring(_outputNamePrepend.length()); } String nestedName = _genNestedName(getName(), portName); List<?> list = port.connectedPortList(); if (list != null) { Iterator<?> conPorts = list.iterator(); while (conPorts.hasNext()) { IOPort p = (IOPort) conPorts.next(); NamedObj owner = p.getContainer(); if (owner instanceof TypedCompositeActor && owner.getName().equals(nestedName)) { composite = (TypedCompositeActor) owner; retval = true; } } } // if the composite actor was found and the port will be // deleted soon or the input/output mechanism is simple, delete it. if (retval && (portWillBeDeleted || _getIOSetting(port.isInput()) == IO_SIMPLE)) { composite.removeAllRelations(); composite.removeAllEntities(); composite.removeAllPorts(); composite.setContainer(null); } // if the composite actor was not found and the i/o mechanism // is composite, unlink any relations if (!retval && _getIOSetting(port.isInput()) == IO_COMPOSITE && ((TypedIOPort) port).getType() == BaseType.XMLTOKEN) { List<?> relations = port.linkedRelationList(); for (Object object : relations) { if (object != null) { ((IORelation) object).setContainer(null); } } //port.unlinkAll(); } return retval; } /** * Create a port for a composite actor and link it to an existing port. The * parameter compEnt determines whether the link is internal or external. * * @param compActor * the composite actor * @param fullName * port name * @param port * the port to link to * @param sameDirection * if the new port is same direction as port * @param compEnt * the container in which to create the relation * @return the create port */ private TypedIOPort _addPortForComposite(TypedCompositeActor compActor, String fullName, TypedIOPort port, boolean sameDirection, CompositeEntity compEnt) throws IllegalActionException, NameDuplicationException { boolean isInput = port.isInput(); // see if we need to reverse direction // XXX is always opposite direction if external link, // so do we need sameDirection? if (!sameDirection) { isInput = !isInput; } String msg = "creating port " + fullName + " for composite '" + compActor.getName() + "' input: " + isInput; _debugMessage(msg); // create a port for the container String portName = _getNestedChildName(fullName); // see if port already exists with same name List<IOPort> portList = compActor.portList(); for (IOPort curPort : portList) { if (curPort.getName().equals(portName)) { portName = "_" + portName; break; } } TypedIOPort conPort = _createPort(compActor, portName, isInput, port.getType()); // show the name unless connected to web service actor if (compActor == compEnt) { new Attribute(conPort, "_showName"); } // link the inside port to the outside one TypedIORelation rel = new TypedIORelation(compEnt, compEnt.uniqueName(fullName)); port.link(rel); conPort.link(rel); _updateVergilLocation(port.getContainer(), conPort, isInput); return conPort; } /** * Convenience routine to create a port for an actor, set the direction, and * optionally the type. * * @param ent * the actor for which to create the port * @param name * the name of the port * @param isInput * if this is an input port * @param type * an optional type to set the port * @return the port */ private TypedIOPort _createPort(ComponentEntity ent, String name, boolean isInput, Type type) throws IllegalActionException, NameDuplicationException { _debugMessage("creating port " + name + " for " + ent.getFullName()); TypedIOPort retval = (TypedIOPort) ent.newPort(name); // set direction retval.setInput(isInput); retval.setOutput(!isInput); // if type exists, set and save in the moml if (type != null) { retval.setTypeEquals(type); _updateVergilType(ent, name, type); } return retval; } /** * Update vergil and the MoML with type information. * * @param context * @param portName * the name of the port * @param portTypeStr * the type */ private void _updateVergilType(NamedObj context, String portName, Type type) { // System.out.println("updating vergil for port " + portName); String str = "<group>" + "<port name =\"" + portName + "\">" + "<property name=\"_type\"" + "class = \"ptolemy.actor.TypeAttribute\" value = \"" + type.toString() + "\"/>" + "</port>" + "</group>"; MoMLChangeRequest request = new MoMLChangeRequest(this, context, str); request.setPersistent(true); requestChange(request); } /** * Change the location and draw the appropriate icon for NamedObj. * * @param old * the reference NamedObj * @param cur * the NamedObj being placed * @param toLeft * whether the NamedObj being placed is to the left of the * reference NamedObj. */ private void _updateVergilLocation(NamedObj old, NamedObj cur, boolean toLeft) throws IllegalActionException, NameDuplicationException { /* * System.out.println("setting location for " + cur.getName()); if(cur * instanceof TypedIOPort) System.out.println("is port"); else if(cur * instanceof TypedAtomicActor) System.out.println("is atomic actor"); * else if(cur instanceof TypedCompositeActor) * System.out.println("is comp actor"); else * System.out.println("ERROR: don't know type"); */ Location loc = (Location) old.getAttribute("_location"); double[] coords = loc.getLocation(); double[] curCoords = new double[coords.length]; if (toLeft) { curCoords[0] = coords[0] - X_INC; } else { curCoords[0] = coords[0] + X_INC; } int n = 0; // only get and update the next port number for old // if cur is a composite actor or old is not the web service // actor. i.e. do NOT update the port number when cur // is connected to the web service actor and is an atomic // actor or port. if ((cur instanceof TypedCompositeActor) || old != this) { n = _linkedPortsMap.getNextPortNumber(old, toLeft); } curCoords[1] = coords[1] + (n * Y_INC); // System.out.println("loc n for " + old.getName() + " is " + n); Location curLoc = new Location(cur, "_location"); curLoc.setLocation(curCoords); // add draw the icon for the actor. try { ComponentEntityConfig.addSVGIconTo(cur); } catch (IOException e) { throw new IllegalActionException(this, "IOException: " + e.getMessage()); } } /** * Build a SOAPBodyElement based on the WSDL and method by reading the data * from input ports. * * @return the SOAPBodyElement */ private SOAPBodyElement _buildSOAPBodyRoot() throws SOAPException, IllegalActionException { SOAPBodyElement retval = null; String targetNS = _wsdlParser.getSymbolTable().getDefinition().getTargetNamespace(); retval = new SOAPBodyElement(XMLUtils.StringToElement(targetNS, _methodStr, "")); // add all the namespace types to the body Iterator<String> iter = _nsTypeMap.keySet().iterator(); while (iter.hasNext()) { String ns = iter.next(); String nsType = _nsTypeMap.get(ns); if (!nsType.equals("xsd") && !nsType.equals("xsi")) { retval.addNamespaceDeclaration(nsType, ns); } } // add encoding style attribute if encoded // "soapenv:encodingStyle" -> // "http://schemas.xmlsoap.org/soap/encoding/" if (_methodUse.equals(Use.ENCODED_STR)) { retval.setAttribute(Constants.NS_PREFIX_SOAP_ENV + ":" + Constants.ATTR_ENCODING_STYLE, Constants.URI_SOAP11_ENC); } Object[] inputs = inputPortList().toArray(); // add an element for each of the method's input parameters for (int i = 0; i < _wsdlParams.list.size(); i++) { Parameter p = (Parameter) _wsdlParams.list.get(i); // find the input port with the same name TypedIOPort port = null; for (int j = 0; j < inputs.length; j++) { TypedIOPort tmp = (TypedIOPort) inputs[j]; if (tmp.getName().equals(p.getName())) { // System.out.println("found input port match " + // p.getName()); port = tmp; break; } } // make sure we found it if (port == null) { throw new IllegalActionException(this, "Missing input port " + p.getName()); } // if port is not connected, do not read from it. if (port.numberOfSources() == 0) { continue; } // consume a token from the input port and add it to the // SOAPBodyElement if (port.getType() == BaseType.XMLTOKEN) { XMLToken token = (XMLToken) port.get(0); Element root = token.getDomTree().getDocumentElement(); retval.addChildElement(_buildSOAPBody(root, p.getName(), p.getType(), p.isNillable(), p.getQName().getNamespaceURI())); } else { String value = null; if (port.getType() == BaseType.STRING) { value = ((StringToken) port.get(0)).stringValue(); } else { value = port.get(0).toString(); } SOAPBodyElement child = new SOAPBodyElement(XMLUtils.StringToElement("", p.getName(), "")); child.setValue(value); _addTypeOrNamespace(child, p.getQName().getNamespaceURI(), p.getType().getQName().getLocalPart()); retval.addChildElement(child); } } return retval; } /** * Depending on document style, either add the namespace or type attribute. * * @param el * the element to modify * @param namespace * the namespace * @param typeStr * the type string */ private void _addTypeOrNamespace(SOAPBodyElement el, String namespace, String typeStr) { if (_methodUse.equals(Use.LITERAL_STR)) { el.setNamespaceURI(namespace); } else if (_methodUse.equals(Use.ENCODED_STR)) { String nsForType = _nsTypeMap.get(namespace); if (nsForType != null) { el.setAttribute("xsi:type", nsForType + ":" + typeStr); } } } /** * Recursively create a SOAPBodyElement for a namespace based on an XML * Element. * * @param root * the XML Element containing the data * @param name * the name * @param te * the SOAP TypeEntry * @param nillable * whether it can be nil * @param parentNS * the parent XML namespace * @return the SOAPBodyElement */ private SOAPBodyElement _buildSOAPBody(Element root, String name, TypeEntry te, boolean nillable, String parentNS) throws SOAPException, IllegalActionException { SOAPBodyElement retval = new SOAPBodyElement(XMLUtils.StringToElement(parentNS, name, "")); if (root.hasAttribute("nil")) { // System.out.println("is nil"); if (!nillable) { throw new IllegalActionException(this, "Must supply " + name); } else { retval.setAttribute("xsi:nil", "true"); } } else if (!te.isSimpleType() && !te.isBaseType()) { // System.out.println("is complex"); Vector<?> parts = te.getContainedElements(); if (parts != null) { for (int i = 0; i < parts.size(); i++) { ElementDecl decl = (ElementDecl) parts.get(i); String declName = _getDeclName(decl); NodeList nl = root.getElementsByTagName(declName); if (nl.getLength() == 0) { System.out.println("WARNING: missing: " + name); } else { if (nl.getLength() > 1) { // xxx array? for (int j = 0; j < nl.getLength(); j++) { retval.addChildElement(_buildSOAPBody((Element) nl.item(j), declName, decl.getType(), decl.getNillable(), decl.getQName().getNamespaceURI())); } } else { // System.out.println("found " + nl.item(0)); retval.addChildElement(_buildSOAPBody((Element) nl.item(0), declName, decl.getType(), decl.getNillable(), decl.getQName().getNamespaceURI())); } } } } else if (te.isReferenced()) { TypeEntry newTe = te.getRefType(); return _buildSOAPBody(root, name, newTe, nillable, parentNS); } } else { retval.setValue(root.getChildNodes().item(0).getNodeValue()); _addTypeOrNamespace(retval, parentNS, te.getQName().getLocalPart()); } return retval; } /** * Create a mapping from namespaces to namespace numbers. * * @param inputs * @return the mapping */ private Map<String, String> createNSMapping(List<?> inputs) { Map<String, String> retval = new HashMap<String, String>(); int curNS = 2; // add the defaults retval.put("http://www.w3.org/2001/XMLSchema", "xsd"); retval.put("http://www.w3.org/2001/XMLSchema-instance", "xsi"); // add target name space retval.put(_wsdlParser.getSymbolTable().getDefinition().getTargetNamespace(), "ns1"); Iterator<?> iter = inputs.iterator(); while (iter.hasNext()) { Parameter p = (Parameter) iter.next(); TypeEntry te = p.getType(); curNS = addNSForTypeEntry(te, curNS, retval); } return retval; } /** * Recursively create namespace to namespace numbers mapping. * * @param te * the type * @param curNS * the last used namespace number * @param mapping * the mapping * @return the last used namespace number */ private int addNSForTypeEntry(TypeEntry te, int curNS, Map<String, String> mapping) { String ns = te.getQName().getNamespaceURI(); if (!mapping.containsKey(ns)) { curNS++; mapping.put(ns, "ns" + curNS); // System.out.println("adding " + ns + " = ns" + curNS); } List<?> parts = te.getContainedElements(); for (int i = 0; parts != null && i < parts.size(); i++) { ElementDecl ed = (ElementDecl) parts.get(i); curNS = addNSForTypeEntry(ed.getType(), curNS, mapping); } return curNS; } /** Convenience routine to get the name from an ElementDecl. */ private String _getDeclName(ElementDecl decl) { return _getNestedLeafName(decl.getQName().getLocalPart()); } /** * Convenience routine to get a leaf name from a fully nested name. e.g. * "foo>bar>blah" returns "blah". */ private String _getNestedLeafName(String name) { String retval = name; int index; if ((index = name.lastIndexOf(">")) != -1) { retval = name.substring(index + 1); } return retval; } /** * Convenience routine to get a nested child name from a fully nested name. * e.g. "foo>bar>blah" returns "bar>blah". */ private String _getNestedChildName(String name) { String retval = name; int index; if ((index = name.indexOf(">")) != -1) { retval = name.substring(index + 1); } return retval; } /** Convenience routine to generate a nested name. */ private String _genNestedName(String parent, String child) { return parent + ">" + _getNestedLeafName(child); } /** * Invoke the WSDL operation and return any results. * * @param sbe * a SOAPBodyElement containing data for input parameters * @return the result, if any */ private List<?> _invokeMethod(SOAPBodyElement sbe) throws IllegalActionException, ServiceException, RemoteException, SAXException { org.apache.axis.client.Service svcClient = new org.apache.axis.client.Service(_wsdlParser, _wsdlService.getQName()); Call call = svcClient.createCall(QName.valueOf(_wsdlPort.getName()), QName.valueOf(_methodStr)); // set username, password, timeout if not empty. String str = username.stringValue(); if (!str.equals("")) { call.setProperty(Call.USERNAME_PROPERTY, str); } str = password.stringValue(); if (!str.equals("")) { call.setProperty(Call.PASSWORD_PROPERTY, str); } str = timeout.stringValue(); if (!str.equals("")) { // we can do this cast since we used an axis service // to create the call ((org.apache.axis.client.Call) call).setTimeout(new Integer(str)); } if (_isDebugging || _debugging) { // attempt to print the request xml _debugMessage("Web Service REQUEST:"); StringWriter writer = new StringWriter(); XMLUtils.PrettyElementToWriter(sbe, writer); _debugMessage("\n" + writer.toString()); } // invoke the call and return the results return (List<?>) call.invoke(new Object[] { sbe }); } /** Send the response from the web service to the output ports. */ private void _parseResponse(List<?> response) throws IllegalActionException { if (response != null) { // is top level always $operation + "response" ? MessageElement msg = (MessageElement) response.get(0); Document doc = null; try { doc = msg.getAsDocument(); if (_isDebugging) { _debugMessage("response:"); _debugMessage(XSLTUtilities.toString(doc)); } } catch (Exception e) { throw new IllegalActionException(this, "Exception: " + e.getMessage()); } // remove hadError output port from set of ports to generate XML output for List<?> outPorts = new LinkedList<Object>(outputPortList()); outPorts.remove(hadError); _helper.splitOutXML(_methodStr + "Response", doc, outPorts, _outputNamePrepend); } } private void _debugMessage(String msg) { _log.debug(msg); _debug(msg); //System.out.println(msg); } /** * A Comparator class for org.apache.axis.wsdl.symbolTable.Parameter names. */ private class ParameterComparator implements Comparator { public ParameterComparator() { } public int compare(Object o1, Object o2) { int retval = 0; if (!(o1 instanceof Parameter)) { retval = -1; } else if (!(o2 instanceof Parameter)) { retval = 1; } else { Parameter p1 = (Parameter) o1; Parameter p2 = (Parameter) o2; retval = p1.getName().compareTo(p2.getName()); } return retval; } } /** * A Comparator class for org.apache.axis.wsdl.symbolTable.ElementDecl * names. */ private class ElementDeclComparator implements Comparator { public ElementDeclComparator() { } public int compare(Object o1, Object o2) { int retval = 0; if (!(o1 instanceof ElementDecl)) { retval = -1; } else if (!(o2 instanceof ElementDecl)) { retval = 1; } else { String e1 = _getDeclName((ElementDecl) o1); String e2 = _getDeclName((ElementDecl) o2); retval = e1.compareTo(e2); } return retval; } } /** * This class is used to aid in placing objects on the canvas. It keeps * track of how many connections we've made to an actor. */ private class LinkMap { /** * Count the number of connections our web service actor already * contains. */ public LinkMap(TypedAtomicActor actor, boolean forInput) { _map = new HashMap<NamedObj, Integer>(); Object[] portArray = null; if (forInput) { portArray = actor.inputPortList().toArray(); } else { portArray = actor.outputPortList().toArray(); } int total = -1; for (int i = 0; i < portArray.length; i++) { total += ((ptolemy.kernel.Port) portArray[i]).numLinks(); } _map.put(actor, new Integer(total)); // System.out.println("adding " + total + " for " + // actor.getName()); } /** * This is called when making another connection; get the number (if * any) and increment. */ public int getNextPortNumber(NamedObj no, boolean isInput) { int retval = 0; Integer val; if ((val = _map.get(no)) != null) { retval = val.intValue() + 1; } _map.put(no, new Integer(retval)); return retval; } private Map<NamedObj, Integer> _map; } // ///////////////////////////////////////////////////////////////// // // private members //// // the current web service WSDL URL private String _wsdlStr = ""; // the current web service operation name private String _methodStr = ""; private Parser _wsdlParser = null; private Service _wsdlService = null; private Port _wsdlPort = null; private Parameters _wsdlParams = null; private String _methodUse = null; private Map<String, String> _nsTypeMap = null; // whether to output nil values private boolean _outputNilVal = false; // used to split the response to corresponding ports private XMLHelper _helper = null; // comparators to sort wsdl parameters and elementdecl names private ParameterComparator _paramComp = null; private ElementDeclComparator _elDeclComp = null; // coordinate increments that control the spacing on the canvas // of newly created actors. private static final int X_INC = 150; private static final int Y_INC = 90; // i/o mechanism types private static final String[] _ioTypes = { "simple", "composite" }; private static final int IO_SIMPLE = 0; private static final int IO_COMPOSITE = 1; // the current i/o mechanism settings private int _inputIO = -1; private int _outputIO = -1; // string that is prepended to output port (if any) to avoid // having identically named input and output port (not allowed in ptolemy). private static final String _outputNamePrepend = "> "; private LinkMap _linkedPortsMap; private static final Log _log = LogFactory.getLog(WSWithComplexTypes.class.getName()); private static final boolean _isDebugging = _log.isDebugEnabled(); }