001    /*
002     * Copyright 2010 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.osedu.org/licenses/ECL-2.0
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.XSAnnotation;
019    import com.sun.xml.xsom.XSComplexType;
020    import com.sun.xml.xsom.XSContentType;
021    import com.sun.xml.xsom.XSElementDecl;
022    import com.sun.xml.xsom.XSFacet;
023    import com.sun.xml.xsom.XSModelGroup;
024    import com.sun.xml.xsom.XSParticle;
025    import com.sun.xml.xsom.XSSchema;
026    import com.sun.xml.xsom.XSSchemaSet;
027    import com.sun.xml.xsom.XSSimpleType;
028    import com.sun.xml.xsom.XSTerm;
029    import com.sun.xml.xsom.XSType;
030    import com.sun.xml.xsom.parser.AnnotationContext;
031    import com.sun.xml.xsom.parser.AnnotationParser;
032    import com.sun.xml.xsom.parser.AnnotationParserFactory;
033    import com.sun.xml.xsom.parser.SchemaDocument;
034    import com.sun.xml.xsom.parser.XSOMParser;
035    import com.sun.xml.xsom.util.DomAnnotationParserFactory;
036    import java.io.File;
037    import java.io.IOException;
038    import java.util.ArrayList;
039    import java.util.LinkedHashMap;
040    import java.util.List;
041    import java.util.Map;
042    import java.util.Set;
043    import org.kuali.student.contract.model.MessageStructure;
044    import org.kuali.student.contract.model.Service;
045    import org.kuali.student.contract.model.ServiceContractModel;
046    import org.kuali.student.contract.model.ServiceMethod;
047    import org.kuali.student.contract.model.ServiceMethodReturnValue;
048    import org.kuali.student.contract.model.XmlType;
049    import org.xml.sax.Attributes;
050    import org.xml.sax.ContentHandler;
051    import org.xml.sax.EntityResolver;
052    import org.xml.sax.ErrorHandler;
053    import org.xml.sax.Locator;
054    import org.xml.sax.SAXException;
055    
056    /**
057     *
058     * @author nwright
059     */
060    public class ServiceContractModelPescXsdLoader implements
061            ServiceContractModel {
062    
063        private List<String> xsdFileNames;
064        private List<Service> services = null;
065        private List<ServiceMethod> serviceMethods = null;
066        private Map<String, XmlType> xmlTypeMap = null;
067        private List<MessageStructure> messageStructures;
068    
069        public ServiceContractModelPescXsdLoader(List<String> xsdFileNames) {
070            this.xsdFileNames = xsdFileNames;
071        }
072    
073        @Override
074        public List<ServiceMethod> getServiceMethods() {
075            if (this.serviceMethods == null) {
076                this.parse();
077            }
078            return this.serviceMethods;
079        }
080    
081        @Override
082        public List<String> getSourceNames() {
083            List<String> list = new ArrayList();
084            list.addAll(this.xsdFileNames);
085            return list;
086        }
087    
088        @Override
089        public List<Service> getServices() {
090            if (services == null) {
091                this.parse();
092            }
093            return services;
094        }
095    
096        @Override
097        public List<XmlType> getXmlTypes() {
098            if (xmlTypeMap == null) {
099                this.parse();
100            }
101            return new ArrayList(xmlTypeMap.values());
102        }
103    
104        @Override
105        public List<MessageStructure> getMessageStructures() {
106            if (messageStructures == null) {
107                this.parse();
108            }
109            return this.messageStructures;
110        }
111    
112        private void parse() {
113            System.out.println("ServiceContractModelQDoxLoader: Starting parse");
114            services = new ArrayList();
115            Service service = new Service();
116            services.add(service);
117            service.setKey("Pesc");
118            service.setName("PescService");
119            service.setComments("Derived from pesc CoreMain");
120            serviceMethods = new ArrayList();
121            ServiceMethod method = new ServiceMethod();
122            serviceMethods.add(method);
123            method.setName("dummy");
124            method.setDescription("Dummy method so validation won't fail");
125            method.setService("Pesc");
126            method.setParameters(new ArrayList());
127            ServiceMethodReturnValue rv = new ServiceMethodReturnValue();
128            rv.setType("void");
129            rv.setDescription("returns nothing");
130            method.setReturnValue(rv);
131            xmlTypeMap = new LinkedHashMap();
132            messageStructures = new ArrayList();
133    
134            XSOMParser parser = new XSOMParser(); 
135            parser.setAnnotationParser(new DomAnnotationParserFactory());
136    //        parser.setAnnotationParser(new XsdAnnotationParserFactory());        
137            try {
138                for (String xsdFileName : this.xsdFileNames) {
139    //                System.out.println("Parsing " + xsdFileName);
140                    parser.parse(new File(xsdFileName));
141                }
142            } catch (SAXException ex) {
143                throw new IllegalArgumentException(ex);
144            } catch (IOException ex) {
145                throw new IllegalArgumentException(ex);
146            }
147            XSSchemaSet schemas;
148            try {
149                schemas = parser.getResult();
150            } catch (SAXException ex) {
151                throw new IllegalArgumentException(ex);
152            }
153    //        for (XSSchema schema : schemas.getSchemas()) {
154    //            this.processSchema(schema);
155    //        }
156            Set<SchemaDocument> schemaDocuments = parser.getDocuments();
157            for (SchemaDocument doc : schemaDocuments) {
158                XSSchema schema = doc.getSchema();
159                this.processSchema(schema);
160            }
161        }
162    
163        private void processSchema(XSSchema schema) {
164            for (XSElementDecl element : schema.getElementDecls().values()) {
165                this.addElementDecl(element);
166            }
167            for (XSSimpleType st : schema.getSimpleTypes().values()) {
168    //            System.out.println("SimpleType =" + st.getName() + " namespace=" + st.getTargetNamespace());
169                addSimpleType(st);
170            }
171            for (XSComplexType ct : schema.getComplexTypes().values()) {
172                if (!shouldInclude(ct)) {
173    //                System.out.println("Skipping ComplexType =" + ct.getName() + " namespace=" + ct.getTargetNamespace());
174                    continue;
175                }
176    //            System.out.println("ComplexType =" + ct.getName() + " namespace=" + ct.getTargetNamespace());
177                addComplexType(ct);
178            }
179        }
180    
181        private boolean shouldInclude(XSComplexType ct) {
182    //        if (ct.getTargetNamespace().equals("urn:org:pesc:core:CoreMain:v1.8.0")) {
183            return true;
184    //        }
185    //        return false;
186        }
187    
188        private void addSimpleType(XSSimpleType simpleType) {
189            XmlType xmlType = xmlTypeMap.get(simpleType.getName());
190            if (xmlType != null) {
191    //            System.out.println("Already processed simple Type="
192    //                    + simpleType.getName());
193                return;
194            }
195            xmlType = new XmlType();
196            xmlTypeMap.put(simpleType.getName(), xmlType);
197            xmlType.setName(simpleType.getName());
198            xmlType.setDesc(calcMissing(calcDesc(simpleType.getAnnotation())));
199            xmlType.setComments("???");
200            xmlType.setExamples("???");
201            xmlType.setService("Pesc");
202            xmlType.setPrimitive("Primitive");
203        }
204    
205        private void addComplexType(XSComplexType complexType) {
206            addComplexType(complexType, complexType.getName());
207        }
208    
209        private void addElementDecl(XSElementDecl element) {
210            String name = element.getName();
211            XmlType xmlType = xmlTypeMap.get(name);
212            if (xmlType != null) {
213    //            System.out.println("Already processed element name=" + name);
214                return;
215            }
216    //        System.out.println("processing element=" + name);
217            xmlType = new XmlType();
218            xmlTypeMap.put(name, xmlType);
219            xmlType.setName(name);
220            xmlType.setDesc(calcMissing(calcDesc(element.getAnnotation())));
221            xmlType.setComments("???");
222            xmlType.setExamples("???");
223            xmlType.setService("Pesc");
224            xmlType.setPrimitive(XmlType.COMPLEX);
225            addMessageStructure(xmlType.getName(), element);
226        }
227    
228        private void addComplexType(XSComplexType complexType, String name) {
229            XmlType xmlType = xmlTypeMap.get(name);
230            if (xmlType != null) {
231    //            System.out.println("Already processed complex Type=" + name);
232                return;
233            }
234    //        System.out.println("processing complex type=" + name);
235            xmlType = new XmlType();
236            xmlTypeMap.put(name, xmlType);
237            xmlType.setName(name);
238            xmlType.setDesc(calcMissing(calcDesc(complexType.getAnnotation())));
239            xmlType.setComments("???");
240            xmlType.setExamples("???");
241            xmlType.setService("Pesc");
242            xmlType.setPrimitive(XmlType.COMPLEX);
243    
244            boolean found = false;
245            XSContentType contentType = complexType.getContentType();
246            XSParticle particle = contentType.asParticle();
247            if (particle != null) {
248                XSTerm term = particle.getTerm();
249                if (term.isModelGroup()) {
250                    XSModelGroup xsModelGroup = term.asModelGroup();
251                    XSParticle[] particles = xsModelGroup.getChildren();
252                    for (XSParticle p : particles) {
253                        XSTerm pterm = p.getTerm();
254                        if (pterm.isElementDecl()) { //xs:element inside complex type
255                            XSElementDecl element = pterm.asElementDecl();
256                            addMessageStructure(xmlType.getName(), element);
257                            found = true;
258                        }
259                    }
260                }
261            }
262    //        if (!found) {
263    //            System.out.println("*** WARNING *** Complex Type, " + xmlType.getName() + ", has no message structure fields");
264    //        }
265        }
266    
267        private String calcMissing(String str) {
268            if (str == null) {
269                return "???";
270            }
271            if (str.trim().isEmpty()) {
272                return "???";
273            }
274            return str;
275        }
276    
277        private String calcDesc(XSAnnotation annotation) {
278            if (annotation == null) {
279                return null;
280            }
281            if (annotation.getAnnotation() == null) {
282                return null;
283            }
284            return annotation.getAnnotation().toString();
285        }
286    
287        private void addMessageStructure(String xmlObject, XSElementDecl element) {
288            MessageStructure ms = new MessageStructure();
289            this.messageStructures.add(ms);
290            ms.setXmlObject(xmlObject);
291            ms.setShortName(element.getName());
292            ms.setName("???");
293            ms.setId(xmlObject + "." + ms.getShortName());
294            ms.setType(calcType(element, xmlObject + "" + ms.getShortName()));
295            ms.setDescription(calcMissing(calcDesc(element.getAnnotation())));
296    //        System.out.println("Element " + ms.getId() + " " + ms.getType());
297            ms.setRequired(calcRequired(element));
298            ms.setCardinality(calcCardinality(element));
299        }
300    
301        private String calcType(XSElementDecl element, String inLinePrefix) {
302            String type = calcType(element.getType(), inLinePrefix);
303            if (type != null) {
304                return type;
305            }
306            return type;
307        }
308    
309        private String calcType(XSType xsType, String inLinePrefix) {
310            if (xsType.isSimpleType()) {
311                XSSimpleType st = xsType.asSimpleType();
312                return st.getBaseType().getName();
313    //   if (st.isRestriction ())
314    //   {
315    //    XSRestrictionSimpleType res = st.asRestriction ();
316    //    return res.getBaseType ().getName ();
317    //   }
318            }
319            String type = xsType.getName();
320            if (type != null) {
321                return type;
322            }
323            if ((xsType.isComplexType())) {
324                XSComplexType ct = xsType.asComplexType();
325                String baseType = ct.getBaseType().getName();
326                if (baseType.equals("anyType")) {
327                    baseType = "";
328                }
329                String inlineTypeName = inLinePrefix + baseType;
330                addComplexType(ct, inlineTypeName);
331                return inlineTypeName;
332            }
333            throw new IllegalArgumentException("cannot calculate the type of the field " + inLinePrefix);
334        }
335    
336        private String calcRequired(XSElementDecl element) {
337            // TODO: isNillable seems to ALWAYS be true so... figure out another way        
338    //        if (element.isNillable()) {
339    //            return null;
340    //        }
341    //        return "Required";
342            // TODO: facets only hold min/maxLength not min/maxOccurs find out where min/maxOccurs is parsed into        
343    //        String minOccurs = this.getFacetValue(element, "minOccurs");
344    //        if (minOccurs == null) {
345    //            return "No";
346    //        }
347    //        if (minOccurs.equals("0")) {
348    //            return "No";
349    //        }
350    //        System.out.println(element.getName() + " has a minOccurs that is " + minOccurs);
351    //        return "Required";
352            return "???";
353        }
354    
355        private String calcCardinality(XSElementDecl element) {
356    //        if (element.getName().equals ("NoteMessage")) {
357    //            System.out.println ("start debugging because NoteMessage has maxOccurs=unbounded");
358    //        }
359            if (this.getIsRepeated(element)) {
360                return "Many";
361            }
362            // TODO: facets only hold min/maxLength not min/maxOccurs find out where min/maxOccurs is parsed into
363    //        String maxOccurs = this.getFacetValue(element, "maxOccurs");
364    //        if (maxOccurs == null) {
365    //            return "One";
366    //        }
367    //        if (maxOccurs.equals("unbounded")) {
368    //            return "Many";
369    //        }
370    //        System.out.println(element.getName() + " has a maxOccurs that is " + maxOccurs);
371    //        return "Many";
372            return null;
373        }
374        
375        private boolean getIsRepeated (XSElementDecl element) {
376            XSType type = element.getType();
377            if (type == null) {
378                return false;
379            }
380            if (type.isComplexType()) {
381                XSContentType contentType = type.asComplexType().getContentType();
382                if (contentType == null) {
383                    return false;
384                }
385                XSParticle particle = contentType.asParticle();
386                if (particle == null) {
387                    return false;
388                }
389                particle.isRepeated();
390            }
391    //        if (type.isSimpleType())
392            return false;
393        }
394    
395        private String getFacetValue(XSElementDecl element, String name) {
396            XSType type = element.getType();
397            if (type == null) {
398                return null;
399            }
400            if (type.isSimpleType()) {
401                XSFacet facet = type.asSimpleType().getFacet(name);
402                if (facet == null) {
403                    return null;
404                }
405                return facet.getValue().toString();
406            }
407            if (type.isComplexType()) {
408                XSContentType contentType = type.asComplexType().getContentType();
409                if (contentType == null) {
410                    return null;
411                }
412                XSSimpleType simpleType = contentType.asSimpleType();
413                if (simpleType == null) {
414                    return null;
415                }
416                XSFacet facet = simpleType.getFacet(name);
417                if (facet == null) {
418                    return null;
419                }
420                return facet.getValue().toString();
421            }
422            return null;
423        }
424    
425        /**
426         * helper class
427         */
428        private static class XsdAnnotationParserFactory implements AnnotationParserFactory {
429    
430            @Override
431            public AnnotationParser create() {
432                return new XsdAnnotationParser();
433            }
434        }
435    
436        /** 
437         * helper class
438         */
439        private static class XsdAnnotationParser extends AnnotationParser {
440    
441            private StringBuilder documentation = new StringBuilder();
442    
443            @Override
444            public ContentHandler getContentHandler(AnnotationContext context,
445                    String parentElementName,
446                    ErrorHandler handler,
447                    EntityResolver resolver) {
448                return new XsdContentHandler(documentation);
449            }
450    
451            @Override
452            public Object getResult(Object existing) {
453                return documentation.toString().trim();
454            }
455        }
456    
457        /**
458         * helper class
459         */
460        private static class XsdContentHandler implements ContentHandler {
461    
462            private StringBuilder documentation;
463    
464            public XsdContentHandler(StringBuilder documentation) {
465                this.documentation = documentation;
466            }
467            private boolean parsingDocumentation = false;
468    
469            @Override
470            public void characters(char[] ch, int start, int length)
471                    throws SAXException {
472                if (parsingDocumentation) {
473                    documentation.append(ch, start, length);
474                }
475            }
476    
477            @Override
478            public void endElement(String uri, String localName, String name)
479                    throws SAXException {
480                if (localName.equals("documentation")) {
481                    parsingDocumentation = false;
482                }
483            }
484    
485            @Override
486            public void startElement(String uri, String localName, String name,
487                    Attributes atts) throws SAXException {
488                if (localName.equals("documentation")) {
489                    parsingDocumentation = true;
490                }
491            }
492    
493            @Override
494            public void endDocument() throws SAXException {
495                // do nothing
496            }
497    
498            @Override
499            public void endPrefixMapping(String prefix) throws SAXException {
500                // do nothing
501            }
502    
503            @Override
504            public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
505                // do nothing
506            }
507    
508            @Override
509            public void processingInstruction(String target, String data) throws SAXException {
510                // do nothing
511            }
512    
513            @Override
514            public void setDocumentLocator(Locator locator) {
515                // do nothing
516            }
517    
518            @Override
519            public void skippedEntity(String name) throws SAXException {
520                // do nothing
521            }
522    
523            @Override
524            public void startDocument() throws SAXException {
525                // do nothing
526            }
527    
528            @Override
529            public void startPrefixMapping(String prefix, String uri) throws SAXException {
530                // do nothing
531            }
532        }
533    }