Skip to content
Taha Ghasemi edited this page May 22, 2014 · 28 revisions

Initializer

Initializer helps you read a set of related objects (object graph) from several xml files or annotated interfaces. The goal is to write these xml files or annotated interfaces with intuitive structure and easy object referencing and left the hard work of reading them to Initializer. The project can be used to initialize (populate) your database with your JPA entities or read a complex configuration file.

The main advantages of this project, over other currently available approaches is in its easy but flexible mechanism for object referencing, unification of xml-based and annotation-based description of object graph, and support of default values.

Initializer is an open-source project published under Apache 2.0 license. Initializer does not depend on other accelerate projects. It is written in Java 7 and can be used in both Java EE and Java SE environments.

## Motivation

Suppose you are using JPA and you want to initialize you database with several records. One approach is to use a sql script that consists of several insert into's. This approach has these drawbacks:

  1. It depends on your sql dialect so it is not portable.
  2. When an entity has an association to another entity, you should state this by using foreign key/primary key which is not intuitive and readable. Also specifying times,dates,.. are not easy.
  3. If you refactor your entity classes, you should change this script too.

Another approach is to construct entity instances manually in the code, and insert them into the database using JPA infrastructure. While this approach solves the above problems, it is too verbose and cumbersome to write such codes.

Initializer offers a third approach. In this approach you specify these entities using xml or annotated interfaces, Initializer loads them into entities and then you can persist them by JPA. The xml-based approach has the 3th drawback in contrast to annotation-based approach but unlike the latter it dose not need developing annotation classes.

## Quick Introduction

As a motivating example, suppose your project has the following three entities: (only important parts are listed)

class Author {
	String name;
}

class Category {
	String name;
	Category parent;
}

class Book {
	String title;
	List<Author> authors;
	Category category;
	int edition;
}

You can configure Initializer to read the following XML file an return the corresponding object graph:

data.xml:

<data>
	<Author name="A1" />
	<Author name="A2" />
	
	<Category name="Computer">
		<Category name="Software">
			<Book title="How to develop" authors="@A1"/>
			<Book title="How to not develop" authors="@A2;@A1" edition="2" />
		</Category>
		<Category name="Hardware">
		</Category>
	</Category>
</data>

To achieve this, the above entities should be annotated as follows:

@InitEntity
class Author {
	@InitKey
	String name;
}

@InitEntity
class Category {
	String name;
	@InitProperty("@parent?")
	Category parent;
}

@InitEntity
class Book {
	String title;
	List<Author> authors;
	@InitProperty("@parent")
	Category category;
	int edition;
}

Then using the Initializer API you can read the data.xml file:

Initializer initializer = new InitializerFactory().entityClasses(Book.class, Author.class, Category.class).create();
Map<String,List<?>> data = initializer.read("data.xml");
System.out.println("Number of authors: " + data.get("Author").size());
Author a1 = initiailzier.getObject("A1", Author.class);

or you may register a listener to receive events:

Initializer initializer = new InitializerFactory().entityClasses(Book.class, Author.class, Category.class).create(new BaseInitListener() {
			@Override
			public void entityCreated(InitEntityMetaData initEntity, Object entityObj) {
				System.out.println("Created " + entityObj);
			}
		});
initializer.read("data.xml");

If Initializer CDI extension is present, it automatically discovers @InitEntity's and fires events. So in CDI environment you can do this to populate your database from data.xml file:

@ApplicationScoped
class DbInitializer {
	@Inject
	Initializer initializer;
	
	@Inject
	EntityManager entityManager;

	public void read() {
		initializer.read("data.xml");
	}
	
	public void onNewEntity(@Observes InitEntityCreated event) {
		entityManager.persist(event.getEntityObject());
	}
}

Instead of annotating the entities you can also use the following XML-based meta data definition file:

metadata.xml:

<metadata>
	<entity class="foo.Author" key="name" />
	<entity class="foo.Category">
		<property name="parent" default="@parent(1)?" />
	</entity>
	<entity class="foo.Book">
		<property name="category" default="@parent(1)" />
	</entity>
</metadata>

In this case, to obtain an instance of InitializerFactory you can do this:

InitializerFactory factory = new InitializerFactory().metadata("metadata.xml");

Instead of reading from an XML file, the same object graph can also be read from the following annotated classes:

@InitData
interface Data {
	interface Authors {
	  @Author
	  interface A1 {}

	  @Author
	  interface A2 {}
	}
	
