Skip to end of metadata
Go to start of metadata

Sculptor Advanced Tutorial

This tutorial describes the features of Sculptor business tier. It presents how Sculptor works out-of-the-box, customization is often needed and that is the topic of the Developer's Guide.

Before you start you must follow the instructions in the Installation Guide. It is also recommended that you try the Hello World Tutorial to get a feeling of the environment.

Table of Contents:

Library Example

The example used in this tutorial is a simple system for a library of movies and books. It is "over designed" compared with the simple functionality it provides. The reason for this is to be able to illustrate many code generation ideas.

The core of the system is a Domain Model, see Figure 1. A Library consists of PhysicalMedia. Books and Movies are different types of Media, which are stored on a PhysicalMedia, e.g. DVD, VHS, paper books, eBooks on CD. A Media has Characters, e.g. James Bond, which can be played by a Person, e.g. Pierce Brosnan. A person can be involved (Engagement) in different Media, actually a Person can have several Engagements in the same Media. E.g. Quentin Tarantino is both actor and director in the movie 'Reservoir Dogs'.


Figure 1. Domain model of the Library example. This diagram is generated by Sculptor

Setup Project

In this part we will setup the project structure for maven and eclipse.

1. Use the following command (one line) to create a maven pom and file structure.

Unknown macro: {div}

mvn archetype:generate -DarchetypeGroupId=org.fornax.cartridges -DarchetypeArtifactId=fornax-cartridges-sculptor-archetype-standalone -DarchetypeVersion=2.1.0 -DarchetypeRepository=http://fornax-platform.org/nexus/content/repositories/public

Fill in groupId and archetypeId:

2. In the new directory, run mvn eclipse:eclipse to create an Eclipse project with the same dependencies as in the pom.

3. Open Eclipse and import the project.

Generate Code

In this part we will write a Sculptor DSL file and generate code from it.

1. Modify the file named model.btdesign in the folder
src/main/resources/model

2. Open the design file with Scupltor DSL editor, double-click on it.
Add the following code to the design file. You can see that it defines two Modules, containing one Service each. The operations in the Services delegates directly to the Repositories. It defines the same Domain Objects, including attributes and references, as in Figure 1. The details will be explained further on.

model.btdesign

3. Run mvn clean install to generate code and build. The JUnit test will fail.

Domain-Driven Design

Now it is time to explain the fundamental Domain-Driven Design concepts in the DSL. If you don't have the book you can download and read more in DDD Quickly. It is highly recommended that you study one of these books, at least the concepts mentioned in this part of the tutorial. The below quotes are from DDD Quickly.

Domain Objects

In the DSL there are three types of Domain Objects.

Entity

Entities have an identity and the state of the object may change during the lifecycle.

"There is a category of objects which seem to have an identity,
which remains the same throughout the states of the software.
For these objects it is not the attributes which matter, but a
thread of continuity and identity, which spans the life of a
system and can extend beyond it. Such objects are called Entities."

ValueObject

For Value Objects the values of the attributes are interesting, and not which object it is. Value Objects are typically immutable.

"There are cases when we need to contain some attributes of a
domain element. We are not interested in which object it is, but
what attributes it has. An object that is used to describe certain
aspects of a domain, and which does not have identity, is named
Value Object."

Note that Value Object is not the same as Data Transfer Object. Core J2EE patterns caused a lot of confusion when they used the term Value Object. They have renamed it to Transfer Object.

BasicType

BasicType is typically used for fundamental types, for example different quantities such as Money,
or range of values.

BasicType is a ValueObject which is stored in the same table as the Domain Object referencing it. It corresponds to JPA @Embeddable.

Aggregate

"An Aggregate is a group of
associated objects which are considered as one unit with regard
to data changes. The Aggregate is demarcated by a boundary
which separates the objects inside from those outside. Each
Aggregate has one root. The root is an Entity, and it is the only
object accessible from outside. The root can hold references to
any of the aggregate objects, and the other objects can hold
references to each other, but an outside object can hold
references only to the root object."

With Sculptor each Entity is by default an aggregate root, but you can use !aggregateRoot to define that it is not. Sculptor will validate the reference constraints described in the quote above.

Repository

A repository is used for:

  • retrieving domain objects from the underlying database
  • persisting new domain objects
  • deleting domain objects

The interface of the Repository should always speak the Ubiquitous Language of the domain. It provides domain centric operations to the client. Repositories provide controlled access to the underlying data in the sense that it exposes only the Aggregate roots of the domain model.

"Databases are part of the infrastructure. A poor solution is for
the client to be aware of the details needed to access a database.
...
Therefore, use a Repository, the purpose of which is to
encapsulate all the logic needed to obtain object references. The
domain objects won't have to deal with the infrastructure to get
the needed references to other objects of the domain. They will
just get them from the Repository and the model is regaining its
clarity and focus."

You might feel confused regarding the difference between DAOs and repositories. The DAO is at a lower level of abstraction than the Repository and can contain plumbing codes to pull out data from the database. We typically have one DAO per database table, but one repository per domain type or aggregate.

I can recommend that you read Inject Repositories, not DAOs in Domain Entities.

Service

The Services act as a Service Layer around the domain model. It provides a well defined interface with a set of available operations to the clients.

The transaction boundary is at the service layer.

