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