/* * Copyright (c) 2007, Dennis M. Sosnoski. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of * JiBX nor the names of its contributors may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jibx.ws.wsdl; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.jibx.binding.generator.BindingGenerator; import org.jibx.binding.generator.ClassCustom; import org.jibx.binding.generator.CustomBase; import org.jibx.binding.generator.GlobalCustom; import org.jibx.binding.generator.BindingMappingDetail; import org.jibx.binding.generator.SchemaGenerator; import org.jibx.binding.generator.SchemaMappingDetail; import org.jibx.binding.model.BindingElement; import org.jibx.binding.model.BindingHolder; import org.jibx.binding.model.CollectionElement; import org.jibx.binding.model.DocumentFormatter; import org.jibx.binding.model.IClass; import org.jibx.binding.model.IClassLocator; import org.jibx.binding.model.MappingElement; import org.jibx.binding.model.ValidationContext; import org.jibx.runtime.BindingDirectory; import org.jibx.runtime.IBindingFactory; import org.jibx.runtime.IMarshallable; import org.jibx.runtime.IMarshallingContext; import org.jibx.runtime.JiBXException; import org.jibx.runtime.QName; import org.jibx.runtime.Utility; import org.jibx.schema.IComponent; import org.jibx.schema.SchemaHolder; import org.jibx.schema.elements.AnnotationElement; import org.jibx.schema.elements.ComplexTypeElement; import org.jibx.schema.elements.DocumentationElement; import org.jibx.schema.elements.ElementElement; import org.jibx.schema.elements.SchemaElement; import org.jibx.schema.elements.SequenceElement; import org.jibx.schema.types.Count; import org.jibx.util.InsertionOrderedSet; import org.jibx.util.Types; import org.w3c.dom.Node; /** * Start-from-code WSDL generator using JiBX data binding. This starts from one or more service classes, each with one * or more methods to be exposed as service operations, and generates complete bindings and WSDL for the services. * * @author Dennis M. Sosnoski */ public class Jibx2Wsdl { /** Parameter information for generation. */ private final WsdlGeneratorCommandLine m_generationParameters; /** Binding generator. */ private final BindingGenerator m_bindingGenerator; /** Schema generator. */ private final SchemaGenerator m_schemaGenerator; /** Map from schema namespace URIs to schema holders. */ private final Map m_uriSchemaMap; /** Map from fully qualified class name to schema type name. */ private Map m_classTypeMap; /** Document used for annotations (null if none). */ private final DocumentFormatter m_formatter; /** * Constructor. * * @param parms generation parameters */ private Jibx2Wsdl(WsdlGeneratorCommandLine parms) { m_generationParameters = parms; GlobalCustom global = parms.getGlobal(); m_bindingGenerator = new BindingGenerator(global); m_schemaGenerator = new SchemaGenerator(parms.getLocator(), global); m_uriSchemaMap = new HashMap(); m_formatter = new DocumentFormatter(); } /** * Get the qualified name used for an abstract mapping. This throws an exception if the qualified name is not found. * * @param type * @param mapping * @return qualified name */ private QName getMappingQName(String type, MappingElement mapping) { SchemaMappingDetail detail = m_schemaGenerator.getMappingDetail(mapping); if (detail == null) { throw new IllegalStateException("No mapping found for type " + type); } else if (detail.isType()) { return detail.getTypeName(); } else { throw new IllegalStateException("Need abstract mapping for type " + type); } } /** * Build an element representing a parameter or return value. * * @param parm * @param typemap map from parameterized type to abstract mapping name * @param hold containing schema holder * @return constructed element */ private ElementElement buildValueElement(ValueCustom parm, Map typemap, SchemaHolder hold) { // create the basic element definition ElementElement elem = new ElementElement(); if (!parm.isRequired()) { elem.setMinOccurs(Count.COUNT_ZERO); } String type = parm.getType(); if (type.endsWith("[]")) { elem.setMaxOccurs(Count.COUNT_UNBOUNDED); } // check type or reference for element boolean isref = false; String ptype = parm.getBoundType(); QName tname = (QName)typemap.get(ptype); if (tname == null) { tname = Types.schemaType(ptype); if (tname == null) { String usetype = ptype.endsWith(">") ? type : ptype; BindingMappingDetail detail = m_bindingGenerator.getMappingDetail(usetype); if (detail == null) { throw new IllegalStateException("No mapping found for type " + usetype); } else if (detail.isExtended()) { elem.setRef(detail.getElementQName()); isref = true; } else { MappingElement mapping = detail.getAbstractMapping(); tname = mapping.getTypeQName(); if (tname == null) { tname = getMappingQName(usetype, mapping); } } } } if (!isref) { // set element type and name m_schemaGenerator.setElementType(tname, elem, hold); String ename = parm.getElementName(); if (ename == null) { ename = tname.getName(); } elem.setName(ename); } // add documentation if available List nodes = parm.getDocumentation(); if (nodes != null) { AnnotationElement anno = new AnnotationElement(); DocumentationElement doc = new DocumentationElement(); for (Iterator iter = nodes.iterator(); iter.hasNext();) { Node node = (Node)iter.next(); doc.addContent(node); } anno.getItemsList().add(doc); elem.setAnnotation(anno); } return elem; } /** * Add reference defined by element to schema. This finds the namespace of the type or element reference used by the * provided element, and adds that namespace to the schema references. * * @param elem * @param holder */ private void addSchemaReference(ElementElement elem, SchemaHolder holder) { QName qname = elem.getType(); if (qname == null) { qname = elem.getRef(); } if (qname != null) { String rns = qname.getUri(); if (!Utility.safeEquals(holder.getNamespace(), rns) && !IComponent.SCHEMA_NAMESPACE.equals(rns)) { holder.addReference((SchemaHolder)m_uriSchemaMap.get(rns)); } } } /** * Build WSDL for service. * * @param service * @param typemap map from parameterized type to abstract mapping name * @return constructed WSDL definitions */ private Definitions buildWSDL(ServiceCustom service, Map typemap) { // initialize root object of definition String wns = service.getWsdlNamespace(); String sns = service.getNamespace(); String spfx = wns.equals(sns) ? "tns" : "sns"; Definitions def = new Definitions(service.getPortTypeName(), service.getBindingName(), service.getServiceName(), service.getPortName(), "tns", wns, spfx, sns); def.setServiceLocation(service.getServiceAddress()); // add service documentation if available IClassLocator locator = m_generationParameters.getLocator(); IClass info = locator.getClassInfo(service.getClassName()); if (info != null) { List nodes = m_formatter.docToNodes(info.getJavaDoc()); def.setPortTypeDocumentation(nodes); } // find or create the schema element and namespace SchemaHolder holder = (SchemaHolder)m_uriSchemaMap.get(sns); if (holder == null) { holder = new SchemaHolder(sns); } SchemaElement schema = holder.getSchema(); def.getSchemas().add(schema); // process messages and operations used by service ArrayList ops = service.getOperations(); Map fltmap = new HashMap(); for (int i = 0; i < ops.size(); i++) { // get information for operation OperationCustom odef = (OperationCustom)ops.get(i); String oname = odef.getOperationName(); Operation op = new Operation(oname); op.setDocumentation(odef.getDocumentation()); op.setSoapAction(odef.getSoapAction()); // generate input message information QName qname = new QName(sns, odef.getRequestWrapperName()); MessagePart part = new MessagePart("part", qname); Message msg = new Message(odef.getRequestMessageName(), part); op.addInputMessage(msg); def.addMessage(msg); // add corresponding schema definition to schema SequenceElement seq = new SequenceElement(); ArrayList parms = odef.getParameters(); for (int j = 0; j < parms.size(); j++) { ValueCustom parm = (ValueCustom)parms.get(j); ElementElement pelem = buildValueElement(parm, typemap, holder); seq.getParticleList().add(pelem); addSchemaReference(pelem, holder); } ComplexTypeElement tdef = new ComplexTypeElement(); tdef.setContentDefinition(seq); ElementElement elem = new ElementElement(); elem.setName(odef.getRequestWrapperName()); elem.setTypeDefinition(tdef); schema.getTopLevelChildren().add(elem); // generate output message information qname = new QName(sns, odef.getResponseWrapperName()); part = new MessagePart("part", qname); msg = new Message(odef.getResponseMessageName(), part); op.addOutputMessage(msg); def.addMessage(msg); // add corresponding schema definition to schema seq = new SequenceElement(); ValueCustom ret = odef.getReturn(); if (!"void".equals(ret.getType())) { ElementElement relem = buildValueElement(ret, typemap, holder); seq.getParticleList().add(relem); addSchemaReference(relem, holder); } tdef = new ComplexTypeElement(); tdef.setContentDefinition(seq); elem = new ElementElement(); elem.setName(odef.getResponseWrapperName()); elem.setTypeDefinition(tdef); schema.getTopLevelChildren().add(elem); // process fault message(s) for operation ArrayList thrws = odef.getThrows(); WsdlCustom wsdlcustom = m_generationParameters.getWsdlCustom(); for (int j = 0; j < thrws.size(); j++) { ThrowsCustom thrw = (ThrowsCustom)thrws.get(j); String type = thrw.getType(); msg = (Message)fltmap.get(type); if (msg == null) { // first time for this throwable, create the message FaultCustom fault = wsdlcustom.forceFaultCustomization(type); qname = new QName(sns, fault.getElementName()); part = new MessagePart("fault", qname); msg = new Message(fault.getFaultName(), part); def.addMessage(msg); // make sure the corresponding mapping exists BindingMappingDetail detail = m_bindingGenerator.getMappingDetail(fault.getDataType()); if (detail == null) { throw new IllegalStateException("No mapping found for type " + type); } // record that the fault has been defined fltmap.put(type, msg); } // add fault to operation definition op.addFaultMessage(msg); } // add operation to list of definitions def.addOperation(op); } holder.finish(); return def; } /** * Accumulate data type(s) from value to be included in binding. * * @param value * @param dataset set of types for binding */ private void accumulateData(ValueCustom value, Set dataset) { String type = value.getBoundType(); if (!dataset.contains(type) && !Types.isSimpleValue(type)) { String itype = value.getItemType(); if (itype == null) { dataset.add(type); } else { dataset.add(itype); } } } /** * Add the <mapping> definition for a typed collection to a binding. This always creates an abstract mapping with * the type name based on both the item type and the collection type. * * @param value collection value * @param typemap map from parameterized type to abstract mapping name * @param bind target binding * @return qualified name for collection */ public QName addCollectionBinding(ValueCustom value, Map typemap, BindingHolder bind) { // check for existing mapping String ptype = value.getBoundType(); QName qname = (QName)typemap.get(ptype); if (qname == null) { // create abstract mapping for collection class type MappingElement mapping = new MappingElement(); mapping.setClassName(value.getType()); mapping.setAbstract(true); mapping.setCreateType(value.getCreateType()); mapping.setFactoryName(value.getFactoryMethod()); // generate the mapping type name from item class name and suffix String suffix; String type = value.getType(); GlobalCustom global = m_generationParameters.getGlobal(); IClass clas = global.getClassInfo(type); if (clas.isImplements("Ljava/util/List;")) { suffix = "List"; } else if (clas.isImplements("Ljava/util/Set;")) { suffix = "Set"; } else { suffix = "Collection"; } String itype = value.getItemType(); ClassCustom cust = global.forceClassCustomization(itype); // register the type name for mapping String name = cust.getSimpleName() + suffix; qname = new QName(bind.getNamespace(), CustomBase.convertName(name, CustomBase.CAMEL_CASE_NAMES)); mapping.setTypeQName(qname); typemap.put(ptype, qname); // add collection definition details CollectionElement coll = new CollectionElement(); m_bindingGenerator.defineCollection(itype, value.getItemElementName(), coll, bind); mapping.addChild(coll); // add mapping to binding bind.addMapping(mapping); } return qname; } /** * Generate based on list of service classes. * * @param classes service class list * @param extras list of extra classes for binding * @return list of WSDLs * @throws JiBXException * @throws IOException */ private ArrayList generate(List classes, List extras) throws JiBXException, IOException { // add any service classes not already present in customizations WsdlCustom wsdlcustom = m_generationParameters.getWsdlCustom(); for (int i = 0; i < classes.size(); i++) { String sclas = (String)classes.get(i); if (wsdlcustom.getServiceCustomization(sclas) == null) { wsdlcustom.addServiceCustomization(sclas); } } // accumulate the data classes used by all service operations // TODO: throws class handling, with multiple services per WSDL InsertionOrderedSet abstrs = new InsertionOrderedSet(); InsertionOrderedSet concrs = new InsertionOrderedSet(); ArrayList qnames = new ArrayList(); List services = wsdlcustom.getServices(); String[] adduris = new String[services.size()]; int index = 0; for (Iterator iter = services.iterator(); iter.hasNext();) { ServiceCustom service = (ServiceCustom)iter.next(); adduris[index++] = service.getNamespace(); List ops = service.getOperations(); for (Iterator iter1 = ops.iterator(); iter1.hasNext();) { OperationCustom op = (OperationCustom)iter1.next(); List parms = op.getParameters(); for (Iterator iter2 = parms.iterator(); iter2.hasNext();) { accumulateData((ValueCustom)iter2.next(), abstrs); } accumulateData(op.getReturn(), abstrs); ArrayList thrws = op.getThrows(); for (int i = 0; i < thrws.size(); i++) { // add concrete mapping for data type, if used ThrowsCustom thrw = (ThrowsCustom)thrws.get(i); FaultCustom fault = wsdlcustom.forceFaultCustomization(thrw.getType()); if (!concrs.contains(fault.getDataType())) { concrs.add(fault.getDataType()); qnames.add(new QName(service.getNamespace(), fault.getElementName())); } } } } // include extra classes as needing concrete mappings GlobalCustom global = m_generationParameters.getGlobal(); for (int i = 0; i < extras.size(); i++) { String type = (String)extras.get(i); if (!concrs.contains(type)) { concrs.add(type); global.forceClassCustomization(type); qnames.add(null); } } // generate bindings for all data classes used m_bindingGenerator.generateSpecified(qnames, concrs.asList(), abstrs.asList()); // add binding definitions for collections passed or returned Map typemap = new HashMap(); for (Iterator iter = services.iterator(); iter.hasNext();) { ServiceCustom service = (ServiceCustom)iter.next(); List ops = service.getOperations(); BindingHolder hold = null; String uri = service.getNamespace(); for (Iterator iter1 = ops.iterator(); iter1.hasNext();) { OperationCustom op = (OperationCustom)iter1.next(); List parms = op.getParameters(); for (Iterator iter2 = parms.iterator(); iter2.hasNext();) { ValueCustom parm = (ValueCustom)iter2.next(); if (parm.getItemType() != null) { if (hold == null) { hold = m_bindingGenerator.findBinding(uri); } addCollectionBinding(parm, typemap, hold); } } ValueCustom ret = op.getReturn(); if (ret.getItemType() != null) { if (hold == null) { hold = m_bindingGenerator.findBinding(uri); } addCollectionBinding(ret, typemap, hold); } } } // write the binding(s), then validate from file (to get line numbers) String name = m_generationParameters.getBindingName(); File path = m_generationParameters.getGeneratePath(); BindingHolder rhold = m_bindingGenerator.finish(name, adduris, path); File file = new File(path, rhold.getFileName()); ValidationContext vctx = new ValidationContext(m_generationParameters.getLocator()); BindingElement binding = BindingElement.validateBinding(name, file.toURL(), new FileInputStream(file), vctx); if (binding == null) { return null; } else { // replace generated binding references with validated versions rhold.setBinding(binding); ArrayList uris = m_bindingGenerator.getNamespaces(); ArrayList holders = new ArrayList(); URL base = path.toURL(); for (int i = 0; i < uris.size(); i++) { String uri = (String)uris.get(i); BindingHolder hold = m_bindingGenerator.findBinding(uri); holders.add(hold); if (hold != rhold) { URL url = new URL(base, hold.getFileName()); if (binding.addIncludePath(url.toExternalForm())) { throw new IllegalStateException("Binding not found when read from file"); } else { hold.setBinding(binding.getExistingIncludeBinding(url)); } } } // build and record the schemas ArrayList schemas = m_schemaGenerator.generate(holders); // TODO: fix this for (int i = 0; i < schemas.size(); i++) { SchemaHolder holder = (SchemaHolder)schemas.get(i); m_uriSchemaMap.put(holder.getNamespace(), holder); } // build the WSDL for each service ArrayList wsdls = new ArrayList(); for (Iterator iter = services.iterator(); iter.hasNext();) { wsdls.add(buildWSDL((ServiceCustom)iter.next(), typemap)); } return wsdls; } } /** * Run the WSDL generation using command line parameters. * * @param args * @throws JiBXException * @throws IOException */ public static void main(String[] args) throws JiBXException, IOException { WsdlGeneratorCommandLine parms = new WsdlGeneratorCommandLine(); if (args.length > 0 && parms.processArgs(args)) { // generate services, bindings, and WSDLs Jibx2Wsdl inst = new Jibx2Wsdl(parms); ArrayList extras = new ArrayList(parms.getExtraTypes()); ArrayList classes = parms.getGlobal().getUnmarshalledClasses(); for (int i = 0; i < classes.size(); i++) { ClassCustom clas = (ClassCustom)classes.get(i); if (clas.isForceMapping()) { extras.add(clas.getName()); } } ArrayList wsdls = inst.generate(parms.getExtraArgs(), extras); if (wsdls != null) { // write the corresponding schemas IBindingFactory fact = BindingDirectory.getFactory(SchemaElement.class); for (Iterator iter = inst.m_uriSchemaMap.values().iterator(); iter.hasNext();) { SchemaHolder holder = (SchemaHolder)iter.next(); IMarshallingContext ictx = fact.createMarshallingContext(); File file = new File(parms.getGeneratePath(), holder.getFileName()); ictx.setOutput(new FileOutputStream(file), null); ictx.setIndent(2); ((IMarshallable)holder.getSchema()).marshal(ictx); ictx.getXmlWriter().flush(); } // output the generated WSDLs WsdlWriter writer = new WsdlWriter(); for (int i = 0; i < wsdls.size(); i++) { Definitions def = (Definitions)wsdls.get(i); File file = new File(parms.getGeneratePath(), def.getServiceName() + ".wsdl"); writer.writeWSDL(def, new FileOutputStream(file)); } } } else { if (args.length > 0) { System.err.println("Terminating due to command line errors"); } else { parms.printUsage(); } System.exit(1); } } }