Friday, October 30, 2009

Develop in JCR - from database development to content development

(Disclaimer: This post is my personal understanding about database desgin and JCR content design, it might not be correct.)

Let's design a simple blog system and persistence domain objects in both database and content repository.

The Class diagram for this simple blog system shows below:
http://yuml.me/3f487850

In traditional database development:
1. Choose Database Management System, MySQL.
2. create schema "blog.sql" to define data structure:

CREATE DATABASE blog;
USE blog;
CREATE TABLE BLOGGER(
ID bigint(20) NOT NULL auto_increment,
NAME varchar(50) NOT NULL,
PASSWORD varchar(50) NOT NULL,
EMAIL varchar(80) NOT NULL,
PRIMARY KEY (ID)
) DEFAULT CHARSET=utf8;

CREATE TABLE POST(
ID bigint(20) NOT NULL auto_increment,
TITLE varchar(100) NOT NULL,
CONTENT text NOT NULL,
POSTTIME datetime default NULL,
BLOGGER_ID bigint(20) NOT NULL,
PRIMARY KEY (ID)
) DEFAULT CHARSET=utf8;

CREATE TABLE COMMENT(
ID bigint(20) NOT NULL auto_increment,
TITLE varchar(100) NOT NULL,
CONTENT text NOT NULL,
POSTTIME datetime default NULL,
POST_ID bigint(20) NOT NULL,
PRIMARY KEY (ID)
) DEFAULT CHARSET=utf8;

ALTER TABLE POST ADD CONSTRAINT FOREIGN KEY( BLOGGER_ID) references BLOGGER(ID);
ALTER TABLE COMMENT ADD CONSTRAINT FOREIGN KEY( POST_ID) references POST (ID);


3. do ORM mapping use JPA annotation

@Entity
@Table(name = "BLOGGER")
public class Blogger {
@Id
@GeneratedValue
@Column(name = "ID")
private Long id;
@Column(name = "EMAIL")
private String email;
@Column(name = "NAME")
private String name;
@Column(name = "PASSWORD")
private String password;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List posts;

}

@Entity
@Table(name = "POST")
public class Post {

@Id
@GeneratedValue
@Column(name = "ID")
private Long id;
@Column(name = "TITLE")
private String title;
@Column(name = "CONTENT")
private String content;
@Column(name = "POSTTIME")
private Date postTime;
@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Blogger blogger;

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List comments;

}

@Entity
@Table(name = "COMMENT")
public class Comment {

@Id
@GeneratedValue
@Column(name = "ID")
private Long id;
@Column(name = "COMMENTER")
private String commenter;
@Column(name = "CONTENT")
private String content;
@Column(name = "POSTTIME")
private Date postTime;

}

Now, let's have a look how to do those in JCR Content Development
1. Choose content repository implementation, Apache Jackrabbit

2.In JCR world, we do "Content type definition". I will use xml since most of us are familiar with it. It also can be done in Compact Node type Definition(CND), but you have to learn the syntax.




xmlns:rep="internal" xmlns:sv="http://www.jcp.org/jcr/sv/1.0"
xmlns:test="http://www.apache.org/jackrabbit/test" xmlns:mix="http://www.jcp.org/jcr/mix/1.0"
xmlns:ocm="http://jackrabbit.apache.org/ocm" xmlns:ax="http://absorbx.com/jcr">



nt:base

requiredType="String" autoCreated="false" mandatory="true"
onParentVersion="COPY" protected="false" multiple="false" />




mix:referenceable
nt:unstructured

autoCreated="false" mandatory="true" onParentVersion="COPY"
protected="false" multiple="false" />
requiredType="String" autoCreated="false" mandatory="true"
onParentVersion="COPY" protected="false" multiple="false" />
autoCreated="false" mandatory="true" onParentVersion="COPY"
protected="false" multiple="false" />
autoCreated="false" mandatory="true" onParentVersion="COPY"
protected="false" multiple="true" />




mix:referenceable
nt:unstructured

autoCreated="false" mandatory="true" onParentVersion="COPY"
protected="false" multiple="false" />
autoCreated="false" mandatory="false" onParentVersion="COPY"
protected="false" multiple="false" />
requiredType="Date" autoCreated="false" mandatory="false"
onParentVersion="COPY" protected="false" multiple="false" />
autoCreated="false" mandatory="true" onParentVersion="COPY"
protected="false" multiple="false" />
requiredType="String" autoCreated="false" mandatory="true"
onParentVersion="COPY" protected="false" multiple="true" />




mix:referenceable
nt:unstructured