	@Category(
	interface Computer {
		@Category
		interface Software {
			@Book(title="How to develop", authors=Authors.A1.class)
			interface HowToDev {}

			@Book(title="How to not develop", edition="2", authors={Authors.A2.class,Authors.A1.class})
			interface HowToNotDev {}
		}
		@Category
		interface Hardware {}
		}
	}
}

To achieve this, the following annotation should also be developed:

@Retention(RetentionPolicy.RUNTIME)
@InitAnnotation
@interface Author {
	String name() default "@type.name";
}

@Retention(RetentionPolicy.RUNTIME)
@InitAnnotation
@interface Category {
	String name() default "@type.name";
}

@Retention(RetentionPolicy.RUNTIME)
@InitAnnotation
@interface Book {
	String title();
	Class<?>[] authors();
	String edition() default "";
}

Now we can read it:

initializer.read(Data.class);
Author a1 = initializer.getObject(Data.Authors.A1.class, Author.class);
or
Author a1 = initializer.getObject("Author:A1", Author.class)
## Setup

Add the project as a dependency to your maven pom.xml:

<dependency>
	<groupId>co.pishfa.accelerate</groupId>
	<artifactId>accelerate-initializer</artifactId>
	<version>0.0.1</version>
</dependency>

The main interface is Initializer. To obtain an instance of this interface you should first configure an InitializerFactory. Once InitializerFactory is configured you can call its create methods to obtain an Initializer. In contrast to Initializer which is not thread safe, InitializerFactory is thread safe and can be shared among all threads. InitializerFactory has a fluent interface. The following properties can be configured in InitializerFactory:

