blog.smart-java.nl
Ordina J-Technologies – Java Blog

Archief ‘Tools/Frameworks’ categorie




Using Unified Expression Language in Maven-Jetty-Plugin

Door: Jan-Kees van Andel, 27 February 2010

I like the maven-jetty-plugin. If I download an Open Source project, I usually first look for this baby, because it allows me to quickly run the code in a tested environment. This saves me from a lot of configuration, which would otherwise cause me to lose interest. This often doesn’t take long… :-)

Also, you can completely configure the server in your POM, centralizing configuration and making it thus easy to store server settings in version control. Also, the mvn-jetty-plugin benefits from your existing Maven project configuration, like dependencyManagement.

Some of my buddies at Apache even use the maven-jetty-plugin on a daily basis for their real work. I never got this far, mostly because I’m more familiar with Tomcat, but also because I didn’t really see it as a mature development tool. However, today I decided to give it a chance.

The first attempt
So, I created a simple webapp in my favorite IDE: IntelliJ IDEA and added a Maven2 POM to enable Maven2 support. All well so far.

This was the initial version of my POM:

All was fine, my test app was running, but I needed to enable Unified EL to test MyFaces BeanValidator.

Adding UEL libraries
So, I added a dependency to the UEL API and Impl in my POM, but there was an issue. Every web container already provides the old Expression Language in the jsp-api.jar. So I’m not allowed to package my own EL libraries.

However, I’m quite stubborn, so I tried anyway:

So, let’s give it a try:

mvn jetty:run-exploded

(MyFaces 2.0 requires exploded deployment in Jetty).

Result? BOOOM!

The fix, replacing libraries
The error is completely appropriate. You’re just not allowed to package your own version of the servlet libraries. That’s the job of the servlet container. Failing to do so will result in the error shown above.

So we need to fix this issue by somehow replacing the Jetty libraries or at least changing the way Jetty loads its jsp-api.jar. This is no trivial task however, since jetty is initialized by Maven and doesn’t have a fixed directory structure on disk.

So we need to have some way in Maven to configure the Jetty libraries.

First, Jetty doesn’t have an endorsed mechanism, so that’s a no-go.

But the fix is actually quite easy, just pass some dependencies into the jetty plugin in the POM.

The final POM looks like this:

As you can see, Maven takes care of the heavy lifting. You only need to specify your dependencies and they will override any dependencies with the same groupId, artifactId and type.

So, now I don’t have any reason not to use mvn-jetty-run to test my code!

Happy coding!




JPA 2.0 … finally final

Door: Peter Schuler, 11 December 2009

Thursday 10 December the people from EclipseLink release version 2.0 of their Object Relation Mapping framework. Besides other improvements this release includes the reference implementation of JPA 2.0.  This means that the JPA 2.0 and JSR-317 are now final!

The second release of the Jave Persistence API adds a lot of new features to the JPA framework. The team responsible for this release now claims to serve 95% of the persistence needs of Java programmers. You can get the final version of JSR-317 or download the reference implementation on the eclipselink website. Two older blogs by Mike Keith introduces all the new features here and here.

Version 2.0 now has (among others) support for:

  • Increased locking possibilities;
  • Support for Criteria Queries and a MetaModel;
  • (A little) support for second level caching;
  • Basic Element and embeddable collections;
  • Orphan removals for sets;
  • Derived Identifiers (using @ManyToOne as past of your primary key);
  • Ordered list.

With these new features the Java Persistence API becomes even more useful. In the next  few weeks I will blog some more about the new features and go deeper into some of the new features and how to use them to your advantange.

So stay tuned.




Temporale data – deel 4: bitemporale collections

Door: gnoij, 7 December 2009

Het is alweer enige tijd geleden dat ik heb geschreven over temporale patterns. In dit deel wil ik het bitemporale pattern uitbreiden met bitemporale collections. Werd met de bitemporale property nog een enkele property historisch gemaakt, in dit deel maken we een lijst historisch. Dat wil zeggen dat er meerdere elementen van een historische lijst tegelijkertijd geldig kunnen zijn.

De klasse BiTemporalObject uit het derde deel blijft hetzelfde. En naast de BiTemporalProperty voor enkelvoudige historische relaties krijgen we nu ook een klasse BiTemporalCollection voor meervoudige historische relaties.

BiTemporalCollection

public class BiTemporalCollection {
 
    protected List alleHistorischeVersies = new ArrayList();
 
    public BiTemporalCollection() {
        super();
    }
 
    public List getActueleVersies() {
        return getVersieOp(new Date(), new Date());
    }
 
    public List getVersieOp(final Date registratieTijdstip, final Date peilDatum) {
        return (List) CollectionUtils.select(this.alleHistorischeVersies, new Predicate() {
            public boolean evaluate(Object object) {
                return ((BiTemporalObject) object).isGeldigOp(registratieTijdstip, peilDatum);
            }
        });
    }
 
    public void voegActueleVersieToe(BiTemporalObject actueleVersie) {
        if (actueleVersie == null) {
            throw new IllegalArgumentException("actueleVersie is null");
        }
        this.alleHistorischeVersies.add(actueleVersie);
    }
 
    public void wijzigActueleVersie(BiTemporalObject nieuweVersie, BiTemporalObject oudeVersie) {
        if (nieuweVersie == null) {
            throw new IllegalArgumentException("nieuweVersie is null");
        }
        if (oudeVersie == null) {
            throw new IllegalArgumentException("oudeVersie is null");
        }
        beeindigVorigeVersie(nieuweVersie, oudeVersie);
        this.alleHistorischeVersies.add(nieuweVersie);
    }
 
    private void beeindigVorigeVersie(final BiTemporalObject nieuweVersie, final BiTemporalObject oudeVersie) {
 
        BiTemporalObject afTeVoerenVersie = zoekActueleVersie(oudeVersie, nieuweVersie.getIngangsdatum(), nieuweVersie.getOpvoerTijdstip()); 
 
        if (afTeVoerenVersie == null) {
            throw new IllegalStateException("Geen oude actuele versie gevonden!");
        }
 
        beeindigVersie(afTeVoerenVersie, nieuweVersie.getIngangsdatum(), nieuweVersie.getOpvoerTijdstip());
    }
 
    public void beeindig(final BiTemporalObject actueleVersie, final Date einddatum) {
        BiTemporalObject afTeVoerenVersie = zoekActueleVersie(actueleVersie, einddatum, new Date());
        if (afTeVoerenVersie == null) {
            throw new IllegalStateException("Geen oude actuele versie gevonden!");
        }
        beeindigVersie(afTeVoerenVersie, einddatum, new Date());
    }
 
    public void beeindig(final Date einddatum) {
        List afTeVoerenVersies = (List) CollectionUtils.select(this.alleHistorischeVersies, new Predicate() {
            public boolean evaluate(Object object) {
                BiTemporalObject versie = (BiTemporalObject) object;
                return versie.isGeldigOp(new Date(), einddatum);
            }
        });
 
        CollectionUtils.forAllDo(afTeVoerenVersies, new Closure() {
            public void execute(Object input) {
                beeindigVersie((BiTemporalObject) input, einddatum, new Date());
            }
        });
    }
 
    private void beeindigVersie(BiTemporalObject versie, Date einddatum, Date registratieTijdstip) {
        BiTemporalObject kopieVersie = versie.kopieer(einddatum, registratieTijdstip);
        this.alleHistorischeVersies.add(kopieVersie);
    }
 
    private BiTemporalObject zoekActueleVersie(final BiTemporalObject actueleVersie, final Date peildatum, final Date registratieTijdstip) {
        return (BiTemporalObject) CollectionUtils.find(this.alleHistorischeVersies, new Predicate() {
            public boolean evaluate(Object object) {
                BiTemporalObject versie = (BiTemporalObject) object;
                return versie.equals(actueleVersie) && versie.isGeldigOp(registratieTijdstip, peildatum);
            }
        });
    }

De wijzigingen ten opzichte van de BiTemporalProperty uit het vorige deel zijn dat er nu niet één maar meer versies tegelijkertijd geldig kunnen zijn (getActueleVersies) en dat we versies kunnen toevoegen (voegActueleVersieToe) en kunnen wijzigen (wijzigActueleVersie). Bij deze laatste methode we de te wijzigen versie meegeven, zodat deze beëindigd kan worden en de nieuwe versie toegevoegd wordt. Ook kunnen we alle versies van de collection beëindigen (beeindig) of een enkele versie (die dan weer aan de methode meegegeven moet worden).

Een voorbeeld

In dit voorbeeld wordt het voorbeeld uit deel 3 uitgebreid met een manager (zelf ook een werknemer), die meerdere werknemers onder zich heeft.

Manager

public class Manager extends Werknemer {
 