Module

"Modules are used as a method of organizing related concepts
and tasks in order to reduce complexity.
...
Another reason for using modules is related to code quality. It is
widely accepted that software code should have a high level of
cohesion and a low level of coupling."

With Sculptor the Modules are realized as Java packages. By default they are subpackages of the application package, but it is possible to define another package by specifying the basePackage attribute of the Module.

Circular references between Modules are not allowed. Interaction between a Service in one Module and a Repository in another Module is not allowed, it must go via a Service. Sculptor will validate these constraints.

How to Generate Domain Objects

In the context of Sculptor, Domain Object is a common term for Entity, ValueObject and BasicType.

Gap Class

Separation of generated and manually written code is done by a generated base class and manually written subclass, a gap class. It is in the subclass you add methods to implement the behavior of the Domain Object. The subclass is also generated, but only once, it will never be overwritten by the generator. You can of course remove it to regenerate it.

The gap class is not generated initially. When you need a gap class you specify that in the DSL with gap.

Note that in the gap class some annotations are specified, e.g. @Entity and @Table. Those are only generated once, and must be maintained manually. E.g. when changing the natural key attributes the @UniqueConstraint must be modified. In the JavaDoc of the generated base class the correct annotations are defined, for convenient copy to the hand written subclass.

It is possible to configure that gap classes are always to be generated, except when specified otherwise. Then you add the following property in sculptor-generator.properties:

In the DSL you can specify that a gap class is not needed:

Operations

Domain Objects can of course contain behavior, otherwise it wouldn't be a rich domain model. However, the behavior logic is always written manually in the gap class or in a trait. You can write the methods directly in the java gap class. Optionally you can define the signature of important methods in model.btdesign. You still have to implement it manually in the gap class. The advantage of defining the operation in the model is that then it is included in generated documentation and diagrams.

Operations are defined with a java like syntax. To mark it as an operation you must use def or * in front of it. It must end with ;.

Traits

Traits provide a mixin composition mechanism that is missing in Java. Similar to interfaces in Java, traits are used to define object types by specifying the signature of the supported methods. Unlike interfaces, traits can be partially implemented; i.e. it is possible to define implementations for some methods. Sculptor has support for traits, inspired by Scala traits, but with some limitations compared to Scala traits.

Let us say that the Library system also should be used to sell products.

This means that you can implement the logic for the price operation in one place, in the trait class. It will be available in classes that mixin the trait. Book with Product. The internal implementation is based on delegation, no magic.

Attributes and references in the trait will be mixed in to the domain object, i.e. stored together with the Book entity.

abstract priceFactor in the example above means that that operation has to be implemented in the domain object containing the trait, i.e. Book gap class.

Attributes, references and operations can be defined as protected if you for example don't want to expose them in the interface of the trait.

It is possible to mixin several traits.

In the case the same operation, attribute or reference is defined in several of the traits the last one will dominate. In this example, if you invoke the busy method on a Person instance it will end up in the Student implementation. There is no support for stackable traits, i.e. to be able to call super from one trait and that will invoke next trait. In the above example that would have been useful, since you would probably want to check the schedule for both Student and Worker.

There is a special self() method that you can use if you from the trait need to get hold of the instance containing the trait.

Another example of traits is illustrated in this article.

Attributes and References

Domain Objects can have simple attributes and references to other Domain Objects and enums.

Primitive types or any fully qualified Java class can be used as type of the attributes. Built in types:

  • String
  • Integer (int), Long (long), Float (float), Double (double)
  • BigDecimal
  • Boolean (boolean)
  • Date
  • DateTime, Timestamp
  • Clob
  • Blob

It is easy to add your own DSL types and mapping to database and Java types. See Developer's Guide. Sculptor supports Joda Time instead of the Java date and time classes. It is also described in the Developer's Guide how to activate Joda Time.

To distinguish references from simple attributes, declarations of references starts with a -. In the same way as in other places you must also use an @ in front of the declaration when referring to a Domain Object. When the relation is one-to-many or many-to-many you define a collection as the type of the reference. Bidirectional associations are defined with the opposite <-> syntax.

There is an alternative notation for references and bidirectional. Instead of - you can use reference and instead of <-> you can use opposite.

Collections

Supported collection types:

  • Set - unordered collection
  • List - ordered collection using order column (additional options available for JPA2, see below)
  • Bag - ordered collection using orderby sorting

For Bag collections you can specify orderby

For bi-directional many-to-many associations it is possible to define the JPA/Hibernate inverse side.

Associations with cardinality "many" (Set, Bag, List) that are not bidirectional are by default generated as many-to-many, with a separate relation table.

By defining the reference as inverse it will be generated as an ordinary foreign key in the child table, i.e. an one-to-many relation.

In a JPA2 environment there are a few new possibilities sorting a List collection.
For List collections you can now specify orderby="properties". properties is a string that contains one or more comma-separated attributes of the object to be ordered or in a referenced BasicType or Entity.

To define a persistently ordered List you can specify orderColumn="columnName". columnName is contains the name of the database column that is added, to store the order. columnName is optional. If not given, a default name is used (reference name + "_INDEX").

You can use orderby or orderColumn, but not both. It is possible not to specify a special order. The result is a default order. If the List references Entities, the List will be ordered by the primary keys. In other cases the ordering is undefined.

