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