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