Element Collections

This feature correspondents to the JPA @ElementCollection.

In addition to define collections (Set, List) as relationships to other entities, it is possible to specify collections of BasicTypes and simple types (String, ...). This elements will be stored in a separate collection table. This collection table is a kind of one-to-many relation (aggregation) with a join column that refers to the containing entity table.

Example using a collection of Strings.

Example using a BasicType.

In both cases the contents of the collection will be stored in a collection table automatically.
If the collection should not be persistent use the transient keyword.

Key

equals and hashCode requires some thought when used with JPA/Hibernate, see the discussion at the Hibernate site. Sculptor takes care of the details. You only have to mark the attributes that is the natural key of the Domain Object, or if there is no natural key Sculptor will generate a UUID automatically.

If the key is a composite key, consisting of several attributes, a separate internal key class will be generated.

It is also possible to use a separate BasicType object as key. This key object may have one or several attributes marked as key. Some keys are important objects in a domain model and may contain logic. It is also more clarifying to pass around a real type, instead of a String or Integer, e.g. AccountNumber.

All persistent Entities and Value Objects will also have a surrogate id attribute, which is the primary key in the database.

Changeable

Attributes and References can be defined as changeable or not. By default Entities are mutable, and ValueObjects and BasicType immutable. ValueObjects and BasicTypes can be defined as !immutable to be changeable. Individual Attributes and References can be declared as !changeable. Natural keys are never changeable.

Attributes and References that are defined as !changeable are included in the constructor and have no setter method.

There is an alternative notation for !. Instead of !immutable you can use not immutable.

Required

As a complement to changeable you can use required for Attributes and References that are to be included in the constructor, but still have a setter method.

Nullable

An Attribute can be defined as nullable which means that it may have null as value when stored in database, i.e. no NOT NULL declaration for the database column. By default Attributes are not nullable.

Copy mutator

It may feel inconvenient to use constructors with many parameters for immutable ValueObjects. Therefore copy mutator methods with a fluent interface are generated in immutable ValueObjects.

The above Address may be constructed like this:

Domain Object Builders

Sculptor generates a builder class for each domain object. The builder class provides a fluent interface to build domain objects in a manner that can be easier to work with and read. The following example additionally uses static imports to make the builder code a bit more succinct.

The builder can also be useful to get around restrictions on access to no-argument constructors and changing immutable attributes.

Generation of domain object builder classes is an optional feature that is enabled by default, but can be disabled via the generate.domainObject.builder property.

By default the builder classes are generated in the domain package. To generate the builder classes in a different package use the package.builder property.

Validation

Sculptor supports bean validation by adding Hibernate Validator annotations to the Domain Objects.

To validate a Domain Object add validation constraints to the model. You can add constraints to Entities, Attributes and References.

All built-in Hibernate validation constraints are supported by keywords. Each keyword can be used in several ways, e.g.

     range="1"
     range="1,10"
     range="1,10,'must be between 1 - 10'"

or qualified like

     range="min=1"
     range="min='1',max=10"
     range="min='1',max=10,message='must be between 1 - 10'"

If you want to omit a parameter you have to use the qualified constraint like

    range="max=10"

It's also possible to define custom validation annotations. Create your own constraints and add it to the model via the validate keyword.

You can use fully qualified constraints or shortcuts. If want to use a shortcut add it to your sculptor-generator.properties, e.g.

Of course combinations of built-in and custom constraints and multiple custom constraints are possible.

Sculptor will automatically validate Domain Objects on every save operation (create or update). In case of a constraint violation a ValidationException is thrown, which gives you detailed information about the cause. If you need more control over the validation process, a validation by hand is supported by using the frameworks DomainObjectValidator class.

It is also possible to define validation at the class level, which is useful for validation of related properties, associations, or aggregate of objects.

An alternative way of implementing class level validation is to trigger your own validation method using JPA @PrePersist and @PreUpdate annotations.

Movie.java

Auditable

Entities are by default auditable, which means that when the objects are saved an interceptor will automatically update properties 'lastUpdated', 'lastUpdatedBy', 'createdDate' and 'createdBy'. These attributes are automatically added for auditable Domain Objects. You can turn off auditing for an Entity with !auditable.

Optimistic Locking

By default a version attribute is automatically added to each Entity and mutable persistent ValueObject. This is used for optimistic locking checks by Hibernate.
You can skip this feature by specifying !optimisticLocking for the Domain Object.

Aggregate

By default all Entities are considered to be aggregate roots, but you can use !aggregateRoot to specify that the Entity is part of an aggregate and not the root of it. The default values for cascade and fetch features take the aggregate into account.

Read more about the example below in the Domain-Driven Design book, page 134.

Basic Type

A BasicType corresponds to JPA @Embeddable / Hibernate Component, i.e. the properties are stored in the same table as the parent. It makes it easy to create object types.

In the Library example PersonName is an example of a BasicType. Look at annotations of the PersonName class.

A classical example is Money.

BasicTypes can contain nested BasicTypes, relationships to Entities and collections of simple types, BasicTypes and Entities.

Non-persistent ValueObject

It is possible to specify that a ValueObject is not persistent, i.e. not stored in database. This is for example useful for some parameters and return values for service operations. It can also be used for domain objects containing only logic, without any persistent state. E.g. an algorithm you want to make explicit.

