import java.util.Map;
import java.util.StringTokenizer;
-import javax.jcr.Item;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
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";
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;
}
}
+ /**
+ * 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();
.getName());
Class propClass = pd.getPropertyType();
- // list
+ // Process case of List and its derived classes
+ // primitive list
if (propClass != null && List.class.isAssignableFrom(propClass)) {
List<Object> lst = new ArrayList<Object>();
Class<?> valuesClass = classFromProperty(prop);
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);
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<Object> lst;
try {
- lst = (List<Object>) Class.forName(lstClass).newInstance();
+ lst = (List<Object>) loadClass(lstClass).newInstance();
} catch (Exception e) {
lst = new ArrayList<Object>();
}
continue nodes;
}
+ // objects map
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)
+ map = (Map<Object, Object>) loadClass(mapClass)
.newInstance();
} catch (Exception e) {
map = new HashMap<Object, Object>();
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);
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;
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;
}
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()) {
// }
// 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);
}
}
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 {
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;
}
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;
+ }
}