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     */
016    package org.kuali.student.contract.model.impl;
017    
018    import com.sun.xml.xsom.*;
019    import com.sun.xml.xsom.parser.AnnotationContext;
020    import com.sun.xml.xsom.parser.AnnotationParser;
021    import com.sun.xml.xsom.parser.AnnotationParserFactory;
022    import com.sun.xml.xsom.parser.SchemaDocument;
023    import com.sun.xml.xsom.parser.XSOMParser;
024    import com.sun.xml.xsom.util.DomAnnotationParserFactory;
025    
026    import org.kuali.student.contract.model.MessageStructure;
027    import org.kuali.student.contract.model.Service;
028    import org.kuali.student.contract.model.ServiceContractModel;
029    import org.kuali.student.contract.model.ServiceMethod;
030    import org.kuali.student.contract.model.ServiceMethodReturnValue;
031    import org.kuali.student.contract.model.XmlType;
032    import org.slf4j.Logger;
033    import org.slf4j.LoggerFactory;
034    import org.xml.sax.Attributes;
035    import org.xml.sax.ContentHandler;
036    import org.xml.sax.EntityResolver;
037    import org.xml.sax.ErrorHandler;
038    import org.xml.sax.Locator;
039    import org.xml.sax.SAXException;
040    
041    import java.io.File;
042    import java.io.IOException;
043    import java.util.ArrayList;
044    import java.util.LinkedHashMap;
045    import java.util.List;
046    import java.util.Map;
047    import java.util.Set;
048    
049    /**
050     *
051     * @author nwright
052     */
053    public 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    }