001 /**
002 * Copyright 2004-2013 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 }