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
) {
103 String parentPath
= JcrUtils
.parentPath(path
);
104 // find or create parent node
106 if (session
.itemExists(path
))
107 parentNode
= (Node
) session
.getItem(parentPath
);
109 parentNode
= JcrUtils
.mkdirs(session
, parentPath
, null,
114 if (primaryNodeType
!= null)
115 node
= parentNode
.addNode(JcrUtils
.lastPathElement(path
),
118 node
= parentNode
.addNode(JcrUtils
.lastPathElement(path
));
120 // Check specific cases
121 if (nodeMapperProvider
!= null) {
122 NodeMapper nodeMapper
= nodeMapperProvider
.findNodeMapper(node
);
123 if (nodeMapper
!= this) {
124 nodeMapper
.update(node
, obj
);
130 } catch (ArgeoException e
) {
132 } catch (Exception e
) {
133 throw new ArgeoException("Cannot save or update " + obj
+ " under "
139 * Parse the FQN of a class to string with '/' delimiters Prefix the
140 * returned string with "/objects/"
142 public String
storagePath(Object obj
) {
143 String clss
= obj
.getClass().getName();
144 StringBuffer buf
= new StringBuffer("/objects/");
145 StringTokenizer st
= new StringTokenizer(clss
, ".");
146 while (st
.hasMoreTokens()) {
147 buf
.append(st
.nextToken()).append('/');
149 buf
.append(obj
.toString());
150 return buf
.toString();
153 @SuppressWarnings("unchecked")
155 * Transforms a node into an object of the class defined by classProperty Property
157 protected Object
nodeToBean(Node node
) throws RepositoryException
{
158 if (log
.isTraceEnabled())
159 log
.trace("Load " + node
);
162 String clssName
= node
.getProperty(classProperty
).getValue()
165 BeanWrapper beanWrapper
= createBeanWrapper(loadClass(clssName
));
167 // process properties
168 PropertyIterator propIt
= node
.getProperties();
169 props
: while (propIt
.hasNext()) {
170 Property prop
= propIt
.nextProperty();
171 if (!beanWrapper
.isWritableProperty(prop
.getName()))
174 PropertyDescriptor pd
= beanWrapper
.getPropertyDescriptor(prop
176 Class propClass
= pd
.getPropertyType();
178 if (log
.isTraceEnabled())
179 log
.trace("Load " + prop
+ ", propClass=" + propClass
180 + ", property descriptor=" + pd
);
183 if (propClass
!= null && List
.class.isAssignableFrom(propClass
)) {
184 List
<Object
> lst
= new ArrayList
<Object
>();
185 Class
<?
> valuesClass
= classFromProperty(prop
);
186 if (valuesClass
!= null)
187 for (Value value
: prop
.getValues()) {
188 lst
.add(asObject(value
, valuesClass
));
193 // Case of other type of property accepted by jcr
194 // Long, Double, String, Binary, Date, Boolean, Name
195 Object value
= asObject(prop
.getValue(), pd
.getPropertyType());
197 beanWrapper
.setPropertyValue(prop
.getName(), value
);
200 // process children nodes
201 NodeIterator nodeIt
= node
.getNodes();
202 nodes
: while (nodeIt
.hasNext()) {
203 Node childNode
= nodeIt
.nextNode();
204 String name
= childNode
.getName();
205 if (!beanWrapper
.isWritableProperty(name
))
208 PropertyDescriptor pd
= beanWrapper
.getPropertyDescriptor(name
);
209 Class propClass
= pd
.getPropertyType();
212 if (propClass
!= null && List
.class.isAssignableFrom(propClass
)) {
213 String lstClass
= childNode
.getProperty(classProperty
)
217 lst
= (List
<Object
>) loadClass(lstClass
).newInstance();
218 } catch (Exception e
) {
219 lst
= new ArrayList
<Object
>();
222 if (childNode
.hasNodes()) {
223 // Look for children nodes
224 NodeIterator valuesIt
= childNode
.getNodes();
225 while (valuesIt
.hasNext()) {
226 Node lstValueNode
= valuesIt
.nextNode();
227 Object lstValue
= nodeToBean(lstValueNode
);
231 // look for a property with the same name which will
234 Property childProp
= childNode
.getProperty(childNode
236 Class
<?
> valuesClass
= classFromProperty(childProp
);
237 if (valuesClass
!= null)
238 if (childProp
.getDefinition().isMultiple())
239 for (Value value
: childProp
.getValues()) {
240 lst
.add(asObject(value
, valuesClass
));
243 lst
.add(asObject(childProp
.getValue(),
246 beanWrapper
.setPropertyValue(name
, lst
);
251 if (propClass
!= null && Map
.class.isAssignableFrom(propClass
)) {
252 String mapClass
= childNode
.getProperty(classProperty
)
254 Map
<Object
, Object
> map
;
256 map
= (Map
<Object
, Object
>) loadClass(mapClass
)
258 } catch (Exception e
) {
259 map
= new HashMap
<Object
, Object
>();
263 PropertyIterator keysPropIt
= childNode
.getProperties();
264 keyProps
: while (keysPropIt
.hasNext()) {
265 Property keyProp
= keysPropIt
.nextProperty();
266 // FIXME: use property editor
267 String key
= keyProp
.getName();
268 if (classProperty
.equals(key
))
271 Class keyPropClass
= classFromProperty(keyProp
);
272 if (keyPropClass
!= null) {
273 Object mapValue
= asObject(keyProp
.getValue(),
275 map
.put(key
, mapValue
);
280 NodeIterator keysIt
= childNode
.getNodes();
281 while (keysIt
.hasNext()) {
282 Node mapValueNode
= keysIt
.nextNode();
283 // FIXME: use property editor
284 Object key
= mapValueNode
.getName();
286 Object mapValue
= nodeToBean(mapValueNode
);
288 map
.put(key
, mapValue
);
290 beanWrapper
.setPropertyValue(name
, map
);
295 Object value
= nodeToBean(childNode
);
296 beanWrapper
.setPropertyValue(name
, value
);
299 return beanWrapper
.getWrappedInstance();
300 } catch (Exception e
) {
301 throw new ArgeoException("Cannot map node " + node
, e
);
306 * Transforms an object to the specified jcr Node in order to persist it.
310 * @throws RepositoryException
312 protected void beanToNode(BeanWrapper beanWrapper
, Node node
)
313 throws RepositoryException
{
314 properties
: for (PropertyDescriptor pd
: beanWrapper
315 .getPropertyDescriptors()) {
316 String name
= pd
.getName();
317 if (!beanWrapper
.isReadableProperty(name
))
318 continue properties
;// skip
320 Object value
= beanWrapper
.getPropertyValue(name
);
322 // remove values when updating
323 if (node
.hasProperty(name
))
324 node
.setProperty(name
, (Value
) null);
325 if (node
.hasNode(name
))
326 node
.getNode(name
).remove();
331 // if (uuidProperty != null && uuidProperty.equals(name)) {
332 // // node.addMixin(ArgeoJcrConstants.MIX_REFERENCEABLE);
333 // node.setProperty(ArgeoJcrConstants.JCR_UUID, value.toString());
334 // continue properties;
337 if ("class".equals(name
)) {
338 if (classProperty
!= null) {
339 node
.setProperty(classProperty
, ((Class
<?
>) value
)
341 // TODO: store a class hierarchy?
346 // Some bean reference other classes. We must deal with this case
347 if (value
instanceof Class
<?
>) {
348 node
.setProperty(name
, ((Class
<?
>) value
).getName());
352 Value val
= asValue(node
.getSession(), value
);
354 node
.setProperty(name
, val
);
358 if (value
instanceof List
<?
>) {
359 List
<?
> lst
= (List
<?
>) value
;
360 addList(node
, name
, lst
);
364 if (value
instanceof Map
<?
, ?
>) {
365 Map
<?
, ?
> map
= (Map
<?
, ?
>) value
;
366 addMap(node
, name
, map
);
370 BeanWrapper child
= createBeanWrapper(value
);
371 // TODO: delegate to another mapper
373 // TODO: deal with references
374 // Node childNode = findChildReference(session, child);
375 // if (childNode != null) {
376 // node.setProperty(name, childNode);
377 // continue properties;
380 // default case (recursive)
381 if (node
.hasNode(name
)) {// update
383 node
.getNode(name
).remove();
385 Node childNode
= node
.addNode(name
);
386 beanToNode(child
, childNode
);
391 * Process specific case of list
396 * @throws RepositoryException
398 protected void addList(Node node
, String name
, List
<?
> lst
)
399 throws RepositoryException
{
400 if (node
.hasNode(name
)) {// update
402 node
.getNode(name
).remove();
405 Node listNode
= node
.addNode(name
);
406 listNode
.setProperty(classProperty
, lst
.getClass().getName());
407 Value
[] values
= new Value
[lst
.size()];
408 boolean atLeastOneSet
= false;
409 for (int i
= 0; i
< lst
.size(); i
++) {
410 Object lstValue
= lst
.get(i
);
411 values
[i
] = asValue(node
.getSession(), lstValue
);
412 if (values
[i
] != null) {
413 atLeastOneSet
= true;
415 Node childNode
= findChildReference(node
.getSession(),
416 createBeanWrapper(lstValue
));
417 if (childNode
!= null) {
418 values
[i
] = node
.getSession().getValueFactory()
419 .createValue(childNode
);
420 atLeastOneSet
= true;
425 // will be either properties or nodes, not both
426 if (!atLeastOneSet
&& lst
.size() != 0) {
427 for (Object lstValue
: lst
) {
428 Node childNode
= listNode
.addNode(NODE_VALUE
);
429 beanToNode(createBeanWrapper(lstValue
), childNode
);
432 listNode
.setProperty(name
, values
);
437 * Process specific case of maps.
442 * @throws RepositoryException
444 protected void addMap(Node node
, String name
, Map
<?
, ?
> map
)
445 throws RepositoryException
{
446 if (node
.hasNode(name
)) {// update
448 node
.getNode(name
).remove();
451 Node mapNode
= node
.addNode(name
);
452 mapNode
.setProperty(classProperty
, map
.getClass().getName());
453 for (Object key
: map
.keySet()) {
454 Object mapValue
= map
.get(key
);
455 // PropertyEditor pe = beanWrapper.findCustomEditor(key.getClass(),
459 if (key
instanceof CharSequence
)
460 keyStr
= key
.toString();
462 throw new ArgeoException(
463 "Cannot find property editor for class "
467 // keyStr = pe.getAsText();
469 // TODO: check string format
471 Value mapVal
= asValue(node
.getSession(), mapValue
);
473 mapNode
.setProperty(keyStr
, mapVal
);
475 Node entryNode
= mapNode
.addNode(keyStr
);
476 beanToNode(createBeanWrapper(mapValue
), entryNode
);
483 protected BeanWrapper
createBeanWrapper(Object obj
) {
484 return new BeanWrapperImpl(obj
);
487 protected BeanWrapper
createBeanWrapper(Class
<?
> clss
) {
488 return new BeanWrapperImpl(clss
);
491 /** Returns null if value cannot be found */
492 protected Value
asValue(Session session
, Object value
)
493 throws RepositoryException
{
494 ValueFactory valueFactory
= session
.getValueFactory();
495 if (value
instanceof Integer
)
496 return valueFactory
.createValue((Integer
) value
);
497 else if (value
instanceof Long
)
498 return valueFactory
.createValue((Long
) value
);
499 else if (value
instanceof Float
)
500 return valueFactory
.createValue((Float
) value
);
501 else if (value
instanceof Double
)
502 return valueFactory
.createValue((Double
) value
);
503 else if (value
instanceof Boolean
)
504 return valueFactory
.createValue((Boolean
) value
);
505 else if (value
instanceof Calendar
)
506 return valueFactory
.createValue((Calendar
) value
);
507 else if (value
instanceof Date
) {
508 Calendar cal
= new GregorianCalendar();
509 cal
.setTime((Date
) value
);
510 return valueFactory
.createValue(cal
);
511 } else if (value
instanceof CharSequence
)
512 return valueFactory
.createValue(value
.toString());
513 else if (value
instanceof InputStream
)
514 return valueFactory
.createValue((InputStream
) value
);
519 protected Class
<?
> classFromProperty(Property property
)
520 throws RepositoryException
{
521 switch (property
.getType()) {
522 case PropertyType
.LONG
:
524 case PropertyType
.DOUBLE
:
526 case PropertyType
.STRING
:
528 case PropertyType
.BOOLEAN
:
529 return Boolean
.class;
530 case PropertyType
.DATE
:
531 return Calendar
.class;
532 case PropertyType
.NAME
:
535 throw new ArgeoException("Cannot find class for property "
536 + property
+ ", type="
537 + PropertyType
.nameFromValue(property
.getType()));
541 protected Object
asObject(Value value
, Class
<?
> propClass
)
542 throws RepositoryException
{
543 if (propClass
.equals(Integer
.class))
544 return (int) value
.getLong();
545 else if (propClass
.equals(Long
.class))
546 return value
.getLong();
547 else if (propClass
.equals(Float
.class))
548 return (float) value
.getDouble();
549 else if (propClass
.equals(Double
.class))
550 return value
.getDouble();
551 else if (propClass
.equals(Boolean
.class))
552 return value
.getBoolean();
553 else if (CharSequence
.class.isAssignableFrom(propClass
))
554 return value
.getString();
555 else if (InputStream
.class.isAssignableFrom(propClass
))
556 return value
.getStream();
557 else if (Calendar
.class.isAssignableFrom(propClass
))
558 return value
.getDate();
559 else if (Date
.class.isAssignableFrom(propClass
))
560 return value
.getDate().getTime();
565 protected Node
findChildReference(Session session
, BeanWrapper child
)
566 throws RepositoryException
{
567 if (child
.isReadableProperty(uuidProperty
)) {
568 String childUuid
= child
.getPropertyValue(uuidProperty
).toString();
570 return session
.getNodeByUUID(childUuid
);
571 } catch (ItemNotFoundException e
) {
572 if (strictUuidReference
)
573 throw new ArgeoException("No node found with uuid "
580 protected Class
<?
> loadClass(String name
) {
581 // log.debug("Class loader: " + classLoader);
583 return classLoader
.loadClass(name
);
584 } catch (ClassNotFoundException e
) {
585 throw new ArgeoException("Cannot load class " + name
, e
);
589 protected String
propertyName(String name
) {
593 public void setVersioning(Boolean versioning
) {
594 this.versioning
= versioning
;
597 public void setUuidProperty(String uuidProperty
) {
598 this.uuidProperty
= uuidProperty
;
601 public void setClassProperty(String classProperty
) {
602 this.classProperty
= classProperty
;
605 public void setStrictUuidReference(Boolean strictUuidReference
) {
606 this.strictUuidReference
= strictUuidReference
;
609 public void setPrimaryNodeType(String primaryNodeType
) {
610 this.primaryNodeType
= primaryNodeType
;
613 public void setClassLoader(ClassLoader classLoader
) {
614 this.classLoader
= classLoader
;
617 public void setNodeMapperProvider(NodeMapperProvider nodeMapperProvider
) {
618 this.nodeMapperProvider
= nodeMapperProvider
;
621 public String
getPrimaryNodeType() {
622 return this.primaryNodeType
;
625 public String
getClassProperty() {
626 return this.classProperty
;