You are on page 1of 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 thats 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 aspects property, AlfrescoNodeProperty:


@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AlfrescoNodeProperty { String value(); }

So

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

now

(and

using

the

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 whats all that for? Its true. Now we must find a way of actually using these annotations. Lets define an AlfrescoPropertyMapper thats 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( convertToAlfrescoMap(getMap(t), nodeURI)); } node, QName.createQName(nodeURI,aspectName),

So as you see in the method above we are using the full power of generics to set an Alfrescos node aspect just by passing an Alfrescos NodeRef, the Alfrescos NodeService and any class instance T (annotated with our annotations). The AlfrescoPropertyMapper doesnt 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 aspects name) and then we just call in the Alfrescos 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 Alfrescos 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 aspects metadata. So now we can finally use our new utility class AlfrescoPropertyMapper and its method setNodeAspect to set in a much easier way a nodes aspect. Lets 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. Isnt 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 thats not necessary to stress the simplicity of the second piece of code compared to the first.

getInstance method
Now lets 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 Alfrescos 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 lets take some benefit of our new AlfrescoPropertyMapper.getInstance method. Normally with the standard Alfresco API youd 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 theres code that looks repeated and theres Alfresco API knowledge being demanded. Doesnt 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. Thats 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/

You might also like