Writing your own TRIMM Java, TRIMM JPA or TRIMM Groovy/Grails generator extension

One of the major strengths of TRIMM Java and its submodules TRIMM JPA and TRIMM Groovy/Grails is that it is very easy to extend the code generator with new features without having to alter the TRIMM Products.

All you need to do is provide your own extension and hook it into TRIMM.

The code generation process and its hook points

The core generator is the dk.tigerteam.trimm.mdsd.java.generator.JavaGenerator and all the submodules (dk.tigerteam.trimm.mdsd.jpa.JpaJavaGenerator and dk.tigerteam.trimm.mdsd.groovy.generator.GroovyGenerator) inherit their processing from it while providing their own instance of a dk.tigerteam.trimm.mdsd.java.generator.JavaCodeGenerationStrategy – which again are specializations of the dk.tigerteam.trimm.mdsd.java.generator.DefaultJavaCodeGenerationStrategy).

The dk.tigerteam.trimm.mdsd.java.generator.JavaCodeGenerationStrategy allows you to fine tune the generation phase in even greater detail than what is described below.

The Java/JPA and Groovy/Grails code generation process

The JavaGenerator handles the entire process of moving from a MetaModel world to a CodeModel world.
The configuration for the JavaGenerator is the dk.tigerteam.trimm.mdsd.java.generator.JavaGeneratorContext instance which is passed to the #transform(JavaGeneratorContext) or #transformAndWriteCode(JavaGeneratorContext, CodeWritingStrategy) method, which kicks off the generation process.
These methods processes all MetaClazz‘ in the MetaModel during the following life cycle:

  1. FirstPassGeneration (#firstPassGeneration(CodeModel, MetaModel))
    1. First a corresponding CodePackage is created for each MetaPackage in the MetaModel.
    2. Then all MetaClazz are first pre-processed through the template method JavaCodeGenerationStrategy#preProcessMetaClazz(MetaClazz).
    3. After this the MetaClazz is run through the JavaCodeGenerationStrategy#shouldGenerateForMetaClazz(MetaClazz).
      If this method returns true then
      – A Clazz, Interface or Enumeration instance is created (depending on whether we are processing a MetaClazz,
      a MetaInterface or a MetaEnumeration).
      – The generated ClazzOrInterface (which is the common class for Clazz, Interface and Enumeration) is processed by JavaCodeGenerationStrategy#preProcessClazz(ClazzOrInterface, MetaClazz)
  2. Reconstruction of the inheritance hierarchy (#reconstructClazzHierarchy(List)) – After all classes have been created we perform the reconstruction of inheritance hierarchies for ClazzOrInterface according to how it’s defined in the MetaModel
  3. SecondPassGeneration (#secondPassGeneration(List))
    For each ClazzOrInterface‘ generated in the #firstPassGeneration(CodeModel, MetaModel) (and returned by
    #reconstructClazzHierarchy(List)) we now do the following:

    • Call either #processClazz(Clazz) (which create a ClazzEvent for both MetaClazz and MetaEnumeration) or #processInterface(Interface) (which create a InterfaceEvent) and execute the event
    • This in turn calls either #generateEnumerators(Enumeration) for Enumeration or
      #generateFieldsAndProperties(ClazzOrInterface) for Clazz‘ or Interface
    • #generateEnumerators(Enumeration) will create and execute EnumeratorLiteralEvent for each MetaProperty in the MetaEnumeration that defined the Enumeration
    • #generateFieldsAndProperties(ClazzOrInterface) will create and execute PropertyEvent
      subtypes (SimplePropertyEvent or a {@AssociationEvent subclass such as OneToOneAssociationEvent or OneToManyAssociationEvent) for each MetaProperty defined in the MetaClazz/MetaInterface that defined the Clazz by delegating it to #createPropertyEvent(ClazzOrInterface, MetaProperty))
    • #generateMethods(dk.tigerteam.trimm.mdsd.java.codedom.ClazzOrInterface)
    • is called. This will generate a MethodEvent for each MetaOperation defined in the meta model

  4. Finally it will run DeferredEventHandler (#runDeferredEventHandlers()) – At this point all ClazzOrInterface‘s are done, but certain GeneratorEventListener (like SerialVersionUIDGeneratorListener and JPANamedTablesAndColumnsListener (from TRIMMJpa) might have needed all Clazz‘ and Property‘ to be created, before they can complete their event handling.
  5. Postprocess Clazzes (JavaCodeGenerationStrategy#postProcessClazzes(List))
    This where the processing ends, unless you’ve called the #transformAndWriteCode(JavaGeneratorContext, CodeWritingStrategy) method in which case the code is written to the destination using the provided CodeWritingStrategy

    Generator Event hierarchy

    As explained above most of the processing is done in an Event driven fashion, where the generator creates Events that it broadcasts to interested dk.tigerteam.trimm.mdsd.java.generator.event.GeneratorEventListener‘s.
    The dk.tigerteam.trimm.mdsd.java.generator.event.GeneratorEvent them selves form a hierarchy that is displayed below:

    GeneratorEvent hierarchy

    GeneratorEvent hierarchy

    GeneratorEventListeners

    Most provided/built-in and customer specific code generator extensions are implemented as dk.tigerteam.trimm.mdsd.java.generator.event.GeneratorEventListener‘s.
    dk.tigerteam.trimm.mdsd.java.generator.event.GeneratorEventListener‘s used for a given Code generation are added to the dk.tigerteam.trimm.mdsd.java.generator.JavaGeneratorContext instance which is passed to the #transform(JavaGeneratorContext) or #transformAndWriteCode(JavaGeneratorContext, CodeWritingStrategy) methods.

    Below is the complete list of GeneratorEventListener subclasses that ship with TRIMM Java, TRIMM JPA and TRIMM Groovy/Grails:

    The GeneratorEventListeners that ship with TRIMM Java, TRIMM JPA and TRIMM Groovy/Grails

    The GeneratorEventListeners that ship with TRIMM Java, TRIMM JPA and TRIMM Groovy/Grails

    Configuring a JavaGenerator your self

    If you don’t want to use our YAML driven CodeGenerators you can always choose to configure the JavaGenerator (or JPA/Groovy subclasses thereof) yourself like this:

    MetaModel metaModel = new EAXmiReader().read(this.getClass().getResource("BuiltInTypesListener.xml"));
    JavaGenerator javaGenerator = new JavaGenerator();
    JavaGeneratorContext generatorContext = new JavaGeneratorContext(metaModel);
    generatorContext.addEventListeners(new BuiltInTypesListener());
    List<ClazzOrInterface> transformResult = javaGenerator.transform(generatorContext);
    

    or if you want to write the generated code directly to the file system in Java code format

    MetaModel metaModel = new EAXmiReader().read(this.getClass().getResource("BuiltInTypesListener.xml"));
    JavaGenerator javaGenerator = new JavaGenerator();
    JavaGeneratorContext generatorContext = new JavaGeneratorContext(metaModel);
    generatorContext.addEventListeners(new BuiltInTypesListener());
    List<ClazzOrInterface> transformResult = javaGenerator.transformAndWriteCode(generatorContext, new FileSystemCodeWritingStrategy(new JavaCodeWriter(...)));
    

    Setting this up for JPA would basically require you to swap the JavaGenerator for the JpaJavaGenerator and register one of the JPA specific GeneratorEventListener‘s.

    For Groovy you would swap the JavaGenerator for the JpaJavaGenerator and the JavaCodeWriter for the GroovyCodeWriter. Of course it would also make sense to register one or more of the Groovy/Grails specific GeneratorEventListener‘s.

    Writing your own GeneratorEventListener

    Basically all you need to do is to implement the GeneratorEventListener interface (either directly or using on the the convenience subclasses such as AbstractGeneratorEventListener, AbstractJpaGeneratorEventListener or BaseJpaGeneratorEventListener).
    All our GeneratorEventListener extend from AbstractGeneratorEventListener as it gives you a very simple way to handle only the specific Events that you want by overriding the designated method for that event and context.

    Here’s an example of how to do this:

    public class BuiltInTypesListener extends AbstractGeneratorEventListener {
    
        private static final Log logger = LogFactory.getLog(BuiltInTypesListener.class);
    
        @Override
        protected boolean handleSimplePropertyEvent(SimplePropertyEvent event) {
            Type type = null;
            if (event.getMetaProperty().getCardinalityType() == CardinalityType.Many) {
                type = event.getProperty().getField() != null ? event.getProperty().getField().getType().getGenerics().get(0) : event.getProperty().getType().getGenerics().get(0);
            } else {
                type = event.getProperty().getField() != null ? event.getProperty().getField().getType() : event.getProperty().getType();
            }
            MetaClazz metaClazz = event.getMetaProperty().getType();
            resolveBuiltInTypes(event.getClazzOrInterface(), type);
            if (type.getClazzOrInterface() == null) {
                // Fix the meta model as well (serves later processes that use the built in types directly, without having the possibility of package rewriting, well)
                metaClazz.setName(type.getName());
            }
            return true;
        }
    
        @Override
        protected boolean handleMethodEvent(MethodEvent event) {
            for (Parameter parameter : event.getMethod().getParameters()) {
                if (parameter.getMetaType().getType().isBuiltInType()) {
                    resolveBuiltInTypes(event.getClazzOrInterface(), parameter.getType());
                    // Fix the meta model as well (serves later processes that use the built in types directly, without having the possibility of package rewriting, well)
                    parameter.getMetaType().getType().setName(parameter.getType().getName());
                }
            }
            if (event.getMethod().getMetaType().getReturnParameter().getType().isBuiltInType()) {
                resolveBuiltInTypes(event.getClazzOrInterface(), event.getMethod().getReturnType());
                event.getMethod().getMetaType().getReturnParameter().getType().setName(event.getMethod().getReturnType().getName());
            }
            return true;
        }
    
        /**
         * Override this method to provide custom resolving of built in types (resolving means fixing the {@link Type#getName()} property)
         *
         * @param type The type to resolve
         */
        protected void resolveBuiltInTypes(ClazzOrInterface owner, Type type) {
            if (type.getClazz() == null && type.getWrappedJavaClass() == null) {
                logger.debug("Trying to resolve built in type '" + type.getName() + "'");
                if (type.getName().equalsIgnoreCase("date")) {
                    type.setName(Date.class.getName()).external();
                } else if (type.getName().equalsIgnoreCase("bigdecimal")) {
                    type.setName(BigDecimal.class.getName()).external();
                }
                 ..... 
                else {
                    throw new RuntimeException("Type '" + type + "' is unknown, please use" +
                            " configuration 'mapUmlPropertyTypes' property to map custom types");
                }
            }
        }
    }
    

    Writing your own JPA generator extension

    As mentioned above most JPA generators can benefit by extending either AbstractJpaGeneratorEventListener or BaseJpaGeneratorEventListener.

    Here’s an example of how to write a JPA extension that inherits from BaseJpaGeneratorEventListener:

    public class Jpa2OrphanRemovalListener extends BaseJpaGeneratorEventListener {
    	@Override
    	protected boolean handleOneToOneOwnerOfAssociation(OneToOneAssociationEvent event) {
    		if (isDeleteOrphanCandidate(event)) {
    			Annotation annotation = event.getProperty().getField().findOrAddAnnotation(OneToOne.class);
    			addOrSetOrphanRemovalAnnotationAttribute(annotation);
    		}
    		return true;
    	}
    
    	@Override
    	protected boolean handleOneToManyOwnerOfAssociation(OneToManyAssociationEvent event) {
    		if (isDeleteOrphanCandidate(event)) {
    			Annotation annotation = event.getProperty().getField().findOrAddAnnotation(OneToMany.class);
    			addOrSetOrphanRemovalAnnotationAttribute(annotation);
    			// Remove the Set collection method since it allows for situations where Hibernate
    			// (and perhaps other JPA implementations) fails, if you overwrite its internal collection wrapper
    			event.getProperty().removeSetterMethod();
    		}
    		return true;
    	}
    
    	protected boolean isDeleteOrphanCandidate(AssociationEvent event) {
    		if (event.getMetaProperty().isOwnerOfAssociation() && !event.getMetaProperty().getAssociation().isBidirectional()
    				&& !event.getMetaProperty().getAssociation().isSelfReferencing()) {
    			// Check the clazz of the opposite property to see what kind of associations it has
    			for (MetaProperty subMetaProperty : event.getMetaProperty().getType().getProperties()) {
    				if (subMetaProperty.isPartInAnAssociation()) {
    					if (subMetaProperty.isOwnerOfAssociation()) {
    						// One-To-One are always good for owning associations, so is Many-to-One,
    						// so if we meet a ManyToMany or a OneToMany we fail it as a candidate for delete orphan
    						if (subMetaProperty.getAssociationType() == AssociationType.ManyToMany
    								|| subMetaProperty.getAssociationType() == AssociationType.OneToMany) {
    							return false;
    						}
    					} else if (subMetaProperty.getAssociation().isBidirectional()) {
    						// The type of the our sub property is not an owning association and we have
    						// a java association in both directions (bidirectional), which hibernate doesn't handle
    						return false;
    					}
    				}
    			}
    			return true;
    		}
    		return false;
    	}
    	
    	private void addOrSetOrphanRemovalAnnotationAttribute(Annotation annotation) {
    		if (annotation.hasAnnotationAttribute("orphanRemoval")) {
    			annotation.getAnnotationAttribute("orphanRemoval").setValues(true);
    		} else {
    			annotation.addAnnotationAttribute("orphanRemoval", true);
    		}
    	}
    }
    

    Deferred Event handling

    Sometimes you want to react to a given GeneratorEvent but you also want to wait until the entire CodeGeneration’s event handling has been completed in the SecondPassGeneration phase (i.e. no new GeneratorEvent‘s will be fired by the JavaGenerator), because your handling depends on information that isn’t at hand when the event arrives (e.g. when handling a ClazzEvent none of its properties or methods have been handled yet) or you want to wait and see if others have performed code changes that renders your event handling void.

    You can either explicitly queue deferred handling of a specific Event instance by calling queueDeferredEventHandler(new DeferredEventHandler() { ... } ) on the currently active JavaGeneratorContext:
    JavaGeneratorContext.getContext().queueDeferredEventHandler(new DeferredEventHandler() { ... } );
    Note: The JavaGeneratorContext is bound to the currently running thread and can be fetched by calling JavaGeneratorContext.getContext() or by calling the #getContext() method from inside your GeneratorEventHandler

    Complete example where we defer handling of ClazzEvent and InterfaceEvent since we want to wait and see if other event handlers introduced Serializable into the code (hierarchy):

    /**
     * Makes sure that any given Clazz or Interface always implement {@link Serializable}.<br/>
     * The processing will occur deferred and will make sure that Serializable only will be applied if it isn't already a part of the
     * inheritance hierarchy
     *
     * @author Jeppe Cramon
     */
    public class SerializablePojoListener extends AbstractGeneratorEventListener {
    
        @Override
        protected boolean handleClazzEvent(final ClazzEvent event) {
    
            JavaGeneratorContext.getContext().queueDeferredEventHandler(new DeferredEventHandler() {
    
                public String getDescription() {
                    return "Serializable POJO for Clazz " + event.getMetaClazz();
                }
    
                public void execute() {
                    if (!implementsSerializable(event.getClazz())) {
                        // Only add Serializable if we don't inherit from a Clazz or Interface
                        if (!event.getClazz().extendsAGeneratedClazz() && !event.getClazz().implementsAGeneratedInterface()) {
                            event.getClazz().addImplementsTypes(Serializable.class);
                        }
                    }
                }
            });
            return true;
        }
    
        @Override
        protected boolean handleInterfaceEvent(final InterfaceEvent event) {
            JavaGeneratorContext.getContext().queueDeferredEventHandler(new DeferredEventHandler() {
    
                public String getDescription() {
                    return "Serializable POJO for Interface " + event.getMetaInterface();
                }
    
                public void execute() {
                    if (!extendsSerializable(event.getInterface())) {
                        // Only add Serializable if we don't inherit from a Clazz or Interface
                        if (!event.getInterface().extendsAGeneratedInterface()) {
                            event.getInterface().addExtendsTypes(Serializable.class);
                        }
                    }
                }
            });
            return true;
        }
    
        private boolean implementsSerializable(Clazz clazz) {
            return clazz.implementsJavaInterface(Serializable.class);
        }
    
        private boolean extendsSerializable(Interface anInterface) {
            return anInterface.extendsJavaInterface(Serializable.class);
        }
    }
    

    In this case we only handle ClazzEvent and InterfaceEvent and not any of the other events, so here it makes sense to explicitly defer handling of only those events (it doesn’t make any sense to defer handling of events we don’t care about). We might have a case where we want to handle many events and in those cases handling the queueing explicitly can become very verbose. For those cases we offer the DeferredGeneratorEventListener marker interface.
    If your GeneratorEventListener implements this method, then all Events will automatically be queued for deferred handling:

    public class YourListener extends AbstractGeneratorEventListener
                           implements DeferredGeneratorEventListener {
        protected boolean handleMethodEvent(MethodEvent event) {
            ...
            return true;
        }
    
        protected boolean handleInterfaceEvent(InterfaceEvent event) {
            ...
            return true;
        }
    
        protected boolean handleManyToManyAssociationEvent(
                ManyToManyAssociationEvent event) {
            return true;
        }
    
        protected boolean handleManyToOneAssociationEvent(
                ManyToOneAssociationEvent event) {
            return true;
        }
    
        protected boolean handleOneToManyAssociationEvent(
                OneToManyAssociationEvent event) {
            ...
            return true;
        }
    
        protected boolean handleOneToOneAssociationEvent(
                OneToOneAssociationEvent event) {
            ...
            return true;
        }
    
        protected boolean handleSimplePropertyEvent(SimplePropertyEvent event) {
            ...
            return true;
        }
    
        protected boolean handleEnumeratorEvent(EnumeratorLiteralEvent literalEvent) {
            ...
            return true;
        }
    
        protected boolean handleClazzEvent(ClazzEvent event) {
            ...
            return true;
        }
    }