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