Showing posts with label Customization. Show all posts
Showing posts with label Customization. Show all posts

Sunday, February 21, 2010

Bonus Material

For my customer's project I created a small addition to Sculptor, that might be useful for you also. The domain model is important for communication, and we need to document it. The difficult thing with documentation is to keep it up to date with changes. Placing the descriptions close to the code makes it easier to do the changes to the documentation at the same time as when you change the code. Since we use Sculptor it was a natural choice to write the descriptions in the model file and generated the documentation.

"A location is our model is stops on a journey, such as cargo
origin or destination, or carrier movement endpoints."
Entity Location {
not optimisticLocking
- @UnLocode unLocode key
'Actual name of this location, e.g. "Stockholm"'
String name not changeable

Repository LocationRepository {
@Location find(@UnLocode unLocode) throws LocationNotFoundException;
findAll;
protected findByKeys;
}
}



From the model we generate HTML documentation of all domain objects including their attributes and associations.



Try it if you like. It is easy to understand and adjust to fit your needs.

Download this generation template and place it in src/main/resources/templates/
In SpecialCases.xpt you add

«AROUND templates::Root::Root FOR Application»
«targetDef.proceed()»
«EXPAND DomainModelDoc::start»
«ENDAROUND»


The generated result is located in src/generated/resources/DomainModelDoc.html

Tuesday, February 9, 2010

Customization of webflows

With the new 1.7 release of sculptor the possibilities to customize flows for the web client is much better.
With our Library example here is what you can do.
For example, lets say you want to implement filter functionality for the library list feature. Here is the steps to do that:

1) Specify that you want gap-files for the list library feature, so in your model.guidesign:

gui Library for Library {
Module for media {
ListTask for Library {
gap
}
}
}

Now you have a bunch of files for the feature:
  • src/generated/java/org...library/ListLibraryActionBase.java
  • src/main/java/org...library/ListLibraryAction.java -> gap
  • src/generated/java/org...library/ListLibraryForm.java
  • src/WEB-INF/generated/flows/media/listLibrary/listLibrary-base.xml
  • src/WEB-INF/generated/flows/media/listLibrary/list_include.xhtml
  • src/WEB-INF/flows/media/listLibrary/listLibrary-flow.xml -> gap
  • src/WEB-INF/flows/media/listLibrary/list.xhtml -> gap

2) Edit the media/listLibrary flow:


<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:ns0="http://www.w3.org/2001/XMLSchema-instance"
ns0:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"
parent="media/listLibraryBase">
<view-state id="list">
<transition on="filterLibrary" to="listByFilter" />
</view-state>
<view-state id="listByFilter" model="listLibraryForm"
view="/WEB-INF/flows/media/listLibrary/list.xhtml" parent="media/listLibraryBase#list">
<on-render>
<evaluate
expression="listLibraryAction.findByFilter(flowRequestContext)" />
</on-render>
</view-state>
</flow>

3) Edit the ListLibraryAction, add the method:


public String findByFilter(RequestContext ctx) {
getRepository().clear();

List<library> allLibraries = getLibraryService().findAll(ServiceContextStore.get());
List<library> filtered = new ArrayList<library>();
String filter = ctx.getRequestParameters().get("libraryFilter");
for (Library library : allLibraries) {
if (library.getName().startsWith(filter)) {
filtered.add(library);
}
}
formObject(ctx).setAllLibraries(filtered);
return "success";
}

4) Add a simple form with a text field and a button to the media/listLibrary/list.xhtml-file:


<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core" xmlns:t="http://myfaces.apache.org/tomahawk"
xmlns:h="http://java.sun.com/jsf/html" xmlns:c="http://java.sun.com/jstl/core"
xmlns:a="ApplicationTaglib">
<body>
<ui:composition template="/WEB-INF/common/template.xhtml">
<ui:define name="content">
<h1>
<h:outputFormat value="#{msg['list.header']}">
<f:param
value="#{msgMedia['model.DomainObject.Library.plural']}" />
</h:outputFormat>
</h1>
<h:form xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core"
xmlns:t="http://myfaces.apache.org/tomahawk"
xmlns:h="http://java.sun.com/jsf/html" xmlns:c="http://java.sun.com/jstl/core"
xmlns:a="ApplicationTaglib">
<div>
<label for="_libraryFilter">#{msgMedia['model.DomainObject.Library.filter']}: </label>
<input type="text" value="#{requestParameters.libraryFilter}" name="libraryFilter" id="_libraryFilter"/>
<h:commandButton value="#{msgMedia['model.DomainObject.Library.filterButton']}" action="filterLibrary" />
</div>
</h:form>
<ui:include
src="/WEB-INF/generated/flows/media/listLibrary/list_include.html" />
</ui:define>
</ui:composition>
</body>
</html>