Enum

Enums are defined like this:

An enum can have attributes. One attribute may be marked as key and that value will be stored in the database as identifier of the enum value. Otherwise the name of the enum values are stored.

It is possible to define an implicit value without defining an attribute. This attribute is automatically named 'value' and can be of String or int type. This attribute will be stored in database as identifier for the enum.

An example with several attributes:

The needed length for database storage will be calculated automatically. It's possible to override this calculation by adding hint="databaseLength=length" to the enum definition.

It is also possible to store the ordinal value representing the enum instead of the name. Use ordinal to do this. ordinal is only allowed for enums without attributes or without an identifier (key) attribute.

JPA

JPA has a built in support for enums. It is recommend to use this default handling to avoid provider specific generation of code and configuration files. Therefore try not to use enums that have an identifier (key) attribute.

Inheritance

An Entity may extend another Entity. A ValueObject may extend another ValueObject. Inheritance is not supported for BasicType. By default JOINED inheritance strategy is used, i.e. fields that are specific to a subclass are mapped to a separate table than the fields that are common to the parent class.

You can also use SINGLE_TABLE strategy, i.e. a single table per class hierarchy.

When using SINGLE_TABLE you can also define several things to customize the mapping, similar to JPA.

Package

By default the Domain Objects are located in a package named domain, which is a sub-package to the package of the module. However, it is possible to specify another package name for an individual Domain Object.

Cache

Domain Objects and collection references can be defined to be cached by JPA/Hibernate second level cache. EhCache is the default cache provider. It is described in the Developer's Guide how to change to another cache provider.

Database Definitions

The names of database tables and columns are normally derived from the names of the domain objects, but it is possible to specify other names by using databaseTable and databaseColumn attributes.

It is possible to define many-to-many join table with databaseJoinTable and its columns with databaseColumn at both sides of the bidirectional association:

Those keywords are also useful for unidirectional to-many associations. Additionally databaseJoinColumn is used, since there is no opposite side to define the column on.

For Attributes you can define nullable, index, databaseColumn, databaseType and length, which all
relates to the database definition.

Fetch

For References you can define fetch which corresponds to JPA/Hibernate feature.

Possible values for fetch:

fetch=

Generated feature

eager

javax.persistence.FetchType.EAGER

join

javax.persistence.FetchType.EAGER

lazy

javax.persistence.FetchType.LAZY

subselect

org.hibernate.annotations.FetchMode.SUBSELECT

Cascade

For References you can define cascade which corresponds to JPA/Hibernate feature.

Possible values for cascade:

cascade=

Implementation type

persist

javax.persistence.CascadeType.PERSIST

merge

javax.persistence.CascadeType.MERGE

remove

javax.persistence.CascadeType.REMOVE

refresh

javax.persistence.CascadeType.REFRESH

all

javax.persistence.CascadeType.ALL

all-delete-orphan

javax.persistence.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN

delete-orphan

org.hibernate.annotations.CascadeType.DELETE_ORPHAN

delete

org.hibernate.annotations.CascadeType.DELETE

save-update

org.hibernate.annotations.CascadeType.SAVE_UPDATE

evict

org.hibernate.annotations.CascadeType.EVICT

replicate

org.hibernate.annotations.CascadeType.REPLICATE

lock

org.hibernate.annotations.CascadeType.LOCK

Several cascade types can be defined by separating them with comma, e.g. cascade="persist,merge".

The default value for cascade and fetch takes the aggregate and module into account. When cascade is not explicitly defined in the DSL the convention is:

  • all-delete-orphan is used if both Entities are in the same Aggregate.
  • all is used if both Entities are in the same Module.

Default values for cascade can be defined in sculptor-generator.properties, see Developer's Guide.

Try the different features of the Domain Objects. Add a few more Entities and Value Objects to model.btdesign. Add different types of Attributes and References. For example you can add a new Module named customer, with a Customer entity, which has a Reference to rented Media. Maybe with a rental contract Value Object in between, containing time period and price.

Regenerate with mvn generate-sources -Dfornax.generator.force.execution=true and look at the generated Java code and JPA annotations.

Documentation of domain model

Sculptor generates summary documentation of domain model in clickable HTML format. The output is placed in src/generated/resources/DomainModelDoc*.html

The descriptions are defined in model.btdesign. Almost all elements in the model can have documentation. It is a simple quoted string before the element.

The documentation is also included as JavaDoc of generated Java code.

Diagram of domain model

Sculptor generates several UML diagrams for the domain model. The above diagram for the Library domain is generated.
We are using Graphviz.
Sculptor generates textual Graphviz .dot files, which is then used to generate images.

There is a maven plugin fornax-graphviz-m2-plugin that generates images (.png) from the .dot files. This plugin is included in the pom.xml created by the archetypes, but you need to install Graphviz and have dot executable in path.

It is possible to mark some domain objects with hint="umlgraph=core" to generate a special diagram with those domain objects in focus.

The colours are customizable as described in Developer's Guide

Subject area class diagrams

It is also possible to create additional class diagrams to portray specific subject areas. One diagram is created per subject area you define, and the classes that belong to each subject area are defined via the "umlgraph.subject" hint:

