X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=server%2Fruntime%2Forg.argeo.server.jackrabbit%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fjcr%2FBeanNodeMapper.java;h=6f282bac7177143da530bc4f55df4d5b7f0f4c4c;hb=3c169a3fd8516288d846d53652b873a0d7dab2fc;hp=165547ae0a5fb6da120cfa7dcf22ae00c47bdd38;hpb=460170049599090170c48ae447c6c68f1fcb2f63;p=lgpl%2Fargeo-commons.git diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jcr/BeanNodeMapper.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jcr/BeanNodeMapper.java index 165547ae0..6f282bac7 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jcr/BeanNodeMapper.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jcr/BeanNodeMapper.java @@ -11,7 +11,6 @@ import java.util.List; import java.util.Map; import java.util.StringTokenizer; -import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; @@ -29,9 +28,7 @@ import org.argeo.ArgeoException; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; -//import org.springframework.beans.BeanWrapperImpl; - -public class BeanNodeMapper { +public class BeanNodeMapper implements NodeMapper { private final static Log log = LogFactory.getLog(BeanNodeMapper.class); private final static String NODE_VALUE = "value"; @@ -42,50 +39,95 @@ public class BeanNodeMapper { private Boolean versioning = false; private Boolean strictUuidReference = false; + + // TODO define a primaryNodeType Strategy private String primaryNodeType = null; - public String storagePath(Object obj) { - String clss = obj.getClass().getName(); - StringBuffer buf = new StringBuffer("/objects/"); - StringTokenizer st = new StringTokenizer(clss, "."); - while (st.hasMoreTokens()) { - buf.append(st.nextToken()).append('/'); + private ClassLoader classLoader = getClass().getClassLoader(); + + private NodeMapperProvider nodeMapperProvider; + + /** + * exposed method to retrieve a bean from a node + */ + public Object load(Node node) { + try { + if (nodeMapperProvider != null) { + NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node); + if (nodeMapper != this) { + return nodeMapper.load(node); + } + } + return nodeToBean(node); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot load object from node " + node, e); + } + } + + /** Update an existing node with an object */ + public void update(Node node, Object obj) { + try { + if (nodeMapperProvider != null) { + + NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node); + if (nodeMapper != this) { + nodeMapper.update(node, obj); + } else + beanToNode(createBeanWrapper(obj), node); + } else + beanToNode(createBeanWrapper(obj), node); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot update node " + node + " with " + + obj, e); } - buf.append(obj.toString()); - return buf.toString(); } - public Node saveOrUpdate(Session session, Object obj) { - return saveOrUpdate(session, storagePath(obj), obj); + /** + * if no storage path is given; we use canonical path + * + * @see this.storagePath() + */ + public Node save(Session session, Object obj) { + return save(session, storagePath(obj), obj); } - public Node saveOrUpdate(Session session, String path, Object obj) { + /** + * Create a new node to store an object. If the parentNode doesn't exist, it + * is created + * + * the primaryNodeType may be initialized before + */ + public Node save(Session session, String path, Object obj) { try { BeanWrapper beanWrapper = createBeanWrapper(obj); final Node node; - if (session.itemExists(path)) { - Item item = session.getItem(path); - if (!item.isNode()) - throw new ArgeoException("An item exist under " + path - + " but it is not a node: " + item); - node = (Node) item; - } else { - String parentPath = JcrUtils.parentPath(path); - Node parentNode; - if (session.itemExists(path)) - parentNode = (Node) session.getItem(parentPath); - else - parentNode = JcrUtils.mkdirs(session, parentPath, null, - versioning); - // create node - if (primaryNodeType != null) - node = parentNode.addNode(JcrUtils.lastPathElement(path), - primaryNodeType); - else - node = parentNode.addNode(JcrUtils.lastPathElement(path)); + String parentPath = JcrUtils.parentPath(path); + // find or create parent node + Node parentNode; + if (session.itemExists(path)) + parentNode = (Node) session.getItem(parentPath); + else { + parentNode = JcrUtils.mkdirs(session, parentPath, null, + versioning); } + // create node - beanToNode(session, beanWrapper, node); + if (primaryNodeType != null) + node = parentNode.addNode(JcrUtils.lastPathElement(path), + primaryNodeType); + else + node = parentNode.addNode(JcrUtils.lastPathElement(path)); + + // Check specific cases + if (nodeMapperProvider != null) { + + NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node); + if (nodeMapper != this) { + nodeMapper.update(node, obj); + return node; + } + } + update(node, obj); return node; } catch (ArgeoException e) { throw e; @@ -95,24 +137,31 @@ public class BeanNodeMapper { } } + /** + * Parse the FQN of a class to string with '/' delimiters Prefix the + * returned string with "/objects/" + */ + public String storagePath(Object obj) { + String clss = obj.getClass().getName(); + StringBuffer buf = new StringBuffer("/objects/"); + StringTokenizer st = new StringTokenizer(clss, "."); + while (st.hasMoreTokens()) { + buf.append(st.nextToken()).append('/'); + } + buf.append(obj.toString()); + return buf.toString(); + } + @SuppressWarnings("unchecked") - public Object nodeToBean(Node node) throws RepositoryException { + /** + * Transforms a node into an object of the class defined by classProperty Property + */ + protected 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); - } + BeanWrapper beanWrapper = createBeanWrapper(loadClass(clssName)); // process properties PropertyIterator propIt = node.getProperties(); @@ -125,7 +174,8 @@ public class BeanNodeMapper { .getName()); Class propClass = pd.getPropertyType(); - // list + // Process case of List and its derived classes + // primitive list if (propClass != null && List.class.isAssignableFrom(propClass)) { List lst = new ArrayList(); Class valuesClass = classFromProperty(prop); @@ -136,6 +186,8 @@ public class BeanNodeMapper { continue props; } + // Case of other type of property accepted by jcr + // Long, Double, String, Binary, Date, Boolean, Name Object value = asObject(prop.getValue(), pd.getPropertyType()); if (value != null) beanWrapper.setPropertyValue(prop.getName(), value); @@ -152,15 +204,13 @@ public class BeanNodeMapper { PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(name); Class propClass = pd.getPropertyType(); - log.debug(childNode.getName() + "=" + propClass); - + // objects list if (propClass != null && List.class.isAssignableFrom(propClass)) { String lstClass = childNode.getProperty(classProperty) .getString(); - // FIXME: use OSGi compatible class loading List lst; try { - lst = (List) Class.forName(lstClass).newInstance(); + lst = (List) loadClass(lstClass).newInstance(); } catch (Exception e) { lst = new ArrayList(); } @@ -176,13 +226,13 @@ public class BeanNodeMapper { continue nodes; } + // objects map if (propClass != null && Map.class.isAssignableFrom(propClass)) { String mapClass = childNode.getProperty(classProperty) .getString(); - // FIXME: use OSGi compatible class loading Map map; try { - map = (Map) Class.forName(mapClass) + map = (Map) loadClass(mapClass) .newInstance(); } catch (Exception e) { map = new HashMap(); @@ -228,21 +278,31 @@ public class BeanNodeMapper { return beanWrapper.getWrappedInstance(); } - protected void beanToNode(Session session, BeanWrapper beanWrapper, - Node node) throws RepositoryException { - if (log.isDebugEnabled()) - log.debug("Map bean to node " + node.getPath()); - + /** + * Transforms an object to the specified jcr Node in order to persist it. + * + * @param beanWrapper + * @param node + * @throws RepositoryException + */ + protected void beanToNode(BeanWrapper beanWrapper, Node node) + throws RepositoryException { properties: for (PropertyDescriptor pd : beanWrapper .getPropertyDescriptors()) { String name = pd.getName(); - if (!beanWrapper.isReadableProperty(name)) continue properties;// skip Object value = beanWrapper.getPropertyValue(name); - if (value == null) - continue properties;// skip + if (value == null) { + // remove values when updating + if (node.hasProperty(name)) + node.setProperty(name, (Value) null); + if (node.hasNode(name)) + node.getNode(name).remove(); + + continue properties; + } // if (uuidProperty != null && uuidProperty.equals(name)) { // // node.addMixin(ArgeoJcrConstants.MIX_REFERENCEABLE); @@ -259,12 +319,13 @@ public class BeanNodeMapper { continue properties; } + // Some bean reference other classes. We must deal with this case if (value instanceof Class) { node.setProperty(name, ((Class) value).getName()); continue properties; } - Value val = asValue(session, value); + Value val = asValue(node.getSession(), value); if (val != null) { node.setProperty(name, val); continue properties; @@ -272,51 +333,65 @@ public class BeanNodeMapper { if (value instanceof List) { List lst = (List) value; - addList(session, node, name, lst); + addList(node, name, lst); continue properties; } if (value instanceof Map) { Map map = (Map) value; - addMap(session, node, name, map); + addMap(node, name, map); continue properties; } BeanWrapper child = createBeanWrapper(value); // TODO: delegate to another mapper - Node childNode = findChildReference(session, child); - if (childNode != null) { - node.setProperty(name, childNode); - continue properties; - } - - // default case (recursive) - childNode = node.addNode(name); - beanToNode(session, child, childNode); - // if (childNode.isNodeType(ArgeoJcrConstants.MIX_REFERENCEABLE)) { - // log.debug("Add reference to " + childNode.getPath()); + // TODO: deal with references + // Node childNode = findChildReference(session, child); + // if (childNode != null) { // node.setProperty(name, childNode); + // continue properties; // } + + // default case (recursive) + if (node.hasNode(name)) {// update + // TODO: optimize + node.getNode(name).remove(); + } + Node childNode = node.addNode(name); + beanToNode(child, childNode); } } - protected void addList(Session session, Node node, String name, List lst) + /** + * Process specific case of list + * + * @param node + * @param name + * @param lst + * @throws RepositoryException + */ + protected void addList(Node node, String name, List lst) throws RepositoryException { + if (node.hasNode(name)) {// update + // TODO: optimize + node.getNode(name).remove(); + } + 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); + values[i] = asValue(node.getSession(), lstValue); if (values[i] != null) { atLeastOneSet = true; } else { - Node childNode = findChildReference(session, + Node childNode = findChildReference(node.getSession(), createBeanWrapper(lstValue)); if (childNode != null) { - values[i] = session.getValueFactory() + values[i] = node.getSession().getValueFactory() .createValue(childNode); atLeastOneSet = true; } @@ -327,16 +402,28 @@ public class BeanNodeMapper { if (!atLeastOneSet && lst.size() != 0) { for (Object lstValue : lst) { Node childNode = listNode.addNode(NODE_VALUE); - beanToNode(session, createBeanWrapper(lstValue), childNode); + beanToNode(createBeanWrapper(lstValue), childNode); } } else { listNode.setProperty(name, values); } } - protected void addMap(Session session, Node node, String name, Map map) + /** + * Process specific case of maps. + * + * @param node + * @param name + * @param map + * @throws RepositoryException + */ + protected void addMap(Node node, String name, Map map) throws RepositoryException { - // TODO: add map specific type + if (node.hasNode(name)) {// update + // TODO: optimize + node.getNode(name).remove(); + } + Node mapNode = node.addNode(name); mapNode.setProperty(classProperty, map.getClass().getName()); for (Object key : map.keySet()) { @@ -357,12 +444,12 @@ public class BeanNodeMapper { // } // TODO: check string format - Value mapVal = asValue(session, mapValue); + Value mapVal = asValue(node.getSession(), mapValue); if (mapVal != null) mapNode.setProperty(keyStr, mapVal); else { Node entryNode = mapNode.addNode(keyStr); - beanToNode(session, createBeanWrapper(mapValue), entryNode); + beanToNode(createBeanWrapper(mapValue), entryNode); } } @@ -373,6 +460,10 @@ public class BeanNodeMapper { return new BeanWrapperImpl(obj); } + protected BeanWrapper createBeanWrapper(Class clss) { + return new BeanWrapperImpl(clss); + } + /** Returns null if value cannot be found */ protected Value asValue(Session session, Object value) throws RepositoryException { @@ -462,6 +553,15 @@ public class BeanNodeMapper { return null; } + protected Class loadClass(String name) { + // log.debug("Class loader: " + classLoader); + try { + return classLoader.loadClass(name); + } catch (ClassNotFoundException e) { + throw new ArgeoException("Cannot load class " + name, e); + } + } + protected String propertyName(String name) { return name; } @@ -470,4 +570,35 @@ public class BeanNodeMapper { this.versioning = versioning; } + public void setUuidProperty(String uuidProperty) { + this.uuidProperty = uuidProperty; + } + + public void setClassProperty(String classProperty) { + this.classProperty = classProperty; + } + + public void setStrictUuidReference(Boolean strictUuidReference) { + this.strictUuidReference = strictUuidReference; + } + + public void setPrimaryNodeType(String primaryNodeType) { + this.primaryNodeType = primaryNodeType; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public void setNodeMapperProvider(NodeMapperProvider nodeMapperProvider) { + this.nodeMapperProvider = nodeMapperProvider; + } + + public String getPrimaryNodeType() { + return this.primaryNodeType; + } + + public String getClassProperty() { + return this.classProperty; + } }