View Javadoc

1   /*
2    * Copyright 2006 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ws.wsdl.wsdl11.builder;
18  
19  import java.io.IOException;
20  import java.util.Iterator;
21  import java.util.List;
22  import javax.wsdl.Definition;
23  import javax.wsdl.Fault;
24  import javax.wsdl.Input;
25  import javax.wsdl.Message;
26  import javax.wsdl.Operation;
27  import javax.wsdl.OperationType;
28  import javax.wsdl.Output;
29  import javax.wsdl.Part;
30  import javax.wsdl.PortType;
31  import javax.wsdl.Service;
32  import javax.wsdl.Types;
33  import javax.wsdl.WSDLException;
34  import javax.wsdl.extensions.schema.Schema;
35  import javax.xml.namespace.QName;
36  import javax.xml.parsers.DocumentBuilder;
37  import javax.xml.parsers.DocumentBuilderFactory;
38  import javax.xml.parsers.ParserConfigurationException;
39  
40  import org.w3c.dom.Document;
41  import org.w3c.dom.Element;
42  import org.xml.sax.SAXException;
43  
44  import org.springframework.beans.factory.InitializingBean;
45  import org.springframework.core.io.Resource;
46  import org.springframework.util.Assert;
47  import org.springframework.util.StringUtils;
48  import org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition;
49  import org.springframework.xml.namespace.QNameUtils;
50  
51  /**
52   * Builds a <code>WsdlDefinition</code> with a SOAP 1.2 binding based on an XSD schema. This builder iterates over all
53   * <code>element</code>s found in the schema, and creates a <code>message</code> for those elements that end with the
54   * request or response suffix. It combines these messages into <code>operation</code>s, and builds a
55   * <code>portType</code> based on the operations.
56   * <p/>
57   * By default, the schema file is inlined in a <code>types</code> block. However, if the <code>schemaLocation</code>
58   * property is set, an XSD <code>import</code> is used instead. As such, the imported schema file can contain further
59   * imports, which will be resolved correctly in accordance with the schema location.
60   * <p/>
61   * To create messages from imported and included schemas, set the <code>followIncludeImport</code> property to
62   * <code>true</code>.
63   * <p/>
64   * Typically used within a {@link DynamicWsdl11Definition}, like so:
65   * <pre>
66   * &lt;bean id=&quot;airline&quot; class=&quot;org.springframework.ws.wsdl.wsdl11.DynamicWsdl11Definition&quot;&gt;
67   *   &lt;property name=&quot;builder&quot;&gt;
68   *     &lt;bean class=&quot;org.springframework.ws.wsdl.wsdl11.builder.XsdBasedSoap11Wsdl4jDefinitionBuilder&quot;&gt;
69   *     &lt;property name=&quot;schema&quot; value=&quot;/WEB-INF/airline.xsd&quot;/&gt;
70   *     &lt;property name=&quot;portTypeName&quot; value=&quot;Airline&quot;/&gt;
71   *     &lt;property name=&quot;locationUri&quot; value=&quot;http://localhost:8080/airline/services&quot;/&gt;
72   *     &lt;/bean&gt;
73   *   &lt;/property&gt;
74   * &lt;/bean&gt;
75   * </pre>
76   * <p/>
77   * Requires the <code>schema</code> and <code>portTypeName</code> properties to be set.
78   *
79   * @author Arjen Poutsma
80   * @author Alex Marshall
81   * @see #setSchema(org.springframework.core.io.Resource)
82   * @see #setPortTypeName(String)
83   * @see #setRequestSuffix(String)
84   * @see #setResponseSuffix(String)
85   * @since 1.5.0
86   * @deprecated as of Spring Web Services 1.5: superseded by {@link org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition}
87   *             and the {@link org.springframework.ws.wsdl.wsdl11.provider} package
88   */
89  public class XsdBasedSoap12Wsdl4jDefinitionBuilder extends AbstractSoap12Wsdl4jDefinitionBuilder
90          implements InitializingBean {
91  
92      /** The default suffix used to detect request elements in the schema. */
93      public static final String DEFAULT_REQUEST_SUFFIX = "Request";
94  
95      /** The default suffix used to detect response elements in the schema. */
96      public static final String DEFAULT_RESPONSE_SUFFIX = "Response";
97  
98      /** The default suffix used to detect fault elements in the schema. */
99      public static final String DEFAULT_FAULT_SUFFIX = "Fault";
100 
101     /** The default prefix used to register the schema namespace in the WSDL. */
102     public static final String DEFAULT_SCHEMA_PREFIX = "schema";
103 
104     /** The default prefix used to register the target namespace in the WSDL. */
105     public static final String DEFAULT_PREFIX = "tns";
106 
107     /** The suffix used to create a service name from a port type name. */
108     public static final String SERVICE_SUFFIX = "Service";
109 
110     private Resource schemaResource;
111 
112     private XsdSchemaHelper schemaHelper;
113 
114     private String schemaLocation;
115 
116     private String targetNamespace;
117 
118     private String portTypeName;
119 
120     private String schemaPrefix = DEFAULT_SCHEMA_PREFIX;
121 
122     private String prefix = DEFAULT_PREFIX;
123 
124     private String requestSuffix = DEFAULT_REQUEST_SUFFIX;
125 
126     private String responseSuffix = DEFAULT_RESPONSE_SUFFIX;
127 
128     private String faultSuffix = DEFAULT_FAULT_SUFFIX;
129 
130     private boolean followIncludeImport = false;
131 
132     /**
133      * Sets the suffix used to detect request elements in the schema.
134      *
135      * @see #DEFAULT_REQUEST_SUFFIX
136      */
137     public void setRequestSuffix(String requestSuffix) {
138         this.requestSuffix = requestSuffix;
139     }
140 
141     /**
142      * Sets the suffix used to detect response elements in the schema.
143      *
144      * @see #DEFAULT_RESPONSE_SUFFIX
145      */
146     public void setResponseSuffix(String responseSuffix) {
147         this.responseSuffix = responseSuffix;
148     }
149 
150     /**
151      * Sets the suffix used to detect fault elements in the schema.
152      *
153      * @see #DEFAULT_FAULT_SUFFIX
154      */
155     public void setFaultSuffix(String faultSuffix) {
156         this.faultSuffix = faultSuffix;
157     }
158 
159     /** Sets the port type name used for this definition. Required. */
160     public void setPortTypeName(String portTypeName) {
161         this.portTypeName = portTypeName;
162     }
163 
164     /** Sets the target namespace used for this definition. */
165     public void setTargetNamespace(String targetNamespace) {
166         this.targetNamespace = targetNamespace;
167     }
168 
169     /**
170      * Sets the prefix used to declare the schema target namespace.
171      *
172      * @see #DEFAULT_SCHEMA_PREFIX
173      */
174     public void setSchemaPrefix(String schemaPrefix) {
175         this.schemaPrefix = schemaPrefix;
176     }
177 
178     /**
179      * Sets the prefix used to declare the target namespace.
180      *
181      * @see #DEFAULT_PREFIX
182      */
183     public void setPrefix(String prefix) {
184         this.prefix = prefix;
185     }
186 
187     /** Sets the XSD schema to use for generating the WSDL. */
188     public void setSchema(Resource schemaResource) {
189         Assert.notNull(schemaResource, "'schema' must not be null");
190         Assert.isTrue(schemaResource.exists(), "schema \"" + schemaResource + "\" does not exit");
191         this.schemaResource = schemaResource;
192     }
193 
194     /**
195      * Sets the location of the schema to import. If this property is set, the <code>schema</code> element in the
196      * generated WSDL will only contain an <code>import</code>, referring to the value of this property.
197      */
198     public void setSchemaLocation(String schemaLocation) {
199         Assert.hasLength(schemaLocation, "'schemaLocation' must not be empty");
200         this.schemaLocation = schemaLocation;
201     }
202 
203     /**
204      * Indicates whether schema <code>&lt;xsd:include/&gt;</code> and <code>&lt;xsd:import/&gt;</code> should be
205      * followed. Default is <code>false</code>.
206      */
207     public void setFollowIncludeImport(boolean followIncludeImport) {
208         this.followIncludeImport = followIncludeImport;
209     }
210 
211     public final void afterPropertiesSet() throws IOException, ParserConfigurationException, SAXException {
212         Assert.notNull(schemaResource, "'schema' is required");
213         Assert.notNull(portTypeName, "'portTypeName' is required");
214         schemaHelper = new XsdSchemaHelper(schemaResource);
215         if (!StringUtils.hasLength(targetNamespace)) {
216             targetNamespace = schemaHelper.getTargetNamespace();
217         }
218     }
219 
220     /** Adds the target namespace and schema namespace to the definition. */
221     protected void populateDefinition(Definition definition) throws WSDLException {
222         super.populateDefinition(definition);
223         definition.setTargetNamespace(targetNamespace);
224         definition.addNamespace(schemaPrefix, schemaHelper.getTargetNamespace());
225         if (!targetNamespace.equals(schemaHelper.getTargetNamespace())) {
226             definition.addNamespace(prefix, targetNamespace);
227         }
228     }
229 
230     /** Does nothing. */
231     protected void buildImports(Definition definition) throws WSDLException {
232     }
233 
234     /**
235      * Creates a {@link Types} object containing a {@link Schema}. By default, the schema set by the <code>schema</code>
236      * property will be inlined into this <code>type</code>. If the <code>schemaLocation</code> is set, </code>object
237      * that is populated with the types found in the schema.
238      *
239      * @param definition the WSDL4J <code>Definition</code>
240      * @throws WSDLException in case of errors
241      */
242     protected void buildTypes(Definition definition) throws WSDLException {
243         Types types = definition.createTypes();
244         Schema schema = (Schema) createExtension(Types.class, XsdSchemaHelper.SCHEMA_NAME);
245         if (!StringUtils.hasLength(schemaLocation)) {
246             schema.setElement(schemaHelper.getSchemaElement());
247         }
248         else {
249             Document document;
250             try {
251                 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
252                 documentBuilderFactory.setNamespaceAware(true);
253                 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
254                 document = documentBuilder.newDocument();
255             }
256             catch (ParserConfigurationException ex) {
257                 throw new WSDLException(WSDLException.PARSER_ERROR, "Could not create DocumentBuilder", ex);
258             }
259             Element importingSchemaElement = document.createElementNS(XsdSchemaHelper.SCHEMA_NAME.getNamespaceURI(),
260                     QNameUtils.toQualifiedName(XsdSchemaHelper.SCHEMA_NAME));
261             schema.setElement(importingSchemaElement);
262             Element importElement = document.createElementNS(XsdSchemaHelper.IMPORT_NAME.getNamespaceURI(),
263                     QNameUtils.toQualifiedName(XsdSchemaHelper.IMPORT_NAME));
264             importingSchemaElement.appendChild(importElement);
265             importElement.setAttribute("namespace", schemaHelper.getTargetNamespace());
266             importElement.setAttribute("schemaLocation", schemaLocation);
267         }
268         types.addExtensibilityElement(schema);
269         definition.setTypes(types);
270     }
271 
272     /**
273      * Creates messages for each element found in the schema for which {@link #isRequestMessage(QName)}, {@link
274      * #isResponseMessage(QName)}, or {@link #isFaultMessage(QName)} is <code>true</code>.
275      *
276      * @param definition the WSDL4J <code>Definition</code>
277      * @throws WSDLException in case of errors
278      */
279     protected void buildMessages(Definition definition) throws WSDLException {
280         List elementDeclarations = schemaHelper.getElementDeclarations(followIncludeImport);
281         for (Iterator iterator = elementDeclarations.iterator(); iterator.hasNext();) {
282             QName elementName = (QName) iterator.next();
283             if (elementName != null &&
284                     (isRequestMessage(elementName) || isResponseMessage(elementName) || isFaultMessage(elementName))) {
285                 if (definition.getPrefix(elementName.getNamespaceURI()) == null) {
286                     int i = 0;
287                     while (true) {
288                         String prefix = schemaPrefix + Integer.toString(i);
289                         if (!StringUtils.hasLength(definition.getNamespace(prefix))) {
290                             definition.addNamespace(prefix, elementName.getNamespaceURI());
291                             break;
292                         }
293                         i++;
294                     }
295                 }
296                 Message message = definition.createMessage();
297                 populateMessage(message, elementName);
298                 Part part = definition.createPart();
299                 populatePart(part, elementName);
300                 message.addPart(part);
301                 message.setUndefined(false);
302                 definition.addMessage(message);
303             }
304         }
305     }
306 
307     /**
308      * Indicates whether the given name name should be included as request {@link Message} in the definition. Default
309      * implementation checks whether the local part ends with the request suffix.
310      *
311      * @param name the name of the element elligable for being a message
312      * @return <code>true</code> if to be included as message; <code>false</code> otherwise
313      * @see #setRequestSuffix(String)
314      */
315     protected boolean isRequestMessage(QName name) {
316         return name.getLocalPart().endsWith(requestSuffix);
317     }
318 
319     /**
320      * Indicates whether the given name should be included as {@link Message} in the definition. Default implementation
321      * checks whether the local part ends with the response suffix.
322      *
323      * @param name the name of the element elligable for being a message
324      * @return <code>true</code> if to be included as message; <code>false</code> otherwise
325      * @see #setResponseSuffix(String)
326      */
327     protected boolean isResponseMessage(QName name) {
328         return name.getLocalPart().endsWith(responseSuffix);
329     }
330 
331     /**
332      * Indicates whether the given name should be included as {@link Message} in the definition. Default implementation
333      * checks whether the local part ends with the fault suffix.
334      *
335      * @param name the name of the element elligable for being a message
336      * @return <code>true</code> if to be included as message; <code>false</code> otherwise
337      * @see #setFaultSuffix(String)
338      */
339     protected boolean isFaultMessage(QName name) {
340         return name.getLocalPart().endsWith(faultSuffix);
341     }
342 
343     /**
344      * Called after the {@link Message} has been created.
345      * <p/>
346      * Default implementation sets the name of the message to the element name.
347      *
348      * @param message     the WSDL4J <code>Message</code>
349      * @param elementName the element name
350      * @throws WSDLException in case of errors
351      */
352     protected void populateMessage(Message message, QName elementName) throws WSDLException {
353         message.setQName(new QName(targetNamespace, elementName.getLocalPart()));
354     }
355 
356     /**
357      * Called after the {@link Part} has been created.
358      * <p/>
359      * Default implementation sets the element name of the part.
360      *
361      * @param part        the WSDL4J <code>Part</code>
362      * @param elementName the elementName
363      * @throws WSDLException in case of errors
364      * @see Part#setElementName(javax.xml.namespace.QName)
365      */
366     protected void populatePart(Part part, QName elementName) throws WSDLException {
367         part.setElementName(elementName);
368         part.setName(elementName.getLocalPart());
369     }
370 
371     protected void buildPortTypes(Definition definition) throws WSDLException {
372         PortType portType = definition.createPortType();
373         populatePortType(portType);
374         createOperations(definition, portType);
375         portType.setUndefined(false);
376         definition.addPortType(portType);
377     }
378 
379     /**
380      * Called after the {@link PortType} has been created.
381      * <p/>
382      * Default implementation sets the name of the port type to the defined value.
383      *
384      * @param portType the WSDL4J <code>PortType</code>
385      * @throws WSDLException in case of errors
386      * @see #setPortTypeName(String)
387      */
388     protected void populatePortType(PortType portType) throws WSDLException {
389         portType.setQName(new QName(targetNamespace, portTypeName));
390     }
391 
392     private void createOperations(Definition definition, PortType portType) throws WSDLException {
393         for (Iterator messageIterator = definition.getMessages().values().iterator(); messageIterator.hasNext();) {
394             Message message = (Message) messageIterator.next();
395             for (Iterator partIterator = message.getParts().values().iterator(); partIterator.hasNext();) {
396                 Part part = (Part) partIterator.next();
397                 if (isRequestMessage(part.getElementName())) {
398                     Message requestMessage = message;
399                     Message responseMessage = definition.getMessage(getResponseMessageName(requestMessage.getQName()));
400                     Message faultMessage = definition.getMessage(getFaultMessageName(requestMessage.getQName()));
401                     Operation operation = definition.createOperation();
402                     populateOperation(operation, requestMessage, responseMessage);
403                     if (requestMessage != null) {
404                         Input input = definition.createInput();
405                         input.setMessage(requestMessage);
406                         input.setName(requestMessage.getQName().getLocalPart());
407                         operation.setInput(input);
408                     }
409                     if (responseMessage != null) {
410                         Output output = definition.createOutput();
411                         output.setMessage(responseMessage);
412                         output.setName(responseMessage.getQName().getLocalPart());
413                         operation.setOutput(output);
414                     }
415                     if (faultMessage != null) {
416                         Fault fault = definition.createFault();
417                         fault.setMessage(faultMessage);
418                         fault.setName(faultMessage.getQName().getLocalPart());
419                         operation.addFault(fault);
420                     }
421                     if (requestMessage != null && responseMessage != null) {
422                         operation.setStyle(OperationType.REQUEST_RESPONSE);
423                     }
424                     else if (requestMessage != null && responseMessage == null) {
425                         operation.setStyle(OperationType.ONE_WAY);
426                     }
427                     else if (requestMessage == null && responseMessage != null) {
428                         operation.setStyle(OperationType.NOTIFICATION);
429                     }
430                     operation.setUndefined(false);
431                     portType.addOperation(operation);
432                 }
433             }
434         }
435     }
436 
437     /**
438      * Given an request message name, return the corresponding response message name.
439      * <p/>
440      * Default implementation removes the request suffix, and appends the response suffix.
441      *
442      * @param requestMessageName the name of the request message
443      * @return the name of the corresponding response message, or null
444      */
445     protected QName getResponseMessageName(QName requestMessageName) {
446         String localPart = requestMessageName.getLocalPart();
447         if (localPart.endsWith(requestSuffix)) {
448             String prefix = localPart.substring(0, localPart.length() - requestSuffix.length());
449             return new QName(requestMessageName.getNamespaceURI(), prefix + responseSuffix);
450         }
451         else {
452             return null;
453         }
454     }
455 
456     /**
457      * Given an request message name, return the corresponding fault message name.
458      * <p/>
459      * Default implementation removes the request suffix, and appends the fault suffix.
460      *
461      * @param requestMessageName the name of the request message
462      * @return the name of the corresponding response message, or null
463      */
464     protected QName getFaultMessageName(QName requestMessageName) {
465         String localPart = requestMessageName.getLocalPart();
466         if (localPart.endsWith(requestSuffix)) {
467             String prefix = localPart.substring(0, localPart.length() - requestSuffix.length());
468             return new QName(requestMessageName.getNamespaceURI(), prefix + faultSuffix);
469         }
470         else {
471             return null;
472         }
473     }
474 
475     /**
476      * Called after the {@link Operation} has been created.
477      * <p/>
478      * Default implementation sets the name of the operation to name of the messages, without suffix.
479      *
480      * @param operation       the WSDL4J <code>Operation</code>
481      * @param requestMessage  the WSDL4J request <code>Message</code>
482      * @param responseMessage the WSDL4J response <code>Message</code>
483      * @throws WSDLException in case of errors
484      * @see #setPortTypeName(String)
485      */
486     protected void populateOperation(Operation operation, Message requestMessage, Message responseMessage)
487             throws WSDLException {
488         String localPart = requestMessage.getQName().getLocalPart();
489         String operationName = null;
490         if (localPart.endsWith(requestSuffix)) {
491             operationName = localPart.substring(0, localPart.length() - requestSuffix.length());
492         }
493         else {
494             localPart = responseMessage.getQName().getLocalPart();
495             if (localPart.endsWith(responseSuffix)) {
496                 operationName = localPart.substring(0, localPart.length() - responseSuffix.length());
497             }
498         }
499         operation.setName(operationName);
500     }
501 
502     /** Sets the name of the service to the name of the port type, with "Service" appended to it. */
503     protected void populateService(Service service) throws WSDLException {
504         service.setQName(new QName(targetNamespace, portTypeName + SERVICE_SUFFIX));
505     }
506 
507 }