requiredType="Date" autoCreated="false" mandatory="false"
onParentVersion="COPY" protected="false" multiple="false" />

requiredType="String" autoCreated="false" mandatory="false"
onParentVersion="COPY" protected="false" multiple="false" />

autoCreated="false" mandatory="false" onParentVersion="COPY"
protected="false" multiple="false" />





3. We need to define path in order to implement similar foreign key, to define relationship between table. Node in JCR repository is tree structure, relationship can be done by prent->children.
in this case, we just use the simplest structure:

root-blog-bloger1-post1-comment1
|-comment2
|-post2-comment1
-|bloger2-post1-comment1


In this path structure, one bloger's posts can be get by retrieve its children. A post's prent is the author.

4. let's do Oject Content Mapping.


import java.util.List;

import org.apache.jackrabbit.ocm.manager.collectionconverter.impl.NTCollectionConverterImpl;
import org.apache.jackrabbit.ocm.mapper.impl.annotation.Collection;
import org.apache.jackrabbit.ocm.mapper.impl.annotation.Field;
import org.apache.jackrabbit.ocm.mapper.impl.annotation.Node;

@Node(jcrType = "ax:blogger")
public class Blogger {

@Field(uuid = true)
private String uuid;
@Field(jcrName = "ax:email")
private String email;
@Field(jcrName = "ax:name")
private String name;
@Field(jcrName = "ax:password")
private String password;
@Collection(collectionConverter=NTCollectionConverterImpl.class,jcrName="ax:posts",jcrType="ax:post")
private List posts;

}




import java.util.Calendar;
import java.util.List;

import org.apache.jackrabbit.ocm.manager.beanconverter.impl.ParentBeanConverterImpl;
import org.apache.jackrabbit.ocm.manager.collectionconverter.impl.NTCollectionConverterImpl;
import org.apache.jackrabbit.ocm.mapper.impl.annotation.Bean;
import org.apache.jackrabbit.ocm.mapper.impl.annotation.Collection;
import org.apache.jackrabbit.ocm.mapper.impl.annotation.Field;
import org.apache.jackrabbit.ocm.mapper.impl.annotation.Node;
@Node(jcrType = "ax:post")
public class Post {
@Field(uuid = true)
private String uuid;
@Field(jcrName = "ax:title")
private String title;
@Field(jcrName = "ax:content")
private String content;
@Field(jcrName = "ax:postTime")
private Calendar postTime = Calendar.getInstance();
@Bean(jcrType = "ax:blogger", jcrOnParentVersion = "IGNORE", jcrName = "ax:blooger", converter = ParentBeanConverterImpl.class)
private Blogger blogger;
@Collection(collectionConverter=NTCollectionConverterImpl.class,jcrName="ax:comments",jcrType="ax:comment")
private List comments;

}



import java.util.Calendar;

import org.apache.jackrabbit.ocm.mapper.impl.annotation.Field;
import org.apache.jackrabbit.ocm.mapper.impl.annotation.Node;

@Node(jcrType = "ax:comment")
public class Comment {

@Field(uuid = true)
private String uuid;
@Field(jcrName = "ax:commenter")
private String commenter;
@Field(jcrName = "ax:content")
private String content;
@Field(jcrName = "ax:postTime")
private Calendar postTime = Calendar.getInstance();
}




Wednesday, October 28, 2009

Develop in JCR - Integrate with Spring

Let's take a step further, use Spring-moudle-jcr to accelerate JCR development.


First of all, there are modifications in pom.xml:
1. add elements to enable maven download source code and java docs for dependencies library:


spring-jcr



org.apache.maven.plugins
maven-eclipse-plugin

true
true




maven-compiler-plugin

1.6
1.6







2: add absorbx repository in your pom.xml:
(see: Unofficial Maven Repository for Spring Modules Jcr 0.9 with Ocm patch for more information. I added spring-module-jcr 0.9 in my repository.)



absorbx
http://absorbx.googlecode.com/svn/trunk/repo





3. Add spring dependencies:


org.springframework
spring-core
${spring.version}


org.springframework
spring-beans
${spring.version}


org.springframework
spring-context
${spring.version}


org.springframework
spring-tx
${spring.version}


org.springmodules
spring-modules-jcr
${springmodules.version}


org.springframework
spring-test
${spring.version}
test



4. Don't forget set the version in element about spring and spring-module-jcr:

2.5.6
0.9


Ok, we've done maven part.

5. In Eclipse, right click on the project, "New->Source Folder", create "src/main/resources".

6. create applicationContext-jcr.xml in "resources" folder


xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
default-lazy-init="true" default-autowire="byName">