Note that the default generated table showing the list result is used by including the generated file. In case you need to modify the generated content you can either overwrite the code generation templates in WebSpecialCases.xpt or simply copy the generated file and maintain it manually

That's it.

Tuesday, February 2, 2010

Sculptor 1.7.0 - GAE, Smartclient, EclipseLink, DataNucleus, JEE

Sculptor 1.7.0 has been released. In this version the list of supported technologies has grown with several popular alternatives.

Sculptor has many built in customization options to pick and choose from. The major new target implementation options in this release:
  • Google App Engine
  • EclipseLink and DataNucleus JPA Provider
  • Smartclient GWT
  • Pure EJB3 (without Spring)
  • Web Services with JAX-WS
Sculptor also has good possibilities for extensibility if you prefer a design or framework that is not supported out-of-the-box. Sculptor is using openArchitectureWare code generation platform, which makes it possible to change the code generation templates using Aspect-Oriented Programming (AOP) features.

The Pure EJB3 implementation, one of the new features in 1.7.0, has already been successfully used in several projects, i.e. it is well tested and production ready. This means that not only Spring users can gain the productivity and quality benefits of Sculptor, but also those who develop on the standard JEE stack.


Thursday, January 28, 2010

Enable scala in your appengine/sculptor project

I've recently started to interest myself in the Scala language. It is a language that runs on the jvm. It's very rich in its syntax and it has a very pleasant mix of OO- and functional programming.
But since I always starts my explorations with the solid foundation of maven the first problem I encountered was how to integrate scala in my build process in a project that already mixes GAE and Sculptor. I.e. I start off from a project that has been created with our maven appengine archetype. So this is a log of my steps for making this happen.

1) In your pom, add a property for the version of Scala:


<scala.version>2.7.7</scala.version>


2) In your pom, add repository for scala:


<repository>
<id>scala-tools.org</id>
<name>Scala-Tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</repository>


3) In your pom , add pluginRepository:


<pluginRepository>
<id>scala-tools.org</id>
<name>Scala-Tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</pluginRepository>


4) In your pom, add dependency:


<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>


5) In your pom, add entry in maven-dependency-plugin in the build section:


<artifactItem>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
<outputDirectory>war/WEB-INF/lib</outputDirectory>
</artifactItem>


6) In your pom, add dependency to specs-library (if you want it):


<dependency>
<groupId>org.specs</groupId>
<artifactId>specs</artifactId>
<version>1.4.3</version>
<scope>test</scope>
</dependency>


7) In your pom, add the scala-plugin to the build section:


<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<scalaVersion>${scala.version}</scalaVersion>
<args>
<arg>-target:jvm-1.5</arg>
</args>
</configuration>
</plugin>


8) In your project file structure, add a simple scala file: src/main/scala/org/foo/App.scala


package org.foo

/**
* Hello world!
*
*/
object App extends Application {
println( "Hello World!" )
}

9) In your project file structure, add a simple scala test: src/test/scala/org/foo/AppTest.scala


package org.foo

import org.junit._
import Assert._

@Test
class AppTest {

@Test
def testOK() = assertTrue(true)

}

10) In the root of your project file structure, run mvn install to see that everything builds ok

Saturday, January 16, 2010

Pick 'n' Choose Target Implementation

This article gives an overview of the options you have regarding the generated target implementation. We add new stuff all the time so this describes what is available at the time of this writing, i.e. in Sculptor 1.7.0.

The default target implementation is using:
  • JPA with Hibernate as provider
  • Spring with annotations, no EJBs
  • Web CRUD GUI client with JSF, Facelets, and Spring Web Flow
  • In-memory Hsqldb as database
  • Deployment as war in Jetty
The default choices gives a good starting environment with minimal requirements of installation of external software, such as database and application server.

For each of these areas there are alternative implementations that are supported by Sculptor out of the box. The features are selected with simple configuration.

















If you have a different reference architecture than what is provided by Sculptor out of the box you can rather easily customize the generator to fit your needs. It is possible to add your own code generator templates or redefine sections of existing templates. All that is described in the Developer's Guide.

Wednesday, October 21, 2009

Even Weird Naming Conventions are Good

The good thing with naming conventions are that they are toolable. The DBAs at my department have strong opinions about database naming. They have good reasons for that, even though I don't fully understand all of them :-)
  • Table names should be prefixed with application/component identifier.
  • Primary key id column should be prefixed with table name (without application prefix) and followed by _GID.
  • Underscore to separate words.
  • Foreign key column is concatenation of role name and primary key column name of target table, except when role and table have the same name.