To include a class in more than one subject area, list each subject area in the hint, separated by a '|'. e.g. hint="umlgraph.subject=routingsvc|cargoext"

How to Generate Services

In the DSL an operation of a Service almost looks like an ordinary Java method with return type, parameters and throws declaration. You don't have to declare the visibility, it is public by default.

When referring to a Domain Object (Entity or ValueObject) you use an @ in front of the declaration. Primitive types or any fully qualified Java class can also be used as parameters and return types. The same built in types as can be used for the Domain Objects can be used in the service operations.

It is also possible to use Data Transfer Objects as parameters and return types.

Collections use the ordinary Java generics syntax. Built in collection types:

  • Set
  • List
  • Map
  • Collection

You can easily delegate to an operation in a Repository or another Service. Then you only have to declare the name of the operation. Return type and parameters are "copied" from the delegate operation.

Sometimes it can be convenient to declare a delegating operation with protected visibility. Such an operation is typically used by another manually coded operation in the Service.

There is an alternative notation for delegation. Instead of => you can use delegates to.

It is also possible to specify a dependency injection of a Repository or any other Spring bean, without having a generated delegation method.

The @ in front of the Repository name indicates that it is an internal reference and the DSL editor will validate that it exists. Skip the @ when you inject other Spring beans, which are not defined in the DSL model.
When you inject other beans you have to implement the setter method yourself in the Service implementation class. You define other Spring beans in more.xml or a Spring file imported from there.

There is an alternative notation for dependency injection. Instead of > you can use inject.

The generated code consists of:

  • Interface, eg. LibraryService
  • Implementation class, e.g. LibraryServiceImpl
  • Base class for the implementation, e.g. LibraryServiceImplBase
  • Test class, e.g. LibraryServiceTest
  • Base class for the test, e.g. LibraryServiceTestBase

Separation of generated and manually written code is done by a generated base class and manually written subclass. The subclass is also generated, but only once, it will never be overwritten by the generator. You can of course remove it to regenerate it. There will not be any subclass, gap class, when the service only consists of operations that delegate to repositories and other other services.

If you have configured to use EJB there will also be Stateless session EJB and Client side proxy for the EJB.

Try to add one more operation to the PersonService, e.g. findPersonsByCountry. Generate with mvn generate-sources -Dfornax.generator.force.execution=true. Note that PersonServiceImpl is not overwritten and therefore you will get compilation errors for the new method. You have to add it manually in PersonServiceImpl. Tips: use ctrl+1 in Eclipse.

How to Generate Repositories

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 JPA/Hibernate specific code is located in the Access Object, and not in the Repository.

Guidelines:

  • The interface of the Repository should always speak the Ubiquitous Language of the domain. It provides domain centric operations to the client.
  • Repositories provide controlled access to the underlying data in the sense that it exposes only the Aggregate roots of the domain model.
  • The Repository operation can typically use several Access Objects or other Repositories to collect the final result.
  • The Access Objects are at a lower level of abstraction than the Repository and can contain plumbing code to pull out data from the database. Even though JPA/Hibernate does a great job, there is often technical details of fetching, joining, caching, etc. that is not part of domain layer.

A Repository generated by Sculptor uses Access Objects, which implement the specialization needed for persistence. Access Objects are implemented as Commands and have separated interface and implementation. The JPA/Hibernate specific code is encapsulated in the implementation classes. A Factory is used to create instances of the Access Objects.

It is possible to use another design approach and implement everything directly in the Repository. This is described in the section Hibernate Repository in the Developer's Guide.

Generic Access Objects

Sculptor runtime framework provides generic access objects for the following operations:

  • findById
  • findAll
  • findByExample
  • findByQuery
  • findByCondition
  • findByCriteria (use findByCondition instead)
  • findByKey
  • findByKeys
  • save
  • delete
  • countAll
  • populateAssociations

JPA2

All generic access objects, including findByExample, findByCondition and findByCriteria, are implemented for all supported JPA2 providers.

To use a generic Access Object you simply have to specify the name of it in the Repository in the DSL.

Note that you can define operations as protected to not expose them in the Repository interface and only use them from other methods with a more domain centric interface.

See the source of the corresponding classes for more information of the functionality of these Access Objects.

Note that setter methods in the interface corresponds to method parameters in the repository operation. For example the findAll operation can be used with parameters for sorting and caching.

You would often like to define sorting and caching without exposing it as parameters, i.e. always use caching and always sort in a specific way. Therefore this is supported with hints.

Custom Access Objects

You can also use Access Objects containing manually written code. You specify => AccessObject in the DSL.

There is an alternative notation for delegation. Instead of => you can use delegates to.

Note that you define return type and parameters in the same way as for Service operations. When referring to a Domain Object (Entity or ValueObject) you use a @ in front of the declaration.

By default the Access Object has a similar name as the repository operation, but you can define another name.

It is in the performExecute method in the AccessImpl class you place the hand written code.

It is also possible to use, and specialize the generic Access Objects.

Repository Operation without Access Object

There is also a third variant of Repository operations, which neither delegates to a generic nor a custom Access Object. It is manually written in the Repository implementation. To avoid automatic delegation you simply don't specify => AccessObject in the DSL.

It is possible to specify a dependency injection of a another Repository, which can be useful for this variant of Repository operation. It is used in the library example to implement findMediaByCharacter in the MediaRepository.

