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.1 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   * @see #setSchema(org.springframework.core.io.Resource)
81   * @see #setPortTypeName(String)
82   * @see #setRequestSuffix(String)
83   * @see #setResponseSuffix(String)
84   * @since 1.0.0
85   * @deprecated as of Spring Web Services 1.5: superseded by {@link org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition}
86   *             and the {@link org.springframework.ws.wsdl.wsdl11.provider} package
87   */
88  public class XsdBasedSoap11Wsdl4jDefinitionBuilder extends AbstractSoap11Wsdl4jDefinitionBuilder
89          implements InitializingBean {
90  
91      /** The default suffix used to detect request elements in the schema. */
92      public static final String DEFAULT_REQUEST_SUFFIX = "Request";
93  
94      /** The default suffix used to detect response elements in the schema. */
95      public static final String DEFAULT_RESPONSE_SUFFIX = "Response";
96  
97      /** The default suffix used to detect fault elements in the schema. */
98      public static final String DEFAULT_FAULT_SUFFIX = "Fault";
99  
100     /** The default prefix used to register the schema namespace in the WSDL. */
101     public static final String DEFAULT_SCHEMA_PREFIX = "schema";
102 
103     /** The default prefix used to register the target namespace in the WSDL. */
104     public static final String DEFAULT_PREFIX = "tns";
105 
106     /** The suffix used to create a service name from a port type name. */
107     public static final String SERVICE_SUFFIX = "Service";
108 
109     private Resource schemaResource;
110 
111     private XsdSchemaHelper schemaHelper;
112 
113     private String schemaLocation;
114 
115     private String targetNamespace;
116 
117     private String portTypeName;
118 
119     private String schemaPrefix = DEFAULT_SCHEMA_PREFIX;
120 
121     private String prefix = DEFAULT_PREFIX;
122 
123     private String requestSuffix = DEFAULT_REQUEST_SUFFIX;
124 
125     private String responseSuffix = DEFAULT_RESPONSE_SUFFIX;
126 
127     private String faultSuffix = DEFAULT_FAULT_SUFFIX;
128 
129     private boolean followIncludeImport = false;
130 
131     /**
132      * Sets the suffix used to detect request elements in the schema.
133      *
134      * @see #DEFAULT_REQUEST_SUFFIX
135      */
136     public void setRequestSuffix(String requestSuffix) {
137         this.requestSuffix = requestSuffix;
138     }
139 
140     /**
141      * Sets the suffix used to detect response elements in the schema.
142      *
143      * @see #DEFAULT_RESPONSE_SUFFIX
144      */
145     public void setResponseSuffix(String responseSuffix) {
146         this.responseSuffix = responseSuffix;
147     }
148 
149     /**
150      * Sets the suffix used to detect fault elements in the schema.
151      *
152      * @see #DEFAULT_FAULT_SUFFIX
153      */
154     public void setFaultSuffix(String faultSuffix) {
155         this.faultSuffix = faultSuffix;
156     }
157 
158     /** Sets the port type name used for this definition. Required. */
159     public void setPortTypeName(String portTypeName) {
160         this.portTypeName = portTypeName;
161     }
162 
163     /** Sets the target namespace used for this definition. */
164     public void setTargetNamespace(String targetNamespace) {
165         this.targetNamespace = targetNamespace;
166     }
167 
168     /**
169      * Sets the prefix used to declare the schema target namespace.
170      *
171      * @see #DEFAULT_SCHEMA_PREFIX
172      */
173     public void setSchemaPrefix(String schemaPrefix) {
174         this.schemaPrefix = schemaPrefix;
175     }
176 
177     /**
178      * Sets the prefix used to declare the target namespace.
179      *
180      * @see #DEFAULT_PREFIX
181      */
182     public void setPrefix(String prefix) {
183         this.prefix = prefix;
184     }
185 
186     /** Sets the XSD schema to use for generating the WSDL. */
187     public void setSchema(Resource schemaResource) {
188         Assert.notNull(schemaResource, "'schema' must not be null");
189         Assert.isTrue(schemaResource.exists(), "schema \"" + schemaResource + "\" does not exit");
190         this.schemaResource = schemaResource;
191     }
192 
193     /**
194      * Sets the location of the schema to import. If this property is set, the <code>schema</code> element in the
195      * generated WSDL will only contain an <code>import</code>, referring to the value of this property.
196      */
197     public void setSchemaLocation(String schemaLocation) {
198         Assert.hasLength(schemaLocation, "'schemaLocation' must not be empty");
199         this.schemaLocation = schemaLocation;
200     }
201 
202     /**
203      * Indicates whether schema <code>&lt;xsd:include/&gt;</code> and <code>&lt;xsd:import/&gt;</code> should be
204      * followed. Default is <code>false</code>.
205      */
206     public void setFollowIncludeImport(boolean followIncludeImport) {
207         this.followIncludeImport = followIncludeImport;
208     }
209 
210     public final void afterPropertiesSet() throws IOException, ParserConfigurationException, SAXException {
211         Assert.notNull(schemaResource, "'schema' is required");
212         Assert.notNull(portTypeName, "'portTypeName' is required");
213         schemaHelper = new XsdSchemaHelper(schemaResource);
214         if (!StringUtils.hasLength(targetNamespace)) {
215             targetNamespace = schemaHelper.getTargetNamespace();
216         }
217     }
218 
219     /** Adds the target namespace and schema namespace to the definition. */
220     protected void populateDefinition(Definition definition) throws WSDLException {
221         super.populateDefinition(definition);
222         definition.setTargetNamespace(targetNamespace);
223         definition.addNamespace(schemaPrefix, schemaHelper.getTargetNamespace());
224         if (!targetNamespace.equals(schemaHelper.getTargetNamespace())) {
225             definition.addNamespace(prefix, targetNamespace);
226         }
227     }
228 
229     /** Does nothing. */
230     protected void buildImports(Definition definition) throws WSDLException {
231     }
232 
233     /**
234      * Creates a {@link Types} object containing a {@link Schema}. By default, the schema set by the <code>schema</code>
235      * property will be inlined into this <code>type</code>. If the <code>schemaLocation</code> is set, </code>object
236      * that is populated with the types found in the schema.
237      *
238      * @param definition the WSDL4J <code>Definition</code>
239      * @throws WSDLException in case of errors
240      */
241     protected void buildTypes(Definition definition) throws WSDLException {
242         Types types = definition.createTypes();
243         Schema schema = (Schema) createExtension(Types.class, XsdSchemaHelper.SCHEMA_NAME);
244         if (!StringUtils.hasLength(schemaLocation)) {
245             schema.setElement(schemaHelper.getSchemaElement());
246         }
247         else {
248             Document document;
249             try {
250                 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
251                 documentBuilderFactory.setNamespaceAware(true);
252                 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
253                 document = documentBuilder.newDocument();
254             }
255             catch (ParserConfigurationException ex) {
256                 throw new WSDLException(WSDLException.PARSER_ERROR, "Could not create DocumentBuilder", ex);
257             }
258             Element importingSchemaElement = document.createElementNS(XsdSchemaHelper.SCHEMA_NAME.getNamespaceURI(),
259                     QNameUtils.toQualifiedName(XsdSchemaHelper.SCHEMA_NAME));
260             schema.setElement(importingSchemaElement);
261             Element importElement = document.createElementNS(XsdSchemaHelper.IMPORT_NAME.getNamespaceURI(),
262                     QNameUtils.toQualifiedName(XsdSchemaHelper.IMPORT_NAME));
263             importingSchemaElement.appendChild(importElement);
264             importElement.setAttribute("namespace", schemaHelper.getTargetNamespace());
265             importElement.setAttribute("schemaLocation", schemaLocation);
266         }
267         types.addExtensibilityElement(schema);
268         definition.setTypes(types);
269     }
270 
271     /**
272      * Creates messages for each element found in the schema for which {@link #isRequestMessage(QName)}, {@link
273      * #isResponseMessage(QName)}, or {@link #isFaultMessage(QName)} is <code>true</code>.
274      *
275      * @param definition the WSDL4J <code>Definition</code>
276      * @throws WSDLException in case of errors
277      */
278     protected void buildMessages(Definition definition) throws WSDLException {
279         List elementDeclarations = schemaHelper.getElementDeclarations(followIncludeImport);
280         for (Iterator iterator = elementDeclarations.iterator(); iterator.hasNext();) {
281             QName elementName = (QName) iterator.next();
282             if (elementName != null &&
283                     (isRequestMessage(elementName) || isResponseMessage(elementName) || isFaultMessage(elementName))) {
284                 if (definition.getPrefix(elementName.getNamespaceURI()) == null) {
285                     int i = 0;
286                     while (true) {
287                         String prefix = schemaPrefix + Integer.toString(i);
288                         if (!StringUtils.hasLength(definition.getNamespace(prefix))) {
289                             definition.addNamespace(prefix, elementName.getNamespaceURI());
290                             break;
291                         }
292                         i++;
293                     }
294                 }
295                 Message message = definition.createMessage();
296                 populateMessage(message, elementName);
297                 Part part = definition.createPart();
298                 populatePart(part, elementName);
299                 message.addPart(part);
300                 message.setUndefined(false);
301                 definition.addMessage(message);
302             }
303         }
304     }
305 
306     /**
307      * Indicates whether the given name name should be included as request {@link Message} in the definition. Default
308      * implementation checks whether the local part ends with the request suffix.
309      *
310      * @param name the name of the element elligable for being a message
311      * @return <code>true</code> if to be included as message; <code>false</code> otherwise
312      * @see #setRequestSuffix(String)
313      */
314     protected boolean isRequestMessage(QName name) {
315         return name.getLocalPart().endsWith(requestSuffix);
316     }
317 
318     /**
319      * Indicates whether the given name should be included as {@link Message} in the definition. Default implementation
320      * checks whether the local part ends with the response suffix.
321      *
322      * @param name the name of the element elligable for being a message
323      * @return <code>true</code> if to be included as message; <code>false</code> otherwise
324      * @see #setResponseSuffix(String)
325      */
326     protected boolean isResponseMessage(QName name) {
327         return name.getLocalPart().endsWith(responseSuffix);
328     }
329 
330     /**
331      * Indicates whether the given name should be included as {@link Message} in the definition. Default implementation
332      * checks whether the local part ends with the fault suffix.
333      *
334      * @param name the name of the element elligable for being a message
335      * @return <code>true</code> if to be included as message; <code>false</code> otherwise
336      * @see #setFaultSuffix(String)
337      */
338     protected boolean isFaultMessage(QName name) {
339         return name.getLocalPart().endsWith(faultSuffix);
340     }
341 
342     /**
343      * Called after the {@link Message} has been created.
344      * <p/>
345      * Default implementation sets the name of the message to the element name.
346      *
347      * @param message     the WSDL4J <code>Message</code>
348      * @param elementName the element name
349      * @throws WSDLException in case of errors
350      */
351     protected void populateMessage(Message message, QName elementName) throws WSDLException {
352         message.setQName(new QName(targetNamespace, elementName.getLocalPart()));
353     }
354 
355     /**
356      * Called after the {@link Part} has been created.
357      * <p/>
358      * Default implementation sets the element name of the part.
359      *
360      * @param part        the WSDL4J <code>Part</code>
361      * @param elementName the elementName
362      * @throws WSDLException in case of errors
363      * @see Part#setElementName(javax.xml.namespace.QName)
364      */
365     protected void populatePart(Part part, QName elementName) throws WSDLException {
366         part.setElementName(elementName);
367         part.setName(elementName.getLocalPart());
368     }
369 
370     protected void buildPortTypes(Definition definition) throws WSDLException {
371         PortType portType = definition.createPortType();
372         populatePortType(portType);
373         createOperations(definition, portType);
374         portType.setUndefined(false);
375         definition.addPortType(portType);
376     }
377 
378     /**
379      * Called after the {@link PortType} has been created.
380      * <p/>
381      * Default implementation sets the name of the port type to the defined value.
382      *
383      * @param portType the WSDL4J <code>PortType</code>
384      * @throws WSDLException in case of errors
385      * @see #setPortTypeName(String)
386      */
387     protected void populatePortType(PortType portType) throws WSDLException {
388         portType.setQName(new QName(targetNamespace, portTypeName));
389     }
390 
391     private void createOperations(Definition definition, PortType portType) throws WSDLException {
392         for (Iterator messageIterator = definition.getMessages().values().iterator(); messageIterator.hasNext();) {
393             Message message = (Message) messageIterator.next();
394             for (Iterator partIterator = message.getParts().values().iterator(); partIterator.hasNext();) {
395                 Part part = (Part) partIterator.next();
396                 if (isRequestMessage(part.getElementName())) {
397                     Message requestMessage = message;
398                     Message responseMessage = definition.getMessage(getResponseMessageName(requestMessage.getQName()));
399                     Message faultMessage = definition.getMessage(getFaultMessageName(requestMessage.getQName()));
400                     Operation operation = definition.createOperation();
401                     populateOperation(operation, requestMessage, responseMessage);
402                     if (requestMessage != null) {
403                         Input input = definition.createInput();
404                         input.setMessage(requestMessage);
405                         input.setName(requestMessage.getQName().getLocalPart());
406                         operation.setInput(input);
407                     }
408                     if (responseMessage != null) {
409                         Output output = definition.createOutput();
410                         output.setMessage(responseMessage);
411                         output.setName(responseMessage.getQName().getLocalPart());
412                         operation.setOutput(output);
413                     }
414                     if (faultMessage != null) {
415                         Fault fault = definition.createFault();
416                         fault.setMessage(faultMessage);
417                         fault.setName(faultMessage.getQName().getLocalPart());
418                         operation.addFault(fault);
419                     }
420                     if (requestMessage != null && responseMessage != null) {
421                         operation.setStyle(OperationType.REQUEST_RESPONSE);
422                     }
423                     else if (requestMessage != null && responseMessage == null) {
424                         operation.setStyle(OperationType.ONE_WAY);
425                     }
426                     else if (requestMessage == null && responseMessage != null) {
427                         operation.setStyle(OperationType.NOTIFICATION);
428                     }
429                     operation.setUndefined(false);
430                     portType.addOperation(operation);
431                 }
432             }
433         }
434     }
435 
436     /**
437      * Given an request message name, return the corresponding response message name.
438      * <p/>
439      * Default implementation removes the request suffix, and appends the response suffix.
440      *
441      * @param requestMessageName the name of the request message
442      * @return the name of the corresponding response message, or null
443      */
444     protected QName getResponseMessageName(QName requestMessageName) {
445         String localPart = requestMessageName.getLocalPart();
446         if (localPart.endsWith(requestSuffix)) {
447             String prefix = localPart.substring(0, localPart.length() - requestSuffix.length());
448             return new QName(requestMessageName.getNamespaceURI(), prefix + responseSuffix);
449         }
450         else {
451             return null;
452         }
453     }
454 
455     /**
456      * Given an request message name, return the corresponding fault message name.
457      * <p/>
458      * Default implementation removes the request suffix, and appends the fault suffix.
459      *
460      * @param requestMessageName the name of the request message
461      * @return the name of the corresponding response message, or null
462      */
463     protected QName getFaultMessageName(QName requestMessageName) {
464         String localPart = requestMessageName.getLocalPart();
465         if (localPart.endsWith(requestSuffix)) {
466             String prefix = localPart.substring(0, localPart.length() - requestSuffix.length());
467             return new QName(requestMessageName.getNamespaceURI(), prefix + faultSuffix);
468         }
469         else {
470             return null;
471         }
472     }
473 
474     /**
475      * Called after the {@link Operation} has been created.
476      * <p/>
477      * Default implementation sets the name of the operation to name of the messages, without suffix.
478      *
479      * @param operation       the WSDL4J <code>Operation</code>
480      * @param requestMessage  the WSDL4J request <code>Message</code>
481      * @param responseMessage the WSDL4J response <code>Message</code>
482      * @throws WSDLException in case of errors
483      * @see #setPortTypeName(String)
484      */
485     protected void populateOperation(Operation operation, Message requestMessage, Message responseMessage)
486             throws WSDLException {
487         String localPart = requestMessage.getQName().getLocalPart();
488         String operationName = null;
489         if (localPart.endsWith(requestSuffix)) {
490             operationName = localPart.substring(0, localPart.length() - requestSuffix.length());
491         }
492         else {
493             localPart = responseMessage.getQName().getLocalPart();
494             if (localPart.endsWith(responseSuffix)) {
495                 operationName = localPart.substring(0, localPart.length() - responseSuffix.length());
496             }
497         }
498         operation.setName(operationName);
499     }
500 
501     /** Sets the name of the service to the name of the port type, with "Service" appended to it. */
502     protected void populateService(Service service) throws WSDLException {
503         service.setQName(new QName(targetNamespace, portTypeName + SERVICE_SUFFIX));
504     }
505 }