View Javadoc
1   /**
2    * Copyright 2004-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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  package org.kuali.student.contract.model.impl;
17  
18  import com.sun.xml.xsom.*;
19  import com.sun.xml.xsom.parser.AnnotationContext;
20  import com.sun.xml.xsom.parser.AnnotationParser;
21  import com.sun.xml.xsom.parser.AnnotationParserFactory;
22  import com.sun.xml.xsom.parser.SchemaDocument;
23  import com.sun.xml.xsom.parser.XSOMParser;
24  import com.sun.xml.xsom.util.DomAnnotationParserFactory;
25  
26  import org.kuali.student.contract.model.MessageStructure;
27  import org.kuali.student.contract.model.Service;
28  import org.kuali.student.contract.model.ServiceContractModel;
29  import org.kuali.student.contract.model.ServiceMethod;
30  import org.kuali.student.contract.model.ServiceMethodReturnValue;
31  import org.kuali.student.contract.model.XmlType;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  import org.xml.sax.Attributes;
35  import org.xml.sax.ContentHandler;
36  import org.xml.sax.EntityResolver;
37  import org.xml.sax.ErrorHandler;
38  import org.xml.sax.Locator;
39  import org.xml.sax.SAXException;
40  
41  import java.io.File;
42  import java.io.IOException;
43  import java.util.ArrayList;
44  import java.util.LinkedHashMap;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.Set;
48  
49  /**
50   *
51   * @author nwright
52   */
53  public class ServiceContractModelPescXsdLoader implements
54          ServiceContractModel {
55  
56      private static Logger log = LoggerFactory.getLogger(ServiceContractModelPescXsdLoader.class);
57      
58      private List<String> xsdFileNames;
59      private List<Service> services = null;
60      private List<ServiceMethod> serviceMethods = null;
61      private Map<String, XmlType> xmlTypeMap = null;
62      private List<MessageStructure> messageStructures;
63  
64      public ServiceContractModelPescXsdLoader(List<String> xsdFileNames) {
65          this.xsdFileNames = xsdFileNames;
66      }
67  
68      @Override
69      public List<ServiceMethod> getServiceMethods() {
70          if (this.serviceMethods == null) {
71              this.parse();
72          }
73          return this.serviceMethods;
74      }
75  
76      @Override
77      public List<String> getSourceNames() {
78          List<String> list = new ArrayList();
79          list.addAll(this.xsdFileNames);
80          return list;
81      }
82  
83      @Override
84      public List<Service> getServices() {
85          if (services == null) {
86              this.parse();
87          }
88          return services;
89      }
90  
91      @Override
92      public List<XmlType> getXmlTypes() {
93          if (xmlTypeMap == null) {
94              this.parse();
95          }
96          return new ArrayList(xmlTypeMap.values());
97      }
98  
99      @Override
100     public List<MessageStructure> getMessageStructures() {
101         if (messageStructures == null) {
102             this.parse();
103         }
104         return this.messageStructures;
105     }
106 
107     private void parse() {
108         log.info("ServiceContractModelQDoxLoader: Starting parse");
109         services = new ArrayList();
110         Service service = new Service();
111         services.add(service);
112         service.setKey("Pesc");
113         service.setName("PescService");
114         service.setComments("Derived from pesc CoreMain");
115         serviceMethods = new ArrayList();
116         ServiceMethod method = new ServiceMethod();
117         serviceMethods.add(method);
118         method.setName("dummy");
119         method.setDescription("Dummy method so validation won't fail");
120         method.setService("Pesc");
121         method.setParameters(new ArrayList());
122         ServiceMethodReturnValue rv = new ServiceMethodReturnValue();
123         rv.setType("void");
124         rv.setDescription("returns nothing");
125         method.setReturnValue(rv);
126         xmlTypeMap = new LinkedHashMap();
127         messageStructures = new ArrayList();
128 
129         XSOMParser parser = new XSOMParser(); 
130         parser.setAnnotationParser(new DomAnnotationParserFactory());
131 //        parser.setAnnotationParser(new XsdAnnotationParserFactory());        
132         try {
133             for (String xsdFileName : this.xsdFileNames) {
134 //                System.out.println("Parsing " + xsdFileName);
135                 parser.parse(new File(xsdFileName));
136             }
137         } catch (SAXException ex) {
138             throw new IllegalArgumentException(ex);
139         } catch (IOException ex) {
140             throw new IllegalArgumentException(ex);
141         }
142         XSSchemaSet schemas;
143         try {
144             schemas = parser.getResult();
145         } catch (SAXException ex) {
146             throw new IllegalArgumentException(ex);
147         }
148 //        for (XSSchema schema : schemas.getSchemas()) {
149 //            this.processSchema(schema);
150 //        }
151         Set<SchemaDocument> schemaDocuments = parser.getDocuments();
152         for (SchemaDocument doc : schemaDocuments) {
153             XSSchema schema = doc.getSchema();
154             this.processSchema(schema);
155         }
156     }
157 
158     private void processSchema(XSSchema schema) {
159         for (XSElementDecl element : schema.getElementDecls().values()) {
160             this.addElementDecl(element);
161         }
162         for (XSSimpleType st : schema.getSimpleTypes().values()) {
163 //            System.out.println("SimpleType =" + st.getName() + " namespace=" + st.getTargetNamespace());
164             addSimpleType(st);
165         }
166         for (XSComplexType ct : schema.getComplexTypes().values()) {
167             if (!shouldInclude(ct)) {
168 //                System.out.println("Skipping ComplexType =" + ct.getName() + " namespace=" + ct.getTargetNamespace());
169                 continue;
170             }
171 //            System.out.println("ComplexType =" + ct.getName() + " namespace=" + ct.getTargetNamespace());
172             addComplexType(ct);
173         }
174     }
175 
176     private boolean shouldInclude(XSComplexType ct) {
177 //        if (ct.getTargetNamespace().equals("urn:org:pesc:core:CoreMain:v1.8.0")) {
178         return true;
179 //        }
180 //        return false;
181     }
182 
183     private void addSimpleType(XSSimpleType simpleType) {
184         XmlType xmlType = xmlTypeMap.get(simpleType.getName());
185         if (xmlType != null) {
186 //            System.out.println("Already processed simple Type="
187 //                    + simpleType.getName());
188             return;
189         }
190         xmlType = new XmlType();
191         xmlTypeMap.put(simpleType.getName(), xmlType);
192         xmlType.setName(simpleType.getName());
193         xmlType.setDesc(calcMissing(calcDesc(simpleType.getAnnotation())));
194         xmlType.setComments("???");
195         xmlType.setExamples("???");
196         xmlType.setService("Pesc");
197         xmlType.setPrimitive("Primitive");
198     }
199 
200     private void addComplexType(XSComplexType complexType) {
201         addComplexType(complexType, complexType.getName());
202     }
203 
204     private void addElementDecl(XSElementDecl element) {
205         String name = element.getName();
206         XmlType xmlType = xmlTypeMap.get(name);
207         if (xmlType != null) {
208 //            System.out.println("Already processed element name=" + name);
209             return;
210         }
211 //        System.out.println("processing element=" + name);
212         xmlType = new XmlType();
213         xmlTypeMap.put(name, xmlType);
214         xmlType.setName(name);
215         xmlType.setDesc(calcMissing(calcDesc(element.getAnnotation())));
216         xmlType.setComments("???");
217         xmlType.setExamples("???");
218         xmlType.setService("Pesc");
219         xmlType.setPrimitive(XmlType.COMPLEX);
220         addMessageStructure(xmlType.getName(), element);
221     }
222 
223     private void addComplexType(XSComplexType complexType, String name) {
224         XmlType xmlType = xmlTypeMap.get(name);
225         if (xmlType != null) {
226 //            System.out.println("Already processed complex Type=" + name);
227             return;
228         }
229 //        System.out.println("processing complex type=" + name);
230         xmlType = new XmlType();
231         xmlTypeMap.put(name, xmlType);
232         xmlType.setName(name);
233         xmlType.setDesc(calcMissing(calcDesc(complexType.getAnnotation())));
234         xmlType.setComments("???");
235         xmlType.setExamples("???");
236         xmlType.setService("Pesc");
237         xmlType.setPrimitive(XmlType.COMPLEX);
238 
239         boolean found = false;
240         XSContentType contentType = complexType.getContentType();
241         XSParticle particle = contentType.asParticle();
242         if (particle != null) {
243             XSTerm term = particle.getTerm();
244             if (term.isModelGroup()) {
245                 XSModelGroup xsModelGroup = term.asModelGroup();
246                 XSParticle[] particles = xsModelGroup.getChildren();
247                 for (XSParticle p : particles) {
248                     XSTerm pterm = p.getTerm();
249                     if (pterm.isElementDecl()) { //xs:element inside complex type
250                         XSElementDecl element = pterm.asElementDecl();
251                         addMessageStructure(xmlType.getName(), element);
252                         found = true;
253                     }
254                 }
255             }
256         }
257 //        if (!found) {
258 //            System.out.println("*** WARNING *** Complex Type, " + xmlType.getName() + ", has no message structure fields");
259 //        }
260     }
261 
262     private String calcMissing(String str) {
263         if (str == null) {
264             return "???";
265         }
266         if (str.trim().isEmpty()) {
267             return "???";
268         }
269         return str;
270     }
271 
272     private String calcDesc(XSAnnotation annotation) {
273         if (annotation == null) {
274             return null;
275         }
276         if (annotation.getAnnotation() == null) {
277             return null;
278         }
279         return annotation.getAnnotation().toString();
280     }
281 
282     private void addMessageStructure(String xmlObject, XSElementDecl element) {
283         MessageStructure ms = new MessageStructure();
284         this.messageStructures.add(ms);
285         ms.setXmlObject(xmlObject);
286         ms.setShortName(element.getName());
287         ms.setName("???");
288         ms.setId(xmlObject + "." + ms.getShortName());
289         ms.setType(calcType(element, xmlObject + "" + ms.getShortName()));
290         ms.setDescription(calcMissing(calcDesc(element.getAnnotation())));
291 //        System.out.println("Element " + ms.getId() + " " + ms.getType());
292         ms.setRequired(calcRequired(element));
293         ms.setCardinality(calcCardinality(element));
294     }
295 
296     private String calcType(XSElementDecl element, String inLinePrefix) {
297         String type = calcType(element.getType(), inLinePrefix);
298         if (type != null) {
299             return type;
300         }
301         return type;
302     }
303 
304     private String calcType(XSType xsType, String inLinePrefix) {
305         if (xsType.isSimpleType()) {
306             XSSimpleType st = xsType.asSimpleType();
307             return st.getBaseType().getName();
308 //   if (st.isRestriction ())
309 //   {
310 //    XSRestrictionSimpleType res = st.asRestriction ();
311 //    return res.getBaseType ().getName ();
312 //   }
313         }
314         String type = xsType.getName();
315         if (type != null) {
316             return type;
317         }
318         if ((xsType.isComplexType())) {
319             XSComplexType ct = xsType.asComplexType();
320             String baseType = ct.getBaseType().getName();
321             if (baseType.equals("anyType")) {
322                 baseType = "";
323             }
324             String inlineTypeName = inLinePrefix + baseType;
325             addComplexType(ct, inlineTypeName);
326             return inlineTypeName;
327         }
328         throw new IllegalArgumentException("cannot calculate the type of the field " + inLinePrefix);
329     }
330 
331     private String calcRequired(XSElementDecl element) {
332         // TODO: isNillable seems to ALWAYS be true so... figure out another way        
333 //        if (element.isNillable()) {
334 //            return null;
335 //        }
336 //        return "Required";
337         // TODO: facets only hold min/maxLength not min/maxOccurs find out where min/maxOccurs is parsed into        
338 //        String minOccurs = this.getFacetValue(element, "minOccurs");
339 //        if (minOccurs == null) {
340 //            return "No";
341 //        }
342 //        if (minOccurs.equals("0")) {
343 //            return "No";
344 //        }
345 //        System.out.println(element.getName() + " has a minOccurs that is " + minOccurs);
346 //        return "Required";
347         return "???";
348     }
349 
350     private String calcCardinality(XSElementDecl element) {
351 //        if (element.getName().equals ("NoteMessage")) {
352 //            System.out.println ("start debugging because NoteMessage has maxOccurs=unbounded");
353 //        }
354         if (this.getIsRepeated(element)) {
355             return "Many";
356         }
357         // TODO: facets only hold min/maxLength not min/maxOccurs find out where min/maxOccurs is parsed into
358 //        String maxOccurs = this.getFacetValue(element, "maxOccurs");
359 //        if (maxOccurs == null) {
360 //            return "One";
361 //        }
362 //        if (maxOccurs.equals("unbounded")) {
363 //            return "Many";
364 //        }
365 //        System.out.println(element.getName() + " has a maxOccurs that is " + maxOccurs);
366 //        return "Many";
367         return null;
368     }
369     
370     private boolean getIsRepeated (XSElementDecl element) {
371         XSType type = element.getType();
372         if (type == null) {
373             return false;
374         }
375         if (type.isComplexType()) {
376             XSContentType contentType = type.asComplexType().getContentType();
377             if (contentType == null) {
378                 return false;
379             }
380             XSParticle particle = contentType.asParticle();
381             if (particle == null) {
382                 return false;
383             }
384             particle.isRepeated();
385         }
386 //        if (type.isSimpleType())
387         return false;
388     }
389 
390     private String getFacetValue(XSElementDecl element, String name) {
391         XSType type = element.getType();
392         if (type == null) {
393             return null;
394         }
395         if (type.isSimpleType()) {
396             XSFacet facet = type.asSimpleType().getFacet(name);
397             if (facet == null) {
398                 return null;
399             }
400             return facet.getValue().toString();
401         }
402         if (type.isComplexType()) {
403             XSContentType contentType = type.asComplexType().getContentType();
404             if (contentType == null) {
405                 return null;
406             }
407             XSSimpleType simpleType = contentType.asSimpleType();
408             if (simpleType == null) {
409                 return null;
410             }
411             XSFacet facet = simpleType.getFacet(name);
412             if (facet == null) {
413                 return null;
414             }
415             return facet.getValue().toString();
416         }
417         return null;
418     }
419 
420     /**
421      * helper class
422      */
423     private static class XsdAnnotationParserFactory implements AnnotationParserFactory {
424 
425         @Override
426         public AnnotationParser create() {
427             return new XsdAnnotationParser();
428         }
429     }
430 
431     /** 
432      * helper class
433      */
434     private static class XsdAnnotationParser extends AnnotationParser {
435 
436         private StringBuilder documentation = new StringBuilder();
437 
438         @Override
439         public ContentHandler getContentHandler(AnnotationContext context,
440                 String parentElementName,
441                 ErrorHandler handler,
442                 EntityResolver resolver) {
443             return new XsdContentHandler(documentation);
444         }
445 
446         @Override
447         public Object getResult(Object existing) {
448             return documentation.toString().trim();
449         }
450     }
451 
452     /**
453      * helper class
454      */
455     private static class XsdContentHandler implements ContentHandler {
456 
457         private StringBuilder documentation;
458 
459         public XsdContentHandler(StringBuilder documentation) {
460             this.documentation = documentation;
461         }
462         private boolean parsingDocumentation = false;
463 
464         @Override
465         public void characters(char[] ch, int start, int length)
466                 throws SAXException {
467             if (parsingDocumentation) {
468                 documentation.append(ch, start, length);
469             }
470         }
471 
472         @Override
473         public void endElement(String uri, String localName, String name)
474                 throws SAXException {
475             if (localName.equals("documentation")) {
476                 parsingDocumentation = false;
477             }
478         }
479 
480         @Override
481         public void startElement(String uri, String localName, String name,
482                 Attributes atts) throws SAXException {
483             if (localName.equals("documentation")) {
484                 parsingDocumentation = true;
485             }
486         }
487 
488         @Override
489         public void endDocument() throws SAXException {
490             // do nothing
491         }
492 
493         @Override
494         public void endPrefixMapping(String prefix) throws SAXException {
495             // do nothing
496         }
497 
498         @Override
499         public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
500             // do nothing
501         }
502 
503         @Override
504         public void processingInstruction(String target, String data) throws SAXException {
505             // do nothing
506         }
507 
508         @Override
509         public void setDocumentLocator(Locator locator) {
510             // do nothing
511         }
512 
513         @Override
514         public void skippedEntity(String name) throws SAXException {
515             // do nothing
516         }
517 
518         @Override
519         public void startDocument() throws SAXException {
520             // do nothing
521         }
522 
523         @Override
524         public void startPrefixMapping(String prefix, String uri) throws SAXException {
525             // do nothing
526         }
527     }
528 
529     @Override
530     public String toString() {
531         return "ServiceContractModelPescXsdLoader{" +
532                 "xsdFileNames=" + xsdFileNames +
533                 ", services=" + services +
534                 ", serviceMethods=" + serviceMethods +
535                 ", xmlTypeMap=" + xmlTypeMap +
536                 ", messageStructures=" + messageStructures +
537                 '}';
538     }
539 }