View Javadoc

1   /*
2    * Copyright 2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may	obtain a copy of the License at
7    *
8    * 	http://www.osedu.org/licenses/ECL-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.student.contract.model.util;
17  
18  import java.io.File;
19  import java.io.FileNotFoundException;
20  import java.io.FileOutputStream;
21  import java.io.PrintStream;
22  import java.util.Collections;
23  import java.util.Date;
24  import java.util.Enumeration;
25  import java.util.List;
26  import java.util.Stack;
27  
28  import org.kuali.student.contract.model.MessageStructure;
29  import org.kuali.student.contract.model.ServiceContractModel;
30  import org.kuali.student.contract.model.XmlType;
31  import org.kuali.student.contract.writer.XmlWriter;
32  
33  /**
34   *
35   * @author nwright
36   */
37  public class XmlKradBaseDictionaryCreator {
38  
39      private ServiceContractModel model;
40      private ModelFinder finder;
41      private String directory;
42      private String className;
43      private XmlType xmlType;
44      private XmlWriter gwriter;
45      private XmlWriter bwriter;
46      private List<MessageStructure> messageStructures;
47  
48      public XmlKradBaseDictionaryCreator(String directory,
49              ServiceContractModel model, String className) {
50          this.directory = directory;
51          this.model = model;
52          this.finder = new ModelFinder(this.model);
53          this.className = className;
54          this.xmlType = this.finder.findXmlType(className);
55          if (xmlType == null) {
56              throw new IllegalArgumentException(className);
57          }
58          this.messageStructures = this.finder.findMessageStructures(className);
59          if (this.messageStructures.isEmpty()) {
60              throw new IllegalStateException(className);
61          }
62      }
63  
64      public void write() {
65          this.initXmlWriters();
66          this.writeSpringHeaderOpen(gwriter);
67          this.writeWarning(gwriter);
68          this.writeGeneratedImports(gwriter);
69          this.writeGeneratedObjectStructure(gwriter);
70          this.writeSpringHeaderClose(gwriter);
71  
72          this.writeSpringHeaderOpen(bwriter);
73          this.writeNote (bwriter);
74          this.writeBaseImports(bwriter);
75          this.writeBaseObjectStructure(bwriter);
76          this.writeSpringHeaderClose(bwriter);
77      }
78  
79      private void initXmlWriters() {
80          String generatedFileName = "/ks-" + initUpper(className) + "-dictionary-generated.xml";
81          String baseFileName = "/ks-" + initUpper(className) + "-dictionary.xml";
82  
83          File dir = new File(this.directory);
84          //System.out.indentPrintln ("Writing java class: " + fileName + " to " + dir.getAbsolutePath ());
85  
86          if (!dir.exists()) {
87              if (!dir.mkdirs()) {
88                  throw new IllegalStateException("Could not create directory "
89                          + this.directory);
90              }
91          }
92  
93          try {
94              PrintStream out = new PrintStream(new FileOutputStream(this.directory + "/" + generatedFileName, false));
95              this.gwriter = new XmlWriter(out, 0);
96          } catch (FileNotFoundException ex) {
97              throw new IllegalStateException(ex);
98          }
99          try {
100             PrintStream out = new PrintStream(new FileOutputStream(this.directory + "/" + baseFileName, false));
101             this.bwriter = new XmlWriter(out, 0);
102         } catch (FileNotFoundException ex) {
103             throw new IllegalStateException(ex);
104         }
105     }
106 
107     private static String initLower(String str) {
108         if (str == null) {
109             return null;
110         }
111         if (str.length() == 0) {
112             return str;
113         }
114         if (str.length() == 1) {
115             return str.toLowerCase();
116         }
117         return str.substring(0, 1).toLowerCase() + str.substring(1);
118     }
119 
120     private static String initUpper(String str) {
121         if (str == null) {
122             return null;
123         }
124         if (str.length() == 0) {
125             return str;
126         }
127         if (str.length() == 1) {
128             return str.toUpperCase();
129         }
130         return str.substring(0, 1).toUpperCase() + str.substring(1);
131     }
132 
133     private void writeSpringHeaderClose(XmlWriter out) {
134         out.decrementIndent();
135         out.indentPrintln("</beans>");
136     }
137 
138     private void writeSpringHeaderOpen(XmlWriter out) {
139         out.indentPrintln("<!--");
140         out.indentPrintln(" Copyright 2011 The Kuali Foundation");
141         out.println("");
142         out.indentPrintln(" Licensed under the Educational Community License, Version 2.0 (the \"License\");");
143         out.indentPrintln(" you may not use this file except in compliance with the License.");
144         out.indentPrintln(" You may obtain a copy of the License at");
145         out.indentPrintln("");
146         out.indentPrintln(" http://www.opensource.org/licenses/ecl2.php");
147         out.println("");
148         out.indentPrintln(" Unless required by applicable law or agreed to in writing, software");
149         out.indentPrintln(" distributed under the License is distributed on an \"AS IS\" BASIS,");
150         out.indentPrintln(" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.");
151         out.indentPrintln(" See the License for the specific language governing permissions and");
152         out.indentPrintln(" limitations under the License.");
153         out.indentPrintln("-->");
154         out.indentPrintln("<beans xmlns=\"http://www.springframework.org/schema/beans\"");
155         out.indentPrintln("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
156         out.indentPrintln("xsi:schemaLocation=\""
157                 + "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" + "\">");
158         out.println("");
159         out.incrementIndent();
160     }
161 
162     private void writeWarning(XmlWriter out) {
163         out.println("");
164         out.indentPrintln("<!-- ********************************************************");
165         out.indentPrintln("                           WARNING ");
166         out.indentPrintln("                 DO NOT UPDATE THIS FILE MANUALLY");
167         out.indentPrintln("    This dictionary file was automatically generated on " + new Date());
168         out.indentPrintln("    The DictionaryGeneratorMojo reads the service contract ");
169         out.indentPrintln("    and creates these ks-XXXX-dictionary-generated.xml files.");
170         out.indentPrintln("    ");
171         out.indentPrintln("    If this file is out of sync with the contract re-run the mojo.");
172         out.indentPrintln("    ");
173         out.indentPrintln("    To add in additional constraints or change these default values (perhaps");
174         out.indentPrintln("    because the generator is not perfect) please update the corresponding ");
175         out.indentPrintln("    ks-XXXX-dictionary.xml instead of this one.");
176         out.indentPrintln("************************************************************* -->");
177     }
178 
179     private void writeNote(XmlWriter out) {
180         out.println("");
181         out.indentPrintln("<!-- ********************************************************");
182         out.indentPrintln("                           NOTE");
183         out.indentPrintln("              THIS FILE WAS INTENDED TO BE MODIFIED");
184         out.println ("");        
185         out.indentPrintln("    While this file was originally generated on " + new Date());
186         out.indentPrintln("    it was intended to be subsequently modified by hand.");
187         out.indentPrintln("    It imports a corresponding ks-XXXX-dictionary-generated.xml file");
188         out.indentPrintln("    that was also automatically generated by the DictionaryCreatorMojo.");
189         out.indentPrintln("    This file gives you the ability to layer in addiditional definitions");
190         out.indentPrintln("    that are not/cannot be generated simply by reading the service contract");
191         out.println ("");
192         out.indentPrintln("    The goal is to be able to easily re-generate the ks-XXXX-dictionary-generated.xml file");        
193         out.indentPrintln("    without affecting the manually entered additions that are coded here.");              
194         out.indentPrintln("************************************************************* -->");
195     }
196 
197     private void writeGeneratedImports(XmlWriter out) {
198         out.indentPrintln("<import resource=\"classpath:ks-base-dictionary.xml\"/>");
199         // TODO: only write out the ones that are used in this structure
200 //        out.indentPrintln("<import resource=\"classpath:ks-RichTextInfo-dictionary.xml\"/>");
201 //        out.indentPrintln("<import resource=\"classpath:ks-MetaInfo-dictionary.xml\"/>");
202     }
203 
204     private void writeBaseImports(XmlWriter out) {
205         out.indentPrintln("<import resource=\"classpath:ks-" + initUpper(className) + "-dictionary-generated.xml\"/>");
206     }
207 
208     private String stripListOffEnd(String name) {
209         if (name.endsWith("List")) {
210             return name.substring(0, name.length() - "List".length());
211         }
212         return name;
213     }
214 
215     private void writeSubStructures(XmlType type, Stack<String> stack) {
216         boolean first = true;
217         for (MessageStructure ms : finder.findMessageStructures(type.getName())) {
218             XmlType st = finder.findXmlType(this.stripListOffEnd(ms.getType()));
219             if (st == null) {
220                 throw new NullPointerException(ms.getType() + " does not exist in list of types with parents " + calcParents(stack));
221             }
222             if (!st.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
223                 continue;
224             }
225             if (first) {
226                 first = false;
227                 gwriter.indentPrintln("<ul>");
228             }
229             if (!stack.contains(st.getName())) {
230                 stack.push(st.getName());
231                 this.writeSubStructures(st, stack);
232                 stack.pop();
233             }
234         }
235         if (!first) {
236             gwriter.indentPrintln("</ul>");
237         }
238     }
239 
240     private String calcParents(Stack<String> stack) {
241         StringBuilder sb = new StringBuilder();
242         String dot = "";
243         Enumeration<String> en = stack.elements();
244         while (en.hasMoreElements()) {
245             sb.append(dot);
246             dot = ".";
247             sb.append(en.nextElement());
248         }
249         return sb.toString();
250     }
251 
252     private void writeGeneratedObjectStructure(XmlWriter out) {
253         //Step 1, create the abstract structure
254         out.println("");
255         out.indentPrintln("<!-- " + className + "-->");
256         out.indentPrintln("<bean id=\"" + initUpper(className) + "-generated\" abstract=\"true\" parent=\"DataObjectEntry\">");
257         out.incrementIndent();
258         writeProperty("name", initLower(className));
259         writeProperty("objectClass", xmlType.getJavaPackage() + "." + initUpper(className));
260         writeProperty("objectLabel", calcObjectLabel());
261         writeProperty("objectDescription", xmlType.getDesc());
262         String titleAttribute = calcTitleAttribute();
263         if (titleAttribute != null) {
264             writeProperty("titleAttribute", titleAttribute);
265         }
266         out.indentPrintln("<property name=\"primaryKeys\">");
267         List<String> pks = calcPrimaryKeys();
268         if (pks != null && !pks.isEmpty()) {
269             out.incrementIndent();
270             out.indentPrintln("<list>");
271             out.incrementIndent();
272             for (String pk : pks) {
273                 addValue(pk);
274             }
275             out.decrementIndent();
276             out.indentPrintln("</list>");
277             out.decrementIndent();
278         }
279         out.indentPrintln("</property>");
280         out.indentPrintln("<property name=\"attributes\">");
281         out.incrementIndent();
282         out.indentPrintln("<list>");
283         out.incrementIndent();
284         this.writeGeneratedAttributeRefBeans(className, null, new Stack<String>(), this.messageStructures, out);
285         out.decrementIndent();
286         out.indentPrintln("</list>");
287         out.decrementIndent();
288         out.indentPrintln("</property>");
289         out.decrementIndent();
290         out.indentPrintln("</bean>");
291 
292         //Step 2, loop through attributes
293         this.writeGeneratedAttributeDefinitions(className, null, new Stack<String>(), this.messageStructures, out);
294 
295     }
296 
297     private void addValue(String value) {
298         gwriter.indentPrintln("<value>" + value + "</value>");
299     }
300 
301     private String calcObjectLabel() {
302         String label = this.className;
303         if (label.endsWith("Info")) {
304             label = label.substring(0, label.length() - "Info".length());
305         }
306         label = initUpper(label);
307         return splitCamelCase(label);
308     }
309 
310     // got this from http://stackoverflow.com/questions/2559759/how-do-i-convert-camelcase-into-human-readable-names-in-java
311     private static String splitCamelCase(String s) {
312         return s.replaceAll(
313                 String.format("%s|%s|%s",
314                 "(?<=[A-Z])(?=[A-Z][a-z])",
315                 "(?<=[^A-Z])(?=[A-Z])",
316                 "(?<=[A-Za-z])(?=[^A-Za-z])"),
317                 " ");
318     }
319 
320     private void writeGeneratedAttributeRefBeans(String className, String parentFieldName,
321             Stack<String> parents, List<MessageStructure> fields, XmlWriter out) {
322         if (parents.contains(className)) {
323             return;
324         }
325         for (MessageStructure ms : fields) {
326             String fieldName = calcFieldName(className, parentFieldName, ms);
327             out.indentPrintln("<ref bean=\"" + fieldName + "\"/>");
328             // Add complex sub-types fields
329             String childTypeName = this.stripListOffEnd(ms.getType());
330             XmlType childType = this.finder.findXmlType(childTypeName);
331             if (childType == null) {
332                 throw new IllegalStateException(childTypeName);
333             }
334             if (childType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
335                 parents.push(className);
336                 List<MessageStructure> childFields = this.finder.findMessageStructures(childTypeName);
337                 if (childFields.isEmpty()) {
338                     throw new IllegalStateException(childTypeName);
339                 }
340                 writeGeneratedAttributeRefBeans(childTypeName, fieldName, parents, childFields, out);
341                 parents.pop();
342             }
343         }
344     }
345 
346     private void writeGeneratedAttributeDefinitions(String className, String parentFieldName,
347             Stack<String> parents, List<MessageStructure> fields, XmlWriter out) {
348         if (parents.contains(className)) {
349             return;
350         }
351         for (MessageStructure ms : fields) {
352             String fieldName = calcFieldName(className, parentFieldName, ms);
353             String childTypeName = this.stripListOffEnd(ms.getType());
354             XmlType childType = this.finder.findXmlType(childTypeName);
355             if (childType == null) {
356                 throw new IllegalStateException(childTypeName);
357             }
358             writeGeneratedAttributeDefinition(className, parentFieldName, ms, out);
359 
360             // Add complex sub-types fields
361             if (childType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
362                 parents.push(className);
363                 List<MessageStructure> childFields = this.finder.findMessageStructures(childTypeName);
364                 if (childFields.isEmpty()) {
365                     throw new IllegalStateException(childTypeName);
366                 }
367                 writeGeneratedAttributeDefinitions(childTypeName, fieldName, parents, childFields, out);
368                 parents.pop();
369             }
370         }
371     }
372 
373     private void writeGeneratedAttributeDefinition(String className, String parentFieldName, MessageStructure ms, XmlWriter out) {
374 
375         //Create the abstract field
376         String fieldName = this.calcFieldName(className, parentFieldName, ms);
377         String baseKualiType = this.calcBaseKualiType(ms);
378         out.println("");
379         out.indentPrintln("<bean id=\"" + fieldName + "-generated\" abstract=\"true\" parent=\"" + baseKualiType + "\">");
380         out.incrementIndent();
381         writeProperty("name", initLower(ms.getShortName()));
382         if (ms.isOverriden()) {
383             writeProperty("shortLabel", ms.getName());
384         }
385         out.decrementIndent();
386         // TODO: implement maxoccurs
387 //        if (isList(pd)) {
388 //            addProperty("maxOccurs", "" + DictionaryConstants.UNBOUNDED, s);
389 //        }
390         out.indentPrintln("</bean>");
391     }
392 
393     private void writeBaseObjectStructure(XmlWriter out) {
394         //Step 1, create the parent bean
395         out.println("");
396         out.indentPrintln("<!-- " + className + "-->");
397         out.indentPrintln("<bean id=\"" + initUpper(className) + "-parent\" abstract=\"true\" parent=\"" + initUpper(className) + "-generated\">");
398         out.writeComment("insert any overrides to the generated object definitions here");
399         out.indentPrintln("</bean>");
400 
401         //Create the actual instance of the bean
402         out.indentPrintln("<bean id=\"" + initUpper(className) + "\" parent=\"" + initUpper(className) + "-parent\"/>");
403         out.println("");
404 
405         //Step 2, loop through attributes
406         this.writeBaseAttributeDefinitions(className, null, new Stack<String>(), this.messageStructures, out);
407 
408     }
409 
410     private void writeBaseAttributeDefinitions(String className, String parentFieldName,
411             Stack<String> parents, List<MessageStructure> fields, XmlWriter out) {
412         if (parents.contains(className)) {
413             return;
414         }
415         for (MessageStructure ms : fields) {
416             String fieldName = calcFieldName(className, parentFieldName, ms);
417             String childTypeName = this.stripListOffEnd(ms.getType());
418             XmlType childType = this.finder.findXmlType(childTypeName);
419             if (childType == null) {
420                 throw new IllegalStateException(childTypeName);
421             }
422             writeBaseAttributeDefinition(className, parentFieldName, ms, out);
423 
424             // Add complex sub-types fields
425             if (childType.getPrimitive().equalsIgnoreCase(XmlType.COMPLEX)) {
426                 parents.push(className);
427                 List<MessageStructure> childFields = this.finder.findMessageStructures(childTypeName);
428                 if (childFields.isEmpty()) {
429                     throw new IllegalStateException(childTypeName);
430                 }
431                 writeBaseAttributeDefinitions(childTypeName, fieldName, parents, childFields, out);
432                 parents.pop();
433             }
434         }
435     }
436 
437     private void writeBaseAttributeDefinition(String className, String parentFieldName, MessageStructure ms, XmlWriter out) {
438 
439         //Create the abstract field
440         String fieldName = this.calcFieldName(className, parentFieldName, ms);
441 //        String baseKualiType = this.calcBaseKualiType(ms);
442         out.println("");
443         out.indentPrintln("<bean id=\"" + fieldName + "-parent\" abstract=\"true\" parent=\"" + fieldName + "-generated\">");
444         out.writeComment("insert any overrides to the generated attribute definitions here");
445         out.indentPrintln("</bean>");
446 
447         //Create the actual bean instance
448         out.indentPrintln("<bean id=\"" + fieldName + "\" parent=\"" + fieldName + "-parent\"/>");
449     }
450 
451     private String calcBaseKualiType(MessageStructure ms) {
452 
453         String name = ms.getShortName();
454         if (name.equals("id")) {
455             return "BaseKuali.id";
456         }
457         if (name.equals("key")) {
458             return "BaseKuali.key";
459         }
460         if (name.equals("descr")) {
461             return "BaseKuali.descr";
462         }
463         if (name.equals("name")) {
464             return "BaseKuali.name";
465         }
466         if (name.equals("typeKey")) {
467             return "BaseKuali.typeKey";
468         }
469         if (name.equals("stateKey")) {
470             return "BaseKuali.stateKey";
471         }
472         // to handle r1 services
473         if (name.equals("type")) {
474             return "BaseKuali.typeKey";
475         }
476         if (name.equals("state")) {
477             return "BaseKuali.stateKey";
478         }
479         if (name.equals("effectiveDate")) {
480             return "BaseKuali.effectiveDate";
481         }
482         if (name.equals("expirationDate")) {
483             return "BaseKuali.expirationDate";
484         }
485         if (name.endsWith("orgId")) {
486             return "BaseKuali.orgId";
487         }
488         if (name.endsWith("personId")) {
489             return "BaseKuali.personId";
490         }
491         if (name.endsWith("principalId")) {
492             return "BaseKuali.principalId";
493         }
494         if (name.endsWith("cluId")) {
495             return "BaseKuali.cluId";
496         }
497         if (name.endsWith("luiId")) {
498             return "BaseKuali.luiId";
499         }
500         if (name.endsWith("Code")) {
501             return "BaseKuali.code";
502         }
503 
504         // now key off the type
505         String type = this.stripListOffEnd(ms.getType());
506         if (type.equalsIgnoreCase("String")) {
507             return "BaseKuali.string";
508         }
509         if (type.equalsIgnoreCase("DateTime")) {
510             return "BaseKuali.dateTime";
511         }
512         if (type.equalsIgnoreCase("Date")) {
513             return "BaseKuali.date";
514         }
515         if (type.equalsIgnoreCase("Boolean")) {
516             return "BaseKuali.boolean";
517         }
518         if (type.equalsIgnoreCase("Integer")) {
519             return "BaseKuali.integer";
520         }
521 //        if (type.equalsIgnoreCase("Float")) {
522 //            return "BaseKuali.Currency";
523 //        }
524         if (type.equalsIgnoreCase(XmlType.COMPLEX)) {
525             return "BaseKuali.complex";
526         }
527         // all else fails call it a string
528         return "BaseKuali.string";
529 
530     }
531 
532     private String calcFieldName(String className, String parentFieldName, MessageStructure ms) {
533         if (parentFieldName == null) {
534             return initUpper(className) + "." + initLower(ms.getShortName());
535         }
536         return parentFieldName + "." + initLower(ms.getShortName());
537     }
538 
539     private String calcTitleAttribute() {
540         MessageStructure ms = null;
541         ms = this.findMessageStructure("name");
542         if (ms != null) {
543             return initLower(ms.getShortName());
544         }
545         ms = this.findMessageStructure("title");
546         if (ms != null) {
547             return initLower(ms.getShortName());
548         }
549         ms = this.findMessageStructureEndsWith("name");
550         if (ms != null) {
551             return initLower(ms.getShortName());
552         }
553         ms = this.findMessageStructureEndsWith("title");
554         if (ms != null) {
555             return initLower(ms.getShortName());
556         }
557         ms = this.findMessageStructure("key");
558         if (ms != null) {
559             return initLower(ms.getShortName());
560         }
561         // TODO: consider checking for ID and just returning null
562         System.out.println("XmlKradBaseDictionaryCreator: could not find a title attribute for " + this.className);
563 //        ms = this.findMessageStructure("id");
564 //        if (ms != null) {
565 //            return initLower(ms.getShortName());
566 //        }
567         return null;
568     }
569 
570     private MessageStructure findMessageStructureEndsWith(String shortNameEndsWith) {
571         shortNameEndsWith = shortNameEndsWith.toLowerCase();
572         for (MessageStructure ms : this.messageStructures) {
573             if (ms.getShortName().toLowerCase().endsWith(shortNameEndsWith)) {
574                 return ms;
575             }
576         }
577         return null;
578     }
579 
580     private MessageStructure findMessageStructure(String shortName) {
581         for (MessageStructure ms : this.messageStructures) {
582             if (ms.getShortName().equalsIgnoreCase(shortName)) {
583                 return ms;
584             }
585         }
586         return null;
587     }
588 
589     private MessageStructure getMessageStructure(String shortName) {
590         MessageStructure ms = this.findMessageStructure(shortName);
591         if (ms == null) {
592             throw new IllegalArgumentException(shortName);
593         }
594         return ms;
595     }
596 
597     private List<String> calcPrimaryKeys() {
598         MessageStructure ms = null;
599         ms = this.findMessageStructure("id");
600         if (ms != null) {
601             return Collections.singletonList(initLower(ms.getShortName()));
602         }
603         ms = this.findMessageStructure("key");
604         if (ms != null) {
605             return Collections.singletonList(initLower(ms.getShortName()));
606         }
607         return Collections.EMPTY_LIST;
608     }
609 
610     private void writeProperty(String propertyName, String propertyValue) {
611         gwriter.indentPrintln("<property name=\"" + propertyName + "\" value=\"" + propertyValue + "\"/>");
612     }
613 }