View Javadoc

1   /*
2    * Copyright 2008 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.xml.xsd.commons;
18  
19  import java.io.IOException;
20  import java.util.ArrayList;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Set;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.ws.commons.schema.ValidationEventHandler;
28  import org.apache.ws.commons.schema.XmlSchema;
29  import org.apache.ws.commons.schema.XmlSchemaCollection;
30  import org.apache.ws.commons.schema.XmlSchemaExternal;
31  import org.apache.ws.commons.schema.XmlSchemaImport;
32  import org.apache.ws.commons.schema.XmlSchemaInclude;
33  import org.apache.ws.commons.schema.XmlSchemaObject;
34  import org.apache.ws.commons.schema.XmlSchemaObjectCollection;
35  import org.apache.ws.commons.schema.resolver.DefaultURIResolver;
36  import org.apache.ws.commons.schema.resolver.URIResolver;
37  import org.xml.sax.InputSource;
38  
39  import org.springframework.beans.factory.InitializingBean;
40  import org.springframework.context.ResourceLoaderAware;
41  import org.springframework.core.io.Resource;
42  import org.springframework.core.io.ResourceLoader;
43  import org.springframework.core.io.UrlResource;
44  import org.springframework.util.Assert;
45  import org.springframework.util.StringUtils;
46  import org.springframework.xml.sax.SaxUtils;
47  import org.springframework.xml.validation.XmlValidator;
48  import org.springframework.xml.validation.XmlValidatorFactory;
49  import org.springframework.xml.xsd.XsdSchema;
50  import org.springframework.xml.xsd.XsdSchemaCollection;
51  
52  /**
53   * Implementation of the {@link XsdSchemaCollection} that uses Apache WS-Commons XML Schema.
54   * <p/>
55   * Setting the {@link #setInline(boolean) inline} flag to <code>true</code> will result in all referenced schemas
56   * (included and imported) being merged into the referred schema. When including the schemas into a WSDL, this greatly
57   * simplifies the deloyment of the schemas.
58   *
59   * @author Arjen Poutsma
60   * @see <a href="http://ws.apache.org/commons/XmlSchema/">Commons XML Schema</a>
61   * @since 1.5.0
62   */
63  public class CommonsXsdSchemaCollection implements XsdSchemaCollection, InitializingBean, ResourceLoaderAware {
64  
65      private static final Log logger = LogFactory.getLog(CommonsXsdSchemaCollection.class);
66  
67      private final XmlSchemaCollection schemaCollection = new XmlSchemaCollection();
68  
69      private final List xmlSchemas = new ArrayList();
70  
71      private Resource[] xsdResources;
72  
73      private boolean inline = false;
74  
75      private ValidationEventHandler validationEventHandler;
76  
77      private URIResolver uriResolver = new ClasspathUriResolver();
78  
79      private ResourceLoader resourceLoader;
80  
81      /**
82       * Constructs a new, empty instance of the <code>CommonsXsdSchemaCollection</code>.
83       * <p/>
84       * A subsequent call to the {@link #setXsds(Resource[])} is required.
85       */
86      public CommonsXsdSchemaCollection() {
87      }
88  
89      /**
90       * Constructs a new instance of the <code>CommonsXsdSchemaCollection</code> based on the given resources.
91       *
92       * @param resources the schema resources to load
93       */
94      public CommonsXsdSchemaCollection(Resource[] resources) {
95          this.xsdResources = resources;
96      }
97  
98      /**
99       * Sets the schema resources to be loaded.
100      *
101      * @param xsdResources the schema resources to be loaded
102      */
103     public void setXsds(Resource[] xsdResources) {
104         this.xsdResources = xsdResources;
105     }
106 
107     /**
108      * Defines whether included schemas should be inlinded into the including schema.
109      * <p/>
110      * Defaults to <code>false</code>.
111      */
112     public void setInline(boolean inline) {
113         this.inline = inline;
114     }
115 
116     /** Sets the WS-Commons validation event handler to use while parsing schemas. */
117     public void setValidationEventHandler(ValidationEventHandler validationEventHandler) {
118         this.validationEventHandler = validationEventHandler;
119     }
120 
121     /**
122      * Sets the WS-Commons uri resolver to use when resolving (relative) schemas.
123      * <p/>
124      * Default is an internal subclass of {@link DefaultURIResolver} which correctly handles schemas on the classpath.
125      */
126     public void setUriResolver(URIResolver uriResolver) {
127         Assert.notNull(uriResolver, "'uriResolver' must not be null");
128         this.uriResolver = uriResolver;
129     }
130 
131     public void setResourceLoader(ResourceLoader resourceLoader) {
132         this.resourceLoader = resourceLoader;
133     }
134 
135     public void afterPropertiesSet() throws IOException {
136         Assert.notEmpty(xsdResources, "'xsds' must not be empty");
137 
138         schemaCollection.setSchemaResolver(uriResolver);
139 
140         Set processedIncludes = new HashSet();
141         Set processedImports = new HashSet();
142 
143         for (int i = 0; i < xsdResources.length; i++) {
144             Resource xsdResource = xsdResources[i];
145             Assert.isTrue(xsdResource.exists(), xsdResource + " does not exit");
146             try {
147                 XmlSchema xmlSchema =
148                         schemaCollection.read(SaxUtils.createInputSource(xsdResource), validationEventHandler);
149                 xmlSchemas.add(xmlSchema);
150 
151                 if (inline) {
152                     inlineIncludes(xmlSchema, processedIncludes, processedImports);
153                     findImports(xmlSchema, processedImports, processedIncludes);
154                 }
155             }
156             catch (Exception ex) {
157                 throw new CommonsXsdSchemaException("Schema [" + xsdResource + "] could not be loaded", ex);
158             }
159         }
160         if (logger.isInfoEnabled()) {
161             logger.info("Loaded " + StringUtils.arrayToCommaDelimitedString(xsdResources));
162         }
163 
164     }
165 
166     public XsdSchema[] getXsdSchemas() {
167         XsdSchema[] result = new XsdSchema[xmlSchemas.size()];
168         for (int i = 0; i < xmlSchemas.size(); i++) {
169             XmlSchema xmlSchema = (XmlSchema) xmlSchemas.get(i);
170             result[i] = new CommonsXsdSchema(xmlSchema, schemaCollection);
171         }
172         return result;
173     }
174 
175     public XmlValidator createValidator() throws IOException {
176         Resource[] resources = new Resource[xmlSchemas.size()];
177         for (int i = xmlSchemas.size() - 1; i >= 0; i--) {
178             XmlSchema xmlSchema = (XmlSchema) xmlSchemas.get(i);
179             resources[i] = new UrlResource(xmlSchema.getSourceURI());
180         }
181         return XmlValidatorFactory.createValidator(resources, XmlValidatorFactory.SCHEMA_W3C_XML);
182     }
183 
184     private void inlineIncludes(XmlSchema schema, Set processedIncludes, Set processedImports) {
185         processedIncludes.add(schema);
186 
187         XmlSchemaObjectCollection schemaItems = schema.getItems();
188         for (int i = 0; i < schemaItems.getCount(); i++) {
189             XmlSchemaObject schemaObject = schemaItems.getItem(i);
190             if (schemaObject instanceof XmlSchemaInclude) {
191                 XmlSchema includedSchema = ((XmlSchemaInclude) schemaObject).getSchema();
192                 if (!processedIncludes.contains(includedSchema)) {
193                     inlineIncludes(includedSchema, processedIncludes, processedImports);
194                     findImports(includedSchema, processedImports, processedIncludes);
195                     XmlSchemaObjectCollection includeItems = includedSchema.getItems();
196                     for (int j = 0; j < includeItems.getCount(); j++) {
197                         XmlSchemaObject includedItem = includeItems.getItem(j);
198                         schemaItems.add(includedItem);
199                     }
200                 }
201                 // remove the <include/>
202                 schemaItems.removeAt(i);
203                 i--;
204             }
205         }
206     }
207 
208     private void findImports(XmlSchema schema, Set processedImports, Set processedIncludes) {
209         processedImports.add(schema);
210         XmlSchemaObjectCollection includes = schema.getIncludes();
211         for (int i = 0; i < includes.getCount(); i++) {
212             XmlSchemaExternal external = (XmlSchemaExternal) includes.getItem(i);
213             if (external instanceof XmlSchemaImport) {
214                 XmlSchemaImport schemaImport = (XmlSchemaImport) external;
215                 XmlSchema importedSchema = schemaImport.getSchema();
216                 if (!"http://www.w3.org/XML/1998/namespace".equals(schemaImport.getNamespace()) &&
217                         importedSchema != null && !processedImports.contains(importedSchema)) {
218                     inlineIncludes(importedSchema, processedIncludes, processedImports);
219                     findImports(importedSchema, processedImports, processedIncludes);
220                     xmlSchemas.add(importedSchema);
221                 }
222                 // remove the schemaLocation
223                 external.setSchemaLocation(null);
224             }
225         }
226     }
227 
228     public String toString() {
229         StringBuffer buffer = new StringBuffer("CommonsXsdSchemaCollection");
230         buffer.append('{');
231         for (int i = 0; i < xmlSchemas.size(); i++) {
232             XmlSchema schema = (XmlSchema) xmlSchemas.get(i);
233             buffer.append(schema.getTargetNamespace());
234             if (i < xmlSchemas.size() - 1) {
235                 buffer.append(',');
236             }
237         }
238         buffer.append('}');
239         return buffer.toString();
240     }
241 
242     private class ClasspathUriResolver extends DefaultURIResolver {
243 
244         public InputSource resolveEntity(String namespace, String schemaLocation, String baseUri) {
245             if (resourceLoader != null) {
246                 Resource resource = resourceLoader.getResource(schemaLocation);
247                 if (resource.exists()) {
248                     return createInputSource(resource);
249                 }
250                 else if (StringUtils.hasLength(baseUri)) {
251                     // let's try and find it relative to the baseUri, see SWS-413
252                     try {
253                         Resource baseUriResource = new UrlResource(baseUri);
254                         resource = baseUriResource.createRelative(schemaLocation);
255                         if (resource.exists()) {
256                             return createInputSource(resource);
257                         }
258                     }
259                     catch (IOException e) {
260                         // fall through
261                     }
262                 }
263                 // let's try and find it on the classpath, see SWS-362
264                 String classpathLocation = ResourceLoader.CLASSPATH_URL_PREFIX + "/" + schemaLocation;
265                 resource = resourceLoader.getResource(classpathLocation);
266                 if (resource.exists()) {
267                     return createInputSource(resource);
268                 }
269             }
270             return super.resolveEntity(namespace, schemaLocation, baseUri);
271         }
272 
273         private InputSource createInputSource(Resource resource) {
274             try {
275                 return SaxUtils.createInputSource(resource);
276             }
277             catch (IOException ex) {
278                 throw new CommonsXsdSchemaException("Could not resolve location", ex);
279             }
280         }
281     }
282 
283 }