    private static final long serialVersionUID = 1L;
 
    private BiTemporalCollection werknemers = new BiTemporalCollection();
 
    public Manager() {
        super();
    }
 
    public Manager(String naam) {
        super(naam);
    }
 
    public void voegWerknemerToe(Werknemer werknemer, Date ingangsdatum) {
        this.werknemers.voegActueleVersieToe(new WerknemerRelatie(werknemer, ingangsdatum));
    }
 
    public List getTeManagenWerknemers() {
        List relaties = getWerknemerRelaties();
        CollectionUtils.transform(relaties, new Transformer() {
            public Object transform(Object object) {
                return ((WerknemerRelatie)object).getWerknemer();
            }
        }) ;
        return relaties;
    }
 
    public List getTeManagenWerknemers(Date registratieTijdstip, Date peildatum) {
        List relaties = getWerknemerRelaties(registratieTijdstip, peildatum);
        CollectionUtils.transform(relaties, new Transformer() {
            public Object transform(Object object) {
                return ((WerknemerRelatie)object).getWerknemer();
            }
        }) ;
        return relaties;
    }
 
    public List getWerknemerRelaties() {
        return this.werknemers.getActueleVersies();
    }
 
    public List getWerknemerRelaties(Date registratieTijdstip, Date peildatum) {
        return this.werknemers.getVersieOp(registratieTijdstip, peildatum);
    }
 
    public void wijzigWerknemer(Werknemer nieuweWerknemer, Werknemer oudeWerknemer, Date wijzigingsdatum) {
        WerknemerRelatie nieuweRelatie = new WerknemerRelatie(nieuweWerknemer, wijzigingsdatum);
        WerknemerRelatie oudeRelatie = bepaalWerknemerRelatie(oudeWerknemer);
        this.werknemers.wijzigActueleVersie(nieuweRelatie, oudeRelatie);
    }
 
    private WerknemerRelatie bepaalWerknemerRelatie(final Werknemer werknemer) {
        return (WerknemerRelatie)CollectionUtils.find(this.werknemers.getActueleVersies(), new Predicate() {
            public boolean evaluate(Object obj) {
                return ((WerknemerRelatie)obj).getWerknemer().equals(werknemer);
            }
        });
    }
 
    public void stopManagenVanWerknemer(Werknemer werknemer, Date einddatum) {
        WerknemerRelatie relatie = bepaalWerknemerRelatie(werknemer);
        this.werknemers.beeindig(relatie, einddatum);
    }
 
    public void stopManagen(Date einddatum) {
        this.werknemers.beeindig(einddatum);
    }
}

Deze klasse representeert de manager. Als een manager een werknemer gaat managen, wordt de werknemer toegevoegd aan de lijst van te managen werknemers (voegWerknemerToe). Als een werknemer wordt vervangen door een andere werknemer wordt de methode wijzigWerknemer aangeroepen, waardoor de relatie met de oude werknemer wordt beëindigd en de nieuwe werknemer wordt begonnen. Als een werknemer uit dienst gaat of naar een andere afdeling, stopt de manager met het managen van deze werknemer (stopManagenVanWerknemer), waardoor de historische relatie van de manager met de werknemer wordt beëindigd. Als de manager stopt met managen (stopManagen), wordt de relatie met alle gemanagede werknemers beëindigd.

De historische relatie van de manager met de werknemer wordt gerepresenteerd door de klasse WerknemerRelatie.

WerknemerRelatie

public class WerknemerRelatie extends BiTemporalObject {
 
    private static final long serialVersionUID = 1L;
 
    private Werknemer werknemer;
 
    protected WerknemerRelatie() {
        super();
    }
 
    protected WerknemerRelatie(Werknemer werknemer, Date ingangsdatum) {
        super(ingangsdatum);
 
        this.werknemer= werknemer;
    }
 
    public Werknemer getWerknemer() {
        return this.werknemer ;
    }
}

De test ziet er als volgt uit:

public void testManager() {
 
	Manager jan = new Manager("Jan");
 
	// simuleer de opvoer tijd 1-1-2003
	Date aanvangsdatum = DateUtils.maakDate(2003, 1, 1);
	BiTemporalObject.TEST_OPVOER_TIJDSTIP = aanvangsdatum; 
 
	Afdeling inkoop = new Afdeling("Inkoop");
	jan.setAfdeling(inkoop, aanvangsdatum);
 
	Werknemer kees = new Werknemer("Kees");
	kees.setAfdeling(inkoop, aanvangsdatum);
	jan.voegWerknemerToe(kees, aanvangsdatum);
 
	Werknemer piet = new Werknemer("Piet");
	piet.setAfdeling(inkoop, aanvangsdatum);
	jan.voegWerknemerToe(piet, aanvangsdatum);
 
	assertEquals(2, jan.getTeManagenWerknemers().size());
	assertTrue(jan.getTeManagenWerknemers().contains(kees));
	assertTrue(jan.getTeManagenWerknemers().contains(piet));
 
	Date wijzigingsdatum = DateUtils.maakDate(2006, 1, 1);
	BiTemporalObject.TEST_OPVOER_TIJDSTIP = wijzigingsdatum; 
 
	Werknemer johan = new Werknemer("Johan");
	johan.setAfdeling(inkoop, wijzigingsdatum);
 
	// Johan vervangt Piet, die uit dienst gaat
	jan.wijzigWerknemer(johan, piet, wijzigingsdatum);
	piet.uitDienst(wijzigingsdatum);
 
	assertEquals(2, jan.getTeManagenWerknemers().size());
	assertTrue(jan.getTeManagenWerknemers().contains(johan));
	assertFalse(jan.getTeManagenWerknemers().contains(piet));
 
	// jan managete gisteren piet nog wel
	Date gisteren = DateUtils.maakDate(2005, 12, 31);
	assertTrue(jan.getTeManagenWerknemers(new Date(), gisteren).contains(piet));
 
	Date einddatum = DateUtils.maakDate(2009, 1, 1);
	BiTemporalObject.TEST_OPVOER_TIJDSTIP = wijzigingsdatum; 
 
	jan.stopManagenVanWerknemer(kees, einddatum);
	kees.uitDienst(einddatum);
 
	assertEquals(1, jan.getTeManagenWerknemers().size());
	assertFalse(jan.getTeManagenWerknemers().contains(kees));
 
	jan.stopManagen(einddatum);
	assertTrue(jan.getTeManagenWerknemers().isEmpty());
}

In deze test maken we een manager Jan aan, die werkt op de afdeling Inkoop op 1 januari 2003. Vanaf deze dag begint hij ook met het managen van twee werknemers Kees en Piet. Vanaf 1 januari 2006 vervangt een nieuwe werknemer Johan Piet, die uit dienst gaat. Op 1 januari 2009 stopt Jan met het managen van al zijn werknemers.

Hibernate mappings

De hibernate mapping van de klasse WerknemerRelatie is analoog aan de mapping van de AfdelingRelatie uit deel 3.

De mapping van de werknemer is uitgebreid met de subclass Manager, die de werknemers als BiTemporalCollection property mapt.

Werknemer.hbm.xml

<hibernate-mapping package="nl.ordina.bitemporal.example" default-access="field">
	<class name="Werknemer" table="Werknemer" discriminator-value="Werknemer">
		<id name="id" column="id">
			<generator class="identity"></generator>
		</id>
		<discriminator column="type" />
		<version name="versie" />
 
		<!-- properties -->
		<property name="naam" column="naam" />
 
		<!-- de verwijzing naar de afdelingrelatie -->
		<component name="afdelingRelatie" 
			class="nl.ordina.temporal.bitemporal.BiTemporalProperty"
			lazy="false">
			<bag name="alleHistorischeVersies"
				cascade="all, delete-orphan" >
				<key column="werknemerId" />
				<one-to-many
					class="AfdelingRelatie" />
			</bag>
		</component>
 
