3 import java
.beans
.PropertyDescriptor
;
4 import java
.io
.InputStream
;
5 import java
.util
.ArrayList
;
6 import java
.util
.Calendar
;
8 import java
.util
.GregorianCalendar
;
9 import java
.util
.HashMap
;
10 import java
.util
.List
;
12 import java
.util
.StringTokenizer
;
14 import javax
.jcr
.ItemNotFoundException
;
15 import javax
.jcr
.Node
;
16 import javax
.jcr
.NodeIterator
;
17 import javax
.jcr
.Property
;
18 import javax
.jcr
.PropertyIterator
;
19 import javax
.jcr
.PropertyType
;
20 import javax
.jcr
.RepositoryException
;
21 import javax
.jcr
.Session
;
22 import javax
.jcr
.Value
;
23 import javax
.jcr
.ValueFactory
;
25 import org
.apache
.commons
.logging
.Log
;
26 import org
.apache
.commons
.logging
.LogFactory
;
27 import org
.argeo
.ArgeoException
;
28 import org
.springframework
.beans
.BeanWrapper
;
29 import org
.springframework
.beans
.BeanWrapperImpl
;
31 public class BeanNodeMapper
implements NodeMapper
{
32 private final static Log log
= LogFactory
.getLog(BeanNodeMapper
.class);
34 private final static String NODE_VALUE
= "value";
36 // private String keyNode = "bean:key";
37 private String uuidProperty
= "uuid";
38 private String classProperty
= "class";
40 private Boolean versioning
= false;
41 private Boolean strictUuidReference
= false;
43 // TODO define a primaryNodeType Strategy
44 private String primaryNodeType
= null;
46 private ClassLoader classLoader
= getClass().getClassLoader();
48 private NodeMapperProvider nodeMapperProvider
;
51 * exposed method to retrieve a bean from a node
53 public Object
load(Node node
) {
55 if (nodeMapperProvider
!= null) {
56 NodeMapper nodeMapper
= nodeMapperProvider
.findNodeMapper(node
);
57 if (nodeMapper
!= this) {
58 return nodeMapper
.load(node
);
61 return nodeToBean(node
);
62 } catch (RepositoryException e
) {
63 throw new ArgeoException("Cannot load object from node " + node
, e
);
67 /** Update an existing node with an object */
68 public void update(Node node
, Object obj
) {
70 if (nodeMapperProvider
!= null) {
72 NodeMapper nodeMapper
= nodeMapperProvider
.findNodeMapper(node
);
73 if (nodeMapper
!= this) {
74 nodeMapper
.update(node
, obj
);
76 beanToNode(createBeanWrapper(obj
), node
);
78 beanToNode(createBeanWrapper(obj
), node
);
79 } catch (RepositoryException e
) {
80 throw new ArgeoException("Cannot update node " + node
+ " with "
86 * if no storage path is given; we use canonical path
88 * @see this.storagePath()
90 public Node
save(Session session
, Object obj
) {
91 return save(session
, storagePath(obj
), obj
);
95 * Create a new node to store an object. If the parentNode doesn't exist, it
98 * the primaryNodeType may be initialized before
100 public Node
save(Session session
, String path
, Object obj
) {
102 BeanWrapper beanWrapper
= createBeanWrapper(obj
);
104 String parentPath
= JcrUtils
.parentPath(path
);
105 // find or create parent node
107 if (session
.itemExists(path
))
108 parentNode
= (Node
) session
.getItem(parentPath
);
110 parentNode
= JcrUtils
.mkdirs(session
, parentPath
, null,
115 if (primaryNodeType
!= null)
116 node
= parentNode
.addNode(JcrUtils
.lastPathElement(path
),
119 node
= parentNode
.addNode(JcrUtils
.lastPathElement(path
));
121 // Check specific cases
122 if (nodeMapperProvider
!= null) {
124 NodeMapper nodeMapper
= nodeMapperProvider
.findNodeMapper(node
);
125 if (nodeMapper
!= this) {
126 nodeMapper
.update(node
, obj
);
132 } catch (ArgeoException e
) {
134 } catch (Exception e
) {
135 throw new ArgeoException("Cannot save or update " + obj
+ " under "
141 * Parse the FQN of a class to string with '/' delimiters Prefix the
142 * returned string with "/objects/"
144 public String
storagePath(Object obj
) {
145 String clss
= obj
.getClass().getName();
146 StringBuffer buf
= new StringBuffer("/objects/");
147 StringTokenizer st
= new StringTokenizer(clss
, ".");
148 while (st
.hasMoreTokens()) {
149 buf
.append(st
.nextToken()).append('/');
151 buf
.append(obj
.toString());
152 return buf
.toString();
155 @SuppressWarnings("unchecked")
157 * Transforms a node into an object of the class defined by classProperty Property
159 protected Object
nodeToBean(Node node
) throws RepositoryException
{
161 String clssName
= node
.getProperty(classProperty
).getValue()
164 BeanWrapper beanWrapper
= createBeanWrapper(loadClass(clssName
));
166 // process properties
167 PropertyIterator propIt
= node
.getProperties();
168 props
: while (propIt
.hasNext()) {
169 Property prop
= propIt
.nextProperty();
170 if (!beanWrapper
.isWritableProperty(prop
.getName()))
173 PropertyDescriptor pd
= beanWrapper
.getPropertyDescriptor(prop
175 Class propClass
= pd
.getPropertyType();
177 // Process case of List and its derived classes
179 if (propClass
!= null && List
.class.isAssignableFrom(propClass
)) {
180 List
<Object
> lst
= new ArrayList
<Object
>();
181 Class
<?
> valuesClass
= classFromProperty(prop
);
182 if (valuesClass
!= null)
183 for (Value value
: prop
.getValues()) {
184 lst
.add(asObject(value
, valuesClass
));
189 // Case of other type of property accepted by jcr
190 // Long, Double, String, Binary, Date, Boolean, Name
191 Object value
= asObject(prop
.getValue(), pd
.getPropertyType());
193 beanWrapper
.setPropertyValue(prop
.getName(), value
);
196 // process children nodes
197 NodeIterator nodeIt
= node
.getNodes();
198 nodes
: while (nodeIt
.hasNext()) {
199 Node childNode
= nodeIt
.nextNode();
200 String name
= childNode
.getName();
201 if (!beanWrapper
.isWritableProperty(name
))
204 PropertyDescriptor pd
= beanWrapper
.getPropertyDescriptor(name
);
205 Class propClass
= pd
.getPropertyType();
208 if (propClass
!= null && List
.class.isAssignableFrom(propClass
)) {
209 String lstClass
= childNode
.getProperty(classProperty
)
213 lst
= (List
<Object
>) loadClass(lstClass
).newInstance();
214 } catch (Exception e
) {
215 lst
= new ArrayList
<Object
>();
218 NodeIterator valuesIt
= childNode
.getNodes();
219 while (valuesIt
.hasNext()) {
220 Node lstValueNode
= valuesIt
.nextNode();
221 Object lstValue
= nodeToBean(lstValueNode
);
225 beanWrapper
.setPropertyValue(name
, lst
);
230 if (propClass
!= null && Map
.class.isAssignableFrom(propClass
)) {
231 String mapClass
= childNode
.getProperty(classProperty
)
233 Map
<Object
, Object
> map
;
235 map
= (Map
<Object
, Object
>) loadClass(mapClass
)
237 } catch (Exception e
) {
238 map
= new HashMap
<Object
, Object
>();
242 PropertyIterator keysPropIt
= childNode
.getProperties();
243 keyProps
: while (keysPropIt
.hasNext()) {
244 Property keyProp
= keysPropIt
.nextProperty();
245 // FIXME: use property editor
246 String key
= keyProp
.getName();
247 if (classProperty
.equals(key
))
250 Class keyPropClass
= classFromProperty(keyProp
);
251 if (keyPropClass
!= null) {
252 Object mapValue
= asObject(keyProp
.getValue(),
254 map
.put(key
, mapValue
);
259 NodeIterator keysIt
= childNode
.getNodes();
260 while (keysIt
.hasNext()) {
261 Node mapValueNode
= keysIt
.nextNode();
262 // FIXME: use property editor
263 Object key
= mapValueNode
.getName();
265 Object mapValue
= nodeToBean(mapValueNode
);
267 map
.put(key
, mapValue
);
269 beanWrapper
.setPropertyValue(name
, map
);
274 Object value
= nodeToBean(childNode
);
275 beanWrapper
.setPropertyValue(name
, value
);
278 return beanWrapper
.getWrappedInstance();
282 * Transforms an object to the specified jcr Node in order to persist it.
286 * @throws RepositoryException
288 protected void beanToNode(BeanWrapper beanWrapper
, Node node
)
289 throws RepositoryException
{
290 properties
: for (PropertyDescriptor pd
: beanWrapper
291 .getPropertyDescriptors()) {
292 String name
= pd
.getName();
293 if (!beanWrapper
.isReadableProperty(name
))
294 continue properties
;// skip
296 Object value
= beanWrapper
.getPropertyValue(name
);
298 // remove values when updating
299 if (node
.hasProperty(name
))
300 node
.setProperty(name
, (Value
) null);
301 if (node
.hasNode(name
))
302 node
.getNode(name
).remove();
307 // if (uuidProperty != null && uuidProperty.equals(name)) {
308 // // node.addMixin(ArgeoJcrConstants.MIX_REFERENCEABLE);
309 // node.setProperty(ArgeoJcrConstants.JCR_UUID, value.toString());
310 // continue properties;
313 if ("class".equals(name
)) {
314 if (classProperty
!= null) {
315 node
.setProperty(classProperty
, ((Class
<?
>) value
)
317 // TODO: store a class hierarchy?
322 // Some bean reference other classes. We must deal with this case
323 if (value
instanceof Class
<?
>) {
324 node
.setProperty(name
, ((Class
<?
>) value
).getName());
328 Value val
= asValue(node
.getSession(), value
);
330 node
.setProperty(name
, val
);
334 if (value
instanceof List
<?
>) {
335 List
<?
> lst
= (List
<?
>) value
;
336 addList(node
, name
, lst
);
340 if (value
instanceof Map
<?
, ?
>) {
341 Map
<?
, ?
> map
= (Map
<?
, ?
>) value
;
342 addMap(node
, name
, map
);
346 BeanWrapper child
= createBeanWrapper(value
);
347 // TODO: delegate to another mapper
349 // TODO: deal with references
350 // Node childNode = findChildReference(session, child);
351 // if (childNode != null) {
352 // node.setProperty(name, childNode);
353 // continue properties;
356 // default case (recursive)
357 if (node
.hasNode(name
)) {// update
359 node
.getNode(name
).remove();
361 Node childNode
= node
.addNode(name
);
362 beanToNode(child
, childNode
);
367 * Process specific case of list
372 * @throws RepositoryException
374 protected void addList(Node node
, String name
, List
<?
> lst
)
375 throws RepositoryException
{
376 if (node
.hasNode(name
)) {// update
378 node
.getNode(name
).remove();
381 Node listNode
= node
.addNode(name
);
382 listNode
.setProperty(classProperty
, lst
.getClass().getName());
383 Value
[] values
= new Value
[lst
.size()];
384 boolean atLeastOneSet
= false;
385 for (int i
= 0; i
< lst
.size(); i
++) {
386 Object lstValue
= lst
.get(i
);
387 values
[i
] = asValue(node
.getSession(), lstValue
);
388 if (values
[i
] != null) {
389 atLeastOneSet
= true;
391 Node childNode
= findChildReference(node
.getSession(),
392 createBeanWrapper(lstValue
));
393 if (childNode
!= null) {
394 values
[i
] = node
.getSession().getValueFactory()
395 .createValue(childNode
);
396 atLeastOneSet
= true;
401 // will be either properties or nodes, not both
402 if (!atLeastOneSet
&& lst
.size() != 0) {
403 for (Object lstValue
: lst
) {
404 Node childNode
= listNode
.addNode(NODE_VALUE
);
405 beanToNode(createBeanWrapper(lstValue
), childNode
);
408 listNode
.setProperty(name
, values
);
413 * Process specific case of maps.
418 * @throws RepositoryException
420 protected void addMap(Node node
, String name
, Map
<?
, ?
> map
)
421 throws RepositoryException
{
422 if (node
.hasNode(name
)) {// update
424 node
.getNode(name
).remove();
427 Node mapNode
= node
.addNode(name
);
428 mapNode
.setProperty(classProperty
, map
.getClass().getName());
429 for (Object key
: map
.keySet()) {
430 Object mapValue
= map
.get(key
);
431 // PropertyEditor pe = beanWrapper.findCustomEditor(key.getClass(),
435 if (key
instanceof CharSequence
)
436 keyStr
= key
.toString();
438 throw new ArgeoException(
439 "Cannot find property editor for class "
443 // keyStr = pe.getAsText();
445 // TODO: check string format
447 Value mapVal
= asValue(node
.getSession(), mapValue
);
449 mapNode
.setProperty(keyStr
, mapVal
);
451 Node entryNode
= mapNode
.addNode(keyStr
);
452 beanToNode(createBeanWrapper(mapValue
), entryNode
);
459 protected BeanWrapper
createBeanWrapper(Object obj
) {
460 return new BeanWrapperImpl(obj
);
463 protected BeanWrapper
createBeanWrapper(Class
<?
> clss
) {
464 return new BeanWrapperImpl(clss
);
467 /** Returns null if value cannot be found */
468 protected Value
asValue(Session session
, Object value
)
469 throws RepositoryException
{
470 ValueFactory valueFactory
= session
.getValueFactory();
471 if (value
instanceof Integer
)
472 return valueFactory
.createValue((Integer
) value
);
473 else if (value
instanceof Long
)
474 return valueFactory
.createValue((Long
) value
);
475 else if (value
instanceof Float
)
476 return valueFactory
.createValue((Float
) value
);
477 else if (value
instanceof Double
)
478 return valueFactory
.createValue((Double
) value
);
479 else if (value
instanceof Boolean
)
480 return valueFactory
.createValue((Boolean
) value
);
481 else if (value
instanceof Calendar
)
482 return valueFactory
.createValue((Calendar
) value
);
483 else if (value
instanceof Date
) {
484 Calendar cal
= new GregorianCalendar();
485 cal
.setTime((Date
) value
);
486 return valueFactory
.createValue(cal
);
487 } else if (value
instanceof CharSequence
)
488 return valueFactory
.createValue(value
.toString());
489 else if (value
instanceof InputStream
)
490 return valueFactory
.createValue((InputStream
) value
);
495 protected Class
<?
> classFromProperty(Property property
)
496 throws RepositoryException
{
497 switch (property
.getType()) {
498 case PropertyType
.LONG
:
500 case PropertyType
.DOUBLE
:
502 case PropertyType
.STRING
:
504 case PropertyType
.BOOLEAN
:
505 return Boolean
.class;
506 case PropertyType
.DATE
:
507 return Calendar
.class;
508 case PropertyType
.NAME
:
511 throw new ArgeoException("Cannot find class for property "
512 + property
+ ", type="
513 + PropertyType
.nameFromValue(property
.getType()));
517 protected Object
asObject(Value value
, Class
<?
> propClass
)
518 throws RepositoryException
{
519 if (propClass
.equals(Integer
.class))
520 return (int) value
.getLong();
521 else if (propClass
.equals(Long
.class))
522 return value
.getLong();
523 else if (propClass
.equals(Float
.class))
524 return (float) value
.getDouble();
525 else if (propClass
.equals(Double
.class))
526 return value
.getDouble();
527 else if (propClass
.equals(Boolean
.class))
528 return value
.getBoolean();
529 else if (CharSequence
.class.isAssignableFrom(propClass
))
530 return value
.getString();
531 else if (InputStream
.class.isAssignableFrom(propClass
))
532 return value
.getStream();
533 else if (Calendar
.class.isAssignableFrom(propClass
))
534 return value
.getDate();
535 else if (Date
.class.isAssignableFrom(propClass
))
536 return value
.getDate().getTime();
541 protected Node
findChildReference(Session session
, BeanWrapper child
)
542 throws RepositoryException
{
543 if (child
.isReadableProperty(uuidProperty
)) {
544 String childUuid
= child
.getPropertyValue(uuidProperty
).toString();
546 return session
.getNodeByUUID(childUuid
);
547 } catch (ItemNotFoundException e
) {
548 if (strictUuidReference
)
549 throw new ArgeoException("No node found with uuid "
556 protected Class
<?
> loadClass(String name
) {
557 // log.debug("Class loader: " + classLoader);
559 return classLoader
.loadClass(name
);
560 } catch (ClassNotFoundException e
) {
561 throw new ArgeoException("Cannot load class " + name
, e
);
565 protected String
propertyName(String name
) {
569 public void setVersioning(Boolean versioning
) {
570 this.versioning
= versioning
;
573 public void setUuidProperty(String uuidProperty
) {
574 this.uuidProperty
= uuidProperty
;
577 public void setClassProperty(String classProperty
) {
578 this.classProperty
= classProperty
;
581 public void setStrictUuidReference(Boolean strictUuidReference
) {
582 this.strictUuidReference
= strictUuidReference
;
585 public void setPrimaryNodeType(String primaryNodeType
) {
586 this.primaryNodeType
= primaryNodeType
;
589 public void setClassLoader(ClassLoader classLoader
) {
590 this.classLoader
= classLoader
;
593 public void setNodeMapperProvider(NodeMapperProvider nodeMapperProvider
) {
594 this.nodeMapperProvider
= nodeMapperProvider
;
597 public String
getPrimaryNodeType() {
598 return this.primaryNodeType
;
601 public String
getClassProperty() {
602 return this.classProperty
;