Does this mean that we have to specify each and every name twice, once for Java and once for the database. Argh... NO, we are using Sculptor. With a straightforward customization I implemented these conventions in the generator and we could continue with natural (java point of view) naming and please the preferences of the DBAs without additional effort.

Naming conventions are important for software quality. Supporting the conventions with a tool is the best way to make sure that they are applied in a consistent way.

Wednesday, September 16, 2009

Customer Specific Addon: Deep Merge

This article illustrates the possibility to add your own features to the Sculptor code generator.

In my customer project we have a need to merge two object graphs. We have a persistent domain model and we receive messages from production systems when changes occur. There are several production systems sending the data in slightly different format and semantics.

We designed this as a first step that converts the production messages to new transient domain object instances.

Next step is to merge that object graph with present persistent objects.

This feels like a tedious and repetitive programming task. If done manually it will require some maintenance when we do changes.

At first I took a look at Dozer, but pretty soon things got complicated and required a lot of XML mapping files. So we gave up that idea.

At home, it struck me that we already have the tool we need. We are already using Sculptor, and it should be a simple addition to generate the merge methods in the domain objects.

Next morning I implemented it like this...

The final java code to be generated looks like this in each domain object. It copies attributes and new associated objects. It traverses existing associations.

  public void deepMerge(Item other) {
Set<Object> processed = new HashSet<Object>();
deepMerge(other, processed);
}

public void deepMerge(Item other, Set<Object> processed) {
if (processed.contains(this)) {
return;
}
processed.add(this);

if (other.getEstimatedTimeOfArrival() != null) {
setEstimatedTimeOfArrival(other.getEstimatedTimeOfArrival());
}

deepMergeShipment(other, processed);

deepMergeEvents(other, processed);

}

public void deepMergeShipment(Item other, Set<Object> processed) {
Shipment currentValue = getShipment();
if (other.getShipment() != null) {
if (currentValue == null) {
setShipment(other.getShipment());
} else {
currentValue.deepMerge(other.getShipment(), processed);
}
}
}

public void deepMergeEvents(Item other, Set<Object> processed) {
for (TrackingEvent each : other.getEvents()) {
if (getEvents().contains(each)) {
TrackingEvent currentValue = eventForKey(other.getKey());
currentValue.deepMerge(each, processed);
} else {
addEvent(each);
}
}
}

protected TrackingEvent eventForKey(Object key) {
for (TrackingEvent each : getEvents()) {
if (each.getKey().equals(key)) {
return each;
}
}
return null;
}
I developed this as a project specific addon, i.e. I invoked a code generation template from SpecialCases.xpt:
«AROUND templates::DomainObject::keyGetter FOR DomainObject»
«targetDef.proceed()»

«EXPAND templates::DeepMerge::deepMerge»
«ENDAROUND»
I started with the simple attributes.
«DEFINE deepMerge FOR DomainObject»

«EXPAND deepMergeMethod»

«ENDDEFINE»

