/** * This file is part of the JCROM project. * Copyright (C) 2008-2014 - All rights reserved. * Authors: Olafur Gauti Gudmundsson, Nicolas Dos Santos * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jcrom; import java.io.IOException; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.jcr.NamespaceRegistry; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javafx.beans.property.ListProperty; import javafx.beans.property.MapProperty; import javafx.beans.property.ObjectProperty; import org.jcrom.annotations.JcrFileNode; import org.jcrom.annotations.JcrReference; import org.jcrom.util.NodeFilter; import org.jcrom.util.PathUtils; import org.jcrom.util.ReflectionUtils; import static org.jcrom.util.JavaFXUtils.*; /** * This class handles mappings of type @JcrReference * * @author Olafur Gauti Gudmundsson * @author Nicolas Dos Santos */ public class ReferenceMapper { private final Mapper mapper; public ReferenceMapper(Mapper mapper) { this.mapper = mapper; } protected String getPropertyName(Field field) { JcrReference jcrReference = mapper.getJcrom().getAnnotationReader().getAnnotation(field, JcrReference.class); String name = field.getName(); if (!jcrReference.name().equals(Mapper.DEFAULT_FIELDNAME)) { name = jcrReference.name(); } return name; } private List<Value> getReferenceValues(List<?> references, Session session, JcrReference jcrReference) throws IllegalAccessException, RepositoryException { List<Value> refValues = new ArrayList<Value>(); for (Object reference : references) { if (jcrReference.byPath()) { String referencePath = mapper.getNodePath(reference); if (referencePath != null && !referencePath.equals("")) { if (session.getRootNode().hasNode(PathUtils.relativePath(referencePath))) { refValues.add(session.getValueFactory().createValue(referencePath)); } } } else { String referenceId = mapper.getNodeId(reference); if (referenceId != null && !referenceId.equals("")) { //Node referencedNode = session.getNodeByUUID(referenceUUID); Node referencedNode = PathUtils.getNodeById(referenceId, session); Value value; if (jcrReference.weak()) { value = session.getValueFactory().createValue(referencedNode, true); } else { value = session.getValueFactory().createValue(referencedNode); } refValues.add(value); } } } return refValues; } private void addSingleReferenceToNode(Field field, Object obj, String propertyName, Node node) throws IllegalAccessException, RepositoryException { // extract the Identifier from the object, load the node, and add a reference to it JcrReference jcrReference = mapper.getJcrom().getAnnotationReader().getAnnotation(field, JcrReference.class); Object referenceObject = getObject(field, obj); if (referenceObject != null) { referenceObject = mapper.clearCglib(referenceObject); } if (referenceObject != null) { mapSingleReference(jcrReference, referenceObject, node, propertyName); } else { // remove the reference node.setProperty(propertyName, (Value) null); } } private void addMultipleReferencesToNode(Field field, Object obj, String propertyName, Node node) throws IllegalAccessException, RepositoryException { JcrReference jcrReference = mapper.getJcrom().getAnnotationReader().getAnnotation(field, JcrReference.class); List<?> references = (List<?>) field.get(obj); if (node.hasProperty(propertyName) && !node.getProperty(propertyName).getDefinition().isMultiple()) { node.setProperty(propertyName, (Value) null); //node.save(); node.getSession().save(); } if (references != null && !references.isEmpty()) { List<Value> refValues = getReferenceValues(references, node.getSession(), jcrReference); if (!refValues.isEmpty()) { node.setProperty(propertyName, refValues.toArray(new Value[refValues.size()])); } else if (node.hasProperty(propertyName)) { node.setProperty(propertyName, (Value[]) null); } } else if (node.hasProperty(propertyName)) { node.setProperty(propertyName, (Value[]) null); } } private void mapSingleReference(JcrReference jcrReference, Object referenceObject, Node containerNode, String propertyName) throws IllegalAccessException, RepositoryException { if (jcrReference.byPath()) { String referencePath = mapper.getNodePath(referenceObject); if (referencePath != null && !referencePath.equals("")) { if (containerNode.getSession().getRootNode().hasNode(PathUtils.relativePath(referencePath))) { containerNode.setProperty(propertyName, containerNode.getSession().getValueFactory().createValue(referencePath)); } } } else { String referenceId = mapper.getNodeId(referenceObject); if (referenceId != null && !referenceId.equals("")) { //Node referencedNode = containerNode.getSession().getNodeByUUID(referenceUUID); Node referencedNode = PathUtils.getNodeById(referenceId, containerNode.getSession()); if (jcrReference.weak()) { containerNode.setProperty(propertyName, containerNode.getSession().getValueFactory().createValue(referencedNode, true)); } else { containerNode.setProperty(propertyName, referencedNode); } } else { // remove the reference containerNode.setProperty(propertyName, (Value) null); } } } /** * Maps a Map<String,Object> or Map<String,List<Object>> to a JCR Node. */ private void addMapOfReferencesToNode(Field field, Object obj, String containerName, Node node) throws IllegalAccessException, RepositoryException { JcrReference jcrReference = mapper.getJcrom().getAnnotationReader().getAnnotation(field, JcrReference.class); // remove previous references if they exist if (node.hasNode(containerName)) { node.getNode(containerName).remove(); } // create a reference container Node referenceContainer = node.addNode(containerName); // map the references as properties on the container node Map<?, ?> referenceMap = (Map<?, ?>) field.get(obj); if (referenceMap != null && !referenceMap.isEmpty()) { Class<?> paramClass = ReflectionUtils.getParameterizedClass(field, 1); for (Map.Entry<?, ?> entry : referenceMap.entrySet()) { String key = (String) entry.getKey(); if (isList(paramClass)) { List<?> references = (List<?>) entry.getValue(); List<Value> refValues = getReferenceValues(references, referenceContainer.getSession(), jcrReference); if (!refValues.isEmpty()) { referenceContainer.setProperty(key, refValues.toArray(new Value[refValues.size()])); } } else { Object referenceObject = entry.getValue(); mapSingleReference(jcrReference, referenceObject, referenceContainer, key); } } } } private void setReferenceProperties(Field field, Object obj, Node node, NodeFilter nodeFilter) throws IllegalAccessException, RepositoryException { String propertyName = getPropertyName(field); // make sure that the reference should be updated if (nodeFilter == null || nodeFilter.isNameIncluded(field.getName())) { if (isList(field.getType())) { // multiple references in a List addMultipleReferencesToNode(field, obj, propertyName, node); } else if (isMap(field)) { // multiple references in a Map addMapOfReferencesToNode(field, obj, propertyName, node); } else { // single reference object addSingleReferenceToNode(field, obj, propertyName, node); } } } void addReferences(Field field, Object obj, Node node) throws IllegalAccessException, RepositoryException { setReferenceProperties(field, obj, node, null); } void updateReferences(Field field, Object obj, Node node, NodeFilter nodeFilter) throws IllegalAccessException, RepositoryException { setReferenceProperties(field, obj, node, nodeFilter); } private Node getSingleReferencedNode(JcrReference jcrReference, Value value, Session session) throws RepositoryException { if (jcrReference.byPath()) { if (session.getRootNode().hasNode(PathUtils.relativePath(value.getString()))) { return PathUtils.getNode(value.getString(), session); } } else { //return session.getNodeByUUID(value.getString()); return PathUtils.getNodeById(value.getString(), session); } return null; } Object createReferencedObject(Field field, Value value, Object obj, Session session, Class<?> referenceObjClass, int depth, NodeFilter nodeFilter, Mapper mapper) throws ClassNotFoundException, InstantiationException, RepositoryException, IllegalAccessException, IOException { JcrReference jcrReference = mapper.getJcrom().getAnnotationReader().getAnnotation(field, JcrReference.class); Node referencedNode = null; if (jcrReference.byPath()) { if (session.getRootNode().hasNode(PathUtils.relativePath(value.getString()))) { referencedNode = PathUtils.getNode(value.getString(), session); } } else { //referencedNode = session.getNodeByUUID(value.getString()); referencedNode = PathUtils.getNodeById(value.getString(), session); } if (referencedNode != null) { Object referencedObject = mapper.createInstanceForNode(referenceObjClass, referencedNode); if (nodeFilter.isIncluded(field.getName(), depth)) { // load and map the object, we don't send the current object as parent referencedObject = mapper.mapNodeToClass(referencedObject, referencedNode, nodeFilter, null, depth + 1); } else { if (jcrReference.byPath()) { // just store the path mapper.setNodePath(referencedObject, value.getString()); } else { // just store the Identifier mapper.setUUID(referencedObject, value.getString()); mapper.setId(referencedObject, value.getString()); } } // support JcrFileNode annotation for references if (field.isAnnotationPresent(JcrFileNode.class) && referencedObject instanceof JcrFile) { JcrFileNode fileNode = field.getAnnotation(JcrFileNode.class); FileNodeMapper.addFileProperties((JcrFile) referencedObject, referencedNode, fileNode, depth, nodeFilter); } return referencedObject; } else { return null; } } List<?> getReferenceList(Field field, String propertyName, Class<?> referenceObjClass, Node node, Object obj, int depth, NodeFilter nodeFilter, Mapper mapper) throws ClassNotFoundException, InstantiationException, RepositoryException, IllegalAccessException, IOException { List<Object> references = new ArrayList<Object>(); if (node.hasProperty(propertyName)) { Value[] refValues; if (node.getProperty(propertyName).getDefinition().isMultiple()) { refValues = node.getProperty(propertyName).getValues(); } else { refValues = new Value[] { node.getProperty(propertyName).getValue() }; } for (Value value : refValues) { Object referencedObject = createReferencedObject(field, value, obj, node.getSession(), referenceObjClass, depth, nodeFilter, mapper); references.add(referencedObject); } } return references; } Map<String, Object> getReferenceMap(Field field, String containerName, Class<?> mapParamClass, Node node, Object obj, int depth, NodeFilter nodeFilter, Mapper mapper, JcrReference jcrReference) throws ClassNotFoundException, InstantiationException, RepositoryException, IllegalAccessException, IOException { Map<String, Object> references = new HashMap<String, Object>(); if (node.hasNode(containerName)) { Node containerNode = node.getNode(containerName); PropertyIterator propertyIterator = containerNode.getProperties(); while (propertyIterator.hasNext()) { Property p = propertyIterator.nextProperty(); if (!p.getName().startsWith("jcr:") && !p.getName().startsWith(NamespaceRegistry.NAMESPACE_JCR)) { if (isList(mapParamClass)) { if (jcrReference.lazy()) { references.put(p.getName(), ProxyFactory.createReferenceListProxy(Object.class, obj, containerNode.getPath(), p.getName(), node.getSession(), mapper, depth, nodeFilter, field)); } else { references.put(p.getName(), getReferenceList(field, p.getName(), Object.class, containerNode, obj, depth, nodeFilter, mapper)); } } else { if (jcrReference.lazy()) { Node referencedNode = getSingleReferencedNode(jcrReference, p.getValue(), node.getSession()); references.put(p.getName(), ProxyFactory.createReferenceProxy(mapper.findClassFromNode(Object.class, referencedNode), obj, containerNode.getPath(), p.getName(), node.getSession(), mapper, depth, nodeFilter, field)); } else { references.put(p.getName(), createReferencedObject(field, p.getValue(), obj, containerNode.getSession(), Object.class, depth, nodeFilter, mapper)); } } } } } return references; } void getReferencesFromNode(Field field, Node node, Object obj, int depth, NodeFilter nodeFilter, Mapper mapper) throws ClassNotFoundException, InstantiationException, RepositoryException, IllegalAccessException, IOException { String propertyName = getPropertyName(field); JcrReference jcrReference = mapper.getJcrom().getAnnotationReader().getAnnotation(field, JcrReference.class); if (isList(field.getType())) { // multiple references in a List Class<?> referenceObjClass = ReflectionUtils.getParameterizedClass(field); List value = null; if (jcrReference.lazy()) { // lazy loading value = ProxyFactory.createReferenceListProxy(referenceObjClass, obj, node.getPath(), propertyName, node.getSession(), mapper, depth, nodeFilter, field); } else { // eager loading value = getReferenceList(field, propertyName, referenceObjClass, node, obj, depth, nodeFilter, mapper); } setObject(field, obj, value); } else if (isMap(field)) { // multiple references in a Map // lazy loading is applied to each value in the Map Class<?> mapParamClass = ReflectionUtils.getParameterizedClass(field, 1); Map<String, Object> value = getReferenceMap(field, propertyName, mapParamClass, node, obj, depth, nodeFilter, mapper, jcrReference); setObject(field, obj, value); } else { // single reference if (node.hasProperty(propertyName)) { Class<?> referenceObjClass = null; if (ObjectProperty.class.isAssignableFrom(field.getType())) { referenceObjClass = ReflectionUtils.getObjectPropertyGeneric(obj, field); } else { referenceObjClass = field.getType(); } Object value = null; if (jcrReference.lazy()) { value = ProxyFactory.createReferenceProxy(referenceObjClass, obj, node.getPath(), propertyName, node.getSession(), mapper, depth, nodeFilter, field); } else { value = createReferencedObject(field, node.getProperty(propertyName).getValue(), obj, node.getSession(), referenceObjClass, depth, nodeFilter, mapper); } setObject(field, obj, value); } } } }