001/**
002 * Copyright 2004-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.student.contract.model.impl;
017
018import com.sun.xml.xsom.*;
019import com.sun.xml.xsom.parser.AnnotationContext;
020import com.sun.xml.xsom.parser.AnnotationParser;
021import com.sun.xml.xsom.parser.AnnotationParserFactory;
022import com.sun.xml.xsom.parser.SchemaDocument;
023import com.sun.xml.xsom.parser.XSOMParser;
024import com.sun.xml.xsom.util.DomAnnotationParserFactory;
025
026import org.kuali.student.contract.model.MessageStructure;
027import org.kuali.student.contract.model.Service;
028import org.kuali.student.contract.model.ServiceContractModel;
029import org.kuali.student.contract.model.ServiceMethod;
030import org.kuali.student.contract.model.ServiceMethodReturnValue;
031import org.kuali.student.contract.model.XmlType;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034import org.xml.sax.Attributes;
035import org.xml.sax.ContentHandler;
036import org.xml.sax.EntityResolver;
037import org.xml.sax.ErrorHandler;
038import org.xml.sax.Locator;
039import org.xml.sax.SAXException;
040
041import java.io.File;
042import java.io.IOException;
043import java.util.ArrayList;
044import java.util.LinkedHashMap;
045import java.util.List;
046import java.util.Map;
047import java.util.Set;
048
049/**
050 *
051 * @author nwright
052 */
053public class ServiceContractModelPescXsdLoader implements
054        ServiceContractModel {
055
056    private static Logger log = LoggerFactory.getLogger(ServiceContractModelPescXsdLoader.class);
057    
058    private List<String> xsdFileNames;
059    private List<Service> services = null;
060    private List<ServiceMethod> serviceMethods = null;
061    private Map<String, XmlType> xmlTypeMap = null;
062    private List<MessageStructure> messageStructures;
063
064    public ServiceContractModelPescXsdLoader(List<String> xsdFileNames) {
065        this.xsdFileNames = xsdFileNames;
066    }
067
068    @Override
069    public List<ServiceMethod> getServiceMethods() {
070        if (this.serviceMethods == null) {
071            this.parse();
072        }
073        return this.serviceMethods;
074    }
075
076    @Override
077    public List<String> getSourceNames() {
078        List<String> list = new ArrayList();
079        list.addAll(this.xsdFileNames);
080        return list;
081    }
082
083    @Override
084    public List<Service> getServices() {
085        if (services == null) {
086            this.parse();
087        }
088        return services;
089    }
090
091    @Override
092    public List<XmlType> getXmlTypes() {
093        if (xmlTypeMap == null) {
094            this.parse();
095        }
096        return new ArrayList(xmlTypeMap.values());
097    }
098
099    @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}