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