Sculptor Developer's Guide
Sculptor is not an one-size-fits-all product. Even though it is a good start for many systems, sooner or later customization is always needed. This guide will give you an understanding of the internal design of Sculptor and explains how to do changes for some typical scenarios. Some things can easily be changed with properties or AOP, while other things requires more effort, since you need to setup the development environment. Some changes are staightforward and some requires more in depth understanding of oAW
and EMF
Table of Contents:
Sculptor Internal Design

Figure 1. Internal Design of Sculptor
1. The developer is using the DSL Editor plugin to edit the application specific model.design, i.e. the source for the concrete model that is the input to the code generation process. Constraints of the DSL is validated while editing.
2. When generating code the application specific workflow.oaw is executed. It doesn't contain much.
3. It invokes the sculptorworkflow.oaw which defines the flow of the code generation process.
4. It starts with parsing the model.design file using the oAW XText parser. Constraints of the DSL are checked.
5. The DSL model is transformed into a model of the type defined by the sculptormetamodel.ecore meta model.
6. Constraint validation is performed.
7. The model is transformed again. Now it is actually modified to add some default values.
8. Constraint validation again.
9. Now the actual generation of Java code and configuration files begin. It is done with code generation templates written in oAW XPand language. The templates extract values from the model and uses oAW Extensions and Java helper classes.
10. Properties of technical nature, which doesn't belong in the DSL or meta model, are used by the templates and the helpers.
Generator Properties
There are a few things that can be easily customized with properties. The default properties are defined in default-sculptor-generator.properties in fornax-cartridges-sculptor-generator. You can override these properties by defining them in sculptor-generator.properties and sculptor-gui-generator.properties. You only have to define the ones that you need to change.
Sculptor will look for properties in the following order:
- System.properties (only intended for temporary tests)
- sculptor-gui-generator.properties
- sculptor-generator.properties
- default-sculptor-generator.properties
Project Nature
The main capabilities and responsibilities of the project is defined by the project.nature property. It is typically used to assign default values of more fine grained properties. E.g. the generated artifact of a presentation tier project is different from a business tier project.
The default project nature is business-tier. For a web project you specify:
project.nature=presentation-tier
There are two different implementations for the web project:
- Spring webflow with pure JSP (default)
- Spring webflow with JSF and Facelets
To use JSF you add the following project nature to the properties file of the web project:
project.nature=presentation-tier, jsf
A project may have several natures and the value of the property can be a comma separated list. E.g. to generate business and presentation tier in the same project you can specify:
project.nature=presentation-tier, business-tier
Framework Classes
You can replace the runtime framework with your own implementations by overriding these properties.
framework.accessBaseClass=\
org.fornax.cartridges.sculptor.framework.accessimpl.AccessBase
framework.accessBaseWithExceptionClass=\
org.fornax.cartridges.sculptor.framework.accessimpl.AccessBaseWithException
framework.abstractDomainObjectClass=\
org.fornax.cartridges.sculptor.framework.domain.AbstractDomainObject
framework.abstractServiceBeanClass=\
org.fornax.cartridges.sculptor.framework.serviceimpl.AbstractServiceBean
framework.abstractConsumerClass=\
org.fornax.cartridges.sculptor.framework.consumer.AbstractMessageConsumer
framework.consumerInterface=\
org.fornax.cartridges.sculptor.framework.consumer.MessageConsumer
framework.abstractMessageBeanClass=\
org.fornax.cartridges.sculptor.framework.consumer.AbstractMessageBean
framework.serviceContextClass=\
org.fornax.cartridges.sculptor.framework.errorhandling.ServiceContext
framework.errorHandlingAdviceClass=\
org.fornax.cartridges.sculptor.framework.errorhandling.ErrorHandlingAdvice
framework.serviceContextStoreClass=\
org.fornax.cartridges.sculptor.framework.errorhandling.ServiceContextStore
framework.serviceContextStoreAdviceClass=\
org.fornax.cartridges.sculptor.framework.errorhandling.ServiceContextStoreAdvice
framework.web.serviceContextServletFilterClass=\
org.fornax.cartridges.sculptor.framework.errorhandling.ServiceContextServletFilter
framework.web.tomcatServiceContextFactoryClass=\
org.fornax.cartridges.sculptor.framework.errorhandling.TomcatServiceContextFactory
framework.auditInterceptorClass=\
org.fornax.cartridges.sculptor.framework.domain.AuditInterceptor
framework.auditableInterface=\
org.fornax.cartridges.sculptor.framework.domain.Auditable
framework.applicationExceptionClass=\
org.fornax.cartridges.sculptor.framework.errorhandling.ApplicationException
framework.systemExceptionClass=\
org.fornax.cartridges.sculptor.framework.errorhandling.SystemException
framework.access.interfacePackage=\
org.fornax.cartridges.sculptor.framework.accessapi
framework.access.implementationPackage=\
org.fornax.cartridges.sculptor.framework.accessimpl
framework.databaseTestCaseClass=\
org.fornax.cartridges.sculptor.framework.util.db.IsolatedDatabaseTestCase
framework.castor.mapperClass=\
org.fornax.cartridges.sculptor.framework.xml.CastorMapper
framework.castor.dateHandlerClass=\
org.fornax.cartridges.sculptor.framework.xml.DateHandler
framework.castor.timeStampHandlerClass=\
org.fornax.cartridges.sculptor.framework.xml.TimeStampHandler
framework.castor.schemaLocationUrlPrefix=\
http:framework.enumUserTypeClass=\
org.fornax.cartridges.sculptor.framework.accessimpl.GenericEnumUserType
framework.fakeObjectInstantiatorClass=\
org.fornax.cartridges.sculptor.framework.util.FakeObjectInstantiator
framework.web.extendedHibernateSessionFlowListenerClass=\
org.fornax.cartridges.sculptor.framework.util.ExtendedHibernateSessionFlowListener
framework.web.disconnectHibernateInterceptorClass=\
org.fornax.cartridges.sculptor.framework.util.DisconnectHibernateInterceptor
framework.web.exceptionUtilClass=\
org.fornax.cartridges.sculptor.framework.web.errorhandling.ExceptionUtil
framework.web.OptionEditorClass=\
org.fornax.cartridges.sculptor.framework.propertyeditor.OptionEditor
framework.web.OptionClass=\
org.fornax.cartridges.sculptor.framework.propertyeditor.Option
framework.web.EnumEditorClass=\
org.fornax.cartridges.sculptor.framework.propertyeditor.EnumEditor
# The below commented framework.access.* properties can be overridden
# to specify other generic access objects.
# By default they are resolved by naming convention.
#framework.access.interface.findById=\
org.fornax.cartridges.sculptor.framework.accessapi.FindByIdAccess
#framework.access.implementation.findById=\
org.fornax.cartridges.sculptor.framework.accessimpl.FindByIdAccessImpl
#framework.access.interface.findAll=\
org.fornax.cartridges.sculptor.framework.accessapi.FindAllAccess
#framework.access.implementation.findAll=\
org.fornax.cartridges.sculptor.framework.accessimpl.FindAllAccessImpl
#framework.access.interface.findByExample=\
org.fornax.cartridges.sculptor.framework.accessapi.FindByExampleAccess
#framework.access.implementation.findByExample=\
org.fornax.cartridges.sculptor.framework.accessimpl.FindByExampleAccessImpl
#framework.access.interface.findByQuery=\
org.fornax.cartridges.sculptor.framework.accessapi.FindByQueryAccess
#framework.access.implementation.findByQuery=\
org.fornax.cartridges.sculptor.framework.accessimpl.FindByQueryAccessImpl
#framework.access.interface.merge=\
org.fornax.cartridges.sculptor.framework.accessapi.MergeAccess
#framework.access.implementation.merge=\
org.fornax.cartridges.sculptor.framework.accessimpl.MergeAccessImpl
#framework.access.interface.save=\
org.fornax.cartridges.sculptor.framework.accessapi.SaveAccess
#framework.access.implementation.save=\
org.fornax.cartridges.sculptor.framework.accessimpl.SaveAccessImpl
#framework.access.interface.delete=\
org.fornax.cartridges.sculptor.framework.accessapi.DeleteAccess
#framework.access.implementation.delete=\
org.fornax.cartridges.sculptor.framework.accessimpl.DeleteAccessImpl
#framework.access.interface.findByKeys=\
org.fornax.cartridges.sculptor.framework.accessapi.FindByKeysAccess
#framework.access.implementation.findByKeys=\
org.fornax.cartridges.sculptor.framework.accessimpl.FindByKeysAccessImpl
#framework.access.interface.findByCriteria=\
org.fornax.cartridges.sculptor.framework.accessapi.FindByCriteriaAccess
#framework.access.implementation.findByCriteria=\
org.fornax.cartridges.sculptor.framework.accessimpl.FindByCriteriaAccessImpl
See How to add custom generic access object for more information about the access properties.
Database Product
MySQL and Oracle are supported out-of-the-box. You can select database with
db.product=mysql
#db.product=oracle
Contribution of support for Apache Derby has been posted in the
Forum
The database properties are only used for generating the DDL and some Hibernate settings in SessionFactory.xml.
You can also change the default type mapping:
db.mysql.type.Boolean=CHAR(1)
db.mysql.type.boolean=CHAR(1)
db.mysql.type.Integer=INTEGER
db.mysql.type.int=INTEGER
db.mysql.type.Long=BIGINT
db.mysql.type.long=BIGINT
db.mysql.type.Date=DATE
db.mysql.type.java.util.Date=TIMESTAMP
db.mysql.type.DateTime=DATETIME
db.mysql.type.Timestamp=TIMESTAMP
db.mysql.type.BigDecimal=DOUBLE
db.mysql.type.Double=DOUBLE
db.mysql.type.double=DOUBLE
db.mysql.type.String=VARCHAR
db.mysql.length.String=100
db.mysql.length.Enum=40
For parent child (one-to-many) relations the database can do cascaded delete of children when deleting parent. This is the default setting. It is possible to disable that and let Hibernate do the delete by setting the following property to false.
db.mysql.onDeleteCascade=false
Types
Mapping from simple types used in the DSL to generated types are defined with these properties:
javaType.Date=java.util.Date
javaType.DateTime=java.util.Date
javaType.Timestamp=java.util.Date
javaType.Map=java.util.Map
javaType.List=java.util.List
javaType.Set=java.util.Set
javaType.Collection=java.util.Collection
Note that the java.lang types, such as String and Integer are not defined, since they have the same name in the DSL and the Java code.
You can add your own types also, e.g.
javaType.AccountNumber=org.foobar.type.AccountNumber
javaType.Currency=String
javaType.Amount=BigDecimal
When you use your own classes, such as AccountNumber, you need to define the corresponding Hibernate user type.
hibernateType.AccountNumber=org.foobar.type.PersistentAccountNumber
If you define the above types you can use them directly in the DSL.
Entity Account {
AccountNumber accountNumber
- @Money balance
}
BasicType Money {
Amount amount
Currency currency
}
Another example, ShortString, which combines the database and java type properties.
db.mysql.type.ShortString=VARCHAR
db.mysql.length.ShortString=10
javaType.ShortString=String
The type of the automatically generated id attribute is defined as
Joda Date and Time API
It is possible to use Joda Time
instead of the Java date and time classes.
You also have to add the dependency to Joda in pom.xml files.
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time-hibernate</artifactId>
<version>0.8</version>
</dependency>
Thereafter you run mvn eclipse:eclipse as usual to update the Eclipse project.
JSF
To use JSF you add the following project nature to the properties file of the web project:
project.nature=presentation-tier, jsf
You also have to add some dependencies in pom.xml files.
<dependency>
<groupId>org.apache.myfaces.tomahawk</groupId>
<artifactId>tomahawk</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>com.sun.facelets</groupId>
<artifactId>jsf-facelets</artifactId>
<version>1.1.11</version>
</dependency>
This is all done by the jsf archetype, which you typically use when creating a new JSF project.
Package Names
You can define the names of the generated packages.
package.serviceInterface=serviceapi
package.serviceImplementation=serviceimpl
package.serviceProxy=serviceproxy
package.consumer=consumer
package.xmlmapper=consumer
package.domain=domain
package.repositoryInterface=domain
package.exception=exception
package.repositoryImplementation=repositoryimpl
package.accessInterface=repositoryimpl
package.accessImplementation=accessimpl
package.web=web
You can also use subpackages.
package.domain=domain
package.repositoryInterface=domain.repository
package.exception=domain.exception
File Header
You can define a header to be generated in java files. The java comments are added automatically, but you need to include the newline character.
javaHeader=Copyright line 1 \n\
line 2 \n\
line 3
Removing the generation of the ServiceContext
For some reason it might be required to switch off the generation of the ServiceContext - for example if you want to create a client without the created/updated functionality.
In this case it can be done easily with setting a property value in the sculptor-generator.properties file.
# specifies if ServiceContext is to be generated
generate.serviceContext=false
This turns off the generation of the ServiceContext. Please note that in this case the auditable feature does not work or must be implemented manually.
Influencing the generation of the UMLGraph feature
By default Sculptor generates a dot file for Graphviz for documentation purposes. This might not be needed for all projects. Please set the following property to drive the generation of this feature.
# specifies if umlgraph for Graphviz is to be generated
generate.umlgraph=false
Hibernate Cache
Cache provider settings are generated based on the property hibernate.cache.provider.
hibernate.cache.provider=EhCache
#hibernate.cache.provider=DeployedTreeCache
#hibernate.cache.provider=TreeCache
There are three built in cache providers.
- EhCache - settings are defined in ehcache.xml file, skeleton is generated
- DeployedTreeCache - settings are defined as a JBoss mbean.
- TreeCache - settings are defined in treecache.xml file
The choice also affects the cache usage attribute in the Hibernate mapping.
EhCache is the default cache provider, since it doesn't require any additional configuration. JBoss DeployedTreeCache is recommended for (clustered) applications that require a transactional cache. To use DeployedTreeCache you define it in sculptor-generator.properties and add a configuration file to JBoss (deploy directory). Refer to JBoss documentation for information about how to configure the TreeCache.
Hibernate Repository
The default implementation of a Repository consists of an implementation class and Access Objects. The intention is a separation of concerns between the domain and the data layer. Repository is close to the business domain and Access Objects are close to the data layer. The Hibernate specific code is located in the Access Object, and not in the Repository.
For some systems this separation might be overkill and you might prefer to implement the data access directly in the Repository. Sculptor supports this design out-of-the-box. You only have to specify a property in sculptor-generator.properties to have Hibernate support directly in the Repository implementation.
generate.repository.hibernateSupport=true
This will result in that the Repository implementation will extend Spring HibernateDaoSupport and you can implement Hibernate specific code directly in the Repository.
EJB Version
EJB 3 is default, but Sculptor also supports EJB 2.1. Activate EJB 2.1 with
Deployment as WAR
Sculptor supports deployment as EAR or WAR. EAR is default. The design differences are:
- Services are exposed as EJBs when deployed as EAR, POJOs when deployed as WAR.
- Transaction management is done with JTA by the application server when deployed as EAR, by Spring when deployed as WAR.
- Consumers are not supported when deployed as WAR.
1. The WAR choice is done with the following property in sculptor-gui-generator.properties in the web project.
2. You also need to adjust the Maven pom.xml of the web project to make sure that the correct jar files are included in the WEB-INF/lib directory of the WAR. Change the scope of the following dependencies.
<dependency>
<groupId>${pom.groupId}</groupId>
<artifactId>helloworld</artifactId>
<version>${pom.version}</version>
<!-- Use scope provided when deployed as ear -->
<!--scope>provided</scope-->
</dependency>
<dependency>
<groupId>org.fornax.cartridges</groupId>
<artifactId>fornax-cartridges-sculptor-framework</artifactId>
<version>${sculptor.version}</version>
<!-- Use scope provided when deployed as ear -->
<!--scope>provided</scope-->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
<!-- Use scope provided when deployed as ear -->
<!-- <scope>provided</scope> -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<!-- Use scope provided when deployed as ear -->
<!-- <scope>provided</scope> -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<!-- Use scope provided when deployed as ear -->
<!-- <scope>provided</scope> -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-support</artifactId>
<version>${spring.version}</version>
<!-- Use scope provided when deployed as ear -->
<!-- <scope>provided</scope> -->
</dependency>
3. The ear project is not used when deployed as war and you can remove it and the module definition in pom.xml in the parent project.
<modules>
<module>../helloworld</module>
<module>../helloworld-web</module>
<!-- <module>../helloworld-ear</module> -->
</modules>
4. Rebuild with mvn clean install in the parent directory.
5. The WAR located in the target directory of the web project can be deployed in JBoss, which is the default application server.
Instead of JBoss you can use Tomcat
.
Deployment in Tomcat requires that you do the following also.
1. Define the following property in sculptor-generator.properties in the business tier project.
deployment.applicationServer=Tomcat
2. When deploying in Tomcat you need to bundle the logging jars in the WAR, which is done by adjusting the pom.xml of the web project.
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>${commons-logging.version}</version>
<!-- Use scope provided when deploying to JBoss -->
<!-- <scope>provided</scope> -->
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
<!-- Use scope provided when deploying to JBoss -->
<!-- <scope>provided</scope> -->
</dependency>
3. The Tomcat definition of the datasource is located in META-INF/context.xml in the WAR. This file is generated once, but you might need to adjust the settings.
4. Java Transaction API (JTA) is needed. It is not available in maven remote repository and therefore you have to make it available manually. You have two options.
a) Download the JTA 1.0.1B jar
and place it in Tomcat lib directory.
b) Download the jar, install it in your local repository and add a dependency to it in the pom, actually you should remove the exclusion of it in the hibernate dependency in the business tier project.
5. Rebuild with mvn clean install in the parent directory.
6. The WAR located in the target directory of the web project can be deployed in Tomcat.
Scaffold
Scaffold operations are defined as a comma separated list.
scaffold.operations=findById,findAll,save,delete
Visibility of setters for not changeable attributes and references
By default the visibility is private for setter methods of not changeable attributes. Some tools might need visible setter methods and you can change the visibility with the following property. When you make these setters visible they will check that the value is not changed, once it has been set.
notChangeablePropertySetter.visibility=private
#notChangeablePropertySetter.visibility=protected
The visibility of setters for not changeable references are public. These setters will also check that the reference value is not changed, once it has been set. The reason for having the public setters is that sometimes the referred object is not available at construction time. Then it is possible to pass in null in the constructor and then afterwards assign the reference. If you would like to hide these setters you can use the following property.
notChangeableReferenceSetter.visibility=private
Spring Configuration
You can change the settings in spring.properties. It is runtime configuration of Spring.
Spring makes it possible to override bean definitions. The last definition wins. It is applicationContext.xml that is loaded first and it imports other configuration files. more.xml is imported last, which means that you can override generated bean definitions by specifying them in more.xml. When running JUnit tests the corresponding file is more-test.xml.
This approach is not recommended, since it might be confusing to have multiple bean definitions. It is also easy to forget to change when something is renamed and that can have dangerous side effects. For temporary modifications it might be an easy solution, but for permanent customization it is better to use one of the other alternatives presented in this article.
Change Generation Templates
You can change the code generation templates using the Aspect-Oriented Programming features in oAW Xpand. It is described in Using AOP in templates
.
To use AOP you add a section in workflow.oaw in your application project.
<component adviceTarget="generator" id="reflectionAdvice"
class="oaw.xpand2.GeneratorAdvice">
<advices value="templates::SpecialCases"/>
<fileEncoding value="iso-8859-1" />
</component>
Define your advice in src/main/resources/templates/SpecialCases.xpt. For example if you need to replace the UUID generation:
«IMPORT sculptormetamodel»
«EXTENSION extensions::helper»
«EXTENSION extensions::properties»
«AROUND *::uuidAccessor FOR DomainObject»
public String getUuid() {
if (uuid == null) {
uuid = org.myorg.MyUUIDGenerator.generate().toString();
}
return uuid;
}
private void setUuid(String uuid) {
this.uuid = uuid;
}
«ENDAROUND»
You find the default templates in fornaxcartridgessculptorgenerator/src/main/resources/templats
.
Everything starts in Root.xpt, which you also can intercept to add more templates or exclude some of the existing templates.
Another alternative is to setup the development environment according to the next section and change the original templates and build a new version of fornax-cartridges-sculptor-generator.
Sometimes you need to override the extension functions used by the templates. This can also be done with the AOP features of oAW.
Add extensionAdvices in the generator advice in workflow.oaw in your application project.
<component adviceTarget="generator" id="reflectionAdvice"
class="oaw.xpand2.GeneratorAdvice">
<advices value="templates::SpecialCases"/>
<extensionAdvices value="extensions::SpecialCases" />
<fileEncoding value="iso-8859-1" />
</component>
Define your advice in src/main/resources/extensions/SpecialCases.ext. For example if would like to change the location of spring resource files:
import sculptormetamodel;
extension extensions::helper;
around extensions::properties::getResourceDir(Application application, String name) :
name == "spring" ?
"spring/" :
ctx.proceed();
around extensions::properties::getResourceDir(Module module, String name) :
name == "spring" ?
"spring-" + module.name + "/" :
ctx.proceed();
Setup Development Environment
To be able to do more adjustments you need to setup Eclipse for development of Sculptor.
Checkout from Subversion
Install Subversive Eclipse plugin as described in the Installation Guide (if you haven't already done that).
Checkout the following projects from https://fornax.svn.sourceforge.net/svnroot/fornax/trunk/cartridges/sculptor/
- fornax-cartridges-sculptor-framework
- fornax-cartridges-sculptor-framework-web
- fornax-cartridges-sculptor-generator
- fornax-cartridges-sculptor-metamodel
- fornax-cartridges-sculptor-parent
- org.fornax.cartridges.sculptor.dsl
- org.fornax.cartridges.sculptor.dsl.editor
- org.fornax.cartridges.sculptor.gui.dsl
- org.fornax.cartridges.sculptor.gui.dsl.editor
- fornax-cartridges-sculptor-archetype
- fornax-cartridges-sculptor-archetype-ear
- fornax-cartridges-sculptor-archetype-jsf
- fornax-cartridges-sculptor-archetype-parent
- fornax-cartridges-sculptor-archetype-standalone
- fornax-cartridges-sculptor-archetype-web
Fornax Maven Launcher
Checkout Fornax maven-launcher as described in the Installation Guide (if you haven't already done that).
When you checkout/import the Fornax Maven Launcher into the workspace you can run maven inside Eclipse. The menu items for this are available in the external tools menu.
Try mvn
Verify that your environment is correct by running Fornax [mvn-install] for fornax-cartridges-sculptor-parent. Refresh all projects in Eclipse. Thereafter you should not have any red crosses (problems) in Eclipse. Sometimes, validation errors in code generation files (.xpt and .ext) must be cleaned. This is can be done with clean of the Eclipse project.
Reference Application
When you customize Sculptor it is important that you have a representative reference application. It is used to verify the changes to the code generator. It must be big enough to span most of the design concepts you use, but still small enough to be supple.
When you do refactoring of the code generator it is recommended that you first do manual refactoring of the reference application. Keep that code base as a baseline. When you have changed the code generation the generated code should correspond to the manual baseline. Use a diff tool to compare the results.
The library example can act as a reference application if you haven't developed one of your own.
Checkout from Subversion:
https://fornax.svn.sourceforge.net/svnroot/fornax/trunk/cartridges/sculptor/fornax-cartridges-sculptor-examples-library
Run the JUnit tests to verify that it works before you start changing.
 | Eclipse project dependencies
It is convenient to use Eclipse project dependencies from the reference application to fornax-cartridges-sculptor-generator. Then you can do changes in the templates and try them directly, without having to build with maven. You can run the code generation directly inside Eclipse. In your reference application project, right click on workflow.oaw and select 'Run as oAW workflow'. |
Constraint Validation
oAW Check Language
is used for validation of model constraints.
DSL Constraints
DSL constraints are validated directly when you edit the DSL file with the DSL editor (error highlight). DSL constraints are defined in Checks.chk in org.fornax.cartridges.sculptor.dsl. If you change these you have to rebuild the plugins, but no other changes are needed.
Note that the constraints are both a help when editing the DSL and also a way to enforce certain design decisions.
Meta Model Constraints
Constraints of the ecore meta model is located in constraints.chk in fornax-cartridges-sculptor-generator.
Transformations
oAW XTend
is used for model transformations.
DSL Model Transformation
Two meta models are used. One for the DSL, which is generated from the XText grammar. Another meta model is used by the code generation templates. They are rather similar, but still different. They serve different purposes, which motivates why two meta models are needed.
It would also be possible to replace the XText DSL meta model with some other, e.g. a graphical DSL implemented with GMF
.
A transformation converts the DSL model to the code generation model. This transformation is implemented in DslTransformation.ext in fornax-cartridges-sculptor-generator.
You can do a lot of powerful things in this transformation, without having to change code generation templates or its meta model. For example, the scaffold feature is implemented in the transformation. There is a property scaffold for DslEntity. It is like an ordinary Entity with attributes and references, but the DslTransformation automatically add Repository and Service with generic CRUD operations.
When you change the DSL, except for simple syntactic sugar, you often have to change the DslTransformation also.
Model Enrichment
There is one transformation that enrich the model with some useful default values. It is implemented in Transformation.ext. For example the id attribute of each DomainObject is added by this transformation.
You can add more features to this transformation when needed. If it is not specific to the concrete syntax of the DSL, it is better to add features to this transformation than to the DslTransformation, since they will be available even if the concrete syntax is replaced by some other DSL.
GUI Transformation
A separate model is used for generation of the CRUD GUI. A transformation takes the domain model as input and and creates a model that is better suited for generation of the user interface. This transformation is located in GuiTransformation.ext
Customize the Transformations
Extension advices can be used to customize the transformations.
Add the following section in workflow.oaw in your application project.
<component adviceTarget="transformation"
class="oaw.xtend.XtendAdvice">
<extensionAdvice
value="extensions::TransformationSpecialCases" />
</component>
<component adviceTarget="dslTransformation"
class="oaw.xtend.XtendAdvice">
<extensionAdvice
value="extensions::DslTransformationSpecialCases" />
</component>
Define your advice in src/main/resources/extensions/TransformationSpecialCases.ext and src/main/resources/extensions/TransformationSpecialCases.ext depending on which transformation you need to customize.
For example, to skip automatic addition of uuid property:
import sculptormetamodel;
around transformation::Transformation::modifyUuid(DomainObject domainObject) :
null;
Meta Model
Input to the code generation is the model, which is structured according to the meta models sculptormetamodel.ecore and sculptorguimetamodel.ecore. The meta models are defined with Ecore
.
Business Tier Model (domain model)

Figure 2. Meta Model for the code generation model (Click to view)
If you have installed Graphical Modelling Framework (Callisto Edition) you will be able to edit the ecore file with a graphical editor, see Figure 2. Open the sculptormetamodel.ecore_diagram with the editor. Another tool for working with ecore models is TOPCASED Eclipse plugin
.
 | MaxPermSize
To prevent OutOfMemoryError when you use the graphical ecore editor you can add -XX:MaxPermSize=128m in eclipse.ini, which is located in the Eclipse installation directory. |
When you add a totally new concept you have to add it to this meta model.
When you change the meta model it is not enough to build with maven. First you must generate EMF model code.
- An EMF genmodel is created from the ecore model. Normally you don't have to create a new genmodel, but when you do major changes you can remove the sculptormetamodel.genmodel and create a new from the ecore model using File > New > Eclipse Modeling Framework > EMF Model.
- Open the genmodel and right click on the top node. Select Generate Model. It generates Java code to src/main/java. It is safe to remove that code and generate it again if needed.
Now you can package the meta model with mvn install.
GUI Model
Sculptor doesn't aim at implementing a general purpose GUI model that can be used for any type of GUI. We are focusing on the kind of CRUD GUI illustrated in the CRUD GUI Tutorial.

Figure 2. GUI Meta Model for the GUI parts of the code generation model (Click to view)
User Task
The model is arranged around User Tasks, The term UserTask is inspired by the presentation: From User Story to User Interface
. In this presentation it is described that:
- Tasks require intentional action on behalf of a tool's user
- Tasks have an objective that can be completed
- Tasks decompose into smaller tasks
Sculptor's implementation with Spring WebFlow is that a UserTask corresponds to Flow. A conversation corresponds to a top level UserTask and its subtasks.
 | Brief description of the standard CRUD User Tasks
The objective of a CreateTask is to create a new instance of a Domain Object and in the end store it the database. When used as a subtask it is the parent task that is responsible for storing in the database. The CreateTask UI consists of an input and a confirm screen. In the input screen you fill in a form with fields for the attributes. For refererences it is possible to select existing or create new objects. Creating a new referred object spawns another CreateTask, a subtask. When input has been entered the next step in the wizard is a confirm screen. Later, the confirm screen may be optional.
The objective of a ListTask is to show all or a subset of all Domain Objects and make them available for other tasks. The ListTask UI consists of a single screen showing all objects of a specific type. Later, search facility and pagination will probably be included. From the ListTask you can spawn ViewTask, DeleteTask and UpdateTask for existing objects.
UpdateTask is similar to CreateTask, but the objective is to modify an existing Domain Object.
The objective of the ViewTask is to present detailed information for a Domain Object. The ViewTask UI consists of a single screen, with possibility to follow references, i.e. spawn other ViewTasks as subtasks.
The objective of the DeleteTask is to remove a Domain Object from persistent storage. When used as subtask it is the parent task that is responsible for saving. The DeleteTask UI consists of a single screen with the purpose to confirm the delete. |
Code Generation Templates
oAW XPand Language
is used for the templates.
The code generation starts in Root.xpt, which selects some course grained elements from the model and invokes other templates. In the templates you can access simple properties of the model objects and extension methods.
«DEFINE attributeTypeAndName FOR Attribute»
«getTypeName()» «name»
«ENDDEFINE»
The extension methods are defined with oAW Extend Lanuage
in helper.ext. Some methods are implemented inside this file with the oAW Expression Language
and some delegate to Java helper classes.
oAW Expression Language
is also used in the templates to for example select elements from the model.
references.select(r | !r.many && (r.referenceType.metaType == BasicType))
How to
 | Some examples are implemented
Some of the examples presented here are already implemented in Sculptor. It was convenient to add documentation and using real examples while implementing new features. |
How to exclude generation
For some special cases the default generation might not be appropriate and it is desirable to handle the special case with manual code instead. For example, assume we have a complex domain object and we need to do the Hibernate mapping manually.
This can be nicely solved with the Aspect-Oriented Programming feautures in in oAW Xpand. It is described in Using AOP in templates
.
To use AOP you add a section in workflow.oaw in your application project.
<component adviceTarget="generator" id="reflectionAdvice"
class="oaw.xpand2.GeneratorAdvice">
<advices value="templates::SpecialCases"/>
</component>
Define your advice in src/main/resources/templates/SpecialCases.xpt.
«IMPORT sculptormetamodel»
«EXTENSION extensions::helper»
«EXTENSION extensions::properties»
«REM»Skip createTable and dropTable for Person«ENDREM»
«AROUND *::*Table FOR DomainObject»
«IF name != "Person" -»
«targetDef.proceed() -»
«ENDIF -»
«ENDAROUND»
«REM»Skip hibernate mapping for Person«ENDREM»
«AROUND *::mapping FOR DomainObject»
«IF name != "Person" -»
«targetDef.proceed() -»
«ENDIF -»
«ENDAROUND»
«REM»Skip hibernate resource in Spring for Person«ENDREM»
«AROUND *::hibernateResource FOR DomainObject»
«IF name != "Person" -»
«targetDef.proceed() -»
«ENDIF -»
«ENDAROUND»
How to change syntactic sugar
It is rather easy to change the concrete syntax of the DSL. It is defined in the XText
grammar sculptordsl.xtxt.
For example, as an alternative to ! we would like to be able to use not.
Define a rule.
Use this rule instead of the "!" literal.
((notChangeable?NOT"changeable") | ("changeable")) |
If you change the core elements of the language, such as DslService, DslEntity, DslAttribute, you have to change the transformation also, which is located in DslTransformation.xpt in the fornax-cartridges-sculptor-generator project.
 | Try DSL changes
When working with the DSL you generate the parser and editor by running the generate.oaw in the
org.fornax.cartridges.sculptor.dsl project (Run as oAW Workflow). Thereafter you can try the DSL editor by launching a runtime workbench as an Eclipse Application, which includes the plugins in the workspace automatically. |
How to change package names in the generated code
In the DSL you can define the root package for the Application. You can define the package of a Module, if the default is not satisfactory. The package name of individual Domain Objects can be specified, if the default is not appropriate.
Application Library {
basePackage = com.mycorp.library
Module common {
basePackage = com.mycorp.common
BasicType Address {
package=types
String street
String city
}
}
}
You can change the names of the subpackages to the module by defining properties in sculptor-generator.properties as described in packageNames.
How to add support for another database
Generation of database schema (DDL) is specific for different database vendors. To add support for your database you can do like this.
1. In the application specific sculptor-generator.properties you define the database properties for your database. Note that the db.product value must be custom.
db.product=custom
db.custom.maxNameLength=27
db.custom.hibernate.dialect=org.hibernate.dialect.OracleDialect
db.custom.onDeleteCascade=true
db.custom.type.Boolean=CHAR(1)
db.custom.type.boolean=CHAR(1)
db.custom.type.Integer=NUMBER
db.custom.length.Integer=10
db.custom.type.int=NUMBER
db.custom.length.int=10
db.custom.type.Long=NUMBER
db.custom.length.Long=20
db.custom.type.long=NUMBER
db.custom.length.long=20
db.custom.type.Date=DATE
db.custom.type.java.util.Date=DATE
db.custom.type.DateTime=DATE
db.custom.type.Timestamp=DATE
db.custom.type.BigDecimal=NUMBER
db.custom.type.Double=NUMBER
db.custom.type.double=NUMBER
db.custom.type.String=VARCHAR2
db.custom.length.String=100
db.custom.length.Enum=40
2. Create a template for the DDL generation and locate it in templates/CustomDDL.xpt in the classpath, before the fornax-cartridges-sculptor-generator jar.
OracleDDL.xpt is a template you can mimic.
Contribution of support for Apache Derby has been posted in the
Forum
How to remove the circular dependency check
The dependency check is done in constraints.chk in fornax-cartridges-sculptor-generator project.
Remove this rule:
context Module ERROR "Cyclic depencencies detected for module " + name:
this.checkCyclicDependencies();
How to add custom generic access object
The Sculptor runtime application framework provides some useful generic AccessObjects, such as findById, findByQuery, save. It is likely that you need to replace these with your own or need to add other generic AccessObjects.
You can replace the implementation and interface with your own by specifying the class names in sculptor-generator.properties. For example findByQuery:
framework.access.interface.findByQuery=\
org.fornax.cartridges.sculptor.framework.accessapi.FindByQueryAccess
framework.access.implementation.findByQuery=\
org.fornax.cartridges.sculptor.framework.accessimpl.FindByQueryAccessImpl
To add a new generic AccessObject you can do like this. Assume you need an AccessObject that can evict from second level cache.
/**
* This AccessObject evicts objects from second level cache.
*/
public interface EvictAccess<T> {
/**
* Evict persistent objects. If {@link #setId(Long) id} is
* not specified all objects of this class
* be evicted.
* @param persistentClass class of the persistent object
*/
public void setPersistentClass(Class persistentClass);
/**
* Evict a specific object. Set
* {@link #setPersistentClass(Class) persistentClass}
* also.
* @param id the id of the object to evict
*/
public void setId(Long id);
/**
* Evict query cache with specified cache region
*/
public void setQueryCacheRegion(String queryCacheRegion);
public void execute();
}
public class EvictAccessImpl<T> extends AccessBaseWithoutException implements EvictAccess<T> {
private Class persistentClass;
private Long id;
private String queryCacheRegion;
public void setPersistentClass(Class persistentClass) {
this.persistentClass = persistentClass;
}
public void setId(Long id) {
this.id = id;
}
public void setQueryCacheRegion(String queryCacheRegion) {
this.queryCacheRegion = queryCacheRegion;
}
public void performExecute() throws HibernateException {
if (queryCacheRegion != null) {
getSessionFactory().evictQueries(queryCacheRegion);
}
if (persistentClass != null) {
if (id == null) {
getSessionFactory().evict(persistentClass);
} else {
getSessionFactory().evict(persistentClass, id);
}
}
}
}
There are a few conventions for the AccessObjects that the templates are based on:
- There must be an execute method. In above example it is implemented in AccessBaseWithoutException, which invokes performExecute.
- There must be a setSessionFactory method in the implementation. In above example it is implemented in AccessBase.
- Input parameters are defined in the interface as setter methods. These corresponds to the parameters of the repository method.
- The result of the AccessObject must be available with getResult method, defined in the interface. This corresponds to the return type of the repository method. If void then no getResult is used.
Define the interface and implementation in sculptor-generator.properties:
framework.access.interface.evict=org.helloworld.common.accessapi.EvictAccess
framework.access.implementation.evict=org.helloworld.common.accessimpl.EvictAccessImpl
The above addition of properties and classes means that you can use the evict AccessObject in your DSL model without any further changes of Sculptor.
E.g.
Repository PlanetRepository {
findByExample;
evict(String queryCacheRegion);
evict(Class persistentClass);
evict(Class persistentClass, Long id);
}
This would result in the following generated code in the Repository:
public void evict(String queryCacheRegion) {
EvictAccess<Planet> ao = planetAccessFactory.createEvictAccess();
ao.setQueryCacheRegion(queryCacheRegion);
ao.execute();
}
public void evict(Class persistentClass) {
EvictAccess<Planet> ao = planetAccessFactory.createEvictAccess();
ao.setPersistentClass(persistentClass);
ao.execute();
}
public void evict(Class persistentClass, Long id) {
EvictAccess<Planet> ao = planetAccessFactory.createEvictAccess();
ao.setPersistentClass(persistentClass);
ao.setId(id);
ao.execute();
}
The above is good enough in most cases, but it is also possible to define default parameters and return values for the RepositoryOperation, which means that you can skip those when using it in the DSL model. By this you can better support "convention over configuration".
For example assume that evict(Class persistentClass) is the most used operation and you would like that a simple evict declaration corresponds to this. Then you need to implement a GenericAccessObjectStrategy and register it in sculptor-generator.properties
public class EvictAccessObjectStrategy
extends AbstractGenericAccessObjectStrategy
implements GenericAccessObjectStrategy {
public void addDefaultValues(RepositoryOperation operation) {
if (operation.getParameters().isEmpty()) {
addParameter(operation, "Class", "persistentClass");
}
}
public String getGenericType(RepositoryOperation operation) {
DomainObject aggregateRoot = operation.getRepository().getAggregateRoot();
String aggregateRootName = GenerationHelper.getPackage(aggregateRoot) + "." +
aggregateRoot.getName();
return "<" + aggregateRootName + ">";
}
public boolean isPersistentClassConstructor() {
return false;
}
}
framework.access.interface.evict=org.helloworld.common.accessapi.EvictAccess
framework.access.implementation.evict=org.helloworld.common.accessimpl.EvictAccessImpl
genericAccessObjectStrategy.evict=org.helloworld.common.accessimpl.EvictAccessObjectStrategy
To understand how to implement the strategy class you can have a look in GenericAccessObjectManager and the strategies implemented for the ordinary generic AccessObjects. It is located in fornax-cartridges-sculptor-generator. These strategies are only used by the code generator. They are not used in runtime.
The above means that you can use evict without any parameters in the DSL model.
Repository PlanetRepository {
findByExample;
evict;
}
and the generated Repository method would look like:
public void evict(Class persistentClass) {
EvictAccess<Planet> ao = planetAccessFactory.createEvictAccess();
ao.setPersistentClass(persistentClass);
ao.execute();
}
For some rare special cases you might need to adjust the code generation template for the Repository, genericBaseRepositoryMethod in Repository.xpt.
How to add a transformation feature
Scaffold
is a feature to be able to mark a Domain Object as scaffold and then automatically add some predefined operations (typically CRUD) to the corresponding Repository and Service. This is implemented in the transformation.
We add a boolean scaffold property to the DSL grammar for DslEntity. This is straightforward.
In the transformation DslTransformation.ext we add a Repository and Service if they doesn't already exist. Operations are added to these, if they don't exist.
scaffold(sculptormetamodel::DomainObject domainObject) :
(domainObject.repository == null ?
domainObject.addRepository() :
null) ->
domainObject.repository.addScaffoldOperations() ->
(domainObject.module.services.exists(s | s.name == (domainObject.name + "Service")) ?
null :
domainObject.module.addService(domainObject.name + "Service")) ->
domainObject.module.services.select(s | s.name == (domainObject.name + "Service")).
addScaffoldOperations(domainObject.repository);
It is easiest to implement the actual creation and addition of model elements in Java. We implement the methods addRepository, addService and addScaffoldOperations in a Java helper class.
public static DomainObject addRepository(DomainObject domainObject) {
SculptormetamodelFactory factory = SculptormetamodelFactoryImpl.eINSTANCE;
Repository repository = factory.createRepository();
repository.setName(domainObject.getName() + "Repository");
domainObject.setRepository(repository);
return domainObject;
}
How to add a new feature in the meta model
We need to be able to define an additional feature for the attributes of the Domain Objects. It should be possible to define that an attribute is included in the constructor, but still have setter method. A complement to the changeable feature. Let us call this new feature required.
1. Open the sculptormetamodel.ecore_diagram with the graphical editor.
Add a new EAttribute to the Attribute "class". Set the name to required in the Properties view. Select EBoolean in the EAttribute Type.

2. Open the sculptormetamodel.genmodel and right click on the top node. Select Generate Model.
3. Open sculptordsl.xtxt and add required in the same way as nullable in the DslAttribute section. Note that required should by default be treated as false when not specified.
4. Run generate.oaw in org.fornax.cartridges.sculptor.dsl project. This can be done by right clicking generate.oaw and selecting "Run as oAW Workflow".
5. Open DslTransformation.ext and add the "copy" of the required property in the same way as the nullable property in the create Attribute method.
setRequired(attribute.required) ->
6. Modify the code generation templates. In this case it was only needed to adjust the logic for the constructorAttributes method, which is located in helper.ext.
7. Build fornax-cartridges-sculptor-parent with mvn clean install.
8. Install the DSL editor plugins and test the new feature with the reference application.
How to Add a New Core Concept
This section describes all steps of how to add a completely new concept, Consumer in this case. Consumer is implemented in Sculptor and since this description is very brief you have to look at the details in the source code to make any sense out of this.
1. Start with the Ecore meta model. Open the sculptormetamodel.ecore_diagram with the graphical editor.
- Add Consumer class.
- Add generalization link from Consumer to NamedElement, a Consumer has a name.
- Add bi-directional aggregate association between Module and Consumer, one Module can contain many Consumers, a Consumer belongs to one Module.
- Add association from Consumer to Service, serviceDependencies, used for dependency injection of Services into the Consumer.
- Add association from Consumer to Repository, repositoryDependencies, used for dependency injection of Repositories into the Consumer.

2. DSL grammar, located in sculptordsl.xtxt
- Define DslConsumer
DslConsumer :
(doc=STRING)?
"Consumer" name=ID "{"
(dependencies+=DslDependency)*
("unmarshall" "to" (("@")?messageRoot=ID))?
("queueName" "=" queue=DslQueueIdentifier )?
"}";
- Add (consumers+=DslConsumer)* to DslModule
- Add DslDependency in the same way as the existing DslRepositoryDependency
- Run generate.oaw in org.fornax.cartridges.sculptor.dsl to generate DSL meta model and DSL editor
3. DSL constraints checks, located in sculptordslChecks.chk.
- Add DslServiceDependency in the same way as the existing DslRepositoryDependency, findService is already implemented in sculptordsl.ext
4. Transformation of DSL model to Ecore meta model, located in DslTransformation.ext in fornax-cartridges-sculptor-generator.
- Add consumers.addAll(module.consumers.transform()) -> to the Module transformation.
- Add create sculptormetamodel::Consumer this transform(DslConsumer consumer)...
- Add module.consumers.transformDependencies() -> to the Module transformation.
- Add transformDependencies(DslConsumer consumer)...
- Add sculptormetamodel::Service transformServiceDependency(DslDependency dependency)...
5. Meta model constraints, located in constraints.chk in fornax-cartridges-sculptor-generator.
- Add context Consumer ERROR "Not allowed to delegate to repository...
- Add check of cyclic dependencies in DependencyConstraints class
6. Code generation template
- Add Consumer.xpt and everything to generate for the Consumers.
- Add properties for package and framework classes. This is done in default-sculptor-generator.properties, properties.ext and helper.ext.
- Add EXPAND Consumer in Root.xpt, you need allConsumers() in helper.ext, which can be implemented in the same way as allServices.
- Add Spring stuff for the Consumers in Spring.xpt.
Grate docu, thanks Patrik.
Denis