1
2
3
4
5
6
7
8
9
10
11
12
13 package com.eviware.soapui.impl.wsdl.support.xsd;
14
15 import java.io.File;
16 import java.io.IOException;
17 import java.net.MalformedURLException;
18 import java.net.URL;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.StringTokenizer;
27
28 import javax.xml.namespace.QName;
29
30 import org.apache.log4j.Logger;
31 import org.apache.xmlbeans.SchemaAnnotation;
32 import org.apache.xmlbeans.SchemaLocalElement;
33 import org.apache.xmlbeans.SchemaParticle;
34 import org.apache.xmlbeans.SchemaType;
35 import org.apache.xmlbeans.SchemaTypeLoader;
36 import org.apache.xmlbeans.SchemaTypeSystem;
37 import org.apache.xmlbeans.SimpleValue;
38 import org.apache.xmlbeans.XmlAnySimpleType;
39 import org.apache.xmlbeans.XmlBase64Binary;
40 import org.apache.xmlbeans.XmlBeans;
41 import org.apache.xmlbeans.XmlCursor;
42 import org.apache.xmlbeans.XmlException;
43 import org.apache.xmlbeans.XmlHexBinary;
44 import org.apache.xmlbeans.XmlObject;
45 import org.apache.xmlbeans.XmlOptions;
46 import org.w3c.dom.Document;
47 import org.w3c.dom.Node;
48
49 import com.eviware.soapui.SoapUI;
50 import com.eviware.soapui.impl.wsdl.support.Constants;
51 import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
52 import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlLoader;
53 import com.eviware.soapui.model.settings.SettingsListener;
54 import com.eviware.soapui.settings.WsdlSettings;
55 import com.eviware.soapui.support.Tools;
56
57 /***
58 * XML-Schema related tools
59 *
60 * @author Ole.Matzura
61 */
62
63 public class SchemaUtils
64 {
65 private final static Logger log = Logger.getLogger( SchemaUtils.class );
66 private static Map<String,XmlObject> defaultSchemas = new HashMap<String,XmlObject>();
67
68 static
69 {
70 initDefaultSchemas();
71
72 SoapUI.getSettings().addSettingsListener( new SettingsListener() {
73
74 public void settingChanged( String name, String newValue, String oldValue )
75 {
76 if( name.equals( WsdlSettings.SCHEMA_DIRECTORY ))
77 {
78 log.info( "Reloading default schemas.." );
79 initDefaultSchemas();
80 }
81 }});
82 }
83
84 public static void initDefaultSchemas()
85 {
86 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
87 Thread.currentThread().setContextClassLoader( SoapUI.class.getClassLoader() );
88
89 try
90 {
91 defaultSchemas.clear();
92 loadDefaultSchema( SoapUI.class.getResource("/xop.xsd") );
93 loadDefaultSchema( SoapUI.class.getResource("/XMLSchema.xsd") );
94 loadDefaultSchema( SoapUI.class.getResource("/xml.xsd") );
95 loadDefaultSchema( SoapUI.class.getResource("/swaref.xsd") );
96 loadDefaultSchema( SoapUI.class.getResource("/xmime200505.xsd") );
97 loadDefaultSchema( SoapUI.class.getResource("/xmime200411.xsd") );
98
99 String schemaDirectory = SoapUI.getSchemaDirectory();
100 if( schemaDirectory != null && schemaDirectory.trim().length() > 0 )
101 loadSchemaDirectory( schemaDirectory );
102 }
103 catch (Exception e)
104 {
105 e.printStackTrace();
106 }
107 finally
108 {
109 Thread.currentThread().setContextClassLoader( contextClassLoader );
110 }
111 }
112
113 private static void loadSchemaDirectory( String schemaDirectory ) throws IOException, MalformedURLException
114 {
115 File dir = new File( schemaDirectory );
116 if( dir.exists() && dir.isDirectory() )
117 {
118 String[] xsdFiles = dir.list();
119 int cnt = 0;
120
121 if( xsdFiles != null && xsdFiles.length > 0 )
122 {
123 for( int c = 0; c < xsdFiles.length; c++ )
124 {
125 try
126 {
127 String xsdFile = xsdFiles[c];
128 if( xsdFile.endsWith( ".xsd" ))
129 {
130 String filename = schemaDirectory + File.separator + xsdFile;
131 loadDefaultSchema( new URL( "file:" + filename ) );
132 cnt++;
133 }
134 }
135 catch( XmlException e )
136 {
137 e.printStackTrace();
138 }
139 }
140 }
141
142 if( cnt == 0 )
143 log.warn( "Missing schema files in schemaDirectory [" + schemaDirectory + "]" );
144 }
145 else log.warn( "Failed to open schemaDirectory [" + schemaDirectory + "]" );
146 }
147
148 private static void loadDefaultSchema( URL url ) throws XmlException, IOException
149 {
150 XmlObject xmlObject = XmlObject.Factory.parse( url );
151 String targetNamespace = getTargetNamespace( xmlObject );
152
153 if( defaultSchemas.containsKey( targetNamespace ))
154 log.warn( "Overriding schema for targetNamespace " + targetNamespace );
155
156 defaultSchemas.put( targetNamespace, xmlObject );
157
158 log.info( "Added default schema from " + url.getPath() + " with targetNamespace " + targetNamespace );
159 }
160
161 public static SchemaTypeLoader loadSchemaTypes(String wsdlUrl, SoapVersion soapVersion, WsdlLoader loader ) throws SchemaException
162 {
163 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
164 Thread.currentThread().setContextClassLoader( SoapUI.class.getClassLoader() );
165
166 try
167 {
168 log.info("Loading schema types from [" + wsdlUrl + "]");
169 ArrayList<XmlObject> schemas = new ArrayList<XmlObject>(getSchemas(
170 wsdlUrl, loader).values());
171
172 return buildSchemaTypes(schemas, soapVersion);
173 }
174 catch (Exception e)
175 {
176 throw new SchemaException( "Error loading schema types", e );
177 }
178 finally
179 {
180 Thread.currentThread().setContextClassLoader( contextClassLoader );
181 }
182 }
183
184 public static SchemaTypeLoader buildSchemaTypes(List<XmlObject> schemas, SoapVersion soapVersion) throws SchemaException
185 {
186 XmlOptions options = new XmlOptions();
187 options.setCompileNoValidation();
188 options.setCompileNoPvrRule();
189 options.setCompileDownloadUrls();
190 options.setCompileNoUpaRule();
191 options.setValidateTreatLaxAsSkip();
192
193 ArrayList errorList = new ArrayList();
194 options.setErrorListener( errorList );
195
196 try
197 {
198
199 for( int c = 0; c < schemas.size(); c++ )
200 {
201 XmlObject s = schemas.get( c );
202
203 Map map = new HashMap();
204 XmlCursor cursor = s.newCursor();
205 cursor.toStartDoc();
206 if( toNextContainer(cursor))
207 cursor.getAllNamespaces( map );
208 else
209 log.warn( "Can not get namespaces for " + s );
210
211 String tns = getTargetNamespace(s);
212
213 log.info( "schema for [" + tns + "] contained [" + map.toString() + "] namespaces" );
214
215
216 if( defaultSchemas.containsKey( tns ) ||
217 tns.equals( getTargetNamespace(soapVersion.getSoapEncodingSchema()) ) ||
218 tns.equals( getTargetNamespace(soapVersion.getSoapEnvelopeSchema() )))
219 {
220 schemas.remove( c );
221 c--;
222 }
223 else
224 {
225 removeImports(s);
226 }
227 }
228
229 schemas.add( soapVersion.getSoapEncodingSchema());
230 schemas.add( soapVersion.getSoapEnvelopeSchema());
231 schemas.addAll( defaultSchemas.values() );
232
233 SchemaTypeSystem sts = XmlBeans.compileXsd(
234 schemas.toArray(new XmlObject[schemas.size()]), XmlBeans.getBuiltinTypeSystem(), options);
235 return XmlBeans.typeLoaderUnion(new SchemaTypeLoader[] { sts, XmlBeans.getBuiltinTypeSystem() });
236 }
237 catch (Exception e)
238 {
239 e.printStackTrace();
240 throw new SchemaException( e, errorList );
241 }
242 finally
243 {
244 for( int c = 0; c < errorList.size(); c++ )
245 {
246 log.warn( "Error: " + errorList.get( c ));
247 }
248 }
249 }
250
251 public static boolean toNextContainer(XmlCursor cursor)
252 {
253 while( !cursor.isContainer() && !cursor.isEnddoc() )
254 cursor.toNextToken();
255
256 return cursor.isContainer();
257 }
258
259 public static String getTargetNamespace(XmlObject s)
260 {
261 return ((Document)s.getDomNode()).getDocumentElement().getAttribute( "targetNamespace" );
262 }
263
264 public static Map<String,XmlObject> getSchemas( String wsdlUrl, WsdlLoader loader ) throws SchemaException
265 {
266 Map<String,XmlObject> result = new HashMap<String,XmlObject>();
267 getSchemas( wsdlUrl, result, loader );
268 return result;
269 }
270
271 /***
272 * Returns a map mapping urls to corresponding XmlSchema XmlObjects for the specified wsdlUrl
273 */
274
275 public static void getSchemas( String wsdlUrl, Map<String,XmlObject> existing, WsdlLoader loader ) throws SchemaException
276 {
277 if( existing.containsKey( wsdlUrl ))
278 return;
279
280 log.info( "Getting schema " + wsdlUrl );
281
282 ArrayList errorList = new ArrayList();
283
284 Map<String,XmlObject> result = new HashMap<String,XmlObject>();
285
286 try
287 {
288 XmlOptions options = new XmlOptions();
289 options.setCompileNoValidation();
290 options.setSaveUseOpenFrag();
291 options.setErrorListener( errorList );
292 options.setSaveSyntheticDocumentElement(new QName( Constants.XSD_NS, "schema"));
293
294 XmlObject xmlObject = loader.loadXmlObject(wsdlUrl, options);
295
296 Document dom = (Document) xmlObject.getDomNode();
297 Node domNode = dom.getDocumentElement();
298 if (domNode.getLocalName().equals("schema")
299 && domNode.getNamespaceURI().equals(
300 Constants.XSD_NS))
301 {
302 result.put(wsdlUrl, xmlObject);
303 }
304 else
305 {
306 XmlObject[] schemas = xmlObject
307 .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:schema");
308
309 for (int i = 0; i < schemas.length; i++)
310 {
311 XmlCursor xmlCursor = schemas[i].newCursor();
312 String xmlText = xmlCursor.getObject().xmlText(options);
313 schemas[i] = XmlObject.Factory.parse(xmlText, options);
314 schemas[i].documentProperties().setSourceName(wsdlUrl);
315
316 result.put(wsdlUrl + "@" + (i+1), schemas[i]);
317 }
318
319 XmlObject[] wsdlImports = xmlObject
320 .selectPath("declare namespace s='" + Constants.WSDL11_NS + "' .//s:import/@location");
321 for (int i = 0; i < wsdlImports.length; i++)
322 {
323 String location = ((SimpleValue) wsdlImports[i]).getStringValue();
324 if (location != null)
325 {
326 if (location.startsWith("file:")
327 || location.indexOf("://") > 0)
328 {
329 getSchemas(location, existing, loader);
330 }
331 else
332 {
333 getSchemas(Tools.joinRelativeUrl(wsdlUrl, location), existing, loader);
334 }
335 }
336 }
337 }
338
339 existing.putAll( result );
340
341 XmlObject[] schemas = result.values().toArray(
342 new XmlObject[result.size()]);
343
344 for (int c = 0; c < schemas.length; c++)
345 {
346 xmlObject = schemas[c];
347
348 XmlObject[] schemaImports = xmlObject
349 .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:import/@schemaLocation");
350 for (int i = 0; i < schemaImports.length; i++)
351 {
352 String location = ((SimpleValue) schemaImports[i])
353 .getStringValue();
354 if (location != null)
355 {
356 if (location.startsWith("file:")
357 || location.indexOf("://") > 0)
358 {
359 getSchemas(location, existing, loader);
360 }
361 else
362 {
363 getSchemas(Tools.joinRelativeUrl(wsdlUrl, location), existing, loader);
364 }
365 }
366 }
367
368 XmlObject[] schemaIncludes = xmlObject
369 .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:include/@schemaLocation");
370 for (int i = 0; i < schemaIncludes.length; i++)
371 {
372 String location = ((SimpleValue) schemaIncludes[i])
373 .getStringValue();
374 if (location != null)
375 {
376 if (location.startsWith("file:")
377 || location.indexOf("://") > 0)
378 {
379 getSchemas(location, existing, loader);
380 }
381 else
382 {
383 getSchemas(Tools.joinRelativeUrl(wsdlUrl, location), existing, loader);
384 }
385 }
386 }
387 }
388 }
389 catch (Exception e)
390 {
391 e.printStackTrace();
392 throw new SchemaException( e, errorList );
393 }
394 }
395
396 /***
397 * Returns a map mapping urls to corresponding XmlObjects for the specified wsdlUrl
398 */
399
400 public static Map<String,XmlObject> getDefinitionParts( WsdlLoader loader ) throws Exception
401 {
402 HashMap<String, XmlObject> result = new HashMap<String,XmlObject>();
403 getDefinitionParts( loader.getBaseURI(), result, loader );
404 return result;
405 }
406
407 public static void getDefinitionParts( String wsdlUrl, Map<String,XmlObject> existing, WsdlLoader loader ) throws Exception
408 {
409 if( existing.containsKey( wsdlUrl ))
410 return;
411
412 XmlObject xmlObject = loader.loadXmlObject( wsdlUrl, null );
413 existing.put( wsdlUrl, xmlObject );
414
415 XmlObject [] wsdlImports = xmlObject.selectPath("declare namespace s='" + Constants.WSDL11_NS + "' .//s:import");
416 for (int i = 0; i < wsdlImports.length; i++)
417 {
418 String location = wsdlImports[i].getDomNode().getAttributes().getNamedItem( "location" ).getNodeValue();
419 if( location != null )
420 {
421 if( location.startsWith( "file:" ) || location.indexOf( "://") > 0 )
422 {
423 getDefinitionParts( location, existing, loader );
424 }
425 else
426 {
427 getDefinitionParts( Tools.joinRelativeUrl( wsdlUrl, location ), existing, loader );
428 }
429 }
430 }
431
432 XmlObject[] schemaImports = xmlObject.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:import/@schemaLocation");
433 for (int i = 0; i < schemaImports.length; i++)
434 {
435 String location = ((SimpleValue)schemaImports[i]).getStringValue();
436 if( location != null )
437 {
438 if( location.startsWith( "file:" ) || location.indexOf( "://") > 0 )
439 {
440 getDefinitionParts( location, existing, loader );
441 }
442 else
443 {
444 getDefinitionParts( Tools.joinRelativeUrl( wsdlUrl, location ), existing, loader );
445 }
446 }
447 }
448
449 XmlObject[] schemaIncludes = xmlObject.selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:include/@schemaLocation");
450 for (int i = 0; i < schemaIncludes.length; i++)
451 {
452 String location = ((SimpleValue)schemaIncludes[i]).getStringValue();
453 if( location != null )
454 {
455 if( location.startsWith( "file:" ) || location.indexOf( "://") > 0 )
456 {
457 getDefinitionParts( location, existing, loader );
458 }
459 else
460 {
461 getDefinitionParts( Tools.joinRelativeUrl( wsdlUrl, location ), existing, loader );
462 }
463 }
464 }
465 }
466
467 /***
468 * Extracts namespaces - used in tool integrations for mapping..
469 */
470
471 public static Collection<String> extractNamespaces( SchemaTypeSystem schemaTypes )
472 {
473 Set<String> namespaces = new HashSet<String>();
474 SchemaType[] globalTypes = schemaTypes.globalTypes();
475 for( int c = 0; c < globalTypes.length; c++ )
476 {
477 namespaces.add( globalTypes[c].getName().getNamespaceURI() );
478 }
479
480 namespaces.remove( Constants.SOAP11_ENVELOPE_NS );
481 namespaces.remove( Constants.SOAP_ENCODING_NS );
482
483 return namespaces;
484 }
485
486 /***
487 * Used when creating a TypeSystem from a complete collection of SchemaDocuments so that referenced
488 * types are not downloaded (again)
489 */
490
491 public static void removeImports(XmlObject xmlObject) throws XmlException
492 {
493 XmlObject[] imports = xmlObject
494 .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:import");
495
496 for( int c = 0; c < imports.length; c++ )
497 {
498 XmlCursor cursor = imports[c].newCursor();
499 cursor.removeXml();
500 cursor.dispose();
501 }
502
503 XmlObject[] includes = xmlObject
504 .selectPath("declare namespace s='" + Constants.XSD_NS + "' .//s:include");
505
506 for( int c = 0; c < includes.length; c++ )
507 {
508 XmlCursor cursor = includes[c].newCursor();
509 cursor.removeXml();
510 cursor.dispose();
511 }
512 }
513
514 public static boolean isInstanceOf( SchemaType schemaType, SchemaType baseType )
515 {
516 if( schemaType == null )
517 return false;
518 return schemaType.equals(baseType) ? true : isInstanceOf( schemaType.getBaseType(), baseType );
519 }
520
521 public static boolean isBinaryType(SchemaType schemaType)
522 {
523 return isInstanceOf( schemaType, XmlHexBinary.type ) ||
524 isInstanceOf( schemaType, XmlBase64Binary.type );
525 }
526
527 public static String getDocumentation( SchemaParticle particle, SchemaType schemaType )
528 {
529 String result = null;
530
531 if( particle instanceof SchemaLocalElement )
532 {
533 SchemaAnnotation annotation = ((SchemaLocalElement)particle).getAnnotation();
534 if( annotation != null )
535 {
536 XmlObject[] userInformation = annotation.getUserInformation();
537 if( userInformation != null && userInformation.length > 0 )
538 {
539 result = userInformation[0].toString();
540 }
541 }
542 }
543
544 if( schemaType != null && schemaType.getAnnotation() != null )
545 {
546 XmlObject[] userInformation = schemaType.getAnnotation().getUserInformation();
547 if( userInformation != null && userInformation.length > 0 )
548 {
549 result = userInformation[0].toString();
550 }
551 }
552
553 if( result != null )
554 {
555 result = result.replaceAll( "<br/>", "\n" );
556
557 StringTokenizer st = new StringTokenizer( result, "\r\n" );
558 StringBuffer buf = new StringBuffer( "<html><body>" );
559 boolean added = false;
560
561 while( st.hasMoreElements() )
562 {
563 if( added )
564 buf.append( "<br>" );
565 String str = st.nextToken().trim();
566 if( str.length() > 0 )
567 {
568 buf.append( str );
569 added = true;
570 }
571 }
572 buf.append( "</body></html>" );
573 result = buf.toString();
574 }
575
576 return result;
577 }
578
579 public static String [] getEnumerationValues( SchemaType schemaType, boolean addNull )
580 {
581 if( schemaType != null )
582 {
583 XmlAnySimpleType[] enumerationValues = schemaType.getEnumerationValues();
584 if( enumerationValues != null && enumerationValues.length > 0 )
585 {
586 if( addNull )
587 {
588 String [] values = new String[enumerationValues.length+1];
589 values[0] = null;
590
591 for( int c = 1; c < values.length; c++ )
592 values[c] = enumerationValues[c-1].getStringValue();
593
594 return values;
595 }
596 else
597 {
598 String [] values = new String[enumerationValues.length];
599
600 for( int c = 0; c < values.length; c++ )
601 values[c] = enumerationValues[c].getStringValue();
602
603 return values;
604 }
605 }
606 }
607
608 return new String[0];
609 }
610 }