First complete node mapper
[lgpl/argeo-commons.git] / server / runtime / org.argeo.server.jackrabbit / src / main / java / org / argeo / jcr / BeanNodeMapper.java
index 931b11f5ca612e0b7688352137adecb1c136ade2..165547ae0a5fb6da120cfa7dcf22ae00c47bdd38 100644 (file)
@@ -1,9 +1,12 @@
 package org.argeo.jcr;
 
 import java.beans.PropertyDescriptor;
-import java.beans.PropertyEditor;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.StringTokenizer;
@@ -11,6 +14,10 @@ import java.util.StringTokenizer;
 import javax.jcr.Item;
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Value;
@@ -22,12 +29,18 @@ import org.argeo.ArgeoException;
 import org.springframework.beans.BeanWrapper;
 import org.springframework.beans.BeanWrapperImpl;
 
+//import org.springframework.beans.BeanWrapperImpl;
+
 public class BeanNodeMapper {
        private final static Log log = LogFactory.getLog(BeanNodeMapper.class);
 
-       private Boolean versioning = false;
+       private final static String NODE_VALUE = "value";
+
+       // private String keyNode = "bean:key";
        private String uuidProperty = "uuid";
        private String classProperty = "class";
+
+       private Boolean versioning = false;
        private Boolean strictUuidReference = false;
        private String primaryNodeType = null;
 
@@ -48,7 +61,7 @@ public class BeanNodeMapper {
 
        public Node saveOrUpdate(Session session, String path, Object obj) {
                try {
-                       BeanWrapper beanWrapper = new BeanWrapperImpl(obj);
+                       BeanWrapper beanWrapper = createBeanWrapper(obj);
                        final Node node;
                        if (session.itemExists(path)) {
                                Item item = session.getItem(path);
@@ -82,6 +95,139 @@ public class BeanNodeMapper {
                }
        }
 
+       @SuppressWarnings("unchecked")
+       public Object nodeToBean(Node node) throws RepositoryException {
+
+               String clssName = node.getProperty(classProperty).getValue()
+                               .getString();
+
+               if (log.isDebugEnabled())
+                       log.debug("Map node " + node.getPath() + " to bean " + clssName);
+
+               BeanWrapper beanWrapper;
+               try {
+                       // FIXME: use OSGi compatible class loading
+                       Class clss = Class.forName(clssName);
+                       beanWrapper = new BeanWrapperImpl(clss);
+               } catch (ClassNotFoundException e1) {
+                       throw new ArgeoException("Cannot initialize been wrapper for node "
+                                       + node.getPath(), e1);
+               }
+
+               // process properties
+               PropertyIterator propIt = node.getProperties();
+               props: while (propIt.hasNext()) {
+                       Property prop = propIt.nextProperty();
+                       if (!beanWrapper.isWritableProperty(prop.getName()))
+                               continue props;
+
+                       PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(prop
+                                       .getName());
+                       Class propClass = pd.getPropertyType();
+
+                       // list
+                       if (propClass != null && List.class.isAssignableFrom(propClass)) {
+                               List<Object> lst = new ArrayList<Object>();
+                               Class<?> valuesClass = classFromProperty(prop);
+                               if (valuesClass != null)
+                                       for (Value value : prop.getValues()) {
+                                               lst.add(asObject(value, valuesClass));
+                                       }
+                               continue props;
+                       }
+
+                       Object value = asObject(prop.getValue(), pd.getPropertyType());
+                       if (value != null)
+                               beanWrapper.setPropertyValue(prop.getName(), value);
+               }
+
+               // process children nodes
+               NodeIterator nodeIt = node.getNodes();
+               nodes: while (nodeIt.hasNext()) {
+                       Node childNode = nodeIt.nextNode();
+                       String name = childNode.getName();
+                       if (!beanWrapper.isWritableProperty(name))
+                               continue nodes;
+
+                       PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(name);
+                       Class propClass = pd.getPropertyType();
+
+                       log.debug(childNode.getName() + "=" + propClass);
+
+                       if (propClass != null && List.class.isAssignableFrom(propClass)) {
+                               String lstClass = childNode.getProperty(classProperty)
+                                               .getString();
+                               // FIXME: use OSGi compatible class loading
+                               List<Object> lst;
+                               try {
+                                       lst = (List<Object>) Class.forName(lstClass).newInstance();
+                               } catch (Exception e) {
+                                       lst = new ArrayList<Object>();
+                               }
+
+                               NodeIterator valuesIt = childNode.getNodes();
+                               while (valuesIt.hasNext()) {
+                                       Node lstValueNode = valuesIt.nextNode();
+                                       Object lstValue = nodeToBean(lstValueNode);
+                                       lst.add(lstValue);
+                               }
+
+                               beanWrapper.setPropertyValue(name, lst);
+                               continue nodes;
+                       }
+
+                       if (propClass != null && Map.class.isAssignableFrom(propClass)) {
+                               String mapClass = childNode.getProperty(classProperty)
+                                               .getString();
+                               // FIXME: use OSGi compatible class loading
+                               Map<Object, Object> map;
+                               try {
+                                       map = (Map<Object, Object>) Class.forName(mapClass)
+                                                       .newInstance();
+                               } catch (Exception e) {
+                                       map = new HashMap<Object, Object>();
+                               }
+
+                               // properties
+                               PropertyIterator keysPropIt = childNode.getProperties();
+                               keyProps: while (keysPropIt.hasNext()) {
+                                       Property keyProp = keysPropIt.nextProperty();
+                                       // FIXME: use property editor
+                                       String key = keyProp.getName();
+                                       if (classProperty.equals(key))
+                                               continue keyProps;
+
+                                       Class keyPropClass = classFromProperty(keyProp);
+                                       if (keyPropClass != null) {
+                                               Object mapValue = asObject(keyProp.getValue(),
+                                                               keyPropClass);
+                                               map.put(key, mapValue);
+                                       }
+                               }
+
+                               // node
+                               NodeIterator keysIt = childNode.getNodes();
+                               while (keysIt.hasNext()) {
+                                       Node mapValueNode = keysIt.nextNode();
+                                       // FIXME: use property editor
+                                       Object key = mapValueNode.getName();
+
+                                       Object mapValue = nodeToBean(mapValueNode);
+
+                                       map.put(key, mapValue);
+                               }
+                               beanWrapper.setPropertyValue(name, map);
+                               continue nodes;
+                       }
+
+                       // default
+                       Object value = nodeToBean(childNode);
+                       beanWrapper.setPropertyValue(name, value);
+
+               }
+               return beanWrapper.getWrappedInstance();
+       }
+
        protected void beanToNode(Session session, BeanWrapper beanWrapper,
                        Node node) throws RepositoryException {
                if (log.isDebugEnabled())
@@ -91,6 +237,9 @@ public class BeanNodeMapper {
                                .getPropertyDescriptors()) {
                        String name = pd.getName();
 
+                       if (!beanWrapper.isReadableProperty(name))
+                               continue properties;// skip
+
                        Object value = beanWrapper.getPropertyValue(name);
                        if (value == null)
                                continue properties;// skip
@@ -110,6 +259,11 @@ public class BeanNodeMapper {
                                continue properties;
                        }
 
+                       if (value instanceof Class<?>) {
+                               node.setProperty(name, ((Class<?>) value).getName());
+                               continue properties;
+                       }
+
                        Value val = asValue(session, value);
                        if (val != null) {
                                node.setProperty(name, val);
@@ -118,52 +272,17 @@ public class BeanNodeMapper {
 
                        if (value instanceof List<?>) {
                                List<?> lst = (List<?>) value;
-                               Value[] values = new Value[lst.size()];
-                               boolean atLeastOneSet = false;
-                               for (int i = 0; i < lst.size(); i++) {
-                                       Object lstValue = lst.get(i);
-                                       values[i] = asValue(session, lstValue);
-                                       if (values[i] != null) {
-                                               atLeastOneSet = true;
-                                       } else {
-                                               Node childNode = findChildReference(session,
-                                                               new BeanWrapperImpl(lstValue));
-                                               if (childNode != null) {
-                                                       values[i] = session.getValueFactory().createValue(
-                                                                       childNode);
-                                                       atLeastOneSet = true;
-                                               }
-                                       }
-                               }
-
-                               if (!atLeastOneSet && lst.size() != 0)
-                                       throw new ArgeoException(
-                                                       "This type of list is not supported "
-                                                                       + lst.getClass());
-
-                               node.setProperty(name, values);
+                               addList(session, node, name, lst);
                                continue properties;
                        }
 
                        if (value instanceof Map<?, ?>) {
                                Map<?, ?> map = (Map<?, ?>) value;
-                               // TODO: add map specific type
-                               Node mapNode = node.addNode(name);
-                               for (Object key : map.keySet()) {
-                                       Object mapValue = map.get(key);
-                                       PropertyEditor pe = beanWrapper.findCustomEditor(key
-                                                       .getClass(), null);
-                                       String keyStr = pe.getAsText();
-                                       // TODO: check string format
-                                       Node entryNode = mapNode.addNode(keyStr);
-                                       beanToNode(session, new BeanWrapperImpl(mapValue),
-                                                       entryNode);
-                               }
-
+                               addMap(session, node, name, map);
                                continue properties;
                        }
 
-                       BeanWrapper child = new BeanWrapperImpl(value);
+                       BeanWrapper child = createBeanWrapper(value);
                        // TODO: delegate to another mapper
 
                        Node childNode = findChildReference(session, child);
@@ -182,6 +301,78 @@ public class BeanNodeMapper {
                }
        }
 
+       protected void addList(Session session, Node node, String name, List<?> lst)
+                       throws RepositoryException {
+               Node listNode = node.addNode(name);
+               listNode.setProperty(classProperty, lst.getClass().getName());
+               Value[] values = new Value[lst.size()];
+               boolean atLeastOneSet = false;
+               for (int i = 0; i < lst.size(); i++) {
+                       Object lstValue = lst.get(i);
+                       values[i] = asValue(session, lstValue);
+                       if (values[i] != null) {
+                               atLeastOneSet = true;
+                       } else {
+                               Node childNode = findChildReference(session,
+                                               createBeanWrapper(lstValue));
+                               if (childNode != null) {
+                                       values[i] = session.getValueFactory()
+                                                       .createValue(childNode);
+                                       atLeastOneSet = true;
+                               }
+                       }
+               }
+
+               // will be either properties or nodes, not both
+               if (!atLeastOneSet && lst.size() != 0) {
+                       for (Object lstValue : lst) {
+                               Node childNode = listNode.addNode(NODE_VALUE);
+                               beanToNode(session, createBeanWrapper(lstValue), childNode);
+                       }
+               } else {
+                       listNode.setProperty(name, values);
+               }
+       }
+
+       protected void addMap(Session session, Node node, String name, Map<?, ?> map)
+                       throws RepositoryException {
+               // TODO: add map specific type
+               Node mapNode = node.addNode(name);
+               mapNode.setProperty(classProperty, map.getClass().getName());
+               for (Object key : map.keySet()) {
+                       Object mapValue = map.get(key);
+                       // PropertyEditor pe = beanWrapper.findCustomEditor(key.getClass(),
+                       // null);
+                       String keyStr;
+                       // if (pe == null) {
+                       if (key instanceof CharSequence)
+                               keyStr = key.toString();
+                       else
+                               throw new ArgeoException(
+                                               "Cannot find property editor for class "
+                                                               + key.getClass());
+                       // } else {
+                       // pe.setValue(key);
+                       // keyStr = pe.getAsText();
+                       // }
+                       // TODO: check string format
+
+                       Value mapVal = asValue(session, mapValue);
+                       if (mapVal != null)
+                               mapNode.setProperty(keyStr, mapVal);
+                       else {
+                               Node entryNode = mapNode.addNode(keyStr);
+                               beanToNode(session, createBeanWrapper(mapValue), entryNode);
+                       }
+
+               }
+
+       }
+
+       protected BeanWrapper createBeanWrapper(Object obj) {
+               return new BeanWrapperImpl(obj);
+       }
+
        /** Returns null if value cannot be found */
        protected Value asValue(Session session, Object value)
                        throws RepositoryException {
@@ -198,7 +389,11 @@ public class BeanNodeMapper {
                        return valueFactory.createValue((Boolean) value);
                else if (value instanceof Calendar)
                        return valueFactory.createValue((Calendar) value);
-               else if (value instanceof CharSequence)
+               else if (value instanceof Date) {
+                       Calendar cal = new GregorianCalendar();
+                       cal.setTime((Date) value);
+                       return valueFactory.createValue(cal);
+               } else if (value instanceof CharSequence)
                        return valueFactory.createValue(value.toString());
                else if (value instanceof InputStream)
                        return valueFactory.createValue((InputStream) value);
@@ -206,6 +401,52 @@ public class BeanNodeMapper {
                        return null;
        }
 
+       protected Class<?> classFromProperty(Property property)
+                       throws RepositoryException {
+               switch (property.getType()) {
+               case PropertyType.LONG:
+                       return Long.class;
+               case PropertyType.DOUBLE:
+                       return Double.class;
+               case PropertyType.STRING:
+                       return String.class;
+               case PropertyType.BOOLEAN:
+                       return Boolean.class;
+               case PropertyType.DATE:
+                       return Calendar.class;
+               case PropertyType.NAME:
+                       return null;
+               default:
+                       throw new ArgeoException("Cannot find class for property "
+                                       + property + ", type="
+                                       + PropertyType.nameFromValue(property.getType()));
+               }
+       }
+
+       protected Object asObject(Value value, Class<?> propClass)
+                       throws RepositoryException {
+               if (propClass.equals(Integer.class))
+                       return (int) value.getLong();
+               else if (propClass.equals(Long.class))
+                       return value.getLong();
+               else if (propClass.equals(Float.class))
+                       return (float) value.getDouble();
+               else if (propClass.equals(Double.class))
+                       return value.getDouble();
+               else if (propClass.equals(Boolean.class))
+                       return value.getBoolean();
+               else if (CharSequence.class.isAssignableFrom(propClass))
+                       return value.getString();
+               else if (InputStream.class.isAssignableFrom(propClass))
+                       return value.getStream();
+               else if (Calendar.class.isAssignableFrom(propClass))
+                       return value.getDate();
+               else if (Date.class.isAssignableFrom(propClass))
+                       return value.getDate().getTime();
+               else
+                       return null;
+       }
+
        protected Node findChildReference(Session session, BeanWrapper child)
                        throws RepositoryException {
                if (child.isReadableProperty(uuidProperty)) {