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 }