«DEFINE deepMergeMethod FOR DomainObject»
public void deepMerge(«getDomainPackage()».«name» other) {
«EXPAND deepMergeAttribute FOREACH attributes.reject(e | !e.changeable)»
«ENDDEFINE»

«DEFINE deepMergeAttribute FOR Attribute»
if (other.«getGetAccessor()»() != null) {
set«name.toFirstUpper()»(other.«getGetAccessor()»());
}
«ENDDEFINE»
I generated and looked at the result.

I noticed that the auditable fields were included. Ok, then I can use the helper function isSystemAttribute() to skip those.
«EXPAND deepMergeAttribute FOREACH attributes
.reject(e | !e.changeable || e.isSystemAttribute())»

The tricky part is the associations and I could imagine that we would have some corner cases that wouldn't be covered by the generated pattern. Therefore I created separate methods for each association so that it will be possible to override the generated methods in gap classes and handle eventual special cases manually.

I added the templates for references. Starting with the to-one references:

«DEFINE deepMergeOneReference FOR Reference»
public void deepMerge«name.toFirstUpper()»(«from.getDomainPackage()».«from.name» other) {
«to.getDomainPackage()».«to.name» currentValue = get«name.toFirstUpper()»();
if (other.get«name.toFirstUpper()»() != null) {
if (currentValue == null) {
set«name.toFirstUpper()»(other.get«name.toFirstUpper()»());
} else {
currentValue.deepMerge(other.get«name.toFirstUpper()»());
}
}
}
«ENDDEFINE»

Continuing with the to-many case. It is a little bit more tricky, since we need to grab existing instance for collection. Added a helper method for that.
«DEFINE deepMergeManyReference FOR Reference»
public void deepMerge«name.toFirstUpper()»(«from.getDomainPackage()».«from.name» other) {
for («getTypeName()» each : other.get«name.toFirstUpper()»()) {
if (get«name.toFirstUpper()»().contains(each)) {
«to.getDomainPackage()».«to.name» currentValue = «name.singular()»ForKey(other.getKey());
currentValue.deepMerge(each);
} else {
add«name.toFirstUpper().singular()»(each);
}
}
}

protected «to.getDomainPackage()».«to.name» «name.singular()»ForKey(Object key) {
for («to.getDomainPackage()».«to.name» each : get«name.toFirstUpper()»()) {
if (each.getKey().equals(key)) {
return each;
}
}
return null;
}
«ENDDEFINE»

I was testing this using the Library sample in Sculptor. I noticed problem with extended objects, such as Book, Movie that extends Media. The getKey method is not defined in Media. However, I just ignore this for now, since we don't have that kind of association in our model, and the intention is not to develop a general purpose solution.

Not completely done yet. We have the classical case with circular references. To avoid infinite recursion I added a collection that was passed as parameter to keep track of which objects that have been processed.

All this took me 2 hours to implement, probably much less than implementing it manually in all domain objects. The big benefit is that it is much less risk of manual faults and requires zero maintenance when making changes to the domain objects.

The final template file below, in case you are interested in implementing something similar:

«IMPORT sculptormetamodel»
«EXTENSION extensions::helper»
«EXTENSION extensions::dbhelper»
«EXTENSION extensions::properties»


«DEFINE deepMerge FOR DomainObject»
«IF !isImmutable()»
«EXPAND deepMergeMethod»

«EXPAND deepMergeOneReference FOREACH references.select(r | !r.many).reject(e | !e.changeable)»
«EXPAND deepMergeManyReference FOREACH references.select(r | r.many)»
«ENDIF»
«ENDDEFINE»

«DEFINE deepMergeMethod FOR DomainObject»
public void deepMerge(«getDomainPackage()».«name» other) {
java.util.Set<Object> processed = new java.util.HashSet<Object>();
deepMerge(other, processed);
}

public void deepMerge(«getDomainPackage()».«name» other, java.util.Set<Object> processed) {
if (processed.contains(this)) {
return;
}
processed.add(this);

«EXPAND deepMergeAttribute FOREACH attributes.reject(e | !e.changeable || e.isSystemAttribute())»

«FOREACH references.reject(e | !e.changeable) AS ref»
deepMerge«ref.name.toFirstUpper()»(other, processed);
«ENDFOREACH»
}
«ENDDEFINE»



«DEFINE deepMergeAttribute FOR Attribute»
«IF isPrimitive() -»
set«name.toFirstUpper()»(other.«getGetAccessor()»());
«ELSE-»
if (other.«getGetAccessor()»() != null) {
set«name.toFirstUpper()»(other.«getGetAccessor()»());
}
«ENDIF-»
«ENDDEFINE»


«DEFINE deepMergeOneReference FOR Reference»
public void deepMerge«name.toFirstUpper()»(«from.getDomainPackage()».«from.name» other, java.util.Set<Object> processed) {
if (other.get«name.toFirstUpper()»() != null) {
«IF to.isImmutable()»
if (!other.get«name.toFirstUpper()»().equals(get«name.toFirstUpper()»())) {
set«name.toFirstUpper()»(other.get«name.toFirstUpper()»());
}
«ELSE»
«to.getDomainPackage()».«to.name» currentValue = get«name.toFirstUpper()»();
if (currentValue == null) {
set«name.toFirstUpper()»(other.get«name.toFirstUpper()»());
} else {
currentValue.deepMerge(other.get«name.toFirstUpper()»(), processed);
}
«ENDIF»
}
}
«ENDDEFINE»

«DEFINE deepMergeManyReference FOR Reference»
public void deepMerge«name.toFirstUpper()»(«from.getDomainPackage()».«from.name» other, java.util.Set<Object> processed) {
for («getTypeName()» each : other.get«name.toFirstUpper()»()) {
if (get«name.toFirstUpper()»().contains(each)) {
«IF to.isImmutable()»
// replace
remove«name.toFirstUpper().singular()»(each);
add«name.toFirstUpper().singular()»(each);
«ELSE»
«to.getDomainPackage()».«to.name» currentValue = «name.singular()»ForKey(each.getKey());
currentValue.deepMerge(each, processed);
«ENDIF»
} else {
add«name.toFirstUpper().singular()»(each);
}
}
}

protected «to.getDomainPackage()».«to.name» «name.singular()»ForKey(Object key) {
for («to.getDomainPackage()».«to.name» each : get«name.toFirstUpper()»()) {
if (each.getKey().equals(key)) {
return each;
}
}
return null;
}
«ENDDEFINE»