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 }