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, 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)
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"> <description>My Model
Rui Monteiro (MECATENA, Engineer) 1.0 My Custom Portlet Aspect <properties> <property name="my:height"> d:int <property name="my:iframesrc"> d:text
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
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 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 so we should take a closer look to the implementation of convertToAlfrescoMap: public static Map convertToAlfrescoMap(Map<String, Serializable> map, String uri) { Map map2 = new HashMap(); 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 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 aspectProperties= new HashMap(); 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 getInstance(NodeRef node, NodeService nodeService, Class 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 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 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/