		<subclass name="Manager" discriminator-value="Manager">
		<!-- de verwijzing naar de werknemerrelatie -->
			<component name="werknemers" 
				class="nl.ordina.temporal.bitemporal.BiTemporalCollection"
				lazy="false">
				<bag name="alleHistorischeVersies"
					cascade="all, delete-orphan" >
					<key column="managerId" />
					<one-to-many
						class="WerknemerRelatie" />
				</bag>
			</component>
		</subclass>
	</class>
</hibernate-mapping>

LET OP: In bovenstaand artikel wordt gesproken over BiTemporalCollection. Dit is niet dezelfde als de BiTemporalCollection van Fowler , wat eigenlijk de BiTemporalProperty uit deel 3 is.




Getting on the cloud!

Door: Roy van Rijn, 5 June 2009

Google App Engine

You’ve probably heard people talking before about ‘cloud computing’.
But what exacly is this cloud computing you might ask?

To figure this out I decided to create my own Google App Engine project and find out about cloud computing along the way.

Wikipedia states that cloud computing is:

Cloud computing is a style of computing in which dynamically scalable and often virtualized resources are provided as a service over the Internet. Users need not have knowledge of, expertise in, or control over the technology infrastructure in the “cloud” that supports them”

So, cloud computing doesn’t only have a vague name, the description isn’t very helpful either. But the key ingredients are “scalable” and “virtualized” and “as a service”. And you don’t have control over the infrastructure…

Lets take a look at Google App Engine. It was released in April 2008 as a platform for developing (Python)cloud applications. App Engine hosts these applications virtually on many machines, the programs are distributed and scaled across a vast amount of servers. And now, since the beginning of this year Google App Engine also supports Java!

The concept is simple, you get to build an application, preferably using Google’s App Engine-eclipse-plugin. And the JRE you build on is a slightly stripped-down version to make it usable on a cloud, like a sandbox.

Because you don’t know on what kind of servers your application will run on, or on how many servers, Google has decided you can’t do the following:

  • Start threads
  • Go to the Filesystem, no I/O
  • Open sockets directly, but you can open connections through HTTP/HTTPS
  • Make calls to System (like exit();, gc(); etc)

And there is more. Because of these restrictions you can’t access a database! So you can’t use something like Hibernate and some Oracle/MySQL/Postgress machine. To still be able to save/persist objects Google has teamed up with Datanucleus. Using JDO or (a stripped down version of) JPA you can persist and retrieve objects on the cloud.

With this in mind I started making my own application. And the frameworks I wanted to use are:

  • Wicket (Web Framework)
  • Spring IOC
  • Spring ORM (for transaction management, using annotations)
  • JPA (instead of the default JDO)

The first problems I encountered was getting Wicket to load. Because of the sandbox-restrictions there are a couple of things you can’t do. For example, Wicket can’t save temporary data to disk (what it normally does). And there are problems with Wicket being in ‘development-mode’ where is wants to start Threads to poll for changed resources.

A good overview on what it takes to get Wicket working is explained here:
http://www.danwalmsley.com/2009/04/08/apache-wicket-on-google-app-engine-for-java/

Next up was installing and running Spring. This was relatively easy at first. The core Spring code ran pretty much as expected. I added the JARs to my project and added this to the web.xml:

	<!-- Spring -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationcontext-*.xml</param-value>
	</context-param>
	<listener>
		<listener-class>
			org.springframework.web.context.ContextLoaderListener
		</listener-class>
	</listener>
	<listener>
		<listener-class>
			org.springframework.web.context.request.RequestContextListener
		</listener-class>
	</listener>

As you can see, I load up multiple XML files. I decided to go with the all-out-annotations method using Spring ORM. This proved to be pretty challenging…

With these annotations you are able to do the following in the code:

@Repository("loginDao")
@Transactional
public class LoginDaoImpl implements LoginDao {
 
	@PersistenceContext
	private EntityManager entityManager;
	... (and more)

As you can see I’m using Spring to inject my EntityManager into the DAO. But you can’t just load the entity manager in Google App Engine, you need a specific piece of configuration. I used the following XML:

	<bean id="data.emf"
		class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
		<property name="persistenceUnitName" value="transactions-optional" />
	</bean>

	<bean class="org.springframework.orm.jpa.JpaTemplate">
		<property name="entityManagerFactory" ref="data.emf" />
	</bean>

	<bean id="transactionManager"
		class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="data.emf" />
	</bean>

To tell Spring to scan for these annotations you need to add the following lines in your applicationcontext:

	<context:annotation-config />
	<context:component-scan base-package="nl.redcode.*" />

And now the problems start… The first problem is that Google App Engine doesn’t support all core classes. When loading these annotations Spring will load its PersistenceAnnotationBeanPostProcessor. But it contains the following piece of code:

try {
	return (EntityManagerFactory) lookup(jndiName, EntityManagerFactory.class);
}
catch (NamingException ex) {
	throw new IllegalStateException("Could not obtain 
		EntityManagerFactory [" + jndiName + "]from JNDI", ex);
}

And the Exception we get is:

org.springframework.beans.factory.BeanCreationException: Error creating
bean with name
'org.springframework.context.annotation.internalPersistenceAnnotationProcessor':
Initialization of bean failed; nested exception is
java.lang.NoClassDefFoundError: javax/naming/NamingException
	at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:480)
	at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:409)
	at java.security.AccessController.doPrivileged(Native Method)
	at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
	at
org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264)
	at
...etc

And when I looked at the white-list it was clear, NamingException isn’t part of the sandbox Google App Engine uses. So I started to google how to solve this. The first thing I encoutered was somebody who added the following lines to his/her applicationContext:

	<bean id="org.springframework.context.annotation.internalPersistenceAnnotationProcessor"
		class="java.lang.String" />

This piece of code, when executed before the annotation-scan, loads a String in the Spring Container under the name “internalPersistenceAnnotationProcessor”. This causes Spring to ignore its own instantiation of the PersistenceAnnotationBeanPostProcessor and we don’t get the Exception anymore.

But this causes some more damage we don’t want in the application. Before my Dao’s received a valid EntityManager, but they are Null now…!

So I took the code of the original Spring PersistenceAnnotationBeanPostProcessor and replaced all the instances of NamingException with just Exception. This removed the dependency to NamingException. I called this new bean “AppEngineJPAPostProcessor”. This is how I configured it in the applicationContext:

	<bean id="org.springframework.context.annotation.internalPersistenceAnnotationProcessor"
		class="nl.redcode.springhack.AppEngineJPAPostProcessor" />

The EntityManager(Factory) is now created, it gets injected into the DAO’s, they have transactions using annotations and everybody is happy!

When I got a little further in my project I decided to deploy my application to the cloud and test it online. Deploying your application to App Engine is very simple, just push the “Deploy” button in the Eclipse plugin and you only need to enter your credentials and a version-number of your release!

But then the old BeanPostProcessor bit me in the back again. On the server I got the following Exception when deploying:

java.lang.SecurityException: Unable to get members for class org.springframework.jndi.JndiLocatorSupport
	at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_$10.run(Class_.java:357)
	at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_$10.run(Class_.java:347)
	at java.security.AccessController.doPrivileged(Native Method)
	at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_.getMembers(Class_.java:347)
	at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_.getDeclaredMethods(Class_.java:174)
	at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:460)
	at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:443)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.findAutowiringMetadata(AutowiredAnnotationBeanPostProcessor.java:299)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(AutowiredAnnotationBeanPostProcessor.java:179)

It seems the runtime is a bit more strict then the development server Google App Engine uses. For some reason it doesn’t like the JndiLocatorSupport. This is excepted because Google App Engine, due to the nature of the cloud, prohibits the use of JNDI.

Soon I found the problem, the only reference to JndiLocatorSupport is my own BeanPostProcessor:

/**
 * Rewritten for use in Google AppEngine
 *
 * @author Roy van Rijn
 */
