org.nuiton.topia.templates.EntityDaoTransformer Maven / Gradle / Ivy
package org.nuiton.topia.templates;
/*
* #%L
* Toolkit :: Templates
* %%
* Copyright (C) 2017 - 2024 Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* .
* #L%
*/
import com.google.common.base.Strings;
import fr.ird.observe.toolkit.templates.entity.query.SqlQueryDefinition;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.codehaus.plexus.component.annotations.Component;
import org.nuiton.eugene.EugeneCoreTagValues;
import org.nuiton.eugene.GeneratorUtil;
import org.nuiton.eugene.Template;
import org.nuiton.eugene.java.ObjectModelTransformerToJava;
import org.nuiton.eugene.java.extension.ObjectModelAnnotation;
import org.nuiton.eugene.models.object.ObjectModel;
import org.nuiton.eugene.models.object.ObjectModelAssociationClass;
import org.nuiton.eugene.models.object.ObjectModelAttribute;
import org.nuiton.eugene.models.object.ObjectModelClass;
import org.nuiton.eugene.models.object.ObjectModelJavaModifier;
import org.nuiton.eugene.models.object.ObjectModelOperation;
import org.nuiton.eugene.models.object.ObjectModelPackage;
import org.nuiton.topia.persistence.TopiaEntity;
import org.nuiton.topia.persistence.TopiaQueryBuilderAddCriteriaOrRunQueryStep;
import org.nuiton.topia.persistence.internal.AbstractTopiaDao;
import org.nuiton.topia.persistence.internal.support.HibernateTopiaJpaSupport;
import org.nuiton.topia.persistence.support.TopiaHibernateSupport;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* To generate all {@code DAO} related classes for a given entity.
*
* @author Tony Chemit - [email protected]
* @since 2.5.4
*/
@Component(role = Template.class, hint = "org.nuiton.topia.templates.EntityDaoTransformer")
public class EntityDaoTransformer extends ObjectModelTransformerToJava {
private static final Logger log = LogManager.getLogger(EntityDaoTransformer.class);
/**
* map of direct usages (values) for each entity (key).
*
* This map is used to generate the findUsages methods for DAOAbstract.
*/
protected Map> usages;
/**
* All entities fqn of the model (used to detect if an attribute is not
* an entity).
*/
protected Set allEntitiesFqn;
protected TopiaTemplateHelper templateHelper;
protected final TopiaCoreTagValues topiaCoreTagValues;
protected final TopiaHibernateTagValues topiaHibernateTagValues;
private boolean useRelativeName;
private EntityClassContext classContext;
public EntityDaoTransformer() {
this.topiaCoreTagValues = new TopiaCoreTagValues();
this.topiaHibernateTagValues = new TopiaHibernateTagValues();
}
@Override
public void transformFromModel(ObjectModel model) {
if (templateHelper == null) {
templateHelper = new TopiaTemplateHelper(model);
}
EugeneCoreTagValues coreTagValues = new EugeneCoreTagValues();
useRelativeName = coreTagValues.isUseRelativeName(model);
usages = templateHelper.searchDirectUsages(model);
generateParentDao();
// keep all classifiers on the model which are entities
List allEntities = templateHelper.getEntityClasses(model, true);
allEntitiesFqn = new HashSet<>(allEntities.size());
for (ObjectModelClass entity : allEntities) {
if (!templateHelper.isAbstract(entity)) {
String fqn = entity.getQualifiedName();
allEntitiesFqn.add(fqn);
}
}
}
@Override
public void transformFromClass(ObjectModelClass clazz) {
if (!templateHelper.isEntity(clazz)) {
// not an entity
return;
}
if (templateHelper.isAbstract(clazz)) {
return;
}
classContext = new EntityClassContext(templateHelper, model, model.getPackage(clazz), clazz);
String clazzName = clazz.getName();
String clazzFQN = clazz.getQualifiedName();
if (isGenerateGeneratedDao(clazz)) {
generateGeneratedDao(clazz, clazzName, clazzFQN);
}
if (isGenerateAbstractDao(clazz)) {
generateAbstractDao(clazz, clazzName, clazzFQN);
}
if (isGenerateConcreteDao(clazz)) {
generateConcreteDao(clazz, clazzName, clazzFQN);
}
}
protected boolean isGenerateConcreteDao(ObjectModelClass input) {
String daoConcreteFqn = templateHelper.getConcreteDaoFqn(input);
if (getResourcesHelper().isJavaFileInClassPath(daoConcreteFqn)) {
// already in class-path
return false;
}
// can safely generate the dao impl
return true;
}
protected boolean isGenerateGeneratedDao(ObjectModelClass input) {
String daoGeneratedFqn = templateHelper.getGeneratedDaoFqn(input);
if (getResourcesHelper().isJavaFileInClassPath(daoGeneratedFqn)) {
// already in class-path
return false;
}
// can safely generate the dao impl
return true;
}
protected boolean isGenerateAbstractDao(ObjectModelClass input) {
String fqn = templateHelper.getAbstractDaoFqn(input);
return !getResourcesHelper().isJavaFileInClassPath(fqn);
}
protected void generateConcreteDao(ObjectModelClass clazz, String clazzName, String clazzFQN) {
String concreteDaoName = templateHelper.getConcreteDaoName(clazz);
ObjectModelClass daoClass = createClass(concreteDaoName, clazz.getPackageName());
setDocumentation(daoClass, "/**\n" +
" * Cette classe etend le DAOImpl pour parametrer la classe avec le bon type\n" +
" * Cette classe est marque finale car l'heritage entre les DAO se fait\n" +
" * sur les DOAImpl, c-a-d que DAOAbstract peut etendre le DAOImpl\n" +
" */");
String superclassQualifiedName = templateHelper.getAbstractDaoFqn(clazz) + "<" + clazzName + ">";
setSuperClass(daoClass, superclassQualifiedName);
// TODO AThimel 25/10/13 Remove the next lines in ToPIA 3.1
// Look for legacy Dao class, then warn user if found in classpath
String legacyConcreteDaoName = templateHelper.getLegacyDaoName(clazz);
warnOnLegacyClassDetected(clazz.getPackageName(), legacyConcreteDaoName, concreteDaoName, null,
templateHelper.getAbstractDaoFqn(clazz) + "<" + clazzName + ">");
}
protected void generateAbstractDao(ObjectModelClass clazz,
String clazzName,
String clazzFQN) {
String abstractDaoName = templateHelper.getAbstractDaoName(clazz);
String daoGenerics = "";
ObjectModelClass abstractDaoClass = createClass(abstractDaoName + daoGenerics, clazz.getPackageName());
String documentation = String.format("/**%n" +
" * Implantation du Dao pour l'entité '%s'.%n" +
" * L'utilisateur peut remplacer cette classe par la sienne en la mettant%n" +
" * simplement dans ses sources. Cette classe générée sera alors simplement%n" +
" * ignorée à la génération suivante.%n" +
" */", clazzName);
setDocumentation(abstractDaoClass, documentation);
String superclassQualifiedName = templateHelper.getGeneratedDaoFqn(clazz) + "";
setSuperClass(abstractDaoClass, superclassQualifiedName);
// TODO AThimel 25/10/13 Remove the next lines in ToPIA 3.1
// Look for legacy Dao class, then warn user if found in classpath
String legacyDaoImplName = templateHelper.getLegacyDaoName(clazz) + "Impl";
warnOnLegacyClassDetected(clazz.getPackageName(), legacyDaoImplName, abstractDaoName, daoGenerics,
superclassQualifiedName);
}
// TODO AThimel 25/10/13 Remove this method in ToPIA 3.1
protected void warnOnLegacyClassDetected(String packageName,
String legacyDaoName,
String daoName,
String daoGenerics,
String superclassQualifiedName) {
String legacyDaoFqn = String.format("%s.%s", packageName, legacyDaoName);
// AThimel 25/10/13 Not using isInClassPath(fqn) because this method logs that file won't be generated
if (log.isWarnEnabled() && getFileInClassPath(legacyDaoFqn) != null) {
String format = "public class %s%s extends %s {";
String superclassName = superclassQualifiedName.substring(superclassQualifiedName.lastIndexOf('.') + 1);
String daoDeclaration = String.format(format, daoName, Strings.nullToEmpty(daoGenerics), superclassName);
String warnMessage = "Legacy DAO detected : %s !%n" +
" - You should consider renaming '%s' to '%s'%n" +
" - Expected class declaration is : %s%n" +
"%n"; // AThimel 29/10/13 Add a new line for log clearness
log.warn(String.format(warnMessage, legacyDaoFqn, legacyDaoName, daoName, daoDeclaration));
}
}
protected void generateParentDao() {
String parentDaoFqn = templateHelper.getParentDaoFqn(this, model);
if (!getResourcesHelper().isJavaFileInClassPath(parentDaoFqn)) {
ObjectModelClass parentDao =
createAbstractClass(
templateHelper.getParentDaoName(model) + "",
templateHelper.getDaoPackage(this, model)
);
addImport(parentDao, TopiaEntity.class);
// super class
setSuperClass(parentDao, AbstractTopiaDao.class.getName() + "");
}
}
protected void generateGeneratedDao(ObjectModelClass clazz,
String clazzName,
String clazzFQN) {
String outputName = templateHelper.getGeneratedDaoName(clazz) + "';
ObjectModelClass daoAbstractClass = createAbstractClass(outputName, clazz.getPackageName());
addImport(daoAbstractClass, clazz.getQualifiedName());
// super class
String superClassName = topiaCoreTagValues.getDaoSuperClassTagValue(clazz, getPackage(clazz), model);
if (superClassName == null) {
superClassName = templateHelper.getParentDaoFqn(this, model);
addImport(daoAbstractClass, superClassName);
}
superClassName += "";
setSuperClass(daoAbstractClass, superClassName);
String prefix = getConstantPrefix(clazz);
setConstantPrefix(prefix);
// imports
addImport(daoAbstractClass, List.class);
addImport(daoAbstractClass, TopiaQueryBuilderAddCriteriaOrRunQueryStep.class);
ObjectModelOperation op;
// getEntityClass
op = addOperation(daoAbstractClass,
"getEntityClass",
"Class",
ObjectModelJavaModifier.PUBLIC);
addAnnotation(daoAbstractClass, op, Override.class);
ObjectModelAnnotation annotation = addAnnotation(daoAbstractClass, op, SuppressWarnings.class);
addAnnotationParameter(daoAbstractClass, annotation, "value", "unchecked");
setOperationBody(op, ""
+"\n"
+" return (Class) "+clazzName+".class;\n"
+" "
);
op = addOperation(daoAbstractClass,
"newInstance0",
"E",
ObjectModelJavaModifier.PUBLIC);
addAnnotation(daoAbstractClass, op, Override.class);
setOperationBody(op, ""
+"\n"
+" return getEntityClass().cast(new "+clazzName+"Impl());\n"
+" "
);
generateDelete(clazz, daoAbstractClass);
for (ObjectModelAttribute attr : classContext.getAttributes()) {
if (!attr.isNavigable()) {
continue;
}
if (!GeneratorUtil.isNMultiplicity(attr)) {
generateNoNMultiplicity(clazzName, daoAbstractClass, attr, false);
} else {
generateNMultiplicity(clazzName, daoAbstractClass, attr);
}
}
if (clazz instanceof ObjectModelAssociationClass) {
ObjectModelAssociationClass assocClass = (ObjectModelAssociationClass) clazz;
for (ObjectModelAttribute attr : assocClass.getParticipantsAttributes()) {
if (attr != null) {
if (!GeneratorUtil.isNMultiplicity(attr)) {
generateNoNMultiplicity(clazzName, daoAbstractClass, attr, true);
} else {
generateNMultiplicity(clazzName, daoAbstractClass, attr);
}
}
}
}
generateUserSqlQueries(daoAbstractClass, classContext.getUserQueryDefinitions());
}
private void generateUserSqlQueries(ObjectModelClass clazz, Set userQueryDefinitions) {
for (SqlQueryDefinition userQueryDefinition : userQueryDefinitions) {
String queryName = userQueryDefinition.getName();
addImport(clazz, "org.hibernate.query.NativeQuery");
String queryMethodName = "query" + queryName;
boolean withParameters = userQueryDefinition.withParameters();
ObjectModelOperation op = addOperation(clazz,
queryMethodName,
" NativeQuery",
withParameters?ObjectModelJavaModifier.PROTECTED:ObjectModelJavaModifier.PUBLIC);
setOperationBody(op, ""
+"\n"
+" return getSqlQuery(\""+queryName+"\");\n"
+" "
);
if (withParameters) {
op = addOperation(clazz,
queryMethodName,
" NativeQuery",
ObjectModelJavaModifier.PUBLIC);
StringBuilder content = new StringBuilder(""+"\n"
+" return this."+queryMethodName+"()");
int index = 1;
for (Map.Entry entry : userQueryDefinition.getParameters().entrySet()) {
String name = entry.getKey();
String type = entry.getValue();
addImport(clazz, type);
addParameter(op, type, name);
content.append("" +"\n"
+" .setParameter("+index+++", "+name+")");
}
content.append(""+";\n"
+" ");
setOperationBody(op, content.toString());
}
}
}
protected void generateDelete(ObjectModelClass clazz, ObjectModelClass result) {
StringBuilder body = new StringBuilder();
String modelName = io.ultreia.java4all.lang.Strings.capitalize(model.getName());
String providerFQN = getDefaultPackageName() + '.' + modelName + "DAOHelper.getImplementationClass";
ObjectModelPackage aPackage = getPackage(clazz);
body.append(""
+"\n"
+" if (!entity.isPersisted()) {\n"
+" throw new IllegalArgumentException(\"entity \" + entity + \" is not persisted, you can't delete it\");\n"
+" }\n"
+""
);
boolean hibernateSupportGenerated = false;
for (ObjectModelAttribute attr : classContext.getAttributes()) {
String attrType = useRelativeName ? attr.getType() : GeneratorUtil.getSimpleName(attr.getType());
String reverseAttrName = attr.getReverseAttributeName();
ObjectModelAttribute reverse = attr.getReverseAttribute();
if (attr.hasAssociationClass() ||
reverse == null || !reverse.isNavigable()) {
// never treat a non reverse and navigable attribute
// never treat an association class attribute
continue;
}
// at this point we are sure to have a attribute which is
// - reverse
// - navigable
// - not from an association class
if (!allEntitiesFqn.contains(attr.getType())) {
// this attribute is not from an entity, don't treat it
if (log.isDebugEnabled()) {
log.debug("[" + result.getName() + "] Skip attribute [" +
attr.getName() + "] with type " + attr.getType());
}
continue;
}
if (!allEntitiesFqn.contains(reverse.getType())) {
// reverse attribute is not from an entity, don't treat it
if (log.isDebugEnabled()) {
log.debug("[" + result.getName() + "] Skip attribute [" +
reverse.getName() + "] with type " + reverse.getType());
}
continue;
}
// At this point, the attribute type is a entity
if (GeneratorUtil.isNMultiplicity(attr) &&
GeneratorUtil.isNMultiplicity(reverse)) {
// On doit absolument supprimer pour les relations many-to-many
// le this de la collection de l'autre cote
String attrDBName = attr.isNavigable() ? templateHelper.getDbName(attr) : templateHelper.getReverseDbName(attr.getReverseAttribute());
String attrClassifierDBNameSchemaName = templateHelper.getSchema(attr.getClassifier());
String attrClassifierDBName = templateHelper.getDbName(attr.getClassifier());
String attrJoinTableName = attr.isNavigable() ? templateHelper.getManyToManyTableName(attr) : templateHelper.getManyToManyTableName(attr.getReverseAttribute());
if (attrClassifierDBNameSchemaName != null) {
attrClassifierDBName = attrClassifierDBNameSchemaName + "." + attrClassifierDBName;
attrJoinTableName = attrClassifierDBNameSchemaName + "." + attrJoinTableName;
}
String attrReverseDBName = templateHelper.getReverseDbName(attr);
if (!hibernateSupportGenerated) {
hibernateSupportGenerated = true;
addImport(result, TopiaHibernateSupport.class);
addImport(result, HibernateTopiaJpaSupport.class);
body.append(""
+"\n"
+" TopiaHibernateSupport hibernateSupport = ((HibernateTopiaJpaSupport) topiaJpaSupport).getHibernateSupport();\n"
+""
);
}
String implementation = attr.getClassifier().getQualifiedName() + "Impl";
String removeName = getJavaBeanMethodName("remove", reverseAttrName);
body.append(""
+"\n"
+" {\n"
+" String sql = \"SELECT main.* \" +\n"
+" \" FROM "+attrClassifierDBName+" main, "+attrJoinTableName+" secondary \" +\n"
+" \" WHERE main.topiaId=secondary."+attrDBName+" \" +\n"
+" \" AND secondary."+attrReverseDBName+"='\" + entity.getTopiaId() + \"'\";\n"
+" List<"+attrType+"> list = hibernateSupport.getHibernateSession()\n"
+" .createNativeQuery(sql)\n"
+" .addEntity(\"main\", \""+implementation+"\")\n"
+" .list();\n"
+"\n"
+" for ("+attrType+" item : list) {\n"
+" item."+removeName+"(entity);\n"
+" }\n"
+" }\n"
+""
);
} else if (!GeneratorUtil.isNMultiplicity(reverse)) {
// On doit mettre a null les attributs qui ont cet objet sur les
// autres entites en one-to-*
// TODO peut-etre qu'hibernate est capable de faire ca tout seul ?
// THIMEL: J'ai remplacé reverse.getName() par reverseAttrName sans certitude
String attrSimpleType = GeneratorUtil.getClassNameFromQualifiedName(attrType);
if (useRelativeName) {
attrSimpleType = attrType;
} else {
addImport(result, attrType);
addImport(result, attr.getType() + "TopiaDao"); // AThimel 30/10/13 Not using attrType because we need FQN // Can use TopiaTemplateHelper.getConcreteDaoFqn(...) ?
}
// XXX brendan 04/10/13 do not hard code concrete dao name
String attrConcreteDaoClassName = attrSimpleType + "TopiaDao";
String getName = getJavaBeanMethodName("get", reverseAttrName);
String setName = getJavaBeanMethodName("set", reverseAttrName);
body.append(""
+"\n"
+" {\n"
+" "+attrConcreteDaoClassName+" dao = topiaDaoSupplier\n"
+" .getDao("+attrSimpleType+".class, "+attrConcreteDaoClassName+".class);\n"
+" List<"+attrSimpleType+"> list = dao\n"
+" .forProperties("+attrSimpleType+"."+getConstantName(reverseAttrName)+", entity)\n"
+" .findAll();\n"
+" for ("+attrSimpleType+" item : list) {\n"
+"\n"
+" // sletellier : Set null only if target is concerned by deletion\n"
+" if (entity.equals(item."+getName+"())) {\n"
+" item."+setName+"(null);\n"
+" }\n"
+" "
);
if (attr.isAggregate()) {
body.append(""
+"\n"
+" topiaDaoSupplier.getDao("+attrSimpleType+".class).delete(item);\n"
+""
);
}
body.append(""
+"\n"
+" }\n"
+" }\n"
+""
);
}
}
if (body.length() > 0) {
// something specific was done, need to generate the method
ObjectModelOperation op;
op = addOperation(result, "delete", "void", ObjectModelJavaModifier.PUBLIC);
addAnnotation(result, op, Override.class);
addParameter(op, "E", "entity");
body.append(""
+"\n"
+" super.delete(entity);\n"
+" "
);
setOperationBody(op, body.toString());
}
}
protected void generateNoNMultiplicity(String clazzName,
ObjectModelClass result,
ObjectModelAttribute attr,
boolean isAssoc) {
String attrName = attr.getName();
String attrType = attr.getType();
String propertyName = clazzName + "." + getConstantName(attrName);
String attrTypeForGeneric;
if (GeneratorUtil.isPrimitiveType(attrType)) {
attrTypeForGeneric = templateHelper.getClassForPrimitiveType(attr);
} else {
attrTypeForGeneric = attrType;
}
if (!isAssoc && attr.hasAssociationClass()) {
String assocClassName = attr.getAssociationClass().getName();
String assocAttrName = GeneratorUtil.getAssocAttrName(attr);
// It is about transitivity : use the property to access the
// associationClass + '.' + the property to access the expected
// attribute
// . + '.' + .
propertyName =
clazzName + '.' + getConstantName(assocAttrName) +
" + '.' + " +
assocClassName + '.' + getConstantName(attrName);
}
addImport(result, Collection.class);
ObjectModelOperation op;
op = addOperation(result,
getJavaBeanMethodName("for", attrName, "In"),
"TopiaQueryBuilderAddCriteriaOrRunQueryStep",
ObjectModelJavaModifier.PUBLIC);
addParameter(op, "Collection<" + attrTypeForGeneric + ">", "v");
setOperationBody(op, ""
+"\n"
+" TopiaQueryBuilderAddCriteriaOrRunQueryStep result = forIn("+propertyName+", (Collection) v);\n"
+" return result;\n"
+" "
);
op = addOperation(result,
getJavaBeanMethodName("for", attrName, "Equals"),
"TopiaQueryBuilderAddCriteriaOrRunQueryStep",
ObjectModelJavaModifier.PUBLIC);
addParameter(op, attrType, "v");
setOperationBody(op, ""
+"\n"
+" TopiaQueryBuilderAddCriteriaOrRunQueryStep result = forEquals("+propertyName+", v);\n"
+" return result;\n"
+" "
);
}
protected void generateNMultiplicity(String clazzName,
ObjectModelClass result,
ObjectModelAttribute attr) {
String attrName = attr.getName();
String attrType = attr.getType();
if (attr.hasAssociationClass()) {
// do nothing for association class, too complex...
return;
}
ObjectModelOperation op;
op = addOperation(result,
getJavaBeanMethodName("for", attrName, "Contains"),
"TopiaQueryBuilderAddCriteriaOrRunQueryStep",
ObjectModelJavaModifier.PUBLIC);
addParameter(op, attrType, "v");
setOperationBody(op, ""
+"\n"
+" return forContains("+clazzName + "." + getConstantName(attrName)+", v);\n"
+" "
);
}
}