View Javadoc
1   /*
2    * #%L
3    * ToPIA :: Persistence
4    * $Id$
5    * $HeadURL$
6    * %%
7    * Copyright (C) 2004 - 2014 CodeLutin
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as 
11   * published by the Free Software Foundation, either version 3 of the 
12   * License, or (at your option) any later version.
13   * 
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Lesser Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Lesser Public 
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22   * #L%
23   */
24  package org.nuiton.topia.generator;
25  
26  import org.apache.commons.lang3.RandomStringUtils;
27  import org.apache.commons.lang3.StringUtils;
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.codehaus.plexus.component.annotations.Component;
31  import org.nuiton.eugene.GeneratorUtil;
32  import org.nuiton.eugene.Template;
33  import org.nuiton.eugene.java.JavaBuilder;
34  import org.nuiton.eugene.java.ObjectModelTransformerToJava;
35  import org.nuiton.eugene.models.object.ObjectModel;
36  import org.nuiton.eugene.models.object.ObjectModelAssociationClass;
37  import org.nuiton.eugene.models.object.ObjectModelAttribute;
38  import org.nuiton.eugene.models.object.ObjectModelClass;
39  import org.nuiton.eugene.models.object.ObjectModelClassifier;
40  import org.nuiton.eugene.models.object.ObjectModelJavaModifier;
41  import org.nuiton.eugene.models.object.ObjectModelModifier;
42  import org.nuiton.eugene.models.object.ObjectModelOperation;
43  import org.nuiton.eugene.models.object.ObjectModelType;
44  import org.nuiton.topia.framework.TopiaQuery;
45  import org.nuiton.topia.persistence.TopiaEntity;
46  
47  import java.util.HashMap;
48  import java.util.Map;
49  
50  /**
51   * Created: 23 juin 2010
52   *
53   * @author fdesbois &lt;fdesbois@codelutin.com&gt;
54   * @version $Id$
55   * @since 2.4
56   */
57  @Component(role = Template.class, hint = "org.nuiton.topia.generator.QueryHelperTransformer")
58  public class QueryHelperTransformer extends ObjectModelTransformerToJava {
59  
60      private static final Log log = LogFactory.getLog(QueryHelperTransformer.class);
61  
62      protected ObjectModelClass helperClass;
63  
64      protected ObjectModelClass abstractEntityPropertyClass;
65  
66      protected static final String ENTITY_PROPERTY_CLASS_NAME = "EntityProperty";
67  
68      protected static final String ENTITY_PROPERTY_SUFFIX = "Property";
69  
70      protected static final String ENTITY_PROPERTY_GENERIC_TYPE = "<E>";
71  
72      protected String mainException;
73  
74      protected Map<String, String> aliases;
75  
76      public static final String CONSTANT_PREFIX = "ALIAS_";
77  
78      /*************************** MAIN PART OF THE HELPER **********************/
79  
80      @Override
81      public void transformFromModel(ObjectModel model) {
82  
83          aliases = new HashMap<String, String>();
84  
85          String modelName = StringUtils.capitalize(model.getName());
86          String packageName = getDefaultPackageName();
87          helperClass = createClass(modelName + "QueryHelper", packageName);
88  
89          addImport(helperClass, TopiaQuery.class);
90          addImport(helperClass, TopiaEntity.class);
91  
92          String exception = TopiaGeneratorUtil.getExceptionClassTagValue(model);
93          if (exception != null) {
94              addImport(helperClass, exception);
95              mainException = TopiaGeneratorUtil.getSimpleName(exception);
96          }
97  
98          initConstantPrefixFromModel();
99  
100         createInnerAbstractEntityPropertyClass();
101         createUtilOperations();
102     }
103 
104     protected void createInnerAbstractEntityPropertyClass() {
105 
106         abstractEntityPropertyClass =  (ObjectModelClass)addInnerClassifier(helperClass,
107                 ObjectModelType.OBJECT_MODEL_CLASS,
108                 ENTITY_PROPERTY_CLASS_NAME + ENTITY_PROPERTY_GENERIC_TYPE,
109                 ObjectModelJavaModifier.ABSTRACT,
110                 ObjectModelJavaModifier.STATIC);
111 
112         addImport(helperClass, HashMap.class);
113         addImport(helperClass, Map.class);
114 
115         addAttribute(abstractEntityPropertyClass, "alias",
116                 String.class, null,
117                 ObjectModelJavaModifier.PROTECTED);
118 
119         addAttribute(abstractEntityPropertyClass, "propertiesCache",
120                 "Map<String, String>", null,
121                 ObjectModelJavaModifier.PROTECTED);
122 
123         // Constructor
124         // FIXME-fdesbois-2010-06-23 : need to take care of generic case in JavaBuilder in EUGene
125 //        ObjectModelOperation constructor =
126 //                addConstructor(abstractEntityPropertyClass, ObjectModelJavaModifier.PUBLIC);
127         ObjectModelOperation constructor =
128                 builder.addOperation(abstractEntityPropertyClass, ENTITY_PROPERTY_CLASS_NAME, null, ObjectModelJavaModifier.PUBLIC);
129 
130         setOperationBody(constructor, ""
131     /*{
132             propertiesCache = new HashMap<String, String>();
133     }*/
134         );
135 
136         // Getter and setter for alias
137         ObjectModelOperation setAlias =
138                 addOperation(abstractEntityPropertyClass, "setAlias", "void",
139                         ObjectModelJavaModifier.PROTECTED);
140         addParameter(setAlias, String.class, "alias");
141 
142 
143         setOperationBody(setAlias, ""
144     /*{
145             this.alias = alias;
146     }*/
147         );
148 
149         ObjectModelOperation getAlias =
150                 addOperation(abstractEntityPropertyClass, "$alias", String.class,
151                         ObjectModelJavaModifier.PUBLIC);
152 
153 
154         setOperationBody(getAlias, ""
155     /*{
156             return alias;
157     }*/
158         );
159 
160         // Getter for properties
161         ObjectModelOperation getProperty =
162                 addOperation(abstractEntityPropertyClass, "$property", String.class,
163                         ObjectModelJavaModifier.PUBLIC);
164         addParameter(getProperty, String.class, "propertyName");
165 
166 
167         setOperationBody(getProperty, ""
168     /*{
169             String result = propertiesCache.get(propertyName);
170             if (result == null) {
171                 result = TopiaQuery.getProperty(alias, propertyName);
172                 propertiesCache.put(propertyName, result);
173             }
174             return result;
175     }*/
176         );
177 
178         ObjectModelOperation topiaCreateDate =
179                 addOperation(abstractEntityPropertyClass, "topiaCreateDate", String.class,
180                         ObjectModelJavaModifier.PUBLIC);
181 
182         setOperationBody(topiaCreateDate, ""
183     /*{
184             return $property(TopiaEntity.TOPIA_CREATE_DATE);
185     }*/
186         );
187 
188         ObjectModelOperation topiaId =
189                 addOperation(abstractEntityPropertyClass, "topiaId", String.class,
190                         ObjectModelJavaModifier.PUBLIC);
191 
192         setOperationBody(topiaId, ""
193     /*{
194             return $property(TopiaEntity.TOPIA_ID);
195     }*/
196         );
197 
198         ObjectModelOperation topiaVersion =
199                 addOperation(abstractEntityPropertyClass, "topiaVersion", String.class,
200                         ObjectModelJavaModifier.PUBLIC);
201 
202         setOperationBody(topiaVersion, ""
203     /*{
204             return $property(TopiaEntity.TOPIA_VERSION);
205     }*/
206         );
207 
208         // Abstract methods
209         addOperation(abstractEntityPropertyClass, "getEntityClass",
210                 "Class" + ENTITY_PROPERTY_GENERIC_TYPE,
211                 ObjectModelJavaModifier.PUBLIC, ObjectModelJavaModifier.ABSTRACT);
212 
213         addOperation(abstractEntityPropertyClass, "defaultAlias", String.class,
214                 ObjectModelJavaModifier.PUBLIC, ObjectModelJavaModifier.ABSTRACT);
215     }
216 
217     protected void createUtilOperations() {
218 
219         // createQuery method with EntityProperty in argument
220         ObjectModelOperation createQuery =
221                 addOperation(helperClass, "createQuery", TopiaQuery.class,
222                         ObjectModelJavaModifier.PUBLIC, ObjectModelJavaModifier.STATIC);
223         addParameter(createQuery, ENTITY_PROPERTY_CLASS_NAME, "property");
224 
225         setOperationBody(createQuery, ""
226     /*{
227         return new TopiaQuery((Class<? extends TopiaEntity>)property.getEntityClass(), property.$alias());
228     }*/
229         );
230 
231         // format method to format statement using $1, $2 corresponding to property names
232         ObjectModelOperation format =
233                 addOperation(helperClass, "format", String.class,
234                         ObjectModelJavaModifier.PUBLIC, ObjectModelJavaModifier.STATIC);
235         addParameter(format, String.class, "statement");
236         addParameter(format, "String...", "propertyNames");
237 
238         setOperationBody(format, ""
239     /*{
240         for (int i = 1; i <= propertyNames.length; i++) {
241            statement = statement.replace("$" + i, propertyNames[i-1]);
242         }
243         return statement;
244     }*/
245         );
246 
247         // Methods to instantiate EntityProperty
248         String genericType = "<P extends " + ENTITY_PROPERTY_CLASS_NAME + "> P";
249         ObjectModelOperation newEntityProperty1 =
250                 addOperation(helperClass, "newEntityProperty", genericType,
251                         ObjectModelJavaModifier.PRIVATE, ObjectModelJavaModifier.STATIC);
252         addParameter(newEntityProperty1, "Class<P>", "propertyClass");
253 
254         setOperationBody(newEntityProperty1, ""
255     /*{
256         return newEntityProperty(propertyClass, null);
257     }*/
258         );
259         ObjectModelOperation newEntityProperty2 =
260                 addOperation(helperClass, "newEntityProperty", genericType,
261                         ObjectModelJavaModifier.PRIVATE, ObjectModelJavaModifier.STATIC);
262         addParameter(newEntityProperty2, "Class<P>", "propertyClass");
263         addParameter(newEntityProperty2, String.class, "alias");
264 
265         StringBuilder buffer = new StringBuilder(""
266     /*{
267         try {
268             P property = propertyClass.newInstance();
269             if (alias == null) {
270                 alias = property.defaultAlias();
271             }
272             property.setAlias(alias);
273             return property;
274         } catch (Exception eee) {
275     }*/
276         );
277 
278         if (mainException != null) {
279             addException(newEntityProperty1, mainException);
280             addException(newEntityProperty2, mainException);
281             buffer.append(""
282     /*{
283             throw new <%=mainException%>("Error instantiate " + propertyClass.getName(), eee);
284     }*/
285             );
286         } else {
287             buffer.append(""
288     /*{
289             throw new Error("Error instantiate " + propertyClass.getName(), eee);
290     }*/
291             );
292         }
293             buffer.append(""
294     /*{
295         }
296     }*/
297             );
298 
299         setOperationBody(newEntityProperty2, buffer.toString());
300     }
301 
302     /*************************** INNER PROPERTY CLASSES ***********************/
303 
304     @Override
305 //    public void transformFromClass(ObjectModelClass clazz) {
306     public void transformFromClassifier(ObjectModelClassifier clazz) {
307         if (!TopiaGeneratorUtil.hasEntityStereotype(clazz)) {
308             return;
309         }
310 
311         // Create default alias for this entity
312         String aliasConstant = createAliasConstant(clazz.getName());
313 
314         // Create inner class for this entity
315         ObjectModelClass entityPropertyClass = createInnerClass(clazz, aliasConstant);
316 
317         // Create methods to instantiate the inner class
318         createNewOperations(entityPropertyClass);
319 
320         addExtraForSubEntity(clazz);
321     }
322 
323     protected String createAliasConstant(String entityName) {
324 
325         String constantName =
326                TopiaGeneratorUtil.convertVariableNameToConstantName(entityName);
327 
328         String[] words = constantName.split("_");
329         String alias = "";
330         // Use first letter of each word as alias
331         for (String word : words) {
332             alias += word.substring(0, 1);
333         }
334 
335         // Case of existing alias, check other letters in lastWord
336         String lastWord = words[words.length - 1];
337         while(aliases.containsKey(alias)) {
338             // Remove first letter
339             lastWord = lastWord.substring(1, lastWord.length());
340             if (!lastWord.isEmpty()) {
341                 // Use first letter of new lastWord to concat the alias
342                 alias += lastWord.charAt(0);
343             } else {
344                 // Generate an alea char to concat
345                 alias += StringUtils.upperCase(RandomStringUtils.randomAlphabetic(1));
346             }
347         }
348 
349         String aliasPropertyName = CONSTANT_PREFIX + constantName;
350 
351         if (log.isDebugEnabled()) {
352             log.debug("Add alias '" + alias + "' named " + aliasPropertyName);
353         }
354 
355         aliases.put(alias, aliasPropertyName);
356 
357         addAttribute(helperClass, aliasPropertyName, String.class, "\"" + alias + "\"",
358                 ObjectModelJavaModifier.PUBLIC, ObjectModelJavaModifier.STATIC, ObjectModelJavaModifier.FINAL);
359 
360         return aliasPropertyName;
361     }
362 
363     protected ObjectModelClass createInnerClass(ObjectModelClassifier input, String aliasConstant) {
364         String className = getPropertyClassName(input);
365 
366         ObjectModelClass propertyClass = (ObjectModelClass)
367                 addInnerClassifier(helperClass,
368                         ObjectModelType.OBJECT_MODEL_CLASS,
369                         className,
370                         ObjectModelJavaModifier.STATIC);
371 
372         if (log.isDebugEnabled()) {
373             log.debug("Generate for entity : " + input.getQualifiedName());
374         }
375 
376         // FIXME-fdesbois-2010-06-23 : need to manage imports for inner classes : EUGene ImportsManager
377         addImport(helperClass, input.getQualifiedName());
378 
379         // Important to keep qualifiedName for setSuperClass
380         String superClassQualifiedName = abstractEntityPropertyClass.getQualifiedName().
381                 replace(ENTITY_PROPERTY_GENERIC_TYPE, "<" + input.getName() + ">");
382 
383         setSuperClass(propertyClass, superClassQualifiedName);
384 
385         ObjectModelOperation constructor =
386                 addConstructor(propertyClass, ObjectModelJavaModifier.PROTECTED);
387 
388         setOperationBody(constructor, ""
389     /*{
390     }*/
391         );
392 
393         ObjectModelOperation getEntityClass =
394                 addOperation(propertyClass, "getEntityClass", "Class<" + input.getName() + ">",
395                         ObjectModelJavaModifier.PUBLIC);
396 
397         addAnnotation(propertyClass, getEntityClass, "Override");
398 
399         setOperationBody(getEntityClass, ""
400     /*{
401             return <%=input.getName()%>.class;
402     }*/
403         );
404 
405         ObjectModelOperation defaultAlias =
406                 addOperation(propertyClass, "defaultAlias", String.class,
407                         ObjectModelJavaModifier.PUBLIC);
408 
409         addAnnotation(propertyClass, defaultAlias, "Override");
410 
411         setOperationBody(defaultAlias, ""
412     /*{
413             return <%=aliasConstant%>;
414     }*/
415         );
416 
417         createGetterOperations(input, propertyClass);
418 
419         return propertyClass;
420     }
421 
422     protected void createGetterOperations(ObjectModelClassifier input, ObjectModelClass propertyClass) {
423 
424         // Generate for all attributes
425         for (ObjectModelAttribute attr : input.getAttributes()) {
426 
427             // Case we don't want generation for
428             if (!attr.isNavigable()) {
429                 continue;
430             }
431 
432             String attrName = getReferenceAttributeName(attr);
433 
434             if (log.isDebugEnabled()) {
435                 log.debug("Entity property : name=" + attrName +
436                         " _ navigable=" + attr.isNavigable() +
437                         " _ maxMultiplicity=" + attr.getMaxMultiplicity() +
438                         " _ associationClass=" + attr.hasAssociationClass() +
439                         " _ referenceClassifier=" + attr.referenceClassifier());
440             }
441 
442             ObjectModelOperation propertyNameOperation =
443                     createGetPropertyNameOperation(propertyClass, attrName, input.getName());
444 
445             createGetPropertyObjectOperation(propertyClass, attr, propertyNameOperation);
446         }
447 
448         // Case of Association class : generate also for participant properties
449         if (input instanceof ObjectModelAssociationClass) {
450 
451             ObjectModelAssociationClass assoc = (ObjectModelAssociationClass)input;
452 
453             for (ObjectModelAttribute attr : assoc.getParticipantsAttributes()) {
454                 ObjectModelOperation propertyNameOperation =
455                         createGetPropertyNameOperation(propertyClass, attr.getName(), input.getName());
456 
457                 createGetPropertyObjectOperation(propertyClass, attr, propertyNameOperation);
458             }
459         }
460     }
461 
462     protected ObjectModelOperation createGetPropertyNameOperation(ObjectModelClass output, String attrName, String entityClassName) {
463         ObjectModelOperation result =
464                     addOperation(output, attrName, String.class, ObjectModelJavaModifier.PUBLIC);
465 
466         String constantName =
467                 entityClassName + "." + getConstantName(attrName);
468 
469         if (log.isDebugEnabled()) {
470             log.debug("Add getter for property : " + attrName +
471                     " _ constantName = " + constantName +
472                     " _ constantPrefix = " + getConstantPrefix());
473         }
474 
475         setOperationBody(result, ""
476     /*{
477             return $property(<%=constantName%>);
478     }*/
479         );
480 
481         return result;
482     }
483 
484     protected ObjectModelOperation createGetPropertyObjectOperation(ObjectModelClass output,
485                                                                     ObjectModelAttribute attrReference,
486                                                                     ObjectModelOperation propertyNameOperation) {
487 
488         ObjectModelClassifier referenceClass = getReferenceAttributeClassifier(attrReference);
489 
490         // No reference, can't add method to getPropertyObject
491         // ANO-#1648: Enum are ignored here, they will be used as simple property
492         if (referenceClass == null || referenceClass.isEnum()) {
493             return null;
494         }
495 
496         String operationName = getReferenceAttributeName(attrReference) +
497                 ENTITY_PROPERTY_SUFFIX;
498 
499         String referencePropertyClassName = getPropertyClassName(referenceClass);
500 
501         ObjectModelOperation result =
502                 addOperation(output, operationName, referencePropertyClassName);
503 
504         if (log.isDebugEnabled()) {
505             log.debug("Extra operation : " + operationName +
506                     " _ className = " + referencePropertyClassName);
507         }
508 
509         setOperationBody(result, ""
510     /*{
511             return new<%=referencePropertyClassName%>(<%=propertyNameOperation.getName()%>());
512     }*/
513         );
514 
515         return result;
516     }
517 
518     protected void createNewOperations(ObjectModelClass entityProperty) {
519 
520         String className = entityProperty.getName();
521 
522         String methodName = "new" + className;
523 
524         ObjectModelOperation newEntityProperty1 =
525                 addOperation(helperClass, "new" + className, className,
526                         ObjectModelJavaModifier.PUBLIC, ObjectModelJavaModifier.STATIC);
527 
528         setOperationBody(newEntityProperty1, ""
529     /*{
530         return <%=methodName%>(null);
531     }*/
532         );
533 
534         ObjectModelOperation newEntityProperty2 =
535                 addOperation(helperClass, "new" + className, className,
536                         ObjectModelJavaModifier.PUBLIC, ObjectModelJavaModifier.STATIC);
537         addParameter(newEntityProperty2, String.class, "alias");
538 
539         setOperationBody(newEntityProperty2, ""
540     /*{
541         return newEntityProperty(<%=className%>.class, alias);
542     }*/
543         );
544     }
545 
546     protected void addExtraForSubEntity(ObjectModelClassifier entityClass) {
547         for (ObjectModelAttribute attr : entityClass.getAttributes()) {
548 
549             if (attr.isNavigable() && attr.referenceClassifier() &&
550                     attr.getClassifier().getName().equals(entityClass.getName())) {
551                 // Same entity
552 
553                 String propertyClassName = getPropertyClassName(entityClass);
554 
555                 String subEntityName = entityClass.getName() + StringUtils.capitalize(attr.getName());
556                 String aliasConstant = createAliasConstant(subEntityName);
557 
558                 ObjectModelOperation newEntityProperty =
559                     addOperation(helperClass, "new" + subEntityName + "Property", propertyClassName,
560                         ObjectModelJavaModifier.PUBLIC, ObjectModelJavaModifier.STATIC);
561 
562                 String callMethodName = "new" + propertyClassName;
563 
564                 setOperationBody(newEntityProperty, ""
565     /*{
566         return <%=callMethodName%>(<%=aliasConstant%>);
567     }*/
568                 );
569 
570             }
571         }
572     }
573 
574     // Helpers
575     protected String getPropertyClassName(ObjectModelClassifier entityClass) {
576         return entityClass.getName() + ENTITY_PROPERTY_SUFFIX;
577     }
578 
579     protected String getReferenceAttributeName(ObjectModelAttribute attrReference) {
580         String attrName = attrReference.getName();
581         if(attrReference.hasAssociationClass()) {
582             attrName = GeneratorUtil.getAssocAttrName(attrReference);
583         }
584         return attrName;
585     }
586 
587     protected ObjectModelClassifier getReferenceAttributeClassifier(ObjectModelAttribute attrReference) {
588         ObjectModelClassifier referenceClass = null;
589         // case for attribute classifier, only for maxMultiplicity = 1
590         if (attrReference.referenceClassifier() && attrReference.getMaxMultiplicity() == 1) {
591             referenceClass = attrReference.getClassifier();
592         // case for association attribute
593         } else if (attrReference.hasAssociationClass()) {
594             referenceClass = attrReference.getAssociationClass();
595         }
596         return referenceClass;
597     }
598 
599     // For tests
600     protected void setBuilder(JavaBuilder builder) {
601         this.builder = builder;
602     }
603 }