public class AppEngineJPAPostProcessor extends JndiLocatorSupport implements
		InstantiationAwareBeanPostProcessor, BeanFactoryAware {
 
	private Map persistenceUnits;
	...

When I removed the ‘extends’ part there were two pieces of code that stopped working, they both look like this:

	try {
	return (EntityManagerFactory) lookup(jndiName, EntityManagerFactory.class);
}

It seems this PostProcessor always does a JNDI lookup to find the correct EntityManager! But we can’t do this if we don’t have access to the JndiLocatorSupport methods anymore. So I decided to hack a little bit in this code, my solution was to load the EntityManagerFactory and EntityManager from the Spring container:

try {
	return ((JpaTransactionManager)beanFactory
		.getBean("transactionManager"))
		.getEntityManagerFactory();
/*	return (EntityManagerFactory) lookup(jndiName,
		EntityManagerFactory.class);*/
} catch (Exception ex) {
...

This solved all the problems and with the changes to the BeanPostProcessor I’m now able to use all the Spring annotations, for persistency and transactions, in Google AppEngine.

I’m now still in the middle of developing my application on Google App Engine, but it seems that Google App Engine works like a charm. The problem is, most frameworks can’t really cope with the sandbox out-of-the-box. But with (some) minor patches and tweaking most frameworks will run using Google App Engine. The only major problem I’m having (which can’t be solved) is Datanucleus. I chose to use JPA because it is much richer and has more features then JDO, but Datanucleus hasn’t implemented much of these features yet.

For example, I had the following annotation on a field: @Column(unique=true)
Datanucleus threw “java.lang.UnsupportedOperationException: No support for uniqueness
constraints
“.

Also, I created a query with this: “username = :username OR emailAddress = :emailAddress
But Datanucleus doesn’t support the operator “OR”.

Other things DataNucleus can’t currently do:

  • Many-to-many relationships
  • Joins in a query (WHAT??)
  • Aggregation queries (group by, having, sum, avg, max, min)
  • Polymorphic queries. You cannot perform a query of a class to get instances of a subclass. Each class is represented by a separate entity kind in the datastore.

So, we’ve seen what a cloud is, what Google App Engine is, and I explained some tweaks/patches needed to get Wicket and Spring working.

Why use Google App Engine? It is free (up to some CPU/mail/data limits) and your project runs on a cloud, and thus is very scalable. You don’t have to worry about the environment or server. Your application will scale when its needed and everything is pre-installed and ready to run.
But be prepared for difficult classloading issues and missing classes. You just can’t expect all frameworks to be working out-of-the-box with the sandbox-limitations. Also don’t expect much support with persisting, the JPA-support is very minimal and won’t let you do much more then persisting and retrieving single objects.

Enough blogging, now its time for me again to tinker on my application, maybe I’ll tell about it here in the future!




Cyclische dependencies @ runtime

Door: Frank Verbruggen, 16 April 2009

In een gelaagd project zijn er meestal aparte lagen gedefinieerd voor services, domein logica en data access. Stel nu dat er een noodzaak is om vanuit een lager gelegen laag gebruik te maken van functionaliteit die thuis hoort in een hoger gelegen laag. Bijvoorbeeld omdat er in de domein objecten een noodzaak is om gebruik te maken van een service. Hoe zorg je er dan voor dat je nette object georienteerde code houdt, zonder dat je compile time dependencies krijgt tussen je projecten / lagen? Het hier gepresenteerde design pattern is een elegante object georienteerde oplossing voor het bovenstaande probleem.

Gegeven een DomeinObject als object uit de lagere laag, en een ServiceImplementation als object uit de hogere laag. De serviceImplementation heeft een een interface methode ’someMethod()’ uit de ServiceInterface die benodigd is om de methode ‘businessMethod()’ uit het DomeinObject te implementeren (zie figuur 1, originele klasse diagram).

N.B. Het design pattern is uitgewerkt met domein objecten en service implementaties, maar dat is slechts een invulling.

—————————————————————-

—————————————————————-
Figuur 1, originele klasse diagram
—————————————————————-

Los dit als volgt op. Trek de ServiceInterface uit de hogere laag, en zet deze in de lagere laag. Maak een RequiredInterfaceFactory die een instantie van de ServiceInterface kan bevatten. Initialiseer bij het opstarten van je applicatie vanuit de bovenste laag de RequiredInterfaceFactory met de ServiceImplementation (dit kan bijvoorbeeld goed met de Spring configuratie uit Listing 1, Spring configuratie). En implementeer de businessMethod door de aanroep naar de ServiceImplementation als volgt te abstraheren:

RequiredInterfaceFactory.getInstance().getServiceInterface().someMethod();

Het klasse diagram ziet er dan uit zoals weergegeven in Figuur 2, klasse diagram.

—————————————————————-

 

—————————————————————-
Listing 1, Spring configuratie
—————————————————————-

—————————————————————-

—————————————————————-
Figuur 2, klasse diagram
—————————————————————-

Voordelen en nadelen

Het gebruik van dit design pattern biedt de volgende voordelen:

  • Compile time dependencies zijn niet nodig
  • At runtime kunnen interface methoden van bovenliggende klassen toch gebruikt worden
  • Ieder object kan gebruik maken van de interface, ongeacht in welk package het zit, dus het ophalen van de interface kan in de code altijd op dezelfde manier gebeuren

Het gebruik van dit design pattern biedt de volgende nadelen:

  • Je moet de RequiredInterfaceFactory injecteren met de ServiceImplementation voordat de rest van de applicatie aangesproken wordt



Wat is er nieuw aan Model-Driven Development?

Door: Eric Jan Malotaux, 13 February 2009

Toen de Object Management Group (OMG) in 2001 de Model Driven Architecture (MDA) introduceerde, veroorzaakte dat een golf van optimisme onder de relatief weinigen die zich nog met code-generatie bezig hielden. Eindelijk een standaard modelleertaal(UML), en dus uitwisseling van modellen tussen tools van verschillende leveranciers. Inmiddels is het enthousiasme voor het pure MDA vrijwel helemaal weggeëbt. Voornamelijk doordat UML nou niet ideaal bleek om behalve als modelleertaal, tegelijkertijd ook te dienen als basis voor het genereren van code. Te ingewikkeld, te groot, niet precies genoeg. En die standaardisatie bleek ook meer theorie dan praktijk. Om bruikbare code uit UML te kunnen genereren moest er nogal wat aan UML gesleuteld worden, dan deed iedere leverancier weer anders. Dus zeg maar dag tegen overdraagbare modellen!

Maar de laatste twee jaar komen de ideeën in een net even andere gedaante weer terug: nu op basis van Domain-Specific Languages (DSL’s). DSL’s zijn bezig in snel tempo enorm populair te worden. Overal wordt er mee geëxperimenteerd, en de conferenties over MDD schieten als paddestoelen uit de grond.  Hiervoor zijn verschillende oorzaken: (1)  DSL’s zijn veel kleiner en hanteerbaarder dan UML. (2) Het opkomen van tools als Microsoft DSL Tools, openArchitectureWare(oAW), en vooral xText, die het zelf maken van DSL’s binnen het bereik van “gewone” ontwikkelaars hebben gebracht. Grappig genoeg zijn al deze tools uiteindelijk in gebaseerd op één of andere gedaante van de Meta Object Facility (MOF), de kern van de MDA. Dit meta-metamodel standaardiseert het ontwikkelen van nieuwe metamodellen, en daarmee van nieuwe (modelleer)talen.  (3) DSL’s komen als geroepen. De complexiteit van de gemiddelde JEE applicatie neemt zo snel toe dat weinig ontwikkelaars het kunnen bijhouden. Aan de achterkant van een applicatie – de kant het dichtst bij de database – valt het nog wel mee: Hibernate is een blijvertje gebleken, al of niet in de incarnatie als JPA-implementatie. En ook het Spring-framework blijft een rots in de branding. Alhoewel, wat is nou beter: configuratie met XML bestanden of met annotaties? En Spring groeit ook als kool: wat moeten we met OSGi en Spring Integration. Maar aan de voorkant – de kant het dichtst bij de gebruiker – is de grote lijn helemaal zoek. Een jaar of acht geleden was Struts éénoog koning, maar nu moet je voor iedere applicatie kiezen tussen JSF, Wicket, GWT, Spring MVC of Webflow, of toch maar weer een Rich Client. En dan vergeten we voor het gemak even de technologieën die tussendoor nog even populair zijn geweest, zoals XML/XSLT. Het valt niet meer te behappen voor een gewoon mens, die ’s-avonds ook nog wel eens een film wil zien in plaats van een tutorial (als dat er is tenminste) van weer een nieuw framework door te werken.

Model-driven Development op basis van DSL’s belooft een oplossing voor deze chaos. We ontwerpen een DSL voor het domein waarvoor we een applicatie maken, en een generator waar we de complexiteit in kunnen verstoppen. Daarvoor heb je dus nog wel een nerd nodig die al die ontwikkelingen wèl heeft kunnen bijbenen, maar de rest hoeft alleen maar de DSL te leren. En de nerd kan zich helemaal uitleven in de codegenerator, dus iedereen tevreden. Zou je zeggen.

Nou hebben we natuurlijk al vaker nieuwe ontwikkelingen meegemaakt die veel beloofden maar uiteindelijk teleurstelden. Object-oriëntatie, aspect-oriëntatie, agile development, test-driven development, whatever-driven-development, frameworks. Dus zo zal het met MDD ook wel weer gaan.  Toch hebben die nieuwe ontwikkelingen wel blijvende vooruitgang gebracht, alleen minder revolutionair dat we hadden gehoopt. Dus is de vraag, waarin ligt de echte verbetering van MDD, en waarin zal het tegenvallen.

Het ontwerpen van een DSL is moeilijk. Niet alleen de taal zelf, maar ook de benodigde tools om er een beetje lekker mee te kunnen werken. Dus zou het prettig zijn ‘m te kunnen hergebruiken. Daarom wordt vaak de applicatie architectuur als domein gekozen wordt in plaats van het domein van de gebruiker van de applicatie. Bij mod4j (http://www.mod4j.org/projectsite) bijvoorbeeld is dat gedaan. Maar de visie van de OMG – scheiden van pure business functionaliteit van implementatiedetails – wordt maar gedeeltelijk bereikt.  Want we moeten de applicatie specificeren in termen van de applicatie architectuur in plaats van concepten die betekenis hebben voor de klant. We maken wel modellen, maar sinds het verschijnen van tekst-gebaseerde tools als xText kunnen we die net zo goed maken in tekstuele vorm als in diagram vorm.  Wat is dan nog het verschil tussen een model en een programma?  Eigenlijk geen.  Vandaar dat Anneke Kleppe in haar nieuwe boek “Software Language Engineering: Creating Domain-Specific Languages Using Metamodels” de term “mogram” introduceert.

Wat is dan eigenlijk nieuw? Eenvoudig dit: met een DSL werkt een programmeur op een hoger abstractieniveau. Hij heeft met minder concepten te maken dan met puur Java. Dat resulteert in veel compactere code. Bij mod4j bijvoorbeeld worden er per regel DSL-code bijna 25 regels Java- en XML-code gegenereerd. Volgens Frederick Brooke in zijn boek “The Mythical Man Month” is de productiviteit van een programmeur constant in termen van het aantal statements per tijdseenheid. Mod4j is nog niet in de praktijk beproefd, dus wat de winst echt is moet nog blijken. Hoopgevend is het in elk geval wel.




JBoss Envers

Door: jeroen weelink, 30 December 2008

Auditing is een van de belangrijkste onderdelen in ieder systeem. Er zijn al enkele manieren om dit te regelen. Het makkelijkste is een audit log, Gerry Noij is nu bezig met het beschrijven van het temporal object pattern.

JBoss heeft sinds dit jaar een nieuw project erbij: Envers. Annoteer je de classes of properties met @Versioned, dan wordt de rest voor je geregeld.

The Envers project aims to enable easy versioning of persistent classes. All that you have to do is annotate your persistent class or some of its properties, that you want to version, with @Versioned. For each versioned entity, a table will be created, which will hold the history of changes made to the entity. You can then retrieve and query historical data without much effort.

Om een voorbeeld te geven hoe je classes er in het vervolg uitzien, een klein stukje overgenomen van de quickstart.

Class Person:

import org.jboss.versions.Versioned;  

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.Column;  

@Entity
@Versioned // that's the important part
public class Person {
    @Id
    @GeneratedValue
    private int id;  

    private String name;  

    private String surname;  

    @ManyToOne
    private Address address;  

    // add getters, setters, constructors, equals and hashCode here
}

    Class Address:

    @Entity
    @Versioned
    public class Address {
        @Id
        @GeneratedValue
        private int id;  
    
        private String streetName;  
    
        private Integer houseNumber;  
    
        private Integer flatNumber;  
    
        @OneToMany(mappedBy = "address")
        private Set<Person> persons;  
    
        // add getters, setters, constructors, equals and hashCode here
    }

    Daarnaast moeten er 6 listeners worden geregistreerd. Als je het schema laat genereren, worden er 2 extra tabellen aangemaakt: address_versions en person_versions en deze worden automatisch gevuld met historische data. Het ophalen van een specifieke versie gebeurt met een VersionReader:

    VersionsReader reader = VersionsReaderFactory.get(entityManager);
    Person oldPerson = reader.find(Person.class, personId, revision); 

    Het is mij net bekend geworden dat dit bestaat, ik heb zelf geen ervaring met Envers. Misschien dat anderen dat wel hebben?




    Temporale data – deel 3: het bitemporale pattern

    Door: gnoij, 22 December 2008

    In dit derde deel van de serie over temporale data breidt ik het temporale pattern uit het tweede deel uit met de registratietijd voor opvoer en afvoer. Hierdoor ontstaat het bitemporale pattern.

    BiTemporalObject

    public abstract class BiTemporalObject implements Cloneable {
     
        private Date ingangsdatum;
     
        private Date einddatum;
     
        private Date opvoerTijdstip;
     
        private Date afvoerTijdstip;
     
        protected BiTemporalObject(Date ingangsdatum) {
            super();
     
            if (ingangsdatum == null) {
                throw new IllegalArgumentException("Ingangsdatum is null");
            }
     
            this.ingangsdatum = ingangsdatum;
     
            this.opvoerTijdstip = new Date();
        }
     
        public boolean isGeldigOp(Date registratieTijdstip, Date peilDatum) {
            return isGeregistreerdOp(registratieTijdstip) && isGeldigOp(peilDatum);
        }
     
        private boolean isGeregistreerdOp(Date registratieTijdstip) {
            return !isAfgevoerd(registratieTijdstip)
                    && (registratieTijdstip.after(this.opvoerTijdstip) || registratieTijdstip.equals(this.opvoerTijdstip));
        }
     
        private boolean isAfgevoerd(Date registratieTijdstip) {
            return this.afvoerTijdstip != null && (registratieTijdstip.after(this.afvoerTijdstip) || registratieTijdstip.equals(this.afvoerTijdstip));
        }
     
        private boolean isGeldigOp(Date peilDatum) {
            boolean geldig = peilDatum.after(this.ingangsdatum) || peilDatum.equals(this.ingangsdatum);
            if (this.einddatum != null) {
                geldig = geldig && peilDatum.before(this.einddatum);
            }
            return geldig;
        }
     
        protected BiTemporalObject kopieer(Date wijzigingsdatum, Date registratieTijdstip) {
            // maak een kopie met gevulde einddatum
            BiTemporalObject versie = (BiTemporalObject) super.kopieer();
            versie.opvoerTijdstip = registratieTijdstip;
            versie.afvoerTijdstip = null;
            versie.einddatum = wijzigingsdatum;
     
            // voer de oude versie af
            this.afvoerTijdstip = registratieTijdstip;
     
            return versie;
        }
     
        public Date getIngangsdatum() {
            return this.ingangsdatum;
        }
     
        public Date getEinddatum() {
            return this.einddatum;
        }
     
        public Date getOpvoerTijdstip() {
            return this.opvoerTijdstip;
        }
     
        public Date getAfvoerTijdstip() {
            return this.afvoerTijdstip;
        }

    In deze klasse vallen (ten opzichte van TemporalObject) een aantal dingen op. Allereerst dat deze klasse clonable is, verder dat een object pas geldig is als de registratietijd binnen de opvoer en afvoer registratie is en dat er een kopieer methode is, die een kopie (clone) van het object maakt met de huidige opvoertijd en einddatum. Het oorspronkelijke object wordt dan afgevoerd door het vullen van de afvoer registratietijd. Bij creatie van een BiTemporalObject wordt automatisch het opvoertijdstip gevuld met de systeemdatum.

    BiTemporalProperty

    public class BiTemporalProperty {
     
        protected List alleHistorischeVersies = new ArrayList();
     
        public BiTemporalProperty() {
            super();
        }
     
        public BiTemporalObject getActueleVersie() {
            return getVersieOp(new Date(), new Date());
        }
     
        public BiTemporalObject getVersieOp(final Date registratieTijdstip, final Date peilDatum) {
            return (BiTemporalObject) CollectionUtils.find(this.alleHistorischeVersies, new Predicate() {
                public boolean evaluate(Object object) {
                    return ((BiTemporalObject) object).isGeldigOp(registratieTijdstip, peilDatum);
                }
            });
        }
     
        public void setActueleVersie(BiTemporalObject actueleVersie) {
            if (actueleVersie == null) {
                throw new IllegalArgumentException("actueleVersie is null");
            }
     
            beeindigVorigeVersie(actueleVersie);
            this.alleHistorischeVersies.add(actueleVersie);
        }
     
        private void beeindigVorigeVersie(final BiTemporalObject actueleVersie) {
            BiTemporalObject afTeVoerenVersie = (BiTemporalObject) CollectionUtils.find(this.alleHistorischeVersies, new Predicate() {
                public boolean evaluate(Object object) {
                    BiTemporalObject versie = (BiTemporalObject) object;
                    return versie.isGeldigOp(actueleVersie.getOpvoerTijdstip(), actueleVersie.getIngangsdatum());
                }
            });
     
            if (afTeVoerenVersie != null) {
                BiTemporalObject kopieVersie = afTeVoerenVersie.kopieer(actueleVersie.getIngangsdatum(), actueleVersie.getOpvoerTijdstip());
                this.alleHistorischeVersies.add(kopieVersie);
            }
        }
     
        public void beeindig(Date einddatum) {
            BiTemporalObject teBeeindigenVersie = getActueleVersie();
            if (teBeeindigenVersie != null) {
                BiTemporalObject kopieVersie = teBeeindigenVersie.kopieer(einddatum, new Date());
                this.alleHistorischeVersies.add(kopieVersie);
            }
        }
    }

    Het verschil van de BiTemporalProperty met de TemporalProperty is dat er nu twee tijdstippen worden meegegeven om de juiste versie te bepalen. Naast de peildatum is dat ook het registratietijdstip. Als er een actuele versie wordt toegevoegd of beëindigd, wordt de op dat moment geldende versie gekopieerd en afgevoerd. De gekopieerde versie wordt beëindigd. Hierdoor zal er altijd bij beëindiging of wijziging een extra record ontstaan. Dit wordt verklaard in de concepten voor bitemporale data in deel 1.

    Een voorbeeld

    We gebruiken hier hetzelfde voorbeeld als uit deel 2.

    Werknemer

    public class Werknemer {
     
        private BiTemporalProperty afdelingRelatie = new BiTemporalProperty();
     
        private String naam;
     
        public Werknemer(String naam) {
            super();
     
            this.naam = naam;
        }
     
        public String getNaam() {
            return this.naam;
        }
     
        public void setAfdeling(Afdeling afdeling, Date ingangsdatum) {
            this.afdelingRelatie.setActueleVersie(new AfdelingRelatie(afdeling, ingangsdatum));
        }
     
        public Afdeling getAfdeling() {
            AfdelingRelatie relatie = getAfdelingRelatie();
            if (relatie != null) {
                return relatie.getAfdeling();
            }
            return null;
        }
     
        public Afdeling getAfdeling(Date registratieTijdstip, Date peildatum) {
            AfdelingRelatie relatie = (AfdelingRelatie) this.afdelingRelatie.getVersieOp(registratieTijdstip, peildatum);
            if (relatie != null) {
                return relatie.getAfdeling();
            }
            return null;
        }
     
        public AfdelingRelatie getAfdelingRelatie() {
            return (AfdelingRelatie) this.afdelingRelatie.getActueleVersie();
        }
     
        public AfdelingRelatie getAfdelingRelatie(Date registratieTijdstip, Date peildatum) {
            return (AfdelingRelatie) this.afdelingRelatie.getVersieOp(registratieTijdstip, peildatum);
        }
     
        public void uitDienst(Date datumUitdienst) {
            this.afdelingRelatie.beeindig(datumUitdienst);
        }
    }

    Ten opzichte van de klasse Werknemer uit deel 2 hebben de methoden getAfdeling en getAfdelingRelatie een extra parameter registratieTijdstip.

    AfdelingRelatie

    public class AfdelingRelatie extends BiTemporalObject {
     
        private Afdeling afdeling;
     
        protected AfdelingRelatie(Afdeling afdeling, Date ingangsdatum) {
            super(ingangsdatum);
     
            this.afdeling = afdeling;
        }
     
        public Afdeling getAfdeling() {
            return this.afdeling;
        }
    }

    Deze klasse AfdelingRelatie is afgeleid van de klasse BiTemporalObject. Verder is deze analoog aan de klasse uit deel 2.

    De klasse Afdeling is analoog aan de klasse Afdeling uit deel 2.

    De test ziet er als volgt uit:

        public void testWerknemer() {
            Werknemer werknemer = new Werknemer("Kees");
     
            // simuleer de opvoer tijd 1-1-2003
            BiTemporalObject.TEST_OPVOER_TIJDSTIP = DateUtils.maakDate(2003, 1, 1);
     
            Afdeling inkoop = new Afdeling("Inkoop");
            werknemer.setAfdeling(inkoop, DateUtils.maakDate(2003, 1, 1));
     
            // simuleer de opvoer tijd 1-2-2006
            BiTemporalObject.TEST_OPVOER_TIJDSTIP = DateUtils.maakDate(2006, 2, 1);
     
            Afdeling verkoop = new Afdeling("Verkoop");
            werknemer.setAfdeling(verkoop, DateUtils.maakDate(2006, 1, 1));
     
            assertEquals("Kees", werknemer.getNaam());
            assertEquals("Verkoop", werknemer.getAfdeling().getNaam());
            assertEquals("Verkoop", werknemer.getAfdeling(DateUtils.vandaag(), DateUtils.maakDate(2006, 1, 15)).getNaam());
            assertEquals("Inkoop", werknemer.getAfdeling(DateUtils.maakDate(2006, 1, 15), DateUtils.maakDate(2006, 1, 15)).getNaam());
     
            // simuleer de opvoer tijd 1-2-2008
            BiTemporalObject.TEST_OPVOER_TIJDSTIP = DateUtils.maakDate(2008, 2, 1);
     
            werknemer.uitDienst(DateUtils.maakDate(2008, 1, 1));
            assertNull(werknemer.getAfdeling());
        }

    In deze test maken we een werknemer Kees aan, die begint te werken op de afdeling Inkoop op 1 januari 2003. Dit wordt op dezelfde datum geregistreerd. Vanaf 1 januari 2006 werkt Kees op de afdeling Verkoop, wat pas op 1 februari wordt geregistreerd en vanaf 1 januari 2008 is Kees uit dienst. Dit wordt een maand later pas geregistreerd.

    Omdat de registratietijd automatisch wordt bepaald heb ik (uitsluitend voor testdoeleinden) de klasse BiTemporalObject uitgebreid met een public static member TEST_OPVOER_TIJDSTIP, om de registratietijd uit het voorbeeld te kunnen simuleren. Hierdoor verandert de constructor van BiTemporalObject in

        protected BiTemporalObject(Date ingangsdatum) {
            super();
     
            if (ingangsdatum == null) {
                throw new IllegalArgumentException("Ingangsdatum is null");
            }
     
            this.ingangsdatum = ingangsdatum;
     
            // voor testdoeleinden
            if (TEST_OPVOER_TIJDSTIP == null) {
                this.opvoerTijdstip = new Date();
            } else {
                this.opvoerTijdstip = TEST_OPVOER_TIJDSTIP;
            }
        }

    Hibernate mappings

    De hibernate mapping van de klasse Werknemer en Afdeling zijn analoog aan die van de klasse Werknemer uit deel 2.
    De mapping van de klasse AfdelingRelatie is uitgebreid met de opvoer- en afvoer registratietijd.

    AfdelingRelatie

    <hibernate-mapping package="nl.ordina.bitemporal.example"
    	default-access="field">
    	<class name="AfdelingRelatie" table="AfdelingRelatie">
    		<id name="id" column="id">
    			<generator class="identity"></generator>
    		</id>
    		<version name="versie" />
     
    		<!-- temporale informatie -->
    		<property name="ingangsdatum" type="date" />
    		<property name="einddatum" type="date" />
    		<property name="opvoerTijdstip" type="date" />
    		<property name="afvoerTijdstip" type="date" />
     
    		<!-- de verwijzing naar de afdeling -->
    		<many-to-one name="afdeling" lazy="false" class="Afdeling"
    			column="afdelingId" cascade="none" />
    	</class>
    </hibernate-mapping>

    Het volgende deel zal het bitemporale pattern uitbreiden met historische collections.




    Temporale data – deel 2: het temporale pattern

    Door: gnoij, 7 December 2008

    Zoals al in het eerste deel te zien was is temporale data op te delen in temporale (alleen geldigheid) of bitemporale data (geldigheid met registratietijd). In dit tweede deel beschrijf ik het pattern voor temporale data.

    De kern van dit pattern zijn de Java klassen TemporalObject en TemporalProperty. TemporalObject bevat de logica voor de geldigheid en TemporalProperty bevat de code om een de juiste versie van een property te kunnen bepalen.

    TemporalObject

    public abstract class TemporalObject {
     
        private Date ingangsdatum;
     
        private Date einddatum;
     
        protected TemporalObject(Date ingangsdatum) {
            super();
     
            if (ingangsdatum == null) {
                throw new IllegalArgumentException("Ingangsdatum is verplicht");
            }
     
            this.ingangsdatum = ingangsdatum;
        }
     
        public boolean isGeldigOp(Date peildatum) {
            boolean geldig = peildatum.after(this.ingangsdatum) || peildatum.equals(this.ingangsdatum);
            if (this.einddatum != null) {
                geldig = geldig && peildatum.before(this.einddatum);
            }
            return geldig;
        }
     
        public Date getIngangsdatum() {
            return this.ingangsdatum;
        }
     
        public Date getEinddatum() {
            return this.einddatum;
        }
     
        public void beeindig(Date einddatum) {
            this.einddatum = einddatum;
        }
    }

    Deze klasse is de superklasse van een klasse met geldigheid. Deze klasse bevat de ingangsdatum en einddatum van een versie. Deze klasse bevat alleen een constructor met ingangsdatum, omdat deze verplicht is (anders kunnen we geen historie bijhouden). Verder kent deze klasse de methode om de te vragen of een object geldig is op een bepaalde datum en een methode om een instantie te kunnen beëindigen. Deze klasse is gebaseerd op het Effectivity Pattern van Martin Fowler.

    TemporalProperty

    public class TemporalProperty {
     
        protected List alleHistorischeVersies = new ArrayList();
     
        public TemporalProperty() {
            super();
        }
     
        public TemporalObject getActueleVersie() {
            return getVersieOp(new Date());
        }
     
        public TemporalObject getVersieOp(final Date peildatum) {
            return (TemporalObject) CollectionUtils.find(this.alleHistorischeVersies, new Predicate() {
                public boolean evaluate(Object object) {
                    return ((TemporalObject) object).isGeldigOp(peildatum);
                }
            });
        }
     
        public void setActueleVersie(TemporalObject actueleVersie) {
            if (actueleVersie == null) {
                throw new IllegalArgumentException("actueleVersie is null");
            }
     
            beeindigVorigeVersie(actueleVersie);
            this.alleHistorischeVersies.add(actueleVersie);
        }
     
        private void beeindigVorigeVersie(final TemporalObject actueleVersie) {
            TemporalObject teBeeindigenVersie = (TemporalObject) CollectionUtils.find(this.alleHistorischeVersies, new Predicate() {
                public boolean evaluate(Object object) {
                    TemporalObject versie = (TemporalObject) object;
                    return versie.isGeldigOp(actueleVersie.getIngangsdatum());
                }
            });
     
            if (teBeeindigenVersie != null) {
                teBeeindigenVersie.beeindig(actueleVersie.getIngangsdatum());
            }
        }
     
        public void beeindig(Date einddatum) {
            TemporalObject teBeeindigenVersie = getActueleVersie();
            if (teBeeindigenVersie != null) {
                teBeeindigenVersie.beeindig(einddatum);
            }
        }
    }

    Deze klasse wordt gebruikt om alle historische versies van een property bij te houden. Deze klasse kent methoden om de actuele versie op te vragen, om de versie die op een bepaalde datum geldig is op te vragen en om een nieuwe actuele versie toe te voegen of te beëindigen. Als er een nieuwe actuele versie wordt toegevoegd, wordt de op dat moment geldende versie beëindigd. Deze klasse is gebaseerd op de TemporalProperty Pattern van Martin Fowler.

    Let op: Fowler noemt deze klasse TemporalCollection. Deze naam gebruik ik voor de lijst properties met historie, die ik in deel 4 zal behandelen.

    Voorbeeld

    Om de werking van bovenstaande klassen uit te leggen gebruiken we het voorbeeld uit deel 1.

    Kees werkte vanaf 1-1-2003 op de afdeling Inkoop. Vanaf 1-1-2006 werkt hij op de afdeling Verkoop. Deze overgang werd op 1-2-2006 geregistreerd in het systeem.

    We hebben hier te maken met de klassen Werknemer en Afdeling. Omdat de afdeling bij meerdere werknemers voor kan komen gebruiken we een AfdelingRelatie klasse om de geldigheid van een afdeling bij een werknemer te registreren.

    Het klasse model ziet er als volgt uit:

    Werknemer

    public class Werknemer {
     
        private TemporalProperty afdelingRelatie = new TemporalProperty();
     
        private String naam;
     
        public Werknemer(String naam) {
            super();
     
            this.naam = naam;
        }
     
        public String getNaam() {
            return this.naam;
        }
     
        public void setAfdeling(Afdeling afdeling, Date ingangsdatum) {
            this.afdelingRelatie.setActueleVersie(new AfdelingRelatie(afdeling, ingangsdatum));
        }
     
        public Afdeling getAfdeling() {
            AfdelingRelatie relatie = getAfdelingRelatie();
            if (relatie != null) {
                return relatie.getAfdeling();
            }
            return null;
        }
     
        public Afdeling getAfdeling(Date peildatum) {
            AfdelingRelatie relatie = (AfdelingRelatie) this.afdelingRelatie.getVersieOp(peildatum);
            if (relatie != null) {
                return relatie.getAfdeling();
            }
            return null;
        }
     
        public AfdelingRelatie getAfdelingRelatie() {
            return (AfdelingRelatie) this.afdelingRelatie.getActueleVersie();
        }
     
        public AfdelingRelatie getAfdelingRelatie(Date peildatum) {
            return (AfdelingRelatie) this.afdelingRelatie.getVersieOp(peildatum);
        }
     
        public void uitDienst(Date datumUitdienst) {
            this.afdelingRelatie.beeindig(datumUitdienst);
        }
    }

    De klasse Werknemer bevat een temporal property afdelingRelatie die alle historische versies van de afdelingen van de werknemer bevat. Verder bevat deze klasse de public methoden om de huidige afdeling op te vragen, de afdeling waar de werknemer werkte op een bepaalde datum op te vragen en om een nieuwe afdeling te koppelen aan de werknemer. Het opvragen en koppelen van de afdeling verloopt via de relatieklasse AfdelingRelatie, die de historie van een enkele versie bijhoudt. Als een werknemer uit dienst gaat, wordt de actuele versie van de relatie beëindigd.

    AfdelingRelatie

    public class AfdelingRelatie extends TemporalObject {
     
        private Afdeling afdeling;
     
        protected AfdelingRelatie(Afdeling afdeling, Date ingangsdatum) {
            super(ingangsdatum);
     
            this.afdeling = afdeling;
        }
     
        public Afdeling getAfdeling() {
            return this.afdeling;
        }

    De klasse AfdelingRelatie is afgeleid van de klasse TemporalObject omdat deze klasse de geldigheid bevat van de relatie tussen de werknemer en de afdeling. Omdat een temporal object immutable is, kan er alleen maar een nieuwe versie via de constructor worden aangemaakt met een ingangsdatum die de datum voorstelt waarop de werknemer op de nieuwe afdeling komt te werken.

    Afdeling

    public class Afdeling {
     
        private String naam;
     
        public Afdeling(String naam) {
            super();
            this.naam = naam;
        }
     
        public String getNaam() {
            return this.naam;
        }
    }

    De klasse Afdeling is een eenvoudige klasse die voor ons voorbeeld alleen de naam van de afdeling bevat.

    Het gebruik van de klassen en methoden volgt uit de volgende unit test.

    public void testWerknemer() {
    		Werknemer werknemer = new Werknemer("Kees");
     
    		Afdeling afdeling = new Afdeling("Inkoop");
    		werknemer.setAfdeling(afdeling, DateUtils.maakDate(2003, 1, 1));
     
    		afdeling = new Afdeling("Verkoop");
    		werknemer.setAfdeling(afdeling, DateUtils.maakDate(2006, 1, 1));
     
    		assertEquals("Kees", werknemer.getNaam());
    		assertEquals("Verkoop", werknemer.getAfdeling().getNaam());
    		assertEquals("Verkoop", werknemer.getAfdeling(DateUtils.maakDate(2006, 1, 15)).getNaam());
            	assertEquals("Inkoop", werknemer.getAfdeling(DateUtils.maakDate(2004, 1, 1)).getNaam());
     
    		werknemer.uitDienst(DateUtils.maakDate(2008,1,1));
            	assertNull(werknemer.getAfdeling());
     
    }

    In deze test maken we een werknemer Kees aan, die begint te werken op de afdeling Inkoop op 1 januari 2003. Vanaf 1 januari 2006 werkt Kees op de afdeling Verkoop en vanaf 1 januari 2008 is Kees uit dienst.

    Hibernate mappings

    Tenslotte wil ik nog de Hibernate mappings voor de klassen Werknemer en AfdelingRelatie tonen om te laten zien hoe dit in zijn werk gaat.

    Werknemer.hbm.xml

    <hibernate-mapping package="nl.ordina.temporal.example" default-access="field">
    	<class name="Werknemer" table="Werknemer">
     
    		... 
    		<property name="naam" column="naam" />
     
    		<!-- de verwijzing naar de afdelingrelatie -->
    		<component name="afdelingRelatie" 
    			class="nl.ordina.temporal.singletemporal.TemporalProperty"
    			lazy="false">
    			<bag name="alleHistorischeVersies"
    				cascade="all, delete-orphan" lazy="false">
    				<key column="werknemerId" />
    				<one-to-many class="AfdelingRelatie" />
    			</bag>
    		</component>
     
    	</class>
    </hibernate-mapping>

    De temporal property afdelingRelatie wordt als een component opgenomen in de mapping van Werknemer. Hierbinnen wordt de lijst alleHistorischeRelaties gemapt als een bag.

    AfdelingRelatie.hbm.xml

    <hibernate-mapping package="nl.ordina.temporal.example" 	default-access="field">
    	<class name="AfdelingRelatie" table="AfdelingRelatie">
     		...
     
    		<!-- temporale informatie -->
    		<property name="ingangsdatum" type="date" />
    		<property name="einddatum" type="date" />
     
    		<!-- de verwijzing naar de afdeling -->
    		<many-to-one name="afdeling" lazy="false" class="Afdeling" column="afdelingId" cascade="none" />
    	</class>
    </hibernate-mapping>

    De mapping van de AfdelingRelatie bevat de properties ingangsdatum en einddatum en een many-to-one relatie met de klasse Afdeling, die hier verder niet getoond wordt.

    In het volgende deel zal het bitemporale pattern getoond worden. Hierbij zal het voorbeeld worden uitgebreid met registratiegegevens.




    Temporale data – deel 1: de concepten

    Door: gnoij, 19 November 2008

    In een serie van 4 artikelen wil ik een framework voor gebruik van temporale data presenteren. In dit eerste deel wordt uitgelegd wat temporale data is en wat de consequenties zijn. In de volgende delen wordt het framework in een aantal stappen gepresenteerd.

    Een korte introductie in temporale data

    Temporale data is data waarbij tijdsaspecten een rol spelen.

    Ter illustratie even een voorbeeld van een simpel HRM systeem:

    Kees werkte vanaf 1-1-2003 op de afdeling Inkoop. Vanaf 1-1-2006 werkt hij op de afdeling Verkoop. Deze overgang werd op 1-2-2006 geregistreerd in het systeem.

    Schematisch ziet dat er als volgt uit:

    HRM voorbeeld

    Uit bovenstaand voorbeeld blijkt dat de tijdsaspecten die een rol kunnen spelen zijn: een ingangsdatum, een einddatum en een registratiedatum. Welke tijdaspecten voor de te ontwikkelen applicatie een rol spelen hangt af van de vragen die de applicatie moet kunnen beantwoorden.

    De mogelijke vragen met betrekking tot de tijdsaspecten in bovenstaand voorbeeld zijn:

    1. Waar werkt Kees nu?
    2. Sinds wanneer werkt Kees op de afdeling Verkoop?
    3. Gedurende welke periode werkte Kees op de afdeling Inkoop?
    4. Op welke afdeling werkte Kees op 15-1-2006 als we keken in het systeem op 15-1-2006?

    Om elke vraag te kunnen beantwoorden heb je andere tijdsaspecten nodig.

    Vraag 1 kun je beantwoorden zonder extra tijdsgegevens op te slaan.Vraag 2 kun je beantwoorden door een ingangsdatum te gebruiken.Voor vraag 3 heb je een ingangsdatum en een einddatum nodig en voor vraag 4 heb je naast de ingangs- en einddatum ook registratiedata nodig.

    Om de vragen 2, 3 en 4 ook voor het verleden te kunnen beantwoorden moet er ook historie van de data worden bijgehouden. Dat betekent dat elke wijziging leidt tot een nieuw record in de database.

    De historievormen met geldigheid (een ingangsdatum en een einddatum) noemen we temporale data en met geldigheid en registratiedata bitemporale data.

    temporale data

    Bij temporale data hebben we te maken met de tijdsaspecten ingangsdatum en einddatum. Willen we ook historie gaan bijhouden, moeten de wijzigingen op een temporal object leiden tot een nieuw record in de database. Dat betekent dat een object in principe immutable is. Wijzigingen op een object leiden altijd tot een nieuw object.

    In principe onderscheiden we drie situaties: inschrijving, wijziging en beëindiging van de data.

    Bij inschrijving wordt er een record aan een tabel toegevoegd in de database toegevoegd. Voor ons voorbeeld ziet dat er als volgt uit:

    temporal insert

    De FK kolom wijst naar de tabel Werknemer met Primary Key 1.

    Voor een wijziging wordt er een nieuw record in de tabel geschreven, en wordt het vorige record afgesloten met een einddatum.

    Tenslotte voor een beëindiging wordt het actuele record afgesloten met een einddatum. Als in het voorbeeld Kees uit dienst gaat op 1-1-2008 krijgen we de volgende records te zien.

    We kunnen nu de vragen 1 tot en met 3 beantwoorden, maar niet vraag 4.

    Antwoorden:

    1. Nergens, want Kees is per 1-1-2008 uit dienst.
    2. Sinds 1-1-2006 werkte Kees op de afdeling Verkoop
    3. Tussen 1-1-2003 en 1-1-2006 werkte Kees op de afdeling Inkoop
    4. Op 15-1-2006 wisten wij dat Kees op de afdeling Verkoop werkte. (Terwijl het systeem dit pas op 1-2-2006 heeft geregistreerd, dus is het antwoord fout. Dit moet Inkoop zijn)

    bitemporale data

    Zoals uit bovenstaande records blijkt kunnen we niet achterhalen wanneer iets is geregistreerd. Of wanneer de organisatie wist wanneer een wijziging is doorgevoerd. In sommige (vooral juridische) situaties is dit wel nodig. Hiervoor moeten we de geldigheid uitbreiden met registratiedata voor opvoer en afvoer. Dit noemen we bitemporale data

    We kunnen dezelfde drie situaties als bij temporale data onderscheiden: inschrijving, wijziging en beëindiging.

    Bij inschrijving wordt er een record aan de database toegevoegd. Dit is te zien in onderstaande tabel.

    Hierbij zijn de kolommen Opvoer en Afvoer toegevoegd aan de tabel.

    Bij wijziging wordt het eerste record afgesloten, wordt er een kopie gemaakt met einddatum en wordt de wijziging toegevoegd.

    Per wijziging wordt dus (naast de wijziging) een extra record in de database toegevoegd met de oorspronkelijke informatie plus een afvoerdatum.

    Bij beëindiging wordt een kopie gemaakt van het actuele record, de actuele versie krijgt een afvoerdatum en de kopie krijgt een einddatum.

    Ook voor beëindiging wordt een extra record toegevoegd met de oorspronkelijke informatie plus een afvoerdatum.

    Met het gebruik van zowel geldigheid als registratiedata is ook vraag 4 juiste te beantwoorden. Door te zoeken naar het record dat opgevoerd is voor 15-1-2006 en niet voor die datum is afgevoerd zien we dat het juiste antwoord Inkoop is.

    Mogelijke oplossingen

    Mogelijke oplossingsrichtingen voor de bouw van applicaties met (bi)temporale data zijn:

    Het framework dat ik in de volgende artikelen wil presenteren is gebaseerd op de maatwerkoplossing, waarbij gebruik wordt gemaakt van de Temporal patterns van Fowler.

    Dit framework bestaat uit patterns voor temporale data en bitemporale data. In het volgende artikel zal ik patterns voor temporale data presenteren. Het derde deel zal patterns voor bitemporale data behandelen en in het (voorlopig) laatste deel zullen we deze patterns uitbreiden voor collections.