The @ in front of the Repository name indicates that it is an internal reference and the DSL editor will validate that it exists. Skip the @ when you inject other Spring beans, which are not defined in the DSL model.
When you inject other beans you have to implement the setter method yourself in the Repository implementation class. You define other Spring beans by annotating the classes with @Repository, @Service, or @Component.

There is an alternative notation for dependency injection. Instead of > you can use inject.

Take a look at the Java code for findLibraryByName in LibraryRepositoryImpl.
As you can see you have to implement it manually, this can be done by something like this:

Scaffold

It is possible to mark a Domain Object with scaffold to automatically generate some predefined CRUD operations in the Repository and corresponding Service.

The above DSL definition would result in the same as the following:

The Repository and Service is also added automatically if they are not defined.

Which scaffolding operations to use can be defined in sculptor-generator.properties, see Developer's Guide.

Pagination

Paging of large result sets is supported for findAll, findByQuery, findByCriteria and custom access objects.
Usage of paging is defined by adding a PagingParameter as parameter to a repository operation.

When using a paged operation it looks like this:

In the initial request you typically ask for number of available pages. To answer this the system must count total number of rows. For findAll there is a built in countAll access object. For findByQuery, findByCriteria custom access objects you must provide a count operation, which can be done in several ways.

For findByQuery the default is to use a naming convention of the named query for the counting query. find is replaced with count.
findByQuery on named query "Person.findByCountry" will use "Person.countByCountry".

For findByCriteria and custom access objects you must define the count operation or named query to use. That is done with hint countOperation or countQuery. Operation parameters are passed to the count operation or count query. Try the following and look at the generated code.

Those hints are available for findAll and findByQuery also.

For findAll and findByCondition it is possible to use a config property in sculptor-generator.properties to say that paging should always be used, i.e. not necessary to add the PagingParameter to model.

findByCondition

findByCondition is one of the built in repository operations. It is used like this.

It takes a list of ConditionalCriteria objects as parameter. Use the fluent api of ConditionalCriteriaBuilder to define the criteria.

Note that for each Domain Object a corresponding Properties class is generated. It contains definitions of all attributes and references of the the Domain Object. It serves two purposes. It is refactoring safe, since you will get compilation errors when something is changed. It provides convenient code completion (ctrl+space) support in your IDE.

You would typically use static imports to make the expression more compact and readable:

The builder supports conditions such as eq, like, between, lessThan, greaterThan, in. The order of the result can be specified with orderBy. Use code completion (ctrl+space) to see all available methods. It also supports logical expressions and, or and not, which can be grouped with lbrace and rbrace.

ConditionalCriteria supports multiple column selects with column aliases, aggregate functions and grouping.

It is also possible to construct the ConditionalCriteria without the builder, using the static factory methods in ConditionalCriteria.

Generation of Finder Operations

Widely used repository operations are operations to find domainobjects. Often this operations use static queries to get the results. In this cases it is now possible to specify the related query in the model file and let sculptor generate the code for the repository operations.

This feature is not activated by default. To allow sculptor to generate finder operations add the following line into your sculptor-generator.properties file.

After activation all repository operations starting with 'find', except built in operations, are potential candidates for finder generation. To prevent a generation and to use handwritten code for a special operation, add the keyword 'gap'.

There two variants of generated finder operations.
One is based on the the built in repository operation findByQuery the other uses findByCondition.

Both variants actually do not support pagination.

Based on findByQuery

This kind of finder operation is using a JPQL query or named query and delegates the query to the built in findByQuery access object. It is not necassary to add a findByQuery operation to the model. It will be added to the repository automatically in case it is not defined.

Here are typical examples.

All these examples will produce the same results.
findByNamedQuery is using a named query defined in the Person entity class.
findBy will execute the given JPQL statement to get the result list.

The second findBy operation is a short form of the first. The generated code is indetical. This means specifing the result type of an operation is optional. The default is List<@AggregateRoot> (in the example List<Person>). For simple queries you can omit some parts of the JPQL query and only add the restrictions.

The parameters of the operation will be used to set the query parameters with matching names.

You are free to choose a name for your finder operations
(it is good style to have a consistent naming, but names must not start with 'find')

Next to the aggregate root any appropriate return type is allowed. This can be simple types, referenced domain objects (BasicType/Entity) and collections (List/Set) of simple types and domain objects. Returning multiple columns is just as possible as using aggregate functions and ordering.

Based on findByCondition

This kind of generated finder operation builds a ConditionalCriteria from a JQPL like syntax and delegate the execution of the query to the built in findByCondition operation. Also here it is not necessary to add a findByCondition operation to the model.

Here is a typical example.

Both findBy operations will produce the same results. The second findBy operation is a short form of the first. The generated code is indetical. Specifing the result type of an operation is optional. If absent the default List<@AggregateRoot> is assumed.

The parameters of the operation will be used to set the condition parameters with matching names. If the operation name starts with 'find' you can omit the condition keyword. In this case the list of operation parameters is used to build the condition. For all parameters sculptor tries to find a property (attribute or reference) in the root entity with a name that matches the parameter name. If not found referenced domain objects (BasicType/Entity) are searched for matching properties. In the above example 'first' and 'last' are attributes in a referenced BasicType 'PersonName'. Operation parameters and domain objects properties are compared with '=' and all parameters are combined with an 'and' operator.