Property Default Description
entityClasses List or array of entity classes for annotation processing. It is not required that the provided classes are annotated with InitEnity. If an init entity with the same alias is already exits, it will be overridden. If the parent class of an entity class is annotated with InitEntity, its parent is also processed.(see [Annotation-based Metadata] (#annotation-based-metadata))
metadata An xml-file listing the entity classes. (see [Xml-based Metadata] (#xml-based-metadata))
autoAnchor true Initializer automatically creates an anchor like this for each entity EntityAlias:key1_key2_key3, provided that all key values (which their property names are either specified here or per entity) are not null. (see [Anchors] (#anchors))
incremental false If true, Initializer first tries to find objects by supplying key values (which their property names are either specified here or per entity) to the listener and if it fails, then creates them. (see [Loading and incremental mode] (#loading_incremental))
keyPropertyName Comma separated list of property names that make an instance of this entity unique. Can also be * which means all properties with not null value. This is used in both auto-anchoring and loading of objects. If the specified value is null, it means that the target entity should not participate in auto-anchoring or loading modes.
expressionFactory Auto discover Sets the javax.el.ExpressionFactory for evaluating dynamic expressions.

For example, to obtain an instance of Initializer one can write like this:

Initializer initializer = new InitializerFactory().entityClasses(Author.class,Book.class,Category.class).keyPropertyName("name").create();

The InitializerFactory may be configured with both entity classes or xml metadata:

new InitializerFactory().entityClasses(Author.class).metadata(metadataXml).entityClasses(Book.class,Category.class);
## Metadata

Clearly, Initializer must know about your entity classes to make their instances from provided data. We call this knowledge the metadata. You can define metadata using either annotations or xml files.

### Annotation-based Metadata

You can introduce your entity classes to Initializer by listing them in entityClasses method of factory. By default, your entity classes need not to be annotated with any additional annotations and Initializer builds its metadata using its default settings. But if you want to change some of these defaults, you can leverage the following intializer metadata annotations:

@InitEntity is used to mark an entity class as an init entity. It is recommended (but not required) to annotate all your entity classes with this annotation. This way, instead of manually listing your entities, you can use an annotation processor to find these classes for you. For instance, the initializer CDI extension, finds all entity classes that annotated with @InitEntity and add them to its default factory. This annotation has the following attributes:

Attribute Default Description
alias The class simple name The entity alias name which will be used in the xml to refer to that entity.
properties additional properties for this entity. Can be used to override properties of parent or properties without a field

@InitProperty is used to change default settings of a property of an entity. By default no properties need to be annotated.

Attribute Default Description
name The name of this property in the class. If you place this annotation above the property itself this is not required but if you put this above a class, you should set this too. It can also be the name of special attributes: anchor,in-parent,action.
alias name An alias to be used in xml instead of this property original name.
value Default value for this property. If dynamic is set to true (default), can be an entity reference or dynamic expression. (see [Dynamic Expressions] (#dynamic))
dynamic true should be false, if the value should not be treated as an entity reference or expression language (EL).

@InitKey is used to mark a property or several properties of an entity as its key(s). The combined values of these properties uniquely determines the entity instance. This will be used in auto-anchoring and auto-loading modes.

Lets investigate an example:

class Note {
	String title;
}

class Book extends Note {
	String author;
	int edition;
}

If you build the factory by calling new InitializerFactory().entityClasses(Note.class,Book.class) you can define your data like this:

<data>
	<Note title="my note" />
	<Book author="me" edition="1"/>
</data>

By default the entity alias is its simple class name. Also note that factory does not process the attributes of a parent of an entity class if the parent is not annotated with @InitEntity. So you can not use title when defining your Book. To solve this the Note class must annotated with @InitEntity.

@InitEntity
class Note {
	String title;
}

@InitEntity(alias="book")
class Book extends Note {
	@InitProperty(alias="by")
	String author;
	
	@InitProperty(default="1")
	int edition;
}

Now you can define your data like this:

<data>
	<book title="my book" by="me" />
</data>

If you also wanted to use caption instead of title in your book definition, you can override the parent property like this:

@InitEntity(alias="book",properties={@InitProperty(name="title", alias="caption")})
class Book extends Note {
	...
}
### Xml-based Metadata

In addition of annotation-based metadata, you can define the initializer metadata using one or several xml files. The overall structure of these metadata files should look like this:

<metadata xmlns="http://pishfa.co" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pishfa.co https://raw.github.com/pishfa/accelerate/master/accelerate-initializer/src/main/resources/initializer-metadata.xsd">
	<entity class="foo.Entity1">
	</entity>
	<entity class="bar.Entity2">
		<property name="field1" default="@parent(1)" />
		<property name="field2" dynamic="false" />
	</entity>
</metadata>

So a metadata file consists of one or several entity elements, and in each entity you may define several property elements.

entity attributes:

Attribute Required? Default Description
class Yes The fully qualified name of this entity class.
alias No The class simple name The entity alias name which will be used in the xml.
key No The keyPropertyName specified in the factory The name of property or comma separated name of properties whose value(s) specify a unique instance of this entity. Can be "*" which means all properties with not null value. If the specified value is null, it means that the target entity should not participate in auto-anchoring or loading modes.
inherits No A comma separated alias of other entities that this entity inherits their property definitions.

property attributes:

Attribute Required? Default Description
name Yes The name of this property. It can also be the name of special attributes: anchor,in-parent,action.
alias No name An alias to be used in xml instead of this property original name.
default No Default value for this property. If dynamic is set to true (default), can be an entity reference or dyanmic expressoin. (see [Dynamic Expressions] (#dynamic))
dynamic No true should be false, if the value should not be treated as an entity reference or expression language (EL).

Programatic Metadata

You can manually define metadata, by creating InitEntityMetadata objects and pass them to the factory using add method.

## Defining Your Data Data is a representation of your object graph which conforms to a metadata. You can specify data using xml or annotations. ### Xml-based Data

In data xml, you can define your entity, by using its alias as its element name. This entity definition can be placed freely in the root of xml, or inside a first level element with an arbitrary name which is usually used for grouping, or inside another entity definition.

<data>
	<!-- in the root -->
	<Book title="b1" />
	
	<Books>
		<!-- inside a first level grouping element -->
		<Book title="b2" />
	</Books>
	
	<Category>
		<!-- inside another entity definition -->
		<Book title="b3" />
	</Category>
</data>

To define a simple property of an entity you can use an attribute with its name equal to the property alias. For the value, you can use either a simple value or or a dynamic expression.

<data>
	<!-- Simple value for title-->
	<Book title="t1" />
	<!-- Dynamic value for edition -->
	<Book edition="#{2+3}" />
</data>

Instead of using attributes you can use nested elements:

(Not implemented yet)
<data>
	<!-- Simple value for title-->
	<Book>
		<title>t1</title>
	</Book>
	<!-- Dynamic value for edition -->
	<Book>
		<edition>#{2+3}</edition>
	</Book>
</data>

If the property is a list, you can define multiple values by separating the values by ;:

enum Tag {
	A, B, C, D;
}

class Book {
	List<Tag> tags;
}
<data>
	<Book tags="A;C;D" />
</data>

If the property is an association to another entity, you can use entity references, nested elements, or the in-parent attribute:

<data>
	<Author name="a1" />
	<!-- Entity reference using anchors -->
	<Book author="@a1" />

	<Author name="a1">
		<!-- Entity reference using stack -->
		<Book author="@parent" />
	</Author>
	
	<!-- In parent -->
	<Book>
		<Author name="a1" _in-parent_="author" />
	</Book>
	
	<!-- Nested (Not implemented yet) -->
	<Book>
		<author>
			<Author name="a1" />
		</author>
	</Book>
	
	<!-- Child anchor (Not implemented yet) -->
	<Book author="@child">
		<Author name="a1" />
	</Book>
</data>

If the property is a composite entity, which is instantiated when the owning entity instantiated, you can either use the dot notation or nested elements:

class Address {
	String city;
	String country;
}

class Author {
	Address address = new Address():
}
<data>
	<!-- Dot notation -->
	<Author address.city="c1" address.country="c2" />

	<!-- One level, nested element -->
	<Author>
		<address city="c1" country="c2" />
	</Author>

	<!-- Two levels, nested elements (Not implemented yet) -->
	<Author>
		<address>
			<city>c1</city>
			<country>c2</country>
		</address>
	</Author>
</data>

If the property is a list of other entities you can use entity references or nested elements:

class Book {
	List<Author> authors;
}
<data>
	<Author name="a1" />
	<Author name="a2" />
	
	<!-- Multiple anchors -->
	<Book authors="@a1;@a2" />
	
	<!-- Using In-parent -->
	<Book>
		<Author name="a1" _in-parent_="authors"/>
		<Author name="a1" _in-parent_="authors"/>
	</Book>
	
	<!-- Nested (Not implemented yet) -->
	<Book>
		<authors>
			<Author name="a1" />
			<Author name="a2" />
		</authors>
	</Book>
</data>

A default value can be set for in-parent attribute so you don't need to repeat it:

@InitEntity(properties=@InitProperty(name=Initializer.IN_PARENT, value="authors?")
class Book {
	List<Author> authors;
}
<data>
	<Book>
		<Author name="a1" />
		<Author name="a1" />
	</Book>
</data>

The ? mark in authors? means the parent for Author is optional so only set the in-parent attribute if Author has a parent.

### Annotation-based Data ### Entity Referencing When specifying a value or default value of a property, there are two ways to reference other entities in the object graph: either by using anchors or by referring to entities according to their relative positions in the tree with the current entity. entity references start with "@". The possible values are listed in the table below:
Expression Description
null Returns null
@anchor_name Refers to an entity with an anchor named 'anchor_name'. If no anchor with such name found, it tries to find an entity with anchor named alias:anchor_name, where alias is the alias of the init entity corresponding to the current property type (if any). If no object is found, it throws an exception.
@anchor_name? Same as above, but if no anchor is found, it returns null.
@parent(i) Specifies the i'th parent in the stack. @parent(0) means this entity itself. @parent(1) means the first entity that this entity is contained in it (either in xml or in annotated interfaces). An so on. If no parent at the specified depth is found, it throws an exception.
@parent(i)? Same as above, but if the parent at specific depth is not available it returns null.
@parent Finds the first parent in the stack that matches the current property type. If no such parent is found, it throws an exception.
@parent? Same as above, but if an appropriate parent is not found, it returns null.

An anchor is a name attached to an entity. One can use this name to refer to it later. You can specify an anchors for an entity directly using anchor attribute:

<Author _anchor_="jack" name="jack" />
<Book title="My book" author="@jack" />

Anchor names can not start with these reserved words: parent and child. Two entities (even from two different types) can not have the same anchor. So an anchor globally identifies an entity. To avoid conflicts, it is a good practice to start the anchor names with the alias of that entity e.g.:

<Author _anchor_="Author:jack" name="jack" />
<Book title="My book" author="@Author:jack" />

Since Initializer knows the author property of Book is of type Author, it tries to automatically guesses the prefix. This future is called auto scoping. Hence the below code is also valid:

<Author _anchor_="Author:jack" name="jack" />
<Book title="My book" author="@jack" />

Note that if the type of author in the book were a superclass class of Author, e.g. Person, the above code, will not work, since with auto scoping it generates an anchor with name Person:jack which does not exists. So auto scoping is useful when the type of property is exactly the same. You may also define your anchor names relative by starting its name with ':'. In this case, initializer, automatically adds the entity alias to anchor itself:

<Author _anchor_=":jack" name="jack" />
<Book title="My book" author="@jack" />

Unless you turned it off, Initializer has the auto anchoring capability which automatically generates anchors for entities. Auto anchoring works if you specify a unifying property (or properties) for an entity. This can be done at both entity level, or globally in the factory. The anchor that is generated looks like this: entity alias:key property 1_key property 2... So e.g. if name is a unifying property of Author, the auto anchor generated for the author is Author:jack and hence the above code can be simplified to:

<Author name="jack" />
<Book title="My book" author="@jack" />

Anchor is automatically generated for an entity when all of these conditions met:

  • You don't directly specify an anchor for that entity
  • Either key or keyPropertyName is specified.
  • The values of key properties are not null.

Note that, due to one-pass processing, only anchors encountered above the current position, can be referenced.

Using Initializer API, you can access entities by their anchors names:

Author jack = initializer.getObject("Author:jack", Author.class);
//using relative name
Author jack = initializer.getObject(":jack", Author.class);
//using auto scoping
Author jack = initializer.getObject("jack", Author.class);
### Dynamic Expressions

You may use dynamic expressions as the values of properties or their default values. Initializer can use the Java standard expression language for dynamic expressions if an appropriate ExpressionFactory is available. By default, InitializerFactory tries to discover the ExpressionFactory using the Java services API. You may set it manually using the expressionFactory method of InitializerFactory. The EL expression can be in the form #{expr} or ${expr}. They can be mixed with non-dynamic expressions (so called templates) e.g. Hello #{expr}! is a valid expression.

In the EL context the following variables are available:

Variable Type Description
parents ArrayStack Stack of parents, so parents.peek(1) is the first parent above the current element
anchors Map<String, Object> Map of anchors
this Map<String, Object> Refers to the properties of the current entity that are set so far (either directly or by their default values)
entity ProcessEntity Contains all information relating to the current entity under process.

So for instance #{parents.peek(1)} equals to @parent(1) and #{anchors.anchor_name} (or #{anchors['anchor_name']) equals to @anchor_name. The EL expressions have the advantage that you have access to the real objects and can write powerful expressions so you may call a method of that object or get one of its properties or concat several values. For example, if you want the title of a book to be its category name you can set its title to #{parents.peek(1).getName()}, assuming the Category class has a method named getName() which returns its name. As another example, if you want the default title of a book be the name of its author prefixed by word "by" you can set its default value to by #{this.author.name} The important note about "this" variable is that it is a running map of those properties that get their values so far during the processing of an entity so it is not generally safe to use it when specifying a property value. In contrast, since default values are evaluated after those properties that are directly set, using "this" in default values are safe.

You can also pass your own objects into the EL context. You can do this, either when creating the Initializer:

Map<String,Object> vars = new HashMap<>();
vars.put("myName", "jack");
Initializer initializer = factory.create(listener, vars);

or by defining the variables section in your xml data file:

<data>
	<variables>
		<variable name="myName" value="jack" />
	</variables>
	...
</data>

In the latter case, the value can also be an EL itself (e.g. value="Hello #{1+2}") and you can use the variables defined during the creation of Initializer.

Now setting the author name to #{myName} it will be evaluated to "jack" at runtime.

If your property value contains an EL expression itself and you don't want initializer to evaluate it, you can set the "dynamic" attribute of that property to false or escape the EL using '' character.

### Loading and Incremental Mode

Incremental mode is useful when you want to gradually complete your data. For example, when using initializer to populate a database, you might begin with a base initialization data. Since your database is empty, initializer (whether it is in incremental mode or not) creates all your entities. Later, you might need to add some new entities or modify some of the existing entities. Without the incremental mode, you either should drop the database and reinitialize it or add/modify the entities manually in the database. In the incremental mode, for each entity, initializer first tries to load the entity from the database and if it fails it then creates a new one. Since initializer uses the key property of entities to find them, the incremental mode is only useful when all your entities have a valid key property or else those without a key property will be recreated in each run of initializer. Not that nothing will be deleted from the database. To enable the incremental mode, you should set the incremental property of factory during its build. Next, you should property implement the findEntity method of your InitListener to find the entity with the provided key value in the database.

## Logging and Exception Handling

Table of contents

[Metadata] (#metadata)

  • [Annotation-based Metadata] (#annotation-based-metadata)
  • [Xml-based Metadata] (#xml-based-metadata)

[Data] (#data)

  • [Xml-based Data] (#xml-based-data)

  • [Annotation-based Data] (#Annotation-based Data)

  • [Entity Referencing] (#entity_ref)

  • [Dynamic Expressions] (#dynamic)

  • [Loading and Incremental Mode] (#loading_incremental)

  • [Logging and Exception Handling] (#logging)

Clone this wiki locally