001: /*
002: * Copyright 2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.ws.wsdl.wsdl11.builder;
018:
019: import java.io.IOException;
020: import java.util.Iterator;
021: import javax.wsdl.Definition;
022: import javax.wsdl.Fault;
023: import javax.wsdl.Input;
024: import javax.wsdl.Message;
025: import javax.wsdl.Operation;
026: import javax.wsdl.OperationType;
027: import javax.wsdl.Output;
028: import javax.wsdl.Part;
029: import javax.wsdl.PortType;
030: import javax.wsdl.Service;
031: import javax.wsdl.Types;
032: import javax.wsdl.WSDLException;
033: import javax.wsdl.extensions.schema.Schema;
034: import javax.xml.namespace.QName;
035: import javax.xml.parsers.DocumentBuilder;
036: import javax.xml.parsers.DocumentBuilderFactory;
037: import javax.xml.parsers.ParserConfigurationException;
038:
039: import org.springframework.beans.factory.InitializingBean;
040: import org.springframework.core.io.Resource;
041: import org.springframework.util.Assert;
042: import org.springframework.util.StringUtils;
043: import org.springframework.xml.namespace.QNameUtils;
044: import org.springframework.xml.sax.SaxUtils;
045: import org.w3c.dom.Document;
046: import org.w3c.dom.Element;
047: import org.w3c.dom.NodeList;
048: import org.xml.sax.SAXException;
049:
050: /**
051: * Builds a <code>WsdlDefinition</code> with a SOAP 1.1 binding based on an XSD schema. This builder iterates over all
052: * <code>element</code>s found in the schema, and creates a <code>message</code> for those elements that end with the
053: * request or response suffix. It combines these messages into <code>operation</code>s, and builds a
054: * <code>portType</code> based on the operations. The schema itself is inlined in a <code>types</code> block.
055: * <p/>
056: * Requires the <code>schema</code> and <code>portTypeName</code> properties to be set.
057: *
058: * @author Arjen Poutsma
059: * @see #setSchema(org.springframework.core.io.Resource)
060: * @see #setPortTypeName(String)
061: * @see #setRequestSuffix(String)
062: * @see #setResponseSuffix(String)
063: * @since 1.0.0
064: */
065: public class XsdBasedSoap11Wsdl4jDefinitionBuilder extends
066: AbstractSoap11Wsdl4jDefinitionBuilder implements
067: InitializingBean {
068:
069: /** The schema namespace URI. */
070: private static final String SCHEMA_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema";
071:
072: /** The default suffix used to detect request elements in the schema. */
073: public static final String DEFAULT_REQUEST_SUFFIX = "Request";
074:
075: /** The default suffix used to detect response elements in the schema. */
076: public static final String DEFAULT_RESPONSE_SUFFIX = "Response";
077:
078: /** The default suffix used to detect fault elements in the schema. */
079: public static final String DEFAULT_FAULT_SUFFIX = "Fault";
080:
081: /** The default prefix used to register the schema namespace in the WSDL. */
082: public static final String DEFAULT_SCHEMA_PREFIX = "schema";
083:
084: /** The default prefix used to register the target namespace in the WSDL. */
085: public static final String DEFAULT_PREFIX = "tns";
086:
087: /** The suffix used to create a service name from a port type name. */
088: public static final String SERVICE_SUFFIX = "Service";
089:
090: private Resource schema;
091:
092: private Element schemaElement;
093:
094: private String targetNamespace;
095:
096: private String portTypeName;
097:
098: private String schemaPrefix = DEFAULT_SCHEMA_PREFIX;
099:
100: private String prefix = DEFAULT_PREFIX;
101:
102: private String requestSuffix = DEFAULT_REQUEST_SUFFIX;
103:
104: private String responseSuffix = DEFAULT_RESPONSE_SUFFIX;
105:
106: private String faultSuffix = DEFAULT_FAULT_SUFFIX;
107:
108: /**
109: * Sets the suffix used to detect request elements in the schema.
110: *
111: * @see #DEFAULT_REQUEST_SUFFIX
112: */
113: public void setRequestSuffix(String requestSuffix) {
114: this .requestSuffix = requestSuffix;
115: }
116:
117: /**
118: * Sets the suffix used to detect response elements in the schema.
119: *
120: * @see #DEFAULT_RESPONSE_SUFFIX
121: */
122: public void setResponseSuffix(String responseSuffix) {
123: this .responseSuffix = responseSuffix;
124: }
125:
126: /**
127: * Sets the suffix used to detect fault elements in the schema.
128: *
129: * @see #DEFAULT_FAULT_SUFFIX
130: */
131: public void setFaultSuffix(String faultSuffix) {
132: this .faultSuffix = faultSuffix;
133: }
134:
135: /** Sets the port type name used for this definition. Required. */
136: public void setPortTypeName(String portTypeName) {
137: this .portTypeName = portTypeName;
138: }
139:
140: /** Sets the target namespace used for this definition. */
141: public void setTargetNamespace(String targetNamespace) {
142: this .targetNamespace = targetNamespace;
143: }
144:
145: /**
146: * Sets the prefix used to declare the schema target namespace.
147: *
148: * @see #DEFAULT_SCHEMA_PREFIX
149: */
150: public void setSchemaPrefix(String schemaPrefix) {
151: this .schemaPrefix = schemaPrefix;
152: }
153:
154: /**
155: * Sets the prefix used to declare the target namespace.
156: *
157: * @see #DEFAULT_PREFIX
158: */
159: public void setPrefix(String prefix) {
160: this .prefix = prefix;
161: }
162:
163: /** Sets the XSD schema to use for generating the WSDL. */
164: public void setSchema(Resource schema) {
165: Assert.notNull(schema, "schema must not be empty or null");
166: Assert.isTrue(schema.exists(), "schema \"" + schema
167: + "\" does not exit");
168: this .schema = schema;
169: }
170:
171: public final void afterPropertiesSet() throws IOException,
172: ParserConfigurationException, SAXException {
173: Assert.notNull(schema, "schema is required");
174: Assert.notNull(portTypeName, "portTypeName is required");
175: parseSchema();
176: }
177:
178: private void parseSchema() throws ParserConfigurationException,
179: SAXException, IOException {
180: DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
181: .newInstance();
182: documentBuilderFactory.setNamespaceAware(true);
183: DocumentBuilder documentBuilder = documentBuilderFactory
184: .newDocumentBuilder();
185: Document schemaDocument = documentBuilder.parse(SaxUtils
186: .createInputSource(schema));
187: schemaElement = schemaDocument.getDocumentElement();
188: Assert.isTrue("schema".equals(schemaElement.getLocalName()),
189: "schema document root element has invalid local name : ["
190: + schemaElement.getLocalName()
191: + "] instead of [schema]");
192: Assert
193: .isTrue(SCHEMA_NAMESPACE_URI.equals(schemaElement
194: .getNamespaceURI()),
195: "schema document root element has invalid namespace uri: ["
196: + schemaElement.getNamespaceURI()
197: + "] instead of ["
198: + SCHEMA_NAMESPACE_URI + "]");
199: String schemaTargetNamespace = getSchemaTargetNamespace();
200: Assert.hasLength(schemaTargetNamespace,
201: "schema document has no targetNamespace");
202: if (!StringUtils.hasLength(targetNamespace)) {
203: targetNamespace = schemaTargetNamespace;
204: }
205: }
206:
207: private String getSchemaTargetNamespace() {
208: return schemaElement.getAttribute("targetNamespace");
209: }
210:
211: /** Adds the target namespace and schema namespace to the definition. */
212: protected void populateDefinition(Definition definition)
213: throws WSDLException {
214: super .populateDefinition(definition);
215: definition.setTargetNamespace(targetNamespace);
216: definition.addNamespace(schemaPrefix,
217: getSchemaTargetNamespace());
218: if (!targetNamespace.equals(getSchemaTargetNamespace())) {
219: definition.addNamespace(prefix, targetNamespace);
220: }
221: }
222:
223: /** Does nothing. */
224: protected void buildImports(Definition definition)
225: throws WSDLException {
226: }
227:
228: /**
229: * Creates a <code>Types</code> object that is populated with the types found in the schema.
230: *
231: * @param definition the WSDL4J <code>Definition</code>
232: * @throws WSDLException in case of errors
233: */
234: protected void buildTypes(Definition definition)
235: throws WSDLException {
236: Types types = definition.createTypes();
237: Schema schema = (Schema) createExtension(Types.class,
238: QNameUtils.getQNameForNode(schemaElement));
239: schema.setElement(schemaElement);
240: types.addExtensibilityElement(schema);
241: definition.setTypes(types);
242: }
243:
244: /**
245: * Creates messages for each element found in the schema for which <code>isRequestMessage()</code>,
246: * <code>isResponseMessage()</code>, or <code>isFaultMessage()</code> is <code>true</code>.
247: *
248: * @param definition the WSDL4J <code>Definition</code>
249: * @throws WSDLException in case of errors
250: * @see #isRequestMessage(javax.xml.namespace.QName)
251: * @see #isResponseMessage(javax.xml.namespace.QName)
252: * @see #isFaultMessage(javax.xml.namespace.QName)
253: */
254: protected void buildMessages(Definition definition)
255: throws WSDLException {
256: NodeList elements = schemaElement.getElementsByTagNameNS(
257: SCHEMA_NAMESPACE_URI, "element");
258: for (int i = 0; i < elements.getLength(); i++) {
259: Element element = (Element) elements.item(i);
260: QName elementName = getSchemaElementName(element);
261: if (elementName != null
262: && (isRequestMessage(elementName)
263: || isResponseMessage(elementName) || isFaultMessage(elementName))) {
264: Message message = definition.createMessage();
265: populateMessage(message, element);
266: Part part = definition.createPart();
267: populatePart(part, elementName);
268: message.addPart(part);
269: message.setUndefined(false);
270: definition.addMessage(message);
271: }
272: }
273: }
274:
275: /**
276: * Indicates whether the given name name should be included as request <code>Message</code> in the definition.
277: * Default implementation checks whether the local part ends with the request suffix.
278: *
279: * @param name the name of the element elligable for being a message
280: * @return <code>true</code> if to be included as message; <code>false</code> otherwise
281: * @see #setRequestSuffix(String)
282: */
283: protected boolean isRequestMessage(QName name) {
284: return name.getLocalPart().endsWith(requestSuffix);
285: }
286:
287: /**
288: * Indicates whether the given name should be included as <code>Message</code> in the definition. Default
289: * implementation checks whether the local part ends with the response suffix.
290: *
291: * @param name the name of the element elligable for being a message
292: * @return <code>true</code> if to be included as message; <code>false</code> otherwise
293: * @see #setResponseSuffix(String)
294: */
295: protected boolean isResponseMessage(QName name) {
296: return name.getLocalPart().endsWith(responseSuffix);
297: }
298:
299: /**
300: * Indicates whether the given name should be included as <code>Message</code> in the definition. Default
301: * implementation checks whether the local part ends with the fault suffix.
302: *
303: * @param name the name of the element elligable for being a message
304: * @return <code>true</code> if to be included as message; <code>false</code> otherwise
305: * @see #setFaultSuffix(String)
306: */
307: protected boolean isFaultMessage(QName name) {
308: return name.getLocalPart().endsWith(faultSuffix);
309: }
310:
311: /**
312: * Called after the <code>Message</code> has been created.
313: * <p/>
314: * Default implementation sets the name of the message to the element name.
315: *
316: * @param message the WSDL4J <code>Message</code>
317: * @param element the element
318: * @throws WSDLException in case of errors
319: */
320: protected void populateMessage(Message message, Element element) {
321: message.setQName(new QName(targetNamespace, element
322: .getAttribute("name")));
323: }
324:
325: /**
326: * Called after the <code>Part</code> has been created.
327: * <p/>
328: * Default implementation sets the element name of the part.
329: *
330: * @param part the WSDL4J <code>Part</code>
331: * @param elementName the elementName
332: * @throws WSDLException in case of errors
333: * @see Part#setElementName(javax.xml.namespace.QName)
334: */
335: protected void populatePart(Part part, QName elementName) {
336: part.setElementName(elementName);
337: part.setName(elementName.getLocalPart());
338: }
339:
340: protected void buildPortTypes(Definition definition)
341: throws WSDLException {
342: PortType portType = definition.createPortType();
343: populatePortType(portType);
344: createOperations(definition, portType);
345: portType.setUndefined(false);
346: definition.addPortType(portType);
347: }
348:
349: /**
350: * Called after the <code>PortType</code> has been created.
351: * <p/>
352: * Default implementation sets the name of the port type to the defined value.
353: *
354: * @param portType the WSDL4J <code>PortType</code>
355: * @throws WSDLException in case of errors
356: * @see #setPortTypeName(String)
357: */
358: protected void populatePortType(PortType portType)
359: throws WSDLException {
360: portType.setQName(new QName(targetNamespace, portTypeName));
361: }
362:
363: /** @noinspection UnnecessaryLocalVariable */
364: private void createOperations(Definition definition,
365: PortType portType) throws WSDLException {
366: for (Iterator messageIterator = definition.getMessages()
367: .values().iterator(); messageIterator.hasNext();) {
368: Message message = (Message) messageIterator.next();
369: for (Iterator partIterator = message.getParts().values()
370: .iterator(); partIterator.hasNext();) {
371: Part part = (Part) partIterator.next();
372: if (isRequestMessage(part.getElementName())) {
373: Message requestMessage = message;
374: Message responseMessage = definition
375: .getMessage(getResponseMessageName(requestMessage
376: .getQName()));
377: Message faultMessage = definition
378: .getMessage(getFaultMessageName(requestMessage
379: .getQName()));
380: Operation operation = definition.createOperation();
381: populateOperation(operation, requestMessage,
382: responseMessage);
383: if (requestMessage != null) {
384: Input input = definition.createInput();
385: input.setMessage(requestMessage);
386: input.setName(requestMessage.getQName()
387: .getLocalPart());
388: operation.setInput(input);
389: }
390: if (responseMessage != null) {
391: Output output = definition.createOutput();
392: output.setMessage(responseMessage);
393: output.setName(responseMessage.getQName()
394: .getLocalPart());
395: operation.setOutput(output);
396: }
397: if (faultMessage != null) {
398: Fault fault = definition.createFault();
399: fault.setMessage(faultMessage);
400: fault.setName(faultMessage.getQName()
401: .getLocalPart());
402: operation.addFault(fault);
403: }
404: if (requestMessage != null
405: && responseMessage != null) {
406: operation
407: .setStyle(OperationType.REQUEST_RESPONSE);
408: } else if (requestMessage != null
409: && responseMessage == null) {
410: operation.setStyle(OperationType.ONE_WAY);
411: } else if (requestMessage == null
412: && responseMessage != null) {
413: operation.setStyle(OperationType.NOTIFICATION);
414: }
415: operation.setUndefined(false);
416: portType.addOperation(operation);
417: }
418: }
419: }
420: }
421:
422: /**
423: * Given an request message name, return the corresponding response message name.
424: * <p/>
425: * Default implementation removes the request suffix, and appends the response suffix.
426: *
427: * @param requestMessageName the name of the request message
428: * @return the name of the corresponding response message, or null
429: */
430: protected QName getResponseMessageName(QName requestMessageName) {
431: String localPart = requestMessageName.getLocalPart();
432: if (localPart.endsWith(requestSuffix)) {
433: String prefix = localPart.substring(0, localPart.length()
434: - requestSuffix.length());
435: return new QName(requestMessageName.getNamespaceURI(),
436: prefix + responseSuffix);
437: } else {
438: return null;
439: }
440: }
441:
442: /**
443: * Given an request message name, return the corresponding fault message name.
444: * <p/>
445: * Default implementation removes the request suffix, and appends the fault suffix.
446: *
447: * @param requestMessageName the name of the request message
448: * @return the name of the corresponding response message, or null
449: */
450: protected QName getFaultMessageName(QName requestMessageName) {
451: String localPart = requestMessageName.getLocalPart();
452: if (localPart.endsWith(requestSuffix)) {
453: String prefix = localPart.substring(0, localPart.length()
454: - requestSuffix.length());
455: return new QName(requestMessageName.getNamespaceURI(),
456: prefix + faultSuffix);
457: } else {
458: return null;
459: }
460: }
461:
462: /**
463: * Called after the <code>Operation</code> has been created.
464: * <p/>
465: * Default implementation sets the name of the operation to name of the messages, without suffix.
466: *
467: * @param operation the WSDL4J <code>Operation</code>
468: * @param requestMessage the WSDL4J request <code>Message</code>
469: * @param responseMessage the WSDL4J response <code>Message</code>
470: * @throws WSDLException in case of errors
471: * @see #setPortTypeName(String)
472: */
473: protected void populateOperation(Operation operation,
474: Message requestMessage, Message responseMessage)
475: throws WSDLException {
476: String localPart = requestMessage.getQName().getLocalPart();
477: String operationName = null;
478: if (localPart.endsWith(requestSuffix)) {
479: operationName = localPart.substring(0, localPart.length()
480: - requestSuffix.length());
481: } else {
482: localPart = responseMessage.getQName().getLocalPart();
483: if (localPart.endsWith(responseSuffix)) {
484: operationName = localPart.substring(0, localPart
485: .length()
486: - responseSuffix.length());
487: operationName = localPart;
488: }
489: }
490: operation.setName(operationName);
491: }
492:
493: /** Sets the name of the service to the name of the port type, with "Service" appended to it. */
494: protected void populateService(Service service)
495: throws WSDLException {
496: service.setQName(new QName(targetNamespace, portTypeName
497: + SERVICE_SUFFIX));
498: }
499:
500: /**
501: * Returns the qualified name of the element. This is a combination of schema target namespace and the value of the
502: * "name" attribute value.
503: *
504: * @param element an element
505: * @return the value of the name attribute
506: */
507: private QName getSchemaElementName(Element element) {
508: String attributeValue = element.getAttribute("name");
509: if (StringUtils.hasLength(attributeValue)) {
510: return new QName(getSchemaTargetNamespace(), attributeValue);
511: } else {
512: return null;
513: }
514: }
515: }
|