/**
* 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.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.jcr.Binary;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.ValueFactory;
import javax.jcr.nodetype.NodeType;
import org.jcrom.annotations.JcrFileNode;
import org.jcrom.annotations.JcrNode;
import org.jcrom.annotations.JcrProperty;
import org.jcrom.util.NodeFilter;
import org.jcrom.util.ReflectionUtils;
import org.jcrom.util.io.IOUtils;
/**
* This class handles mappings of type @JcrFileNode
*
* @author Olafur Gauti Gudmundsson
* @author Nicolas Dos Santos
*/
class FileNodeMapper {
private final Mapper mapper;
public FileNodeMapper(Mapper mapper) {
this.mapper = mapper;
}
private String getNodeName(Field field) {
JcrFileNode jcrFileNode = mapper.getJcrom().getAnnotationReader().getAnnotation(field, JcrFileNode.class);
String name = field.getName();
if (!jcrFileNode.name().equals(JcrProperty.DEFAULT_FIELDNAME)) {
name = jcrFileNode.name();
}
return name;
}
private Node createFileFolderNode(JcrNode jcrNode, String containerName, Node parentNode, Mapper mapper) throws RepositoryException {
if (!parentNode.hasNode(mapper.getCleanName(containerName))) {
if (jcrNode != null && (jcrNode.nodeType().equals("nt:unstructured") || jcrNode.nodeType().equals(NodeType.NT_UNSTRUCTURED))) {
return parentNode.addNode(mapper.getCleanName(containerName));
} else {
// assume it is an nt:file or extension of that,
// so we create an nt:folder
return parentNode.addNode(mapper.getCleanName(containerName), NodeType.NT_FOLDER);
}
} else {
return parentNode.getNode(mapper.getCleanName(containerName));
}
}
private <T extends JcrFile> void setFileNodeProperties(Node contentNode, T file) throws RepositoryException, IOException {
contentNode.setProperty(Property.JCR_MIMETYPE, file.getMimeType());
contentNode.setProperty(Property.JCR_LAST_MODIFIED, file.getLastModified());
if (file.getEncoding() != null) {
contentNode.setProperty(Property.JCR_ENCODING, file.getEncoding());
}
// add the file data
JcrDataProvider dataProvider = file.getDataProvider();
if (dataProvider != null) {
ValueFactory valueFactory = contentNode.getSession().getValueFactory();
if (dataProvider.isFile() && dataProvider.getFile() != null) {
//contentNode.setProperty("jcr:data", new FileInputStream(dataProvider.getFile()));
Binary binary = valueFactory.createBinary(new FileInputStream(dataProvider.getFile()));
contentNode.setProperty(Property.JCR_DATA, binary);
} else if (dataProvider.isBytes() && dataProvider.getBytes() != null) {
//contentNode.setProperty("jcr:data", new ByteArrayInputStream(dataProvider.getBytes()));
Binary binary = valueFactory.createBinary(new ByteArrayInputStream(dataProvider.getBytes()));
contentNode.setProperty(Property.JCR_DATA, binary);
} else if (dataProvider.isStream() && dataProvider.getInputStream() != null) {
try {
//contentNode.setProperty("jcr:data", dataProvider.getInputStream());
Binary binary = valueFactory.createBinary(dataProvider.getInputStream());
contentNode.setProperty(Property.JCR_DATA, binary);
} catch (RepositoryException re) {
System.out.println("FILE NODE: " + contentNode.getPath() + " " + ((FileInputStream) dataProvider.getInputStream()).available());
throw re;
}
}
}
}
private <T extends JcrFile> void addFileNode(JcrNode jcrNode, Node parentNode, T file, Mapper mapper) throws IllegalAccessException, RepositoryException, IOException {
Node fileNode;
if (jcrNode == null || (jcrNode.nodeType().equals("nt:unstructured") || jcrNode.nodeType().equals(NodeType.NT_UNSTRUCTURED))) {
fileNode = parentNode.addNode(mapper.getCleanName(file.getName()));
} else {
fileNode = parentNode.addNode(mapper.getCleanName(file.getName()), jcrNode.nodeType());
}
// add annotated mixin types
if ((jcrNode != null) && (jcrNode.mixinTypes() != null)) {
for (final String mixinType : jcrNode.mixinTypes()) {
if (fileNode.canAddMixin(mixinType)) {
fileNode.addMixin(mixinType);
}
}
}
// update the object name and path
file.setName(fileNode.getName());
file.setPath(fileNode.getPath());
// Update the object identifier
mapper.setId(file, fileNode.getIdentifier());
if (fileNode.hasProperty(Property.JCR_UUID)) {
//mapper.setUUID(file, fileNode.getUUID());
mapper.setUUID(file, fileNode.getIdentifier());
}
//Node contentNode = fileNode.addNode(Property.JCR_CONTENT, NodeType.NT_RESOURCE);
//setFileNodeProperties(contentNode, file);
mapper.addNode(fileNode, file, null, false, null);
}
<T extends JcrFile> void addFileNode(Node fileNode, T file, Mapper mapper) throws IllegalAccessException, RepositoryException, IOException {
// update the object name and path
file.setName(fileNode.getName());
file.setPath(fileNode.getPath());
// Update the object identifier
mapper.setId(file, fileNode.getIdentifier());
if (fileNode.hasProperty(Property.JCR_UUID)) {
//mapper.setUUID(file, fileNode.getUUID());
mapper.setUUID(file, fileNode.getIdentifier());
}
Node contentNode;
if (fileNode.hasNode(Property.JCR_CONTENT)) {
contentNode = fileNode.getNode(Property.JCR_CONTENT);
} else {
contentNode = fileNode.addNode(Property.JCR_CONTENT, NodeType.NT_RESOURCE);
}
setFileNodeProperties(contentNode, file);
}
private <T extends JcrFile> void updateFileNode(Node fileNode, T file, NodeFilter nodeFilter, int depth, Mapper mapper) throws RepositoryException, IllegalAccessException, IOException {
Node contentNode = fileNode.getNode(Property.JCR_CONTENT);
setFileNodeProperties(contentNode, file);
mapper.updateNode(fileNode, file, file.getClass(), nodeFilter, depth + 1, null);
}
private void removeChildren(Node containerNode) throws RepositoryException {
NodeIterator nodeIterator = containerNode.getNodes();
while (nodeIterator.hasNext()) {
nodeIterator.nextNode().remove();
}
}
private void addSingleFileToNode(Field field, Object obj, String nodeName, Node node, Mapper mapper, int depth, NodeFilter nodeFilter) throws IllegalAccessException, RepositoryException, IOException {
JcrNode fileJcrNode = ReflectionUtils.getJcrNodeAnnotation(field.getType());
Node fileContainer = createFileFolderNode(fileJcrNode, nodeName, node, mapper);
if (!fileContainer.hasNodes()) {
if (field.get(obj) != null) {
addFileNode(fileJcrNode, fileContainer, (JcrFile) field.get(obj), mapper);
}
} else {
if (field.get(obj) != null) {
updateFileNode(fileContainer.getNodes().nextNode(), (JcrFile) field.get(obj), nodeFilter, depth, mapper);
} else {
// field is now null, so we remove the files
removeChildren(fileContainer);
}
}
}
private void updateFileList(List<?> children, Node fileContainer, JcrNode fileJcrNode, Mapper mapper, int depth, NodeFilter nodeFilter) throws IllegalAccessException, RepositoryException, IOException {
if (children != null && !children.isEmpty()) {
if (fileContainer.hasNodes()) {
// children exist, we must update
NodeIterator childNodes = fileContainer.getNodes();
while (childNodes.hasNext()) {
Node child = childNodes.nextNode();
JcrFile childEntity = (JcrFile) mapper.findEntityByPath(children, child.getPath());
if (childEntity == null) {
// this child was not found, so we remove it
child.remove();
} else {
updateFileNode(child, childEntity, nodeFilter, depth, mapper);
}
}
// we must add new children, if any
for (Object child : children) {
String childPath = mapper.getNodePath(child);
if (childPath == null || childPath.equals("") || !fileContainer.hasNode(mapper.getCleanName(mapper.getNodeName(child)))) {
addFileNode(fileJcrNode, fileContainer, (JcrFile) child, mapper);
}
}
} else {
// no children exist, we add
for (int i = 0; i < children.size(); i++) {
addFileNode(fileJcrNode, fileContainer, (JcrFile) children.get(i), mapper);
}
}
} else {
// field list is now null (or empty), so we remove the file nodes
removeChildren(fileContainer);
}
}
private void addMultipleFilesToNode(Field field, Object obj, String nodeName, Node node, Mapper mapper, int depth, NodeFilter nodeFilter) throws IllegalAccessException, RepositoryException, IOException {
JcrNode fileJcrNode = ReflectionUtils.getJcrNodeAnnotation(ReflectionUtils.getParameterizedClass(field));
Node fileContainer = createFileFolderNode(fileJcrNode, nodeName, node, mapper);
List<?> children = (List<?>) field.get(obj);
updateFileList(children, fileContainer, fileJcrNode, mapper, depth, nodeFilter);
}
private void addMapOfFilesToNode(Field field, Object obj, String nodeName, Node node, Mapper mapper, int depth, NodeFilter nodeFilter) throws IllegalAccessException, RepositoryException, IOException {
Class<?> fileClass;
if (ReflectionUtils.implementsInterface(ReflectionUtils.getParameterizedClass(field, 1), List.class)) {
fileClass = ReflectionUtils.getTypeArgumentOfParameterizedClass(field, 1, 0);
} else {
fileClass = ReflectionUtils.getParameterizedClass(field, 1);
}
JcrNode fileJcrNode = ReflectionUtils.getJcrNodeAnnotation(fileClass);
String cleanName = mapper.getCleanName(nodeName);
Node fileContainer = node.hasNode(cleanName) ? node.getNode(cleanName) : node.addNode(cleanName); // this is just a nt:unstructured node
Map<?, ?> children = (Map<?, ?>) field.get(obj);
if (children != null && !children.isEmpty()) {
Class<?> paramClass = ReflectionUtils.getParameterizedClass(field, 1);
if (fileContainer.hasNodes()) {
// nodes already exist, we need to update
Map<String, String> mapWithCleanKeys = new HashMap<String, String>();
Iterator<?> it = children.keySet().iterator();
while (it.hasNext()) {
String key = (String) it.next();
String cleanKey = mapper.getCleanName(key);
if (fileContainer.hasNode(cleanKey)) {
if (ReflectionUtils.implementsInterface(paramClass, List.class)) {
// update the file list
List<?> childList = (List<?>) children.get(key);
Node listContainer = createFileFolderNode(fileJcrNode, cleanKey, fileContainer, mapper);
updateFileList(childList, listContainer, fileJcrNode, mapper, depth, nodeFilter);
} else {
// update the file
updateFileNode(fileContainer.getNode(cleanKey), (JcrFile) children.get(key), nodeFilter, depth, mapper);
}
} else {
// this child does not exist, so we add it
addMapFile(paramClass, fileJcrNode, fileContainer, children, key, mapper);
}
mapWithCleanKeys.put(cleanKey, "1");
}
// remove nodes that no longer exist
NodeIterator childNodes = fileContainer.getNodes();
while (childNodes.hasNext()) {
Node child = childNodes.nextNode();
if (!mapWithCleanKeys.containsKey(child.getName())) {
child.remove();
}
}
} else {
// no children exist, we simply add all
Iterator<?> it = children.keySet().iterator();
while (it.hasNext()) {
String key = (String) it.next();
addMapFile(paramClass, fileJcrNode, fileContainer, children, key, mapper);
}
}
} else {
// field list is now null (or empty), so we remove the file nodes
removeChildren(fileContainer);
}
}
private void addMapFile(Class<?> paramClass, JcrNode fileJcrNode, Node fileContainer, Map<?, ?> childMap, String key, Mapper mapper) throws IllegalAccessException, RepositoryException, IOException {
if (ReflectionUtils.implementsInterface(paramClass, List.class)) {
List<?> childList = (List<?>) childMap.get(key);
Node listContainer = createFileFolderNode(fileJcrNode, mapper.getCleanName(key), fileContainer, mapper);
for (int i = 0; i < childList.size(); i++) {
addFileNode(fileJcrNode, listContainer, (JcrFile) childList.get(i), mapper);
}
} else {
addFileNode(fileJcrNode, fileContainer, (JcrFile) childMap.get(key), mapper);
}
}
private void setFiles(Field field, Object obj, Node node, Mapper mapper, int depth, NodeFilter nodeFilter) throws IllegalAccessException, RepositoryException, IOException {
String nodeName = getNodeName(field);
// make sure that this child is supposed to be updated
if (nodeFilter == null || nodeFilter.isIncluded(field.getName(), depth)) {
if (ReflectionUtils.implementsInterface(field.getType(), List.class)) {
// multiple file nodes in a List
addMultipleFilesToNode(field, obj, nodeName, node, mapper, depth, nodeFilter);
} else if (ReflectionUtils.implementsInterface(field.getType(), Map.class)) {
// dynamic map of file nodes
addMapOfFilesToNode(field, obj, nodeName, node, mapper, depth, nodeFilter);
} else {
// single child
addSingleFileToNode(field, obj, nodeName, node, mapper, depth, nodeFilter);
}
}
}
@SuppressWarnings("unchecked")
private <T extends JcrFile> void mapNodeToFileObject(JcrFileNode jcrFileNode, T fileObj, Node fileNode, NodeFilter nodeFilter, Object parentObject, int depth, Mapper mapper) throws ClassNotFoundException, InstantiationException, RepositoryException, IllegalAccessException, IOException {
Node contentNode = fileNode.getNode(Property.JCR_CONTENT);
fileObj.setName(fileNode.getName());
fileObj.setPath(fileNode.getPath());
fileObj.setMimeType(contentNode.getProperty(Property.JCR_MIMETYPE).getString());
fileObj.setLastModified(contentNode.getProperty(Property.JCR_LAST_MODIFIED).getDate());
if (contentNode.hasProperty(Property.JCR_ENCODING)) {
fileObj.setEncoding(contentNode.getProperty(Property.JCR_ENCODING).getString());
}
// file data
if (nodeFilter.isIncluded("jcr:data", depth)) {
if (jcrFileNode == null || jcrFileNode.loadType() == JcrFileNode.LoadType.STREAM) {
InputStream is = contentNode.getProperty(Property.JCR_DATA).getBinary().getStream();
JcrDataProviderImpl dataProvider = new JcrDataProviderImpl(is, contentNode.getProperty(Property.JCR_DATA).getLength());
fileObj.setDataProvider(dataProvider);
} else if (jcrFileNode.loadType() == JcrFileNode.LoadType.BYTES) {
InputStream is = contentNode.getProperty(Property.JCR_DATA).getBinary().getStream();
JcrDataProviderImpl dataProvider = new JcrDataProviderImpl(IOUtils.toByteArray(is));
fileObj.setDataProvider(dataProvider);
}
}
// if this is a JcrFile subclass, it may contain custom properties and
// child nodes that need to be mapped
fileObj = (T) mapper.mapNodeToClass(fileObj, fileNode, nodeFilter, depth + 1);
}
void addFiles(Field field, Object obj, Node node, Mapper mapper) throws IllegalAccessException, RepositoryException, IOException {
setFiles(field, obj, node, mapper, NodeFilter.DEPTH_INFINITE, null);
}
void updateFiles(Field field, Object obj, Node node, Mapper mapper, int depth, NodeFilter nodeFilter) throws IllegalAccessException, RepositoryException, IOException {
setFiles(field, obj, node, mapper, depth, nodeFilter);
}
@SuppressWarnings("unchecked")
List<JcrFile> getFileList(Class<?> childObjClass, Node fileContainer, Object obj, JcrFileNode jcrFileNode, int depth, NodeFilter nodeFilter, Mapper mapper) throws ClassNotFoundException, InstantiationException, RepositoryException, IllegalAccessException, IOException {
List<JcrFile> children = jcrFileNode.listContainerClass().newInstance();
NodeIterator iterator = fileContainer.getNodes();
while (iterator.hasNext()) {
JcrFile fileObj = (JcrFile) childObjClass.newInstance();
mapNodeToFileObject(jcrFileNode, fileObj, iterator.nextNode(), nodeFilter, obj, depth, mapper);
children.add(fileObj);
}
return children;
}
void mapSingleFile(JcrFile fileObj, Node fileNode, Object obj, int depth, NodeFilter nodeFilter, Mapper mapper) throws ClassNotFoundException, InstantiationException, RepositoryException, IllegalAccessException, IOException {
mapNodeToFileObject(null, fileObj, fileNode, nodeFilter, obj, depth, mapper);
}
JcrFile getSingleFile(Class<?> childObjClass, Node fileContainer, Object obj, JcrFileNode jcrFileNode, int depth, NodeFilter nodeFilter, Mapper mapper) throws ClassNotFoundException, InstantiationException, RepositoryException, IllegalAccessException, IOException {
JcrFile fileObj = (JcrFile) childObjClass.newInstance();
mapNodeToFileObject(jcrFileNode, fileObj, fileContainer.getNodes().nextNode(), nodeFilter, obj, depth, mapper);
return fileObj;
}
// Add file properties to another node, e.g. reference node
static void addFileProperties(JcrFile fileObj, Node fileNode, JcrFileNode jcrFileNode, int depth, NodeFilter nodeFilter) throws ClassNotFoundException, InstantiationException, RepositoryException, IllegalAccessException, IOException {
Node contentNode = fileNode.getNode("jcr:content");
fileObj.setName(fileNode.getName());
//fileObj.setPath(fileNode.getPath());
fileObj.setMimeType(contentNode.getProperty("jcr:mimeType").getString());
fileObj.setLastModified(contentNode.getProperty("jcr:lastModified").getDate());
if (contentNode.hasProperty("jcr:encoding")) {
fileObj.setEncoding(contentNode.getProperty("jcr:encoding").getString());
}
// file data
if (nodeFilter.isIncluded("jcr:data", depth)) {
if (jcrFileNode == null || jcrFileNode.loadType() == JcrFileNode.LoadType.STREAM) {
InputStream is = contentNode.getProperty(Property.JCR_DATA).getBinary().getStream();
JcrDataProviderImpl dataProvider = new JcrDataProviderImpl(is, contentNode.getProperty(Property.JCR_DATA).getLength());
fileObj.setDataProvider(dataProvider);
} else if (jcrFileNode.loadType() == JcrFileNode.LoadType.BYTES) {
InputStream is = contentNode.getProperty(Property.JCR_DATA).getBinary().getStream();
JcrDataProviderImpl dataProvider = new JcrDataProviderImpl(IOUtils.toByteArray(is));
fileObj.setDataProvider(dataProvider);
}
}
}
@SuppressWarnings("unchecked")
private Map<?, ?> getFileMap(Field field, Node fileContainer, JcrFileNode jcrFileNode, Object obj, int depth, NodeFilter nodeFilter, Mapper mapper) throws ClassNotFoundException, InstantiationException, RepositoryException, IllegalAccessException, IOException {
Class<?> mapParamClass = ReflectionUtils.getParameterizedClass(field, 1);
Map<Object, Object> children = jcrFileNode.mapContainerClass().newInstance();
NodeIterator iterator = fileContainer.getNodes();
while (iterator.hasNext()) {
Node childNode = iterator.nextNode();
if (ReflectionUtils.implementsInterface(mapParamClass, List.class)) {
Class<?> childObjClass = ReflectionUtils.getTypeArgumentOfParameterizedClass(field, 1, 0);
if (jcrFileNode.lazy()) {
// lazy loading
children.put(childNode.getName(), ProxyFactory.createFileNodeListProxy(childObjClass, obj, fileContainer.getPath(), fileContainer.getSession(), mapper, depth, nodeFilter, jcrFileNode));
} else {
children.put(childNode.getName(), getFileList(childObjClass, childNode, obj, jcrFileNode, depth, nodeFilter, mapper));
}
} else {
if (jcrFileNode.lazy()) {
// lazy loading
children.put(childNode.getName(), ProxyFactory.createFileNodeProxy(mapParamClass, obj, fileContainer.getPath(), fileContainer.getSession(), mapper, depth, nodeFilter, jcrFileNode));
} else {
children.put(childNode.getName(), getSingleFile(mapParamClass, fileContainer, obj, jcrFileNode, depth, nodeFilter, mapper));
}
}
}
return children;
}
void getFilesFromNode(Field field, Node node, Object obj, int depth, NodeFilter nodeFilter, Mapper mapper) throws ClassNotFoundException, InstantiationException, RepositoryException, IllegalAccessException, IOException {
String nodeName = getNodeName(field);
JcrFileNode jcrFileNode = mapper.getJcrom().getAnnotationReader().getAnnotation(field, JcrFileNode.class);
if (node.hasNode(nodeName) && nodeFilter.isIncluded(field.getName(), depth)) {
// file nodes are always stored inside a folder node
Node fileContainer = node.getNode(nodeName);
if (ReflectionUtils.implementsInterface(field.getType(), List.class)) {
// we can expect more than one child object here
List<?> children;
Class<?> childObjClass = ReflectionUtils.getParameterizedClass(field);
if (jcrFileNode.lazy()) {
// lazy loading
children = ProxyFactory.createFileNodeListProxy(childObjClass, obj, fileContainer.getPath(), node.getSession(), mapper, depth, nodeFilter, jcrFileNode);
} else {
// eager loading
children = getFileList(childObjClass, fileContainer, obj, jcrFileNode, depth, nodeFilter, mapper);
}
field.set(obj, children);
} else if (ReflectionUtils.implementsInterface(field.getType(), Map.class)) {
// dynamic map of child nodes
// lazy loading is applied to each value in the Map
field.set(obj, getFileMap(field, fileContainer, jcrFileNode, obj, depth, nodeFilter, mapper));
} else {
// instantiate the field class
if (fileContainer.hasNodes()) {
if (jcrFileNode.lazy()) {
// lazy loading
field.set(obj, ProxyFactory.createFileNodeProxy(field.getType(), obj, fileContainer.getPath(), node.getSession(), mapper, depth, nodeFilter, jcrFileNode));
} else {
// eager loading
field.set(obj, getSingleFile(field.getType(), fileContainer, obj, jcrFileNode, depth, nodeFilter, mapper));
}
}
}
}
}
}