Sunteți pe pagina 1din 7

Java Annotations for Your Alfresco Content Model

Rui Monteiro (MECATENA, Engineer)


February 2009

Java Annotations for Your Alfresco Content Model


Rui Monteiro (MECATENA, Engineer), February 2009

The purpose of this article is to present a very easy and handy way of mapping your Alfresco
content model in Java Classes through usage of Java Annotations. This way the access to the
properties of your node is much more transparent than through the direct usage of the Alfresco Java
API. You will be able to get your node, transform it into your own business entiy Java class instance
and work with it directly, and at the end map it again into on your Alfresco content node. We will use
for this example the Alfresco Content Aspects, but the same reasoning and technique could be
applied to the Alfresco Content Types.

We suppose that the reader must have a minimum knowledge of the Alfresco Java API and about
Java in general and Java Annotations in particular. If that’s not the case you can read more about
these subjects on http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html and
http://wiki.alfresco.com/wiki/Main_Page.

The Java Annotations

So we start by creating two Java Annotations Interfaces. The first is going to define the Alfresco
aspect property itself of a node, while the second will be used to define each metadata property of an
aspect. We will call the first interface, that specifies the aspect itself, AlfrescoAspectProperty:

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

public @interface AlfrescoAspectProperty {

String url();

String aspect();

And the second, that specifies each aspect’s property, AlfrescoNodeProperty:

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface AlfrescoNodeProperty {

String value();

So now (and using the


final example from a smalltalk of mine to ZK web,
http://docs.zkoss.org/wiki/ZK_Alfresco_Talk)
we could use our annotations to define the mapping of a Pojo Java
class of ours to a custom node aspect in Alfresco:

This work is licensed under http://creativecommons.org/licenses/by/3.0/


Java Annotations for Your Alfresco Content Model
Rui Monteiro (MECATENA, Engineer)
February 2009

@AlfrescoAspectProperty(url=”my.mymodel“,aspect=”myportlet“)
public class MyPortlet {

private int height;


private String iframesrc;

public MyPortlet(){
}

public MyPortlet(int height,String iframesrc){


setHeight(height);
setIfamesrc(iframesrc);
}

@AlfrescoNodeProperty(”height”)
public int getHeight() {
return height;
}

public void setHeight(int height) {


this.height= height;
}

@AlfrescoNodeProperty(”iframesrc”)
public String getIframesrc() {
return iframesrc;
}

public void setIframesrc(String iframesrc) {


this.iframesrc= iframesrc;
}
}

As you see we are using both annotations to map our MyPortlet class into the my:myportlet aspect,
which is a custom extension of the Alfresco Content Model. Check for that the definition in
myModel.xml (see again http://docs.zkoss.org/wiki/ZK_Alfresco_Talk)
<model name="my:mymodel" xmlns="http://www.alfresco.org/model/dictionary/1.0">
<!-- Optional meta-data about the model -->
<description>My Model</description>
<author>Rui Monteiro (MECATENA, Engineer)</author>
<version>1.0</version>
<imports>
<!-- Import Alfresco Dictionary Definitions -->
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
<!-- Import Alfresco Content Domain Model Definitions -->
<import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
</imports>
<!-- Introduction of new namespaces defined by this model -->
<namespaces>
<namespace uri="my.mymodel" prefix="my"/>
</namespaces>

<aspects>
<!-- Definition of new Content Aspect -->
<aspect name="my:myportlet">
<title>My Custom Portlet Aspect</title>
<properties>
<property name="my:height">
<type>d:int</type>
</property>
<property name="my:iframesrc">
<type>d:text</type>
</property>
</properties>
</aspect>
</aspects>
</model>

This work is licensed under http://creativecommons.org/licenses/by/3.0/


Java Annotations for Your Alfresco Content Model
Rui Monteiro (MECATENA, Engineer)
February 2009

The AlfrescoPropertyMapper

Now you should be asking and what’s all that for? It’s true. Now we must find a way of actually using
these annotations. Let’s define an AlfrescoPropertyMapper that’s capable of mapping any Java
class annotated with our annotations into the corresponding aspect, and at the same time it should be
able to do the other way around: give it a node with an aspect and transform it to us back in the
corresponding Java class instance.

setNodeAspect method
public class AlfrescoPropertyMapper {

public static <T> void setNodeAspect(NodeRef node, NodeService nodeService,

T t) {

AlfrescoAspectProperty
aspectProperty=t.getClass().getAnnotation(AlfrescoAspectProperty.class);

String nodeURI=aspectProperty.url();

String aspectName=aspectProperty.aspect();

nodeService.addAspect( node, QName.createQName(nodeURI,aspectName),


convertToAlfrescoMap(getMap(t), nodeURI));

So as you see in the method above we are using the full power of generics to set an Alfresco’s node
aspect just by passing an Alfresco’s NodeRef, the Alfresco’s NodeService and any class instance T
(annotated with our annotations). The AlfrescoPropertyMapper doesn’t know anything about any
particular aspect or about our final Java classes. By passing the instance of our class, we can consult
the AlfrescoAspectProperty annotation, get the values we need (the url and the aspect’s name) and
then we just call in the Alfresco’s node service the method addAspect. The last argument for this
method is a Map<QName, Serializable> so we should take a closer look to the implementation of
convertToAlfrescoMap:

public static Map<QName, Serializable> convertToAlfrescoMap(Map<String, Serializable> map, String uri)


{
Map<QName, Serializable> map2 = new HashMap<QName, Serializable>();
for (String key : map.keySet()) {
map2.put(QName.createQName(uri, key), map.get(key));
}
return map2;
}

The method receives a normal Map<String,Serializable> and just transforms the key on the map to an
Alfresco’s org.alfresco.service.namespace.QName by “appending” to it the uri. Nothing special in that.
The real thing is going on the other method, getMap(t):
public static <T> Map<String, Serializable> getMap(T t) {
Map<String, Serializable> map = new HashMap<String, Serializable>();
try {
for (PropertyDescriptor pd : Introspector.getBeanInfo(t.getClass()
).getPropertyDescriptors()) {
Method m = pd.getReadMethod();

This work is licensed under http://creativecommons.org/licenses/by/3.0/


Java Annotations for Your Alfresco Content Model
Rui Monteiro (MECATENA, Engineer)
February 2009

if (m != null && m.isAnnotationPresent(AlfrescoNodeProperty.class) ) {


Serializable obj = (Serializable) m.invoke(t, new Object[]{});
if (obj != null) {
String val = m.getAnnotation(AlfrescoNodeProperty.class).value();
if (val == null) {
val = pd.getName();
}
map.put(val, obj);
}
}
}
} catch (IntrospectionException ie) {
throw new RuntimeException(”Introspection error:” + ie.getMessage());
} catch (IllegalAccessException iae) {
throw new RuntimeException(”Illegal Access error:” + iae.getMessage());
} catch (InvocationTargetException ite) {
throw new RuntimeException(”Invocation Target error:” + ite.getMessage());
}
return map;
}

So we are using here the java.beans api for checking which methods are annotated with our
annotations and then fetching the corresponding values to compose a Map<String,Serializable> of
our aspect’s metadata.

So now we can finally use our new utility class AlfrescoPropertyMapper and its method
setNodeAspect to set in a much easier way a node’s aspect. Let’s see first how we would do this
normally with the Alfresco Java API:

Map<QName, Serializable> aspectProperties= new HashMap<QName, Serializable>();

aspectProperties.put(QName.createQName(”my.mymodel”, “height”), new Integer(height));

aspectProperties.put(QName.createQName(”my.mymodel”, “iframesrc”), iframesrc);

QName myAspect=QName.createQName(”my.mymodel”, “myportlet”);

nodeService.addAspect(myNode, myAspect, aspectProperties);

The developer has to know a little bit about the Alfresco Java API to code it. He has to do some
repeated operations for composing the aspectProperties as well. Isn’t easier just to do this?

AlfrescoPropertyMapper.setNodeAspect(myNode,nodeService,new MyPortlet(height,iframesrc) );

The final effect will be exactly the same: our myNode will have its myportlet aspect set. But I think
that’s not necessary to stress the simplicity of the second piece of code compared to the first.

getInstance method

Now let’s do the other way around reading our aspect metadata from our node into our class instance.
For that we would like to have on our AlfrescoPropertyMapper class a method like this:

This work is licensed under http://creativecommons.org/licenses/by/3.0/


Java Annotations for Your Alfresco Content Model
Rui Monteiro (MECATENA, Engineer)
February 2009

public static <T> T getInstance(NodeRef node, NodeService nodeService, Class<T> c) {

T t = null;

try {

t = c.newInstance();

} catch (InstantiationException ex) {

throw new RuntimeException(”Instantiation error:” + ex);

} catch (IllegalAccessException ex) {

throw new RuntimeException(”Illegal Access error:” + ex);

setValuesOnObject(t, convertAlfrescoMap(nodeService.getProperties(node)));

return t;

With such a method we will be able to read the aspect from our node into our object just by specifying
its class. But again there are some methods being called that deserve an explanation. First the
convertAlfrescoMap. This is analogous to our method before convertToAlfrescoMap, but now we
need to do the opposite thing:

public static Map<String, Serializable> convertAlfrescoMap(Map<QName, Serializable> map) {

Map<String, Serializable> map2 = new HashMap<String, Serializable>();

for (QName qname : map.keySet()) {

map2.put(qname.getLocalName(), map.get(qname));

return map2;

We receive an Alfresco’s Map and we transform it into a “normal” one. Simple and no science going
on. The real thing again is on the other method, setValuesOnObject:

public static <T> void setValuesOnObject(T t, Map<String, Serializable> map) {

try {

for (PropertyDescriptor pd : introspector.getBeanInfo(t.getClass()


).getPropertyDescriptors() ) {

Method m = pd.getReadMethod();

if (m != null && m.isAnnotationPresent(AlfrescoNodeProperty.class) ) {

This work is licensed under http://creativecommons.org/licenses/by/3.0/


Java Annotations for Your Alfresco Content Model
Rui Monteiro (MECATENA, Engineer)
February 2009

String val = m.getAnnotation(AlfrescoNodeProperty.class).

value();

m = pd.getWriteMethod();

if (m != null) {

if (val == null) {

val = pd.getName();

Object obj = map.get(val);

if (obj != null) {

m.invoke(t, new Object[]{obj});

} catch (IntrospectionException ie) {

throw new RuntimeException(”Introspection error:” + ie.getMessage());

} catch (IllegalAccessException iae) {

throw new RuntimeException(”Illegal Access error:” + iae.getMessage());

} catch (InvocationTargetException ite) {

throw new RuntimeException(”Invocation Target error:” + ite.getMessage() );

So now we are doing the opposite that we were doing in the getMap method before. (In truth this is
just as we expected since at the end we want to get the opposite: the instance class from the aspect
already set on a node.) We check each set Method on our Pojo class if it corresponds to an annotated
aspect property, and then we fetch the value of the property from the node metadata and use it to set
it.

Now let’s take some benefit of our new AlfrescoPropertyMapper.getInstance method. Normally with
the standard Alfresco API you’d read the values of a node and set them into your business entity Java
class with a code more or less like this:

int height=((Integer)nodeService.getProperty(myNode,QName.createQName(”my.mymodel”,
“height”))).intValue();;

This work is licensed under http://creativecommons.org/licenses/by/3.0/


Java Annotations for Your Alfresco Content Model
Rui Monteiro (MECATENA, Engineer)
February 2009

String iframesrc=(String)nodeService.getProperty(myNode,QName.createQName(”my.mymodel”, “iframesrc”));

MyPortlet myPortlet=new MyPortlet(height,iframesrc);

Again there’s code that looks repeated and there’s Alfresco API knowledge being demanded. Doesn’t
look much better this way?

MyPortlet myPortlet= AlfrescoPropertyMapper.getInstance(myNode,nodeService,MyPortlet.class);

The fact that the second piece code is “better” than the first is again obvious, but imagine that your
aspect had not 2 but 15 properties, it would be even better to be able to reduce the whole bunch of
tedious code needed with the Alfresco API into a simple line like this. That’s what the
AlfrescoPropertyMapper does for you!

Conclusion

You must have it clear that all this is completely generic and could be used for any custom aspect or
content type you create in Alfresco. You can make your life as an Alfresco server developer much
easier by combining the full power of Alfresco content model and Java annotations this way. I hope
you enjoyed this talk and got a feeling of the great advantage this technique means for you.

This work is licensed under http://creativecommons.org/licenses/by/3.0/

S-ar putea să vă placă și