class="org.springmodules.jcr.jackrabbit.ocm.JackrabbitSessionFactory">





















class="org.springmodules.jcr.jackrabbit.LocalTransactionManager">


class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

true




PROPAGATION_REQUIRED
PROPAGATION_REQUIRED, readOnly






7. add jackrabbit-repository.xml:





"http://jackrabbit.apache.org/dtd/repository-1.4.dtd">


































































8. Let's create a test class SpringIntegratingTest.java to use jcrTemplate:



package com.absorbx.jcr.spring;

import static org.junit.Assert.assertNotNull;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;

import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springmodules.jcr.JcrCallback;
import org.springmodules.jcr.JcrTemplate;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/applicationContext-jcr.xml" })
public class SpringIntegratingTest {
@Autowired
public JcrTemplate jcrTemplate;
final Logger log = Logger.getLogger(SpringIntegratingTest.class);
@Test
public void testSaveNode() throws Exception {
jcrTemplate.execute(new JcrCallback() {

public Object doInJcr(Session session) throws RepositoryException {
Node root = session.getRootNode();
log.info("starting from root node " + root);
Node sample = root.addNode("test");
sample.setProperty("sample property", "bla bla");
log.info("saved property " + sample);
session.save();

assertNotNull(root.getNode("test"));
return null;
}
});
}
}


Congratulation, a big step forward!

SVN source code at : https://absorbx.googlecode.com/svn/branches/absorbx-jcr/spring-integrated

Reference:
InfoQ: Integrating Java Content Repository and Spring
Chapter 10. Java Content Repository (JSR-170)

Saturday, October 24, 2009

Develop in JCR - Basic

Today, we are going to create a JCR project using maven to show you the basic operation of JCR API.
This post is generally based on Jackrabbit's First Hops
1. create project using Maven:

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.absorbx.jcr -DartifactId=absorbx-jcr
2. edit the "pom.xml" under the created project "absorbx-jrc"
2. edit pom.xml, add jackrabbit dependencies.


1.5.5
1.0
1.5.6
1.2.12
4.4



org.apache.jackrabbit
jackrabbit-core
${jackrabbit.version}


javax.jcr
jcr
${jcr.version}


org.slf4j
slf4j-log4j12
${slf4j.version}


org.slf4j
slf4j-api
${slf4j.version}


log4j
log4j
${log4j.version}


junit
junit
${junit.version}
test




3. Add a test Class


import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;

import org.apache.jackrabbit.core.TransientRepository;
import org.junit.Test;

public class BasicTest {
@Test
public void testJCR() throws Exception {
Repository repository = new TransientRepository();
Session session = repository.login(
new SimpleCredentials("username", "password".toCharArray()));
try {
Node root = session.getRootNode();

// Store content
Node hello = root.addNode("hello");
Node world = hello.addNode("world");
world.setProperty("message", "Hello, World!");
session.save();

// Retrieve content
Node node = root.getNode("hello/world");
System.out.println(node.getPath());
System.out.println(node.getProperty("message").getString());

// Remove content
root.getNode("hello").remove();
session.save();
} finally {
session.logout();
}


}

}


Now, it's ready to go. Enjoy your first step in JCR development.

You can check out this chapter's code from :
https://absorbx.googlecode.com/svn/branches/absorbx-jcr/basic

Friday, October 23, 2009

Develop in JCR - preface

It has been a while since I've dived into Java Content Repository, and I decided to write something about the change from traditional Rational Database development to JCR.

This series will focus on coding, if you are not familiar with JCR, please read the tutorials I recommended in the references. Open source frameworks and tools will be used, such as Maven, eclipse, Spring, Spring-modules-jcr, Jackarbbit.

Like traditional rational database development, we use JDBC to do the very raw operation at first. After that, ORM framework, such as Hibernate release us from SQL hell. The development in JCR also involves RAW operation, and high level Object Content Mapping (OCM).

Therefore, the first part of this note will just use JCR API. Following, Spring Modules will be introduced to ease the development. At last, Jackrabbit Object Content Mapping is shown to quick develop content base applications.

References
First Hops
Introducing the Java Content Repository API
Catch Jackrabbit and the Java Content Repository API
ONJava.com -- What is Java Content Repository
JCR Primer : Jochen Toppe’s Blog

Monday, October 19, 2009

I'm back


I came back from China yesterday. 4 weeks' vacation is end.

When I got back to work this morning, I'm so moved to see two handmade card (written in A4 paper) covered my keyboard. (by the way, my nick name is Kenny G :))

Those are from Kylie and Jody, thank you so much:)
It's a new start ....