001 /* 002 * Copyright 2011 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.datadictionary.util; 017 018 import java.io.File; 019 import java.io.FileNotFoundException; 020 import java.io.FileOutputStream; 021 import java.io.PrintStream; 022 import java.text.BreakIterator; 023 import java.util.ArrayList; 024 import java.util.Collections; 025 import java.util.Date; 026 import java.util.HashMap; 027 import java.util.List; 028 import java.util.Map; 029 import java.util.Stack; 030 import org.apache.commons.lang.StringEscapeUtils; 031 032 import org.kuali.student.contract.model.MessageStructure; 033 import org.kuali.student.contract.model.ServiceContractModel; 034 import org.kuali.student.contract.model.XmlType; 035 import org.kuali.student.contract.model.util.ModelFinder; 036 import org.kuali.student.contract.writer.XmlWriter; 037 038 /** 039 * 040 * @author nwright 041 */ 042 public class KradDictionaryCreator { 043 044 private ServiceContractModel model; 045 private ModelFinder finder; 046 private String directory; 047 private String className; 048 private XmlType xmlType; 049 private XmlWriter gwriter; 050 private XmlWriter mwriter; 051 private List<MessageStructure> messageStructures; 052 private boolean writeManual; 053 private boolean writeGenerated; 054 055 public KradDictionaryCreator(String directory, 056 ServiceContractModel model, String className, boolean writeManual, boolean writeGenerated) { 057 this.directory = directory; 058 this.model = model; 059 this.finder = new ModelFinder(this.model); 060 this.className = className; 061 this.xmlType = this.finder.findXmlType(className); 062 if (xmlType == null) { 063 throw new IllegalArgumentException(className); 064 } 065 this.messageStructures = this.finder.findMessageStructures(className); 066 this.writeManual = writeManual; 067 this.writeGenerated = writeGenerated; 068 // if (this.messageStructures.isEmpty()) { 069 // throw new IllegalStateException(className); 070 // } 071 } 072 073 public void write() { 074 this.initXmlWriters(); 075 if (writeGenerated) { 076 this.writeSpringHeaderOpen(gwriter); 077 this.writeWarning(gwriter); 078 this.writeGeneratedImports(gwriter); 079 this.writeGeneratedObjectStructure(gwriter); 080 this.writeSpringHeaderClose(gwriter); 081 } 082 if (this.writeManual) { 083 this.writeSpringHeaderOpen(mwriter); 084 this.writeNote(mwriter); 085 this.writeManualImports(mwriter); 086 this.writeManualObjectStructure(mwriter); 087 this.writeSpringHeaderClose(mwriter); 088 } 089 } 090 091 private void initXmlWriters() { 092 String generatedFileName = "/ks-" + initUpper(className) + "-dictionary-generated.xml"; 093 String manualFileName = "/ks-" + initUpper(className) + "-dictionary.xml"; 094 095 File dir = new File(this.directory); 096 //System.out.indentPrintln ("Writing java class: " + fileName + " to " + dir.getAbsolutePath ()); 097 098 if (!dir.exists()) { 099 if (!dir.mkdirs()) { 100 throw new IllegalStateException("Could not create directory " 101 + this.directory); 102 } 103 } 104 105 if (writeGenerated) { 106 try { 107 PrintStream out = new PrintStream(new FileOutputStream(this.directory + "/" + generatedFileName, false)); 108 this.gwriter = new XmlWriter(out, 0); 109 } catch (FileNotFoundException ex) { 110 throw new IllegalStateException(ex); 111 } 112 } 113 if (this.writeManual) { 114 try { 115 PrintStream out = new PrintStream(new FileOutputStream(this.directory + "/" + manualFileName, false)); 116 this.mwriter = new XmlWriter(out, 0); 117 } catch (FileNotFoundException ex) { 118 throw new IllegalStateException(ex); 119 } 120 } 121 } 122 123 private static String initLower(String str) { 124 if (str == null) { 125 return null; 126 } 127 if (str.length() == 0) { 128 return str; 129 } 130 if (str.length() == 1) { 131 return str.toLowerCase(); 132 } 133 return str.substring(0, 1).toLowerCase() + str.substring(1); 134 } 135 136 private static String initUpper(String str) { 137 if (str == null) { 138 return null; 139 } 140 if (str.length() == 0) { 141 return str; 142 } 143 if (str.length() == 1) { 144 return str.toUpperCase(); 145 } 146 return str.substring(0, 1).toUpperCase() + str.substring(1); 147 } 148 149 private void writeSpringHeaderClose(XmlWriter out) { 150 out.decrementIndent(); 151 out.indentPrintln("</beans>"); 152 } 153 154 private void writeSpringHeaderOpen(XmlWriter out) { 155 out.indentPrintln("<!--"); 156 out.indentPrintln(" Copyright 2011 The Kuali Foundation"); 157 out.println(""); 158 out.indentPrintln(" Licensed under the Educational Community License, Version 2.0 (the \"License\");"); 159 out.indentPrintln(" you may not use this file except in compliance with the License."); 160 out.indentPrintln(" You may obtain a copy of the License at"); 161 out.indentPrintln(""); 162 out.indentPrintln(" http://www.opensource.org/licenses/ecl2.php"); 163 out.println(""); 164 out.indentPrintln(" Unless required by applicable law or agreed to in writing, software"); 165 out.indentPrintln(" distributed under the License is distributed on an \"AS IS\" BASIS,"); 166 out.indentPrintln(" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied."); 167 out.indentPrintln(" See the License for the specific language governing permissions and"); 168 out.indentPrintln(" limitations under the License."); 169 out.indentPrintln("-->"); 170 out.indentPrintln("<beans xmlns=\"http://www.springframework.org/schema/beans\""); 171 out.indentPrintln("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""); 172 out.indentPrintln("xsi:schemaLocation=\"" 173 + "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" + "\">"); 174 out.println(""); 175 out.incrementIndent(); 176 } 177 178 private void writeWarning(XmlWriter out) { 179 out.println(""); 180 out.indentPrintln("<!-- ********************************************************"); 181 out.incrementIndent(); 182 out.indentPrintln(" WARNING "); 183 out.indentPrintln(" DO NOT UPDATE THIS FILE MANUALLY"); 184 out.indentPrintln("This dictionary file was automatically generated on " + new Date()); 185 out.indentPrintln("The DictionaryGeneratorMojo reads the service contract "); 186 out.indentPrintln("and creates these ks-XXXX-dictionary-generated.xml files."); 187 out.println(""); 188 out.indentPrintln("If this file is out of sync with the contract re-run the mojo."); 189 out.println(""); 190 out.indentPrintln("To add additional constraints or change these default values (perhaps"); 191 out.indentPrintln("because the generator is not perfect) please update the corresponding "); 192 out.indentPrintln("ks-XXXX-dictionary.xml instead of this one."); 193 out.decrementIndent(); 194 out.indentPrintln("************************************************************* -->"); 195 } 196 197 private void writeNote(XmlWriter out) { 198 out.println(""); 199 out.indentPrintln("<!-- ********************************************************"); 200 out.incrementIndent(); 201 out.indentPrintln(" NOTE"); 202 out.indentPrintln(" THIS FILE WAS INTENDED TO BE MODIFIED"); 203 out.println(""); 204 out.indentPrintln("While this file was originally generated on " + new Date() + ", it"); 205 out.indentPrintln("was intended to be subsequently modified by hand."); 206 out.indentPrintln("It imports a corresponding ks-XXXX-dictionary-generated.xml file, "); 207 out.indentPrintln("that was also automatically generated by the ContractDocMojo."); 208 out.indentPrintln("This file gives you the ability to layer on addiditional definitions and constrints"); 209 out.indentPrintln("that are not/cannot be generated simply by reading the service contract."); 210 out.println(""); 211 out.indentPrintln("The goal of this file is to be able to re-generate the corresponding"); 212 out.indentPrintln("ks-XXXX-dictionary-generated.xml file without affecting these manually entered additions"); 213 out.indentPrintln("that are encoded here."); 214 out.decrementIndent(); 215 out.indentPrintln("************************************************************* -->"); 216 } 217 218 private void writeGeneratedImports(XmlWriter out) { 219 // don't actually generate imports because it slows down the springbean generation 220 out.writeCommentBox("The following file is required for this file to load:\n ks-base-dictionary.xml\nplus any of its dependencies"); 221 out.indentPrintln("<import resource=\"classpath:ks-base-dictionary.xml\"/>"); 222 // TODO: only write out the ones that are used in this structure 223 // out.indentPrintln("<import resource=\"classpath:ks-RichTextInfo-dictionary.xml\"/>"); 224 // out.indentPrintln("<import resource=\"classpath:ks-MetaInfo-dictionary.xml\"/>"); 225 } 226 227 private void writeManualImports(XmlWriter out) { 228 out.writeComment("The following file gets generated during the build and gets put into the target/classes directory"); 229 out.indentPrintln("<import resource=\"classpath:ks-" + initUpper(className) + "-dictionary-generated.xml\"/>"); 230 List<String> imports = this.getComplexSubObjectsThatAreLists(); 231 if (!imports.isEmpty()) { 232 out.writeComment("TODO: remove these once the jira about lists of complex objects gets fixed"); 233 for (String impName : imports) { 234 out.indentPrintln("<import resource=\"classpath:ks-" + initUpper(impName) + "-dictionary.xml\"/>"); 235 } 236 } 237 } 238 239 private List<String> getComplexSubObjectsThatAreLists() { 240 List<String> list = new ArrayList(); 241 for (MessageStructure ms : this.messageStructures) { 242 switch (this.calculateCategory(ms)) { 243 case LIST_OF_COMPLEX: 244 list.add(this.stripListOffEnd(ms.getType())); 245 } 246 } 247 return list; 248 } 249 250 private String stripListOffEnd(String name) { 251 if (name.endsWith("List")) { 252 return name.substring(0, name.length() - "List".length()); 253 } 254 return name; 255 } 256 257 private String calcDataObjectClass(XmlType xmlType) { 258 // this is those packages that are not included in the sources for Enroll-API for the model 259 // so the package is null but the name is the full package spec 260 if (xmlType.getJavaPackage() == null || xmlType.getJavaPackage().isEmpty()) { 261 return xmlType.getName(); 262 } 263 return xmlType.getJavaPackage() + "." + initUpper(xmlType.getName()); 264 } 265 266 private void writeGeneratedObjectStructure(XmlWriter out) { 267 //Step 1, create the abstract structure 268 out.println(""); 269 out.indentPrintln("<!-- " + className + "-->"); 270 out.indentPrintln("<bean id=\"" + initUpper(className) + "-generated\" abstract=\"true\" parent=\"DataObjectEntry\">"); 271 out.incrementIndent(); 272 writeProperty("name", initLower(className), out); 273 writeProperty("dataObjectClass", calcDataObjectClass(xmlType), out); 274 writeProperty("objectLabel", calcObjectLabel(), out); 275 writePropertyValue("objectDescription", xmlType.getDesc(), out); 276 String titleAttribute = calcTitleAttribute(); 277 if (titleAttribute != null) { 278 writeProperty("titleAttribute", titleAttribute, out); 279 } 280 out.indentPrintln("<property name=\"primaryKeys\">"); 281 List<String> pks = calcPrimaryKeys(); 282 if (pks != null && !pks.isEmpty()) { 283 out.incrementIndent(); 284 out.indentPrintln("<list>"); 285 out.incrementIndent(); 286 for (String pk : pks) { 287 addValue(pk); 288 } 289 out.decrementIndent(); 290 out.indentPrintln("</list>"); 291 out.decrementIndent(); 292 } 293 out.indentPrintln("</property>"); 294 295 this.writeAllGeneratedAttributeRefBeans(className, null, new Stack<String>(), this.messageStructures, out); 296 297 out.indentPrintln("</bean>"); 298 299 //Step 2, loop through attributes 300 this.writeGeneratedAttributeDefinitions(className, null, new Stack<String>(), this.messageStructures, out); 301 } 302 303 private void writeAllGeneratedAttributeRefBeans(String currentClassName, 304 String parentName, 305 Stack<String> parents, 306 List<MessageStructure> fields, 307 XmlWriter out) { 308 if (parents.contains(currentClassName)) { 309 return; 310 } 311 out.println(""); 312 out.indentPrintln("<property name=\"attributes\">"); 313 out.incrementIndent(); 314 out.indentPrintln("<list>"); 315 out.incrementIndent(); 316 this.writeGeneratedAttributeRefBeans(currentClassName, parentName, parents, fields, out, Category.PRIMITIVE); 317 out.decrementIndent(); 318 out.indentPrintln("</list>"); 319 out.decrementIndent(); 320 out.indentPrintln("</property>"); 321 322 out.println(""); 323 out.indentPrintln("<property name=\"complexAttributes\">"); 324 out.incrementIndent(); 325 out.indentPrintln("<list>"); 326 out.incrementIndent(); 327 this.writeGeneratedAttributeRefBeans(currentClassName, parentName, parents, fields, out, Category.COMPLEX); 328 out.decrementIndent(); 329 out.indentPrintln("</list>"); 330 out.decrementIndent(); 331 out.indentPrintln("</property>"); 332 333 out.println(""); 334 out.indentPrintln("<property name=\"collections\">"); 335 out.incrementIndent(); 336 out.indentPrintln("<list>"); 337 out.incrementIndent(); 338 this.writeGeneratedAttributeRefBeans(currentClassName, parentName, parents, fields, out, Category.LIST_OF_COMPLEX); 339 out.decrementIndent(); 340 out.indentPrintln("</list>"); 341 out.decrementIndent(); 342 out.indentPrintln("</property>"); 343 out.decrementIndent(); 344 } 345 346 private void addValue(String value) { 347 gwriter.indentPrintln("<value>" + value + "</value>"); 348 } 349 350 private String calcObjectLabel() { 351 String label = this.className; 352 if (label.endsWith("Info")) { 353 label = label.substring(0, label.length() - "Info".length()); 354 } 355 label = initUpper(label); 356 return splitCamelCase(label); 357 } 358 359 // got this from http://stackoverflow.com/questions/2559759/how-do-i-convert-camelcase-into-human-readable-names-in-java 360 private static String splitCamelCase(String s) { 361 if (s == null) { 362 return null; 363 } 364 return s.replaceAll( 365 String.format("%s|%s|%s", 366 "(?<=[A-Z])(?=[A-Z][a-z])", 367 "(?<=[^A-Z])(?=[A-Z])", 368 "(?<=[A-Za-z])(?=[^A-Za-z])"), 369 " "); 370 } 371 372 private enum Category { 373 374 PRIMITIVE, COMPLEX, LIST_OF_COMPLEX, LIST_OF_PRIMITIVE, DYNAMIC_ATTRIBUTE 375 }; 376 377 private Category calculateCategory(MessageStructure ms) { 378 if (ms.getShortName().equals("attributes")) { 379 return Category.DYNAMIC_ATTRIBUTE; 380 } 381 String childXmlTypeName = this.stripListOffEnd(ms.getType()); 382 XmlType childXmlType = this.finder.findXmlType(childXmlTypeName); 383 if (childXmlType == null) { 384 throw new IllegalStateException(childXmlTypeName); 385 } 386 if (ms.getType().endsWith("List")) { 387 if (childXmlType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) { 388 return Category.LIST_OF_COMPLEX; 389 } 390 return Category.LIST_OF_PRIMITIVE; 391 } 392 if (childXmlType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) { 393 return Category.COMPLEX; 394 } 395 return Category.PRIMITIVE; 396 } 397 398 private void writeGeneratedAttributeRefBeans(String currentClass, 399 String parentName, 400 Stack<String> parents, 401 List<MessageStructure> fields, 402 XmlWriter out, 403 Category filter) { 404 if (parents.contains(currentClass)) { 405 return; 406 } 407 for (MessageStructure ms : fields) { 408 Category category = this.calculateCategory(ms); 409 if (!category.equals(filter)) { 410 continue; 411 } 412 String childXmlTypeName = this.stripListOffEnd(ms.getType()); 413 XmlType childXmlType = this.finder.findXmlType(childXmlTypeName); 414 if (childXmlType == null) { 415 throw new IllegalStateException(childXmlTypeName); 416 } 417 String pathName = calcPathName(parentName, ms); 418 String beanName = calcBeanName(pathName); 419 // TODO: change this once they fix the list of complex jira 420 // if (filter.equals(Category.LIST_OF_COMPLEX)) { 421 // beanName = initUpper(childXmlTypeName); 422 // } 423 out.indentPrintln("<ref bean=\"" + beanName + "\"/>"); 424 // 425 // // Add complex sub-types fields 426 // switch (category) { 427 // case COMPLEX: 428 // case LIST_OF_COMPLEX: 429 // parents.push(currentClass); 430 // List<MessageStructure> childFields = this.finder.findMessageStructures(childXmlTypeName); 431 // writeGeneratedAttributeRefBeans(childXmlTypeName, pathName, parents, childFields, out, filter); 432 // parents.pop(); 433 // } 434 } 435 } 436 437 private void writeGeneratedAttributeDefinitions(String currentClassName, 438 String parentName, 439 Stack<String> parents, 440 List<MessageStructure> fields, 441 XmlWriter out) { 442 if (parents.contains(currentClassName)) { 443 return; 444 } 445 for (MessageStructure ms : fields) { 446 Category category = this.calculateCategory(ms); 447 switch (category) { 448 case DYNAMIC_ATTRIBUTE: 449 continue; 450 } 451 String pathName = calcPathName(parentName, ms); 452 String beanName = calcBeanName(pathName); 453 String childXmlTypeName = this.stripListOffEnd(ms.getType()); 454 XmlType childXmlType = this.finder.findXmlType(childXmlTypeName); 455 if (childXmlType == null) { 456 throw new IllegalStateException(childXmlTypeName); 457 } 458 writeGeneratedAttributeDefinition(currentClassName, parentName, parents, ms, out); 459 460 // Add complex sub-types fields 461 switch (category) { 462 case COMPLEX: 463 // case LIST_OF_COMPLEX: 464 parents.push(currentClassName); 465 List<MessageStructure> childFields = this.finder.findMessageStructures(childXmlTypeName); 466 writeGeneratedAttributeDefinitions(childXmlTypeName, pathName, parents, childFields, out); 467 parents.pop(); 468 } 469 } 470 } 471 472 private boolean shouldWriteDetails(MessageStructure ms) { 473 if (predefinedFieldMap.get(ms.getShortName().toLowerCase()) == null) { 474 return true; 475 } 476 if (ms.isOverriden()) { 477 return true; 478 } 479 // don't write out details for predefined fields that have not been overridden 480 return false; 481 } 482 483 private void writeGeneratedAttributeDefinition(String currentClassName, String parentName, Stack<String> parents, MessageStructure ms, XmlWriter out) { 484 485 //Create the abstract field 486 String pathName = calcPathName(parentName, ms); 487 String beanName = calcBeanName(pathName); 488 String baseKualiParentBean = this.calcBaseKualiParentBean(ms); 489 out.println(""); 490 out.indentPrintln("<bean id=\"" + beanName + "-generated\" abstract=\"true\" parent=\"" + baseKualiParentBean + "\">"); 491 out.incrementIndent(); 492 writeProperty("name", calcSimpleName(ms), out); 493 switch (this.calculateCategory(ms)) { 494 case PRIMITIVE: 495 if (this.shouldWriteDetails(ms)) { 496 writeProperty("shortLabel", calcShortLabel(ms), out); 497 writePropertyValue("summary", calcSummary(ms), out); 498 writeProperty("label", calcLabel(ms), out); 499 writePropertyValue("description", calcDescription(ms), out); 500 if (this.calcReadOnly(ms)) { 501 this.writeReadOnlyAttributeSecurity(out); 502 } 503 writeProperty("required", calcRequired(ms), out); 504 } 505 break; 506 case LIST_OF_PRIMITIVE: 507 // TODO: deal with once https://jira.kuali.org/browse/KULRICE-5439 is fixed 508 // for now treat the same as List of Complex, i.e. CollectionDefinition 509 writeProperty("shortLabel", calcShortLabel(ms), out); 510 writePropertyValue("summary", calcSummary(ms), out); 511 writeProperty("label", calcLabel(ms), out); 512 writeProperty("elementLabel", calcElementLabel(ms), out); 513 writePropertyValue("description", calcDescription(ms), out); 514 writeProperty("minOccurs", calcMinOccurs(ms), out); 515 writeProperty("dataObjectClass", calcDataObjectClass(ms), out); 516 break; 517 case LIST_OF_COMPLEX: 518 writeProperty("shortLabel", calcShortLabel(ms), out); 519 writePropertyValue("summary", calcSummary(ms), out); 520 writeProperty("label", calcLabel(ms), out); 521 writeProperty("elementLabel", calcElementLabel(ms), out); 522 writePropertyValue("description", calcDescription(ms), out); 523 writeProperty("minOccurs", calcMinOccurs(ms), out); 524 writeProperty("dataObjectClass", calcDataObjectClass(ms), out); 525 break; 526 case COMPLEX: 527 writeProperty("shortLabel", calcShortLabel(ms), out); 528 writePropertyValue("summary", calcSummary(ms), out); 529 writeProperty("label", calcLabel(ms), out); 530 writePropertyValue("description", calcDescription(ms), out); 531 writeProperty("required", calcRequired(ms), out); 532 writePropertyStart("dataObjectEntry", out); 533 out.indentPrintln("<bean parent=\"DataObjectEntry\">"); 534 out.incrementIndent(); 535 writeProperty("name", calcSimpleName(ms), out); 536 writeProperty("dataObjectClass", calcDataObjectClass(ms), out); 537 writeProperty("objectLabel", calcLabel(ms), out); 538 writePropertyValue("objectDescription", calcDescription(ms), out); 539 540 String childXmlTypeName = this.stripListOffEnd(ms.getType()); 541 List<MessageStructure> childFields = this.finder.findMessageStructures(childXmlTypeName); 542 writeAllGeneratedAttributeRefBeans(childXmlTypeName, pathName, parents, childFields, out); 543 out.indentPrintln("</bean>"); 544 writePropertyEnd(out); 545 break; 546 default: 547 throw new IllegalStateException("unknown/unhandled type " + ms.getId()); 548 } 549 out.decrementIndent(); 550 // TODO: implement maxoccurs 551 // if (isList(pd)) { 552 // addProperty("maxOccurs", "" + DictionaryConstants.UNBOUNDED, s); 553 // } 554 out.indentPrintln("</bean>"); 555 } 556 557 private String calcDataObjectClass(MessageStructure ms) { 558 XmlType msType = this.finder.findXmlType(this.stripListOffEnd(ms.getType())); 559 return this.calcDataObjectClass(msType); 560 } 561 562 private String calcBeanName(String pathName) { 563 return initUpper(className) + "." + pathName; 564 } 565 566 private String calcPathName(String parentName, MessageStructure ms) { 567 String name = this.calcSimpleName(ms); 568 if (parentName == null) { 569 return name; 570 } 571 return parentName + "." + name; 572 } 573 574 private String calcSimpleName(MessageStructure ms) { 575 String name = initLower(ms.getShortName()); 576 return name; 577 } 578 579 private boolean calcReadOnly(MessageStructure ms) { 580 if (ms.getReadOnly() == null) { 581 return false; 582 } 583 return true; 584 } 585 586 private void writeReadOnlyAttributeSecurity(XmlWriter out) { 587 out.indentPrintln("<!-- commented out until KRAD bug gets fixed that requires mask to also be entered"); 588 out.indentPrintln("<property name=\"attributeSecurity\">"); 589 out.indentPrintln("<ref bean=\"BaseKuali.readOnlyAttributeSecurity\"/>"); 590 out.indentPrintln("</property>"); 591 out.indentPrintln("-->"); 592 } 593 594 private String calcElementLabel(MessageStructure ms) { 595 String label = this.calcShortLabel(ms); 596 if (label.endsWith("s")) { 597 label = label.substring(0, label.length() - 1); 598 } 599 return label; 600 } 601 602 private String calcShortLabel(MessageStructure ms) { 603 return this.splitCamelCase(initUpper(ms.getShortName())); 604 } 605 606 private String calcLabel(MessageStructure ms) { 607 return ms.getName(); 608 } 609 610 private String calcSummary(MessageStructure ms) { 611 BreakIterator bi = BreakIterator.getSentenceInstance(); 612 String description = ms.getDescription(); 613 if (description == null) { 614 return "???"; 615 } 616 bi.setText(ms.getDescription()); 617 // one big sentence 618 if (bi.next() == BreakIterator.DONE) { 619 return ms.getDescription(); 620 } 621 String firstSentence = description.substring(0, bi.current()); 622 return firstSentence; 623 } 624 625 private String calcDescription(MessageStructure ms) { 626 return ms.getDescription(); 627 } 628 629 private String calcMinOccurs(MessageStructure ms) { 630 String required = this.calcRequired(ms); 631 if ("false".equals(required)) { 632 return "0"; 633 } 634 return "1"; 635 } 636 637 private String calcRequired(MessageStructure ms) { 638 if (ms.getRequired() == null) { 639 return "false"; 640 } 641 if (ms.getRequired().equalsIgnoreCase("Required")) { 642 return "true"; 643 } 644 // TODO: figure out what to do if it is qualified like "required on update" 645 return "false"; 646 } 647 648 private void writeManualObjectStructure(XmlWriter out) { 649 //Step 1, create the parent bean 650 out.println(""); 651 out.indentPrintln("<!-- " + className + "-->"); 652 //Create the actual instance of the bean 653 out.indentPrintln("<bean id=\"" + initUpper(className) + "\" parent=\"" + initUpper(className) + "-parent\"/>"); 654 out.indentPrintln("<bean id=\"" + initUpper(className) + "-parent\" abstract=\"true\" parent=\"" + initUpper(className) + "-generated\">"); 655 out.writeComment("insert any overrides to the generated object definitions here"); 656 out.indentPrintln("</bean>"); 657 658 //Step 2, loop through attributes 659 this.writeManualAttributeDefinitions(className, null, new Stack<String>(), this.messageStructures, out); 660 661 } 662 663 private void writeManualAttributeDefinitions(String currentClass, String parentName, 664 Stack<String> parents, List<MessageStructure> fields, XmlWriter out) { 665 if (parents.contains(currentClass)) { 666 return; 667 } 668 for (MessageStructure ms : fields) { 669 Category cat = this.calculateCategory(ms); 670 // skip dynamic attributes 671 switch (cat) { 672 case DYNAMIC_ATTRIBUTE: 673 continue; 674 } 675 676 String pathName = calcPathName(parentName, ms); 677 String beanName = calcBeanName(pathName); 678 String childXmlTypeName = this.stripListOffEnd(ms.getType()); 679 XmlType childXmlType = this.finder.findXmlType(childXmlTypeName); 680 if (childXmlType == null) { 681 throw new IllegalStateException(childXmlTypeName); 682 } 683 writeManualAttributeDefinition(currentClass, parentName, ms, out); 684 685 // Add complex sub-types fields 686 switch (cat) { 687 case COMPLEX: 688 parents.push(currentClass); 689 List<MessageStructure> childFields = this.finder.findMessageStructures(childXmlTypeName); 690 // if (childFields.isEmpty()) { 691 // throw new IllegalStateException(childXmlTypeName); 692 // } 693 writeManualAttributeDefinitions(childXmlTypeName, pathName, parents, childFields, out); 694 parents.pop(); 695 } 696 } 697 } 698 699 private void writeManualAttributeDefinition(String currentClass, String parentName, MessageStructure ms, XmlWriter out) { 700 701 //Create the abstract field 702 String pathName = calcPathName(parentName, ms); 703 String beanName = calcBeanName(pathName); 704 // String baseKualiType = this.calcBaseKualiType(ms); 705 //Create the actual bean instance 706 out.println(""); 707 out.indentPrintln("<bean id=\"" + beanName + "\" parent=\"" + beanName + "-parent\"/>"); 708 out.indentPrintln("<bean id=\"" + beanName + "-parent\" abstract=\"true\" parent=\"" + beanName + "-generated\">"); 709 out.writeComment("insert any overrides to the generated attribute definitions here"); 710 out.indentPrintln("</bean>"); 711 } 712 /** 713 * list of predefined fields that should map to entries in ks-base-dictionary.xml 714 */ 715 private static Map<String, String> predefinedFieldMap = null; 716 717 { 718 Map<String, String> map = new HashMap<String, String>(); 719 map.put("id", "BaseKuali.id"); 720 map.put("key", "BaseKuali.key"); 721 map.put("name", "BaseKuali.name"); 722 map.put("descr", "BaseKuali.descr"); 723 map.put("plain", "BaseKuali.descr.plain"); 724 map.put("formatted", "BaseKuali.descr.formatted"); 725 map.put("desc", "BaseKuali.desc"); // r1 compatibility 726 map.put("typeKey", "BaseKuali.typeKey"); 727 map.put("stateKey", "BaseKuali.stateKey"); 728 map.put("type", "BaseKuali.type"); // r1 compatibility 729 map.put("state", "BaseKuali.state"); // r1 compatibility 730 map.put("effectiveDate", "BaseKuali.effectiveDate"); 731 map.put("expirationDate", "BaseKuali.expirationDate"); 732 map.put("meta", "BaseKuali.meta"); 733 map.put("createTime", "BaseKuali.meta.createTime"); 734 map.put("updateTime", "BaseKuali.meta.updateTime"); 735 map.put("createId", "BaseKuali.meta.createId"); 736 map.put("updateId", "BaseKuali.meta.updateId"); 737 map.put("versionInd", "BaseKuali.meta.versionInd"); 738 // convert to lower case 739 predefinedFieldMap = new HashMap(map.size()); 740 for (String key : map.keySet()) { 741 predefinedFieldMap.put(key.toLowerCase(), map.get(key)); 742 } 743 } 744 /** 745 * list of fields that if they end with the key the should be based on the entry 746 * in ks-base-dictionary.xml 747 */ 748 private static Map<String, String> endsWithMap = null; 749 750 { 751 Map<String, String> map = new HashMap<String, String>(); 752 map.put("startDate", "BaseKuali.startDate"); 753 map.put("endDate", "BaseKuali.endDate"); 754 map.put("start", "BaseKuali.start"); 755 map.put("end", "BaseKuali.end"); 756 map.put("OrgId", "BaseKuali.orgId"); 757 map.put("OrgIds", "BaseKuali.orgId"); 758 map.put("PersonId", "BaseKuali.personId"); 759 map.put("PersonIds", "BaseKuali.personId"); 760 map.put("PrincipalId", "BaseKuali.principalId"); 761 map.put("PrincipalIds", "BaseKuali.principalId"); 762 map.put("CluId", "BaseKuali.cluId"); 763 map.put("CluIds", "BaseKuali.cluId"); 764 map.put("LuiId", "BaseKuali.luiId"); 765 map.put("LuiIds", "BaseKuali.luiId"); 766 map.put("AtpId", "BaseKuali.atpId"); 767 map.put("AtpIds", "BaseKuali.atpId"); 768 map.put("TermId", "BaseKuali.termId"); 769 map.put("TermIds", "BaseKuali.termId"); 770 map.put("HolidayCalendarId", "BaseKuali.holidayCalendarId"); 771 map.put("HolidayCalendarIds", "BaseKuali.holidayCalendarId"); 772 map.put("Code", "BaseKuali.code"); 773 // convert to lower case 774 endsWithMap = new HashMap(map.size()); 775 for (String key : map.keySet()) { 776 endsWithMap.put(key.toLowerCase(), map.get(key)); 777 } 778 } 779 /** 780 * list of types that if the type matches this key then 781 * it should be based on that type entry as defined in the 782 * ks-base-dictionary.xml 783 */ 784 private static Map<String, String> typeMap = null; 785 786 { 787 Map<String, String> map = new HashMap<String, String>(); 788 map.put("String", "BaseKuali.string"); 789 map.put("DateTime", "BaseKuali.dateTime"); 790 map.put("Date", "BaseKuali.date"); 791 map.put("Boolean", "BaseKuali.boolean"); 792 map.put("Integer", "BaseKuali.integer"); 793 map.put("Long", "BaseKuali.long"); 794 map.put("Float", "BaseKuali.float"); 795 map.put("Double", "BaseKuali.double"); 796 // convert to lower case 797 typeMap = new HashMap(map.size()); 798 for (String key : map.keySet()) { 799 typeMap.put(key.toLowerCase(), map.get(key)); 800 } 801 } 802 803 private String calcBaseKualiParentBean(MessageStructure ms) { 804 switch (this.calculateCategory(ms)) { 805 case COMPLEX: 806 return "ComplexAttributeDefinition"; 807 case LIST_OF_COMPLEX: 808 return "CollectionDefinition"; 809 case LIST_OF_PRIMITIVE: 810 // TODO: deal with once https://jira.kuali.org/browse/KULRICE-5439 is fixed 811 System.out.println("Treating list of primitives same as collection defintion: " + ms.getId()); 812 return "CollectionDefinition"; 813 case PRIMITIVE: 814 break; 815 default: 816 throw new IllegalStateException("unknown/uhandled category " + ms.getId()); 817 } 818 String name = ms.getShortName(); 819 String baseKualiType = predefinedFieldMap.get(name.toLowerCase()); 820 if (baseKualiType != null) { 821 return baseKualiType; 822 } 823 824 // check ends with 825 for (String key : endsWithMap.keySet()) { 826 if (name.toLowerCase().endsWith(key)) { 827 return endsWithMap.get(key); 828 } 829 } 830 831 // now key off the type 832 String type = this.stripListOffEnd(ms.getType()); 833 baseKualiType = typeMap.get(type.toLowerCase()); 834 if (baseKualiType != null) { 835 return baseKualiType; 836 } 837 throw new IllegalStateException("All primitives are supposed to be handled by a predefined type " + ms.getId()); 838 } 839 840 private String calcTitleAttribute() { 841 MessageStructure ms = null; 842 ms = this.findMessageStructure("name"); 843 if (ms != null) { 844 return initLower(ms.getShortName()); 845 } 846 ms = this.findMessageStructure("title"); 847 if (ms != null) { 848 return initLower(ms.getShortName()); 849 } 850 ms = this.findMessageStructureEndsWith("name"); 851 if (ms != null) { 852 return initLower(ms.getShortName()); 853 } 854 ms = this.findMessageStructureEndsWith("title"); 855 if (ms != null) { 856 return initLower(ms.getShortName()); 857 } 858 ms = this.findMessageStructure("key"); 859 if (ms != null) { 860 return initLower(ms.getShortName()); 861 } 862 // TODO: consider checking for ID and just returning null 863 System.out.println("XmlKradBaseDictionaryCreator: could not find a title attribute for " + this.className); 864 // ms = this.findMessageStructure("id"); 865 // if (ms != null) { 866 // return initLower(ms.getShortName()); 867 // } 868 return null; 869 } 870 871 private MessageStructure findMessageStructureEndsWith(String shortNameEndsWith) { 872 shortNameEndsWith = shortNameEndsWith.toLowerCase(); 873 for (MessageStructure ms : this.messageStructures) { 874 if (ms.getShortName().toLowerCase().endsWith(shortNameEndsWith)) { 875 return ms; 876 } 877 } 878 return null; 879 } 880 881 private MessageStructure findMessageStructure(String shortName) { 882 for (MessageStructure ms : this.messageStructures) { 883 if (ms.getShortName().equalsIgnoreCase(shortName)) { 884 return ms; 885 } 886 } 887 return null; 888 } 889 890 private MessageStructure getMessageStructure(String shortName) { 891 MessageStructure ms = this.findMessageStructure(shortName); 892 if (ms == null) { 893 throw new IllegalArgumentException(shortName); 894 } 895 return ms; 896 } 897 898 private List<String> calcPrimaryKeys() { 899 MessageStructure ms = null; 900 ms = this.findMessageStructure("id"); 901 if (ms != null) { 902 return Collections.singletonList(initLower(ms.getShortName())); 903 } 904 ms = this.findMessageStructure("key"); 905 if (ms != null) { 906 return Collections.singletonList(initLower(ms.getShortName())); 907 } 908 // just use the first field 909 if (this.messageStructures.size() > 0) { 910 ms = this.messageStructures.get(0); 911 return Collections.singletonList(ms.getShortName()); 912 } 913 return Collections.EMPTY_LIST; 914 } 915 916 private void writePropertyStart(String propertyName, XmlWriter out) { 917 out.indentPrintln("<property name=\"" + propertyName + "\">"); 918 out.incrementIndent(); 919 } 920 921 private void writePropertyEnd(XmlWriter out) { 922 out.decrementIndent(); 923 out.indentPrintln("</property>"); 924 } 925 926 private void writeProperty(String propertyName, String propertyValue, XmlWriter out) { 927 out.indentPrintln("<property name=\"" + propertyName + "\" value=\"" + replaceDoubleQuotes(propertyValue) + "\"/>"); 928 } 929 930 private void writePropertyValue(String propertyName, String propertyValue, XmlWriter out) { 931 writePropertyStart(propertyName, out); 932 out.indentPrintln("<value>"); 933 // TODO: worry about the value starting on a new line i.e. is it trimmed when loaded? 934 out.println(escapeHtml(propertyValue)); 935 out.indentPrintln("</value>"); 936 writePropertyEnd(out); 937 } 938 939 private String escapeHtml(String str) { 940 if (str == null) { 941 return null; 942 } 943 return StringEscapeUtils.escapeHtml(str); 944 } 945 946 private String replaceDoubleQuotes(String str) { 947 if (str == null) { 948 return null; 949 } 950 return str.replace("\"", "'"); 951 } 952 }