Author: fdesbois
Date: 2010-03-24 00:29:29 +0100 (Wed, 24 Mar 2010)
New Revision: 1853
Log:
Evo #412 : template for final application -> generate abstract services with skeleton which contains transaction (topiaContext) managment (try/catch/finally, errors, ...)
See javadoc
Added:
trunk/topia-persistence/src/main/java/org/nuiton/topia/generator/BusinessTransformer.java
Modified:
trunk/topia-persistence/src/main/java/org/nuiton/topia/generator/TopiaGeneratorUtil.java
Added: trunk/topia-persistence/src/main/java/org/nuiton/topia/generator/BusinessTransformer.java
===================================================================
--- trunk/topia-persistence/src/main/java/org/nuiton/topia/generator/BusinessTransformer.java (rev 0)
+++ trunk/topia-persistence/src/main/java/org/nuiton/topia/generator/BusinessTransformer.java 2010-03-23 23:29:29 UTC (rev 1853)
@@ -0,0 +1,573 @@
+
+package org.nuiton.topia.generator;
+
+import java.util.ArrayList;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.nuiton.eugene.GeneratorUtil;
+import org.nuiton.eugene.Template;
+import org.nuiton.eugene.java.ObjectModelTransformerToJava;
+import org.nuiton.eugene.models.object.ObjectModel;
+import org.nuiton.eugene.models.object.ObjectModelClass;
+import org.nuiton.eugene.models.object.ObjectModelInterface;
+import org.nuiton.eugene.models.object.ObjectModelModifier;
+import org.nuiton.eugene.models.object.ObjectModelOperation;
+import org.nuiton.eugene.models.object.ObjectModelParameter;
+import org.nuiton.i18n.I18n;
+import org.nuiton.topia.TopiaContext;
+import org.nuiton.topia.TopiaException;
+
+/*{generator option: parentheses = false}*/
+
+/*{generator option: writeString = +}*/
+
+/**
+ * This Template is used to create the skeleton of services for a final
+ * application which using Topia.
+ * <pre>
+ * Generation from a model named 'App' :
+ *
+ * - AppContext : empty super interface to used in application UI. Can
+ * be override in model if put in defaultPackage (ex : org.chorem.app) set
+ * in maven-eugene-plugin configuration.
+ *
+ * - AppContextImplementor : interface which extends AppContext to add
+ * technical methods for the application. Generation of methods :
+ * * doCatch : used to catch all exception from a service method.
+ * * doFinally : used to finally the try/catch of a service method
+ * * beginTransaction : start the transaction using rootContext.
+ * These three methods have to be implemented in a class which implements
+ * AppContextImplementor (ex : AppContextImpl). You can also add others
+ * methods to AppContextImplementor in the same way as AppContext.
+ *
+ * - AppException : exception class which extends RuntimeException for all
+ * technical exceptions which appears in service method. If you want to
+ * manage some specific exceptions, you have to managed them in doCatch
+ * implementation.
+ *
+ * Generation from interfaces with stereotype <<service>> :
+ *
+ * - Service : interface of the service defined in model.
+ *
+ * - ServiceAbstract : abstract class which contains :
+ * * constructor with AppContextImplementor in argument
+ * * for each method : the implementation of the method (skeleton with
+ * try/catch and beginTransaction call to open a new TopiaContext from
+ * AppContextImplementor). Usage of i18n keys for error messages in
+ * exception.
+ * * for each method : an abstract method used to execute the business
+ * code of the method : need to be implemented in subclass.
+ *
+ * Exemple of AppContextImpl :
+ *
+ * public class AppContextImpl implements AppContextImplementor {
+ *
+ * // properties for Topia configuration
+ * protected Properties properties;
+ * ...
+ *
+ * @Override
+ * public void doCatch(TopiaContext transaction, Exception eee,
+ * String message, Object... args) throws AppException {
+ *
+ * // Note that the message from service doesn't directly use _() for
+ * // i18 messages but n_(). In this log, the _() is used to translate
+ * // correctly the message. But the message must be translate when
+ * // catching the AppException in UI.
+ * if (log.isErrorEnabled()) {
+ * log.error(_(message, args), eee);
+ * }
+ *
+ * // rollback of current transaction
+ * if (transaction != null) {
+ * try {
+ * transaction.rollbackTransaction();
+ * } catch (TopiaException ex) {
+ * if (log.isErrorEnabled()) {
+ * log.error(_("app.error.context.rollback"), ex);
+ * }
+ * }
+ * }
+ * // wrapping the exception in a AppException with message and
+ * // arguments for i18n translation
+ * throw new AppException(eee, message, args);
+ * }
+ *
+ * @Override
+ * public void doFinally(TopiaContext transaction) {
+ * if (transaction != null) {
+ * try {
+ * transaction.closeContext();
+ * } catch (TopiaException eee) {
+ * if (log.isErrorEnabled()) {
+ * log.error(_("app.error.context.close"), eee);
+ * }
+ * }
+ * }
+ * }
+ *
+ * @Override
+ * public TopiaContext beginTransaction() throws TopiaException {
+ * TopiaContext rootContext = null;
+ * try {
+ * // You have to manage the properties using ApplicationConfig
+ * // or other lib to have configuration for Topia
+ * rootContext = TopiaContextFactory.getContext(properties);
+ *
+ * return getTopiaRootContext().beginTransaction();
+ *
+ * // only catch exception for rootContext
+ * } catch (TopiaNotFoundException eee) {
+ * doCatch(eee, n_("app.error.context.getTopiaRootContext"));
+ * }
+ * return null;
+ * }
+ * ...
+ * }
+ *
+ * Exemple of ServiceImpl :
+ *
+ * public class ServiceImpl extends ServiceAbstract {
+ *
+ * public ServiceImpl(AppContextImplementor context) {
+ * super(context);
+ * }
+ *
+ * // Implementation of abstract method, the interface method is
+ * // called 'createMyEntity(MyEntity entity)' in this case.
+ * @Override
+ * public void executeCreateMyEntity(TopiaContext transaction,
+ * MyEntity entity) throws TopiaException {
+ *
+ * MyEntityDAO dao = AppDAOHelper.getMyEntityDAO(transaction);
+ * dao.create(entity);
+ * // That's it, no need to manage errors or transaction, the abstract
+ * // service will do this job.
+ * }
+ * }
+ *
+ * TAG_TRANSACTION
+ * ---------------
+ *
+ * You can use the tagValue 'transaction=false' to specify that a method doesn't
+ * need any TopiaContext, so no need to instantiate a new one. This tagValue
+ * can only be put directly in the model and not in properties file (because
+ * of multiple methods with same name problem).
+ *
+ * TAG_ERROR_ARGS
+ * --------------
+ *
+ * You can use the tagValue 'errorArgs=false' to specify that a method doesn't
+ * need any arguments for error message. This tagValue can only be put directly
+ * in the model and not in properties file.
+ *
+ * It is smooth, isn't it :p ?
+ *
+ * TODO : may be refactor to integrate JTA or webservice or may be not in this
+ * transformer.
+ *
+ * TODO : find a good way to change log level
+ *
+ * </pre>
+ *
+ * Created: 23 mars 2010
+ *
+ * @author fdesbois
+ * @version $Revision$
+ *
+ * Mise a jour: $Date$
+ * par : $Author$
+ */
+public class BusinessTransformer extends ObjectModelTransformerToJava {
+
+ protected String modelName;
+
+ protected String defaultPackageName;
+
+ protected String getContextInterfaceName() {
+ return modelName + "Context";
+ }
+
+ protected String getContextImplementorInterfaceName() {
+ return getContextInterfaceName() + "Implementor";
+ }
+
+ protected String getExceptionClassName() {
+ return modelName + "Exception";
+ }
+
+ protected String getServiceAbstractClassName(String serviceName) {
+ return serviceName + "Abstract";
+ }
+
+ @Override
+ public void transformFromModel(ObjectModel model) {
+ modelName = model.getName();
+ defaultPackageName = getOutputProperties().
+ getProperty(Template.PROP_DEFAULT_PACKAGE);
+
+ ObjectModelInterface contextImplementor =
+ model.getInterface(defaultPackageName + "." +
+ getContextImplementorInterfaceName());
+
+ ObjectModelInterface context =
+ model.getInterface(defaultPackageName + "." +
+ getContextInterfaceName());
+
+ ObjectModelClass exception = createExceptionClass();
+
+ ObjectModelInterface newContextImplementor =
+ this.createInterface(getContextImplementorInterfaceName(),
+ defaultPackageName);
+ ObjectModelInterface newContext =
+ this.createInterface(getContextInterfaceName(),
+ defaultPackageName);
+
+ this.addInterface(newContextImplementor,
+ newContext.getQualifiedName());
+
+ if (contextImplementor != null) {
+ // Copy of defined operations
+ // interfaces of contextImplementor are not copied
+ copyInterfaceOperations(contextImplementor, newContextImplementor);
+ }
+
+ if (context != null) {
+ // Copy of defined operations
+ // interfaces of context are not copied
+ copyInterfaceOperations(context, newContext);
+ }
+
+ ObjectModelOperation beginTransaction =
+ this.addOperation(newContextImplementor,
+ "beginTransaction", TopiaContext.class);
+ this.addException(beginTransaction, TopiaException.class);
+
+ ObjectModelOperation doCatch1 =
+ this.addOperation(newContextImplementor, "doCatch", "void");
+ this.addParameter(doCatch1, Exception.class, "eee");
+ this.addParameter(doCatch1, String.class, "message");
+ this.addParameter(doCatch1, "Object...", "args");
+ this.addException(doCatch1, exception.getQualifiedName());
+
+ ObjectModelOperation doCatch2 =
+ this.addOperation(newContextImplementor, "doCatch", "void");
+ this.addParameter(doCatch2, TopiaContext.class, "transaction");
+ this.addParameter(doCatch2, Exception.class, "eee");
+ this.addParameter(doCatch2, String.class, "message");
+ this.addParameter(doCatch2, "Object...", "args");
+ this.addException(doCatch2, exception.getQualifiedName());
+
+ ObjectModelOperation doFinally =
+ this.addOperation(newContextImplementor, "doFinally", "void");
+ this.addParameter(doFinally, TopiaContext.class, "transaction");
+ }
+
+ protected ObjectModelClass createExceptionClass() {
+
+ ObjectModelClass exception =
+ this.createClass(getExceptionClassName(), defaultPackageName);
+
+ this.setSuperClass(exception, RuntimeException.class);
+ this.addAttribute(exception, "args", "Object[]", null,
+ ObjectModelModifier.PROTECTED);
+
+ ObjectModelOperation constructor =
+ this.addConstructor(exception, ObjectModelModifier.PUBLIC);
+
+ this.addParameter(constructor, Throwable.class, "eee");
+ this.addParameter(constructor, String.class, "message");
+ this.addParameter(constructor, "Object...", "args");
+
+ setOperationBody(constructor, ""
+ /*{
+ super(message, eee);
+ this.args = args;
+ }*/
+ );
+
+ ObjectModelOperation getArgs =
+ this.addOperation(exception, "getArgs", "Object[]",
+ ObjectModelModifier.PUBLIC);
+
+ setOperationBody(getArgs, ""
+ /*{
+ return args;
+ }*/
+ );
+
+ ObjectModelOperation hasArgs =
+ this.addOperation(exception, "hasArgs", "boolean",
+ ObjectModelModifier.PUBLIC);
+
+ setOperationBody(hasArgs, ""
+ /*{
+ return args.length > 0;
+ }*/
+ );
+
+ return exception;
+ }
+
+ /**
+ * Used to simply copy the {@code source} interface signature to the
+ * {@code dest} interface.
+ *
+ * @param source interface
+ * @param dest interface
+ */
+ protected void copyInterfaceOperations(ObjectModelInterface source,
+ ObjectModelInterface dest) {
+ for (ObjectModelOperation op : source.getOperations()) {
+ ObjectModelOperation newOp =
+ this.addOperation(dest,
+ op.getName(), op.getReturnType());
+ setDocumentation(newOp.getReturnParameter(),
+ op.getReturnParameter().getDocumentation());
+ for (ObjectModelParameter param : op.getParameters()) {
+ ObjectModelParameter newParam =
+ this.addParameter(newOp, param.getType(),
+ param.getName());
+ setDocumentation(newParam, param.getDocumentation());
+ }
+ for (String ex : op.getExceptions()) {
+ this.addException(newOp, ex);
+ }
+ setDocumentation(newOp, op.getDocumentation());
+ }
+ }
+
+ @Override
+ public void transformFromInterface(ObjectModelInterface interfacez) {
+ // skip ContextImplementor and Context interfaces
+ if (!interfacez.hasStereotype(TopiaGeneratorUtil.STEREOTYPE_SERVICE)) {
+ return;
+ }
+
+ // Create INTERFACE
+ ObjectModelInterface serviceInterface =
+ this.createInterface(interfacez.getName(),
+ interfacez.getPackageName());
+
+ copyInterfaceOperations(interfacez, serviceInterface);
+
+
+ // Create ABSTRACT CLASS
+ ObjectModelClass service = this.createAbstractClass(
+ getServiceAbstractClassName(interfacez.getName()),
+ interfacez.getPackageName());
+
+ this.addInterface(service, serviceInterface.getQualifiedName());
+
+ // Add Logger
+ // FIXME in EUGene, we want the default value not to be managed
+ // for import.
+// this.addAttribute(service, "log",
+// Log.class,
+// "LogFactory.getLog(" + interfacez.getName() + ".class)",
+// ObjectModelModifier.PRIVATE,
+// ObjectModelModifier.STATIC,
+// ObjectModelModifier.FINAL);
+ this.addAttribute(service, "log",
+ Log.class, null,
+ ObjectModelModifier.PRIVATE,
+ ObjectModelModifier.FINAL);
+
+ this.addImport(service, Log.class);
+ this.addImport(service, LogFactory.class);
+
+ String contextFqn = defaultPackageName + "." +
+ getContextImplementorInterfaceName();
+
+ // Add Context Attribute + constructor
+ this.addAttribute(service, "context", contextFqn, null,
+ ObjectModelModifier.PROTECTED);
+
+ // Constructor
+ ObjectModelOperation constructor =
+ this.addConstructor(service, ObjectModelModifier.PUBLIC);
+ this.addParameter(constructor, contextFqn, "context");
+ setOperationBody(constructor, ""
+ /*{
+ this.context = context;
+ // FIXME : must be fixed attribute value in EUGene
+ this.log = LogFactory.getLog(<%=interfacez.getName()%>.class);
+ }*/
+ );
+
+ // Prepare operation generations
+ String first = modelName.substring(0, 1);
+ String serviceName =
+ GeneratorUtil.toLowerCaseFirstLetter(interfacez.getName());
+
+ this.addImport(service, TopiaContext.class);
+ this.addImport(service, I18n.class);
+ this.addImport(service, ArrayList.class);
+
+ for (ObjectModelOperation op : interfacez.getOperations()) {
+
+ // boolean to specify if the method need a transaction or not
+ // Default set to true but can be override by a tagvalue on the
+ // method
+ boolean needTransaction = true;
+
+ String transactionTag =
+ op.getTagValue(TopiaGeneratorUtil.TAG_TRANSACTION);
+
+ if (transactionTag != null) {
+ needTransaction = Boolean.parseBoolean(transactionTag);
+ }
+
+ // boolean to specify if the method need error arguments or not
+ // Default set to true but can be override by a tagvalue on the
+ // method
+ boolean needErrorArgs = true;
+
+ String errorArgsTag =
+ op.getTagValue(TopiaGeneratorUtil.TAG_ERROR_ARGS);
+
+ if (errorArgsTag != null) {
+ needErrorArgs = Boolean.parseBoolean(errorArgsTag);
+ }
+
+ // Implementation of interface operation
+ ObjectModelOperation implOp =
+ this.addOperation(service,
+ op.getName(), op.getReturnType(),
+ ObjectModelModifier.PUBLIC);
+ this.addAnnotation(service, implOp, Override.class.getSimpleName());
+
+ String opName = StringUtils.capitalize(op.getName());
+
+ // Abstract operation to execute method content
+ ObjectModelOperation abstOp =
+ this.addOperation(service, "execute" + opName,
+ op.getReturnType(),
+ ObjectModelModifier.ABSTRACT,
+ ObjectModelModifier.PROTECTED);
+
+ if (needTransaction) {
+ this.addParameter(abstOp, TopiaContext.class, "transaction");
+ this.addException(abstOp, TopiaException.class);
+ }
+
+ // Copy exceptions
+ for (String ex : op.getExceptions()) {
+ this.addException(implOp, ex);
+ this.addException(abstOp, ex);
+ }
+
+ String toStringAppend = "";
+ String separatorLog = " : ";
+ // Prepare operation parameters
+ String opParams = "";
+ String separatorParams = "";
+ if (needErrorArgs) {
+ opParams += "errorArgs";
+ separatorParams = ", ";
+ // Add errorArgs to abstract operation
+ this.addParameter(abstOp,
+ "java.util.List<Object>", "errorArgs");
+ }
+
+ // Copy other operation parameters
+ for (ObjectModelParameter param : op.getParameters()) {
+ String paramName = param.getName();
+ this.addParameter(implOp, param.getType(), param.getName());
+ this.addParameter(abstOp, param.getType(), param.getName());
+
+ // Prepare Log
+ toStringAppend +=
+ "\n\t\t\t.append(\"" + separatorLog + paramName + " = \")" +
+ ".append(" + paramName + ")";
+ separatorLog = " _ ";
+
+ // Prepare Abstract method params
+ opParams += separatorParams + param.getName();
+ separatorParams = ", ";
+ }
+
+ // Use buffer for operation body
+ StringBuilder buffer = new StringBuilder();
+
+ // Error key for i18n
+ String errorKey = StringUtils.lowerCase(modelName) + ".error." +
+ serviceName + "." + op.getName();
+
+ String doCatchParams = "eee, I18n.n_(\"" + errorKey + "\")";
+ doCatchParams += needErrorArgs ? ", errorArgs.toArray()" : "";
+
+ // Return managment
+ String opReturn = "";
+ String finalReturn = "";
+ if (!op.getReturnType().equals("void")) {
+ opReturn = "return ";
+ finalReturn = "return null;";
+ }
+
+ if (needErrorArgs) {
+ // Init errorArgs
+ buffer.append(""
+ /*{
+ List<Object> errorArgs = new ArrayList<Object>();
+ }*/ );
+ }
+
+ if (needTransaction) {
+ // Open the transaction
+ buffer.append(""
+ /*{
+ TopiaContext transaction = null;
+ try {
+ transaction = context.beginTransaction();
+ }*/
+ );
+ // Add transaction in the execute operation parameters
+ // and doCatch parameters
+ opParams = "transaction, " + opParams;
+ doCatchParams = "transaction, " + doCatchParams;
+ } else {
+ buffer.append(""
+ /*{
+ try {
+ }*/
+ );
+ }
+
+ buffer.append(""
+ /*{
+ if (log.isDebugEnabled()) {
+ String message = new StringBuilder("<%=first%>:[ <%=opName%> ]")<%=toStringAppend%>.
+ toString();
+ log.debug(message);
+ }
+
+ <%=opReturn%>execute<%=opName%>(<%=opParams%>);
+ } catch (Exception eee) {
+ context.doCatch(<%=doCatchParams%>);
+ }*/
+ );
+
+ if (needTransaction) {
+ // Finally block to close transaction
+ buffer.append(""
+ /*{
+ } finally {
+ context.doFinally(transaction);
+ }*/
+ );
+ }
+
+ buffer.append(""
+ /*{
+ }
+ <%=finalReturn%>
+ }*/
+ );
+
+ this.setOperationBody(implOp, buffer.toString());
+ }
+
+ }
+}
Property changes on: trunk/topia-persistence/src/main/java/org/nuiton/topia/generator/BusinessTransformer.java
___________________________________________________________________
Added: svn:keywords
+ "Author Date Id Revision HeadURL"
Modified: trunk/topia-persistence/src/main/java/org/nuiton/topia/generator/TopiaGeneratorUtil.java
===================================================================
--- trunk/topia-persistence/src/main/java/org/nuiton/topia/generator/TopiaGeneratorUtil.java 2010-03-23 13:04:37 UTC (rev 1852)
+++ trunk/topia-persistence/src/main/java/org/nuiton/topia/generator/TopiaGeneratorUtil.java 2010-03-23 23:29:29 UTC (rev 1853)
@@ -138,6 +138,16 @@
*/
public static final String TAG_NATURAL_ID_MUTABLE = "naturalIdMutable";
/**
+ * Tag pour specifier si une methode a besoin d'une transaction
+ * (TopiaContext) ou non
+ */
+ public static final String TAG_TRANSACTION = "transaction";
+ /**
+ * Tag pour specifier si une methode de service a besoin d'arguments pour
+ * le message d'erreur ou non
+ */
+ public static final String TAG_ERROR_ARGS = "errorArgs";
+ /**
* Tag pour spécifier la caractère lazy d'une association multiple
*/
public static final String TAG_LAZY = "lazy";