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 //import org.springframework.beans.BeanWrapperImpl;
33 public class BeanNodeMapper
{
34 private final static Log log
= LogFactory
.getLog(BeanNodeMapper
.class);
36 private final static String NODE_VALUE
= "value";
38 // private String keyNode = "bean:key";
39 private String uuidProperty
= "uuid";
40 private String classProperty
= "class";
42 private Boolean versioning
= false;
43 private Boolean strictUuidReference
= false;
44 private String primaryNodeType
= null;
46 private ClassLoader classLoader
= getClass().getClassLoader();
48 public String
storagePath(Object obj
) {
49 String clss
= obj
.getClass().getName();
50 StringBuffer buf
= new StringBuffer("/objects/");
51 StringTokenizer st
= new StringTokenizer(clss
, ".");
52 while (st
.hasMoreTokens()) {
53 buf
.append(st
.nextToken()).append('/');
55 buf
.append(obj
.toString());
56 return buf
.toString();
59 public Node
save(Session session
, Object obj
) {
60 return save(session
, storagePath(obj
), obj
);
63 public void update(Node node
, Object obj
) {
65 beanToNode(createBeanWrapper(obj
), node
);
66 } catch (RepositoryException e
) {
67 throw new ArgeoException("Cannot update node " + node
+ " with "
72 public Node
save(Session session
, String path
, Object obj
) {
74 BeanWrapper beanWrapper
= createBeanWrapper(obj
);
76 String parentPath
= JcrUtils
.parentPath(path
);
77 // find or create parent node
79 if (session
.itemExists(path
))
80 parentNode
= (Node
) session
.getItem(parentPath
);
82 parentNode
= JcrUtils
.mkdirs(session
, parentPath
, null,
85 if (primaryNodeType
!= null)
86 node
= parentNode
.addNode(JcrUtils
.lastPathElement(path
),
89 node
= parentNode
.addNode(JcrUtils
.lastPathElement(path
));
91 beanToNode(beanWrapper
, node
);
93 } catch (ArgeoException e
) {
95 } catch (Exception e
) {
96 throw new ArgeoException("Cannot save or update " + obj
+ " under "
101 @SuppressWarnings("unchecked")
102 public Object
nodeToBean(Node node
) throws RepositoryException
{
104 String clssName
= node
.getProperty(classProperty
).getValue()
107 if (log
.isDebugEnabled())
108 log
.debug("Map node " + node
.getPath() + " to bean " + clssName
);
110 BeanWrapper beanWrapper
= createBeanWrapper(loadClass(clssName
));
112 // process properties
113 PropertyIterator propIt
= node
.getProperties();
114 props
: while (propIt
.hasNext()) {
115 Property prop
= propIt
.nextProperty();
116 if (!beanWrapper
.isWritableProperty(prop
.getName()))
119 PropertyDescriptor pd
= beanWrapper
.getPropertyDescriptor(prop
121 Class propClass
= pd
.getPropertyType();
124 if (propClass
!= null && List
.class.isAssignableFrom(propClass
)) {
125 List
<Object
> lst
= new ArrayList
<Object
>();
126 Class
<?
> valuesClass
= classFromProperty(prop
);
127 if (valuesClass
!= null)
128 for (Value value
: prop
.getValues()) {
129 lst
.add(asObject(value
, valuesClass
));
134 Object value
= asObject(prop
.getValue(), pd
.getPropertyType());
136 beanWrapper
.setPropertyValue(prop
.getName(), value
);
139 // process children nodes
140 NodeIterator nodeIt
= node
.getNodes();
141 nodes
: while (nodeIt
.hasNext()) {
142 Node childNode
= nodeIt
.nextNode();
143 String name
= childNode
.getName();
144 if (!beanWrapper
.isWritableProperty(name
))
147 PropertyDescriptor pd
= beanWrapper
.getPropertyDescriptor(name
);
148 Class propClass
= pd
.getPropertyType();
150 log
.debug(childNode
.getName() + "=" + propClass
);
152 if (propClass
!= null && List
.class.isAssignableFrom(propClass
)) {
153 String lstClass
= childNode
.getProperty(classProperty
)
157 lst
= (List
<Object
>) loadClass(lstClass
).newInstance();
158 } catch (Exception e
) {
159 lst
= new ArrayList
<Object
>();
162 NodeIterator valuesIt
= childNode
.getNodes();
163 while (valuesIt
.hasNext()) {
164 Node lstValueNode
= valuesIt
.nextNode();
165 Object lstValue
= nodeToBean(lstValueNode
);
169 beanWrapper
.setPropertyValue(name
, lst
);
173 if (propClass
!= null && Map
.class.isAssignableFrom(propClass
)) {
174 String mapClass
= childNode
.getProperty(classProperty
)
176 Map
<Object
, Object
> map
;
178 map
= (Map
<Object
, Object
>) loadClass(mapClass
)
180 } catch (Exception e
) {
181 map
= new HashMap
<Object
, Object
>();
185 PropertyIterator keysPropIt
= childNode
.getProperties();
186 keyProps
: while (keysPropIt
.hasNext()) {
187 Property keyProp
= keysPropIt
.nextProperty();
188 // FIXME: use property editor
189 String key
= keyProp
.getName();
190 if (classProperty
.equals(key
))
193 Class keyPropClass
= classFromProperty(keyProp
);
194 if (keyPropClass
!= null) {
195 Object mapValue
= asObject(keyProp
.getValue(),
197 map
.put(key
, mapValue
);
202 NodeIterator keysIt
= childNode
.getNodes();
203 while (keysIt
.hasNext()) {
204 Node mapValueNode
= keysIt
.nextNode();
205 // FIXME: use property editor
206 Object key
= mapValueNode
.getName();
208 Object mapValue
= nodeToBean(mapValueNode
);
210 map
.put(key
, mapValue
);
212 beanWrapper
.setPropertyValue(name
, map
);
217 Object value
= nodeToBean(childNode
);
218 beanWrapper
.setPropertyValue(name
, value
);
221 return beanWrapper
.getWrappedInstance();
224 protected void beanToNode(BeanWrapper beanWrapper
, Node node
)
225 throws RepositoryException
{
226 if (log
.isDebugEnabled())
227 log
.debug("Map bean to node " + node
.getPath());
229 properties
: for (PropertyDescriptor pd
: beanWrapper
230 .getPropertyDescriptors()) {
231 String name
= pd
.getName();
233 if (!beanWrapper
.isReadableProperty(name
))
234 continue properties
;// skip
236 Object value
= beanWrapper
.getPropertyValue(name
);
238 // remove values when updating
239 if (node
.hasProperty(name
))
240 node
.setProperty(name
, (Value
) null);
241 if (node
.hasNode(name
))
242 node
.getNode(name
).remove();
247 // if (uuidProperty != null && uuidProperty.equals(name)) {
248 // // node.addMixin(ArgeoJcrConstants.MIX_REFERENCEABLE);
249 // node.setProperty(ArgeoJcrConstants.JCR_UUID, value.toString());
250 // continue properties;
253 if ("class".equals(name
)) {
254 if (classProperty
!= null) {
255 node
.setProperty(classProperty
, ((Class
<?
>) value
)
257 // TODO: store a class hierarchy?
262 if (value
instanceof Class
<?
>) {
263 node
.setProperty(name
, ((Class
<?
>) value
).getName());
267 Value val
= asValue(node
.getSession(), value
);
269 node
.setProperty(name
, val
);
273 if (value
instanceof List
<?
>) {
274 List
<?
> lst
= (List
<?
>) value
;
275 addList(node
, name
, lst
);
279 if (value
instanceof Map
<?
, ?
>) {
280 Map
<?
, ?
> map
= (Map
<?
, ?
>) value
;
281 addMap(node
, name
, map
);
285 BeanWrapper child
= createBeanWrapper(value
);
286 // TODO: delegate to another mapper
288 // TODO: deal with references
289 // Node childNode = findChildReference(session, child);
290 // if (childNode != null) {
291 // node.setProperty(name, childNode);
292 // continue properties;
295 // default case (recursive)
296 if (node
.hasNode(name
)) {// update
298 node
.getNode(name
).remove();
300 Node childNode
= node
.addNode(name
);
301 beanToNode(child
, childNode
);
305 protected void addList(Node node
, String name
, List
<?
> lst
)
306 throws RepositoryException
{
307 if (node
.hasNode(name
)) {// update
309 node
.getNode(name
).remove();
312 Node listNode
= node
.addNode(name
);
313 listNode
.setProperty(classProperty
, lst
.getClass().getName());
314 Value
[] values
= new Value
[lst
.size()];
315 boolean atLeastOneSet
= false;
316 for (int i
= 0; i
< lst
.size(); i
++) {
317 Object lstValue
= lst
.get(i
);
318 values
[i
] = asValue(node
.getSession(), lstValue
);
319 if (values
[i
] != null) {
320 atLeastOneSet
= true;
322 Node childNode
= findChildReference(node
.getSession(),
323 createBeanWrapper(lstValue
));
324 if (childNode
!= null) {
325 values
[i
] = node
.getSession().getValueFactory()
326 .createValue(childNode
);
327 atLeastOneSet
= true;
332 // will be either properties or nodes, not both
333 if (!atLeastOneSet
&& lst
.size() != 0) {
334 for (Object lstValue
: lst
) {
335 Node childNode
= listNode
.addNode(NODE_VALUE
);
336 beanToNode(createBeanWrapper(lstValue
), childNode
);
339 listNode
.setProperty(name
, values
);
343 protected void addMap(Node node
, String name
, Map
<?
, ?
> map
)
344 throws RepositoryException
{
345 if (node
.hasNode(name
)) {// update
347 node
.getNode(name
).remove();
350 Node mapNode
= node
.addNode(name
);
351 mapNode
.setProperty(classProperty
, map
.getClass().getName());
352 for (Object key
: map
.keySet()) {
353 Object mapValue
= map
.get(key
);
354 // PropertyEditor pe = beanWrapper.findCustomEditor(key.getClass(),
358 if (key
instanceof CharSequence
)
359 keyStr
= key
.toString();
361 throw new ArgeoException(
362 "Cannot find property editor for class "
366 // keyStr = pe.getAsText();
368 // TODO: check string format
370 Value mapVal
= asValue(node
.getSession(), mapValue
);
372 mapNode
.setProperty(keyStr
, mapVal
);
374 Node entryNode
= mapNode
.addNode(keyStr
);
375 beanToNode(createBeanWrapper(mapValue
), entryNode
);
382 protected BeanWrapper
createBeanWrapper(Object obj
) {
383 return new BeanWrapperImpl(obj
);
386 protected BeanWrapper
createBeanWrapper(Class
<?
> clss
) {
387 return new BeanWrapperImpl(clss
);
390 /** Returns null if value cannot be found */
391 protected Value
asValue(Session session
, Object value
)
392 throws RepositoryException
{
393 ValueFactory valueFactory
= session
.getValueFactory();
394 if (value
instanceof Integer
)
395 return valueFactory
.createValue((Integer
) value
);
396 else if (value
instanceof Long
)
397 return valueFactory
.createValue((Long
) value
);
398 else if (value
instanceof Float
)
399 return valueFactory
.createValue((Float
) value
);
400 else if (value
instanceof Double
)
401 return valueFactory
.createValue((Double
) value
);
402 else if (value
instanceof Boolean
)
403 return valueFactory
.createValue((Boolean
) value
);
404 else if (value
instanceof Calendar
)
405 return valueFactory
.createValue((Calendar
) value
);
406 else if (value
instanceof Date
) {
407 Calendar cal
= new GregorianCalendar();
408 cal
.setTime((Date
) value
);
409 return valueFactory
.createValue(cal
);
410 } else if (value
instanceof CharSequence
)
411 return valueFactory
.createValue(value
.toString());
412 else if (value
instanceof InputStream
)
413 return valueFactory
.createValue((InputStream
) value
);
418 protected Class
<?
> classFromProperty(Property property
)
419 throws RepositoryException
{
420 switch (property
.getType()) {
421 case PropertyType
.LONG
:
423 case PropertyType
.DOUBLE
:
425 case PropertyType
.STRING
:
427 case PropertyType
.BOOLEAN
:
428 return Boolean
.class;
429 case PropertyType
.DATE
:
430 return Calendar
.class;
431 case PropertyType
.NAME
:
434 throw new ArgeoException("Cannot find class for property "
435 + property
+ ", type="
436 + PropertyType
.nameFromValue(property
.getType()));
440 protected Object
asObject(Value value
, Class
<?
> propClass
)
441 throws RepositoryException
{
442 if (propClass
.equals(Integer
.class))
443 return (int) value
.getLong();
444 else if (propClass
.equals(Long
.class))
445 return value
.getLong();
446 else if (propClass
.equals(Float
.class))
447 return (float) value
.getDouble();
448 else if (propClass
.equals(Double
.class))
449 return value
.getDouble();
450 else if (propClass
.equals(Boolean
.class))
451 return value
.getBoolean();
452 else if (CharSequence
.class.isAssignableFrom(propClass
))
453 return value
.getString();
454 else if (InputStream
.class.isAssignableFrom(propClass
))
455 return value
.getStream();
456 else if (Calendar
.class.isAssignableFrom(propClass
))
457 return value
.getDate();
458 else if (Date
.class.isAssignableFrom(propClass
))
459 return value
.getDate().getTime();
464 protected Node
findChildReference(Session session
, BeanWrapper child
)
465 throws RepositoryException
{
466 if (child
.isReadableProperty(uuidProperty
)) {
467 String childUuid
= child
.getPropertyValue(uuidProperty
).toString();
469 return session
.getNodeByUUID(childUuid
);
470 } catch (ItemNotFoundException e
) {
471 if (strictUuidReference
)
472 throw new ArgeoException("No node found with uuid "
479 protected Class
<?
> loadClass(String name
) {
481 return classLoader
.loadClass(name
);
482 } catch (ClassNotFoundException e
) {
483 throw new ArgeoException("Cannot load class " + name
, e
);
487 protected String
propertyName(String name
) {
491 public void setVersioning(Boolean versioning
) {
492 this.versioning
= versioning
;
495 public void setUuidProperty(String uuidProperty
) {
496 this.uuidProperty
= uuidProperty
;
499 public void setClassProperty(String classProperty
) {
500 this.classProperty
= classProperty
;
503 public void setStrictUuidReference(Boolean strictUuidReference
) {
504 this.strictUuidReference
= strictUuidReference
;
507 public void setPrimaryNodeType(String primaryNodeType
) {
508 this.primaryNodeType
= primaryNodeType
;
511 public void setClassLoader(ClassLoader classLoader
) {
512 this.classLoader
= classLoader
;