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