If no matching property could be found you will receive a generation time error.

Always the first matching property is used to build the condition. If there is more than one you may need to clarify what property should be used. You can do this as follows (both ways are identical).

You are free to choose a name for your finder operations as long as you provide the dsl keyword condition (also select, groupBy, etc. will do the trick).

Of course you can use more comparison operators than '='. All operators supported by ConditionalCritria are possible ('<','>','<=','>=','!=','is null', etc.). Even braces are supported. You can use a JPQL like syntax to set a condition.

Two special operators can be used to support case insensitve comparison, 'i=' (ignore case equal) and 'ilike' (ignore case like).

Any appropriate return type is allowed. This can be simple types, referenced domain object (BasicType/Entity) and collections (List/Set) of simple types and domain objects. Returning multiple columns is just as possible as using aggregate functions and ordering.

You can add a complete JPQL statement or only the needed parts.
(not all JPQL features are supported, e.g you can not use subselects or explicit joins)

Again all above examples are identical. Sculptor tries to guess the desired select part by the given return type. The root entity is searched for a referenced domain object that matches the return type (simple types are not supported). If none is found all references are searched for a matching type. In case there is no matching domain object you receive an error.

In combination with building the restrictions from a operation parameter list the amount of needed keywords is significantly reduced.

Here are some more advanced examples.

Multiple column results are supported (and only this columns will be selected).
Similar to operations using findByQuery you can use an object array to store the values of the result set.
In addition to an object array a Tuple (javax.persistence.Tuple, JPA2) can be used, which provides some useful methods to extract the elements of the query result.

This is the common way to handle multiple column results. But there is also another option.
You can directly use another domain object as return type, usually a DataTransferObject (DTO), and map the query result columns to the attributes of that return type. Actually only simple attribute types (String,Long,...) are supported.

A usual case is that the DTO and the Entity have matching attributes. In this case it is unnecassary to specify a select part.

In the above example sculptor will generate a query that selects the three matching attributes 'last' (name.last), 'first' (name.first) and 'birthDate'. Attributes that can not be found in the root entity type are ignored and therefore must be nullable in the result type.

If the attribute names of the entity type and the return type do not match, it is possible to use aliases in the select part to map the query results.

Here is an examples using a DTO as return type and attribute mapping with column aliases.

Attribute mapping is the default case using a DataTransferObject as return type. Sometimes it is useful to use a constructor instead of setting attributes directly, e.g. to do additional initializations and/or type conversions. Using a constructor of the return type is provided by the keyword 'construct'.

Here is a simple example. Of course column aliases are useless.

And now putting some things together.

JUnit

For each Service there is a generated JUnit test class that you have to implement. It extends IsolatedDatabaseTestCase which means that DBUnit it used to load the in memory HSQLDB database with test data from the XML file specified in the getDataSetFile method. The database is refreshed for each test method.

Spring beans are injected in the test with ordinary @Autowired annotations.

AbstractDbUnitJpaTests also provides a method to retrieve the ServiceContext, which is always passed in as the first parameter of the service methods.

You can implement the same kind of JUnit tests for the Repositories.

Above tests are kind of integration tests and you should do ordinary unit tests for domain objects and other classes of importance.

Mocking

Sometimes it is useful to test the Services by stubbing dependencies, e.g. to Repositories.

It is possible to use Mockito or some other mocking framework together with Spring if you do as follows.

As example we have a MessageSender, which is implemented using JMS. The MessageSender implementation is normally injected using @Autowired annotation. It is this implementation we want to replace with a mock when testing. We can create the mock instance using the FactoryBean that is included in Sculptor so we only need to add the xml definition in more-test.xml:

In the junit test:

In the previous section about How to Generate Services you added the operation findPersonsByCountry in PersonService. Think about how to implement that method using one of the three different types of Repository operations. It is possible to do it with any of the three variants. Select the alternative you find most attractive and give it a try. Implement a test method also.

You might find the following DBUnit test data useful:

Error Handling

Two types of exceptions are used, a checked exception called ApplicationException and a runtime exception called SystemException.

ApplicationException is used for faults that the system is designed to take care of, i.e. recoverable errors at the level of the application, e.g. validation failures.

System exceptions are used to indicate unrecoverable, unexpected errors that are outside the control of the application, e.g. database failures. The current processing can't continue and the transaction is rolled back when they occur.

Effective Java Exceptions is a good article describing similar error handling strategy.

Both ApplicationException and SystemException contain an error code, which is used by the client to translate to appropriate error message. Subclasses to these exceptions defines specific error situations and it is in these subclasses the error codes are defined, i.e. an exception class can define several error codes to indicate different flavours of the fault.

In the throws clause of operations for Services or Repositories you can define a comma separated list of exceptions. It can be fully qualified or unqualified class names. When it is unqualified name an ApplicationException subclass with that name will be generated.

A Spring advice will catch all exceptions that are thrown from the Services. This advice will log all SystemExceptions to the error log. ApplicationExceptions are logged at debug level. Log4j is used for the logging.

The ServiceContext class is needed to support logging and audit trail functionality through the tiers of an application. A ServiceContext object will typically be sent through all tiers, and represents the context in which a business service is called. It contains information about the user and ids for the session and request.

The first parameter of each method in the Services is a ServiceContext parameter. This is generated automatically. In front of the Services there is an advice, which stores this ServiceContext object in a thread local variable, ServiceContextStore, to make sure that it is available everywhere within that request in the tier. When calling remote methods it must be passed as a method parameter.

It is possible to skip the generation of ServiceContext, see Developer's Guide.

Alternative Notation

There is an alternative notation for some things in the DSL. It is more verbose but maybe easier to understand. You can mix the verbose and compact syntax as you wish.

Compact

Verbose

!

not

=>

delegates to

>

inject

<->

opposite

-

reference

model.btdesign with verbose syntax

Reserved keywords

If you need to define names that clash with the reserved keywords in the Sculptor DSL you can escape your name by prefixing with ^. Example:

Divide model into several files

It is possible to split model.btdesign and define one or more Modules in each file.

By using one file per module it is also possible for Sculptor to do a partial generate of the changed modules and the ones depending on the changed modules. The file must be named the same as the module (media.btdesign, person.btdesign) or prefixed with model_ or model- (model-person.btdesign). This partial generation can shorten the generation time for large projects. fornax-oaw-m2-plugin will detect which model files has changed since previous generator run when using mvn -o generate-sources. Full generate will be done when using -Dfornax.generator.force.execution=true or mvn clean generate-sources

Referenced files are imported with a URI syntax starting with classpath:/ followed by classpath path to the .btdesign file to be imported.

model.btdesign main file
model-person.btdesign containing person module in separate file

The main file doesn't have to define a Module.

model.btdesign empty main file

The partial generation and the fornax-checksum-m2-plugin doesn't play well together, so if you are using the partial generation feature, i.e. running mvn generate-sources without -Dfornax.generator.force.execution=true you need to remove the fornax-checksum-m2-plugin from your pom.xml.

Cross project references

In previous section it was described how the model could be separated into several files. It is also possible to define modules in a separate project to be used from other projects.

In that case the imported module must define a basePackage and use ApplicationPart.

model-sharedtypes.btdesign in separate file, in another project

You don't want to generate code for the imported module in your project, which is going to use the sharedtypes from the common project. To skip generation of a module you define the module (sharedtypes in this example) in sculptor-generator.properties:

You import and use it as normal, i.e.

The dependency to the classes must be added in pom.xml:

Note that if you use classifier client it will be dependency to the -client jar file, which was created by maven-jar-plugin with corresponding client classifier.

If you use ejb you will use <type>ejb-client</type> instead of classifier.

Source

The complete source code for this tutorial is available in Subversion.

Web Access (read only):
http://fisheye3.cenqua.com/browse/fornax/trunk/cartridges/sculptor/fornax-cartridges-sculptor-examples-library

Anonymous Access (read only):
https://fornax.svn.sourceforge.net/svnroot/fornax/trunk/cartridges/sculptor/fornax-cartridges-sculptor-examples-library

Unknown macro: {rating}
  • No labels
  1. Anonymous

    In the sample code buildRestrictions should be renamed to addRestrictions.

    In the DBUnit Dataset the column SSN should be SSN_NUMBER and the column COUNTRY should be SSN_COUNTRY.

    1. Thanks a lot. Fixed!

  2. Anonymous

    The model.design needs a few corrections:

    The MediaRepository is missing the findMediaByCharacter used in LibraryService; and 

    Glenn J Gonzales (ggonzales@exist.com)

  3. Anonymous

    Rather, change to:

    Cheers,

    Glenn J Gonzales (ggonzales@exist.com)

    1. I don't quite follow you. I think the things you request are already in model.design:

  4. Anonymous

    I think you missed the group Id in the initial maven command.

    -DarchetypeGroupId=org.fornax.cartridges

    1. Thanks, correction done.

  5. Anonymous

    I am new to j2ee...

    Could you provide an explanation to how concurrency is managed?  From my brief readings on the subject their are a few different ways to manage concurrency.

    1. Anonymous

  6. Anonymous

    With newest Sculptor version you get a compiler error when using inheritance with DataTransferObject.
    It is because of the generated catch block for "CloneNotSupportedException". This is not ok for Sub-DTOs.
    I made an advice to correct it, but it should be corrected in the code itself. Here is my code for reference:

    1. Thanks for reporting. Fixed in trunk.

  7. Anonymous

    Hi, maybe I do not see the forest in between all the trees ...
    When I need a class from legacy code, how do I reference to it?
    I see that it is possible to write any class into the model without any syntax error. That's fine so far, but how will the import statement get into the generated java file, what have I missed?

    1. You can use fully qualified class name. You can also define your own types as described in Developer's Guide (http://fornax.itemis.de/confluence/display/fornax/7.+Developer's+Guide+(CSC)#7.Developer%27sGuide%28CSC%29-Types).

      1. Anonymous

        Hi Patrik,
        using fully qualified name, what would be my favourite, is not successful in my case. The import statement is still not in the Java code.
        But the hint with the additional javaType is successful (smile)
        Thanks.

        1. Please post an example of your model.btdesign in our forum and we will try solve it.

      2. Anonymous

        Looks that I made a fault somewhere (sad)
        Full qualified name is working fine as well (smile)
        Sorry for the inconvenience.