package org.gcube.data.access.storagehub.handlers; import java.io.InputStream; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import org.apache.jackrabbit.util.Text; import org.apache.jackrabbit.value.BinaryValue; import org.apache.jackrabbit.value.BooleanValue; import org.apache.jackrabbit.value.DateValue; import org.apache.jackrabbit.value.LongValue; import org.apache.jackrabbit.value.StringValue; import org.gcube.common.storagehub.model.annotations.Attribute; import org.gcube.common.storagehub.model.annotations.AttributeRootNode; import org.gcube.common.storagehub.model.annotations.ListNodes; import org.gcube.common.storagehub.model.annotations.MapAttribute; import org.gcube.common.storagehub.model.annotations.NodeAttribute; import org.gcube.common.storagehub.model.items.Item; import org.gcube.common.storagehub.model.items.SharedFolder; import org.gcube.common.storagehub.model.items.TrashItem; import org.reflections.Configuration; import org.reflections.Reflections; import org.reflections.util.ConfigurationBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ItemHandler { private static final Logger logger = LoggerFactory.getLogger(ItemHandler.class); private static ClassHandler classHandler = new ClassHandler(); private static HashMap> typeToSubtypeMap = new HashMap<>(); public static T getItem(Node node, List excludes) throws Exception { @SuppressWarnings("unchecked") Class classToHandle = (Class)classHandler.get(node.getPrimaryNodeType().getName()); T item = classToHandle.newInstance(); item.setId(node.getIdentifier()); item.setName(Text.unescapeIllegalJcrChars(node.getName())); item.setPath(Text.unescapeIllegalJcrChars(node.getPath())); item.setLocked(node.isLocked()); item.setPrimaryType(node.getPrimaryNodeType().getName()); Item parent = null ; if (item instanceof SharedFolder) { logger.debug("I'm in a Shared Folder"); item.setShared(true); }else { try { parent = ItemHandler.getItem(node.getParent(), Arrays.asList("hl:accounting","jcr:content")); item.setShared(parent.isShared()); } catch(Exception e) { item.setShared(false); } } if (item instanceof TrashItem) item.setTrashed(true); else { try { if (parent==null) parent = ItemHandler.getItem(node.getParent(), Arrays.asList("hl:accounting","jcr:content")); item.setTrashed(parent.isTrashed()); } catch(Exception e) { item.setTrashed(false); } } try{ item.setParentId(node.getParent().getIdentifier()); item.setParentPath(node.getParent().getPath()); }catch (Throwable e) { logger.info("Root node doesn't have a parent"); } for (Field field : retrieveAllFields(classToHandle)){ if (field.isAnnotationPresent(Attribute.class)){ Attribute attribute = field.getAnnotation(Attribute.class); field.setAccessible(true); try{ Class returnType = field.getType(); field.set(item, getPropertyValue(returnType, node.getProperty(attribute.value()))); }catch(PathNotFoundException e){ logger.debug("the current node dosn't contain {} property",attribute.value()); } catch (Exception e ) { logger.warn("error setting value",e); } } else if (field.isAnnotationPresent(NodeAttribute.class)){ String fieldNodeName = field.getAnnotation(NodeAttribute.class).value(); //for now it excludes only first level node if (excludes!=null && excludes.contains(fieldNodeName)) continue; logger.debug("retrieving field node "+field.getName()); field.setAccessible(true); try{ Node fieldNode = node.getNode(fieldNodeName); logger.debug("looking in node {} searched with {}",fieldNode.getName(),fieldNodeName); field.set(item, iterateNodeAttributeFields(field.getType(), fieldNode)); }catch(PathNotFoundException e){ logger.debug("the current node dosn't contain {} node",fieldNodeName); } catch (Exception e ) { logger.warn("error setting value",e); } } } return item; } private static T iterateNodeAttributeFields(Class clazz, Node node) throws Exception{ T obj = clazz.newInstance(); for (Field field : retrieveAllFields(clazz)){ if (field.isAnnotationPresent(Attribute.class)){ Attribute attribute = field.getAnnotation(Attribute.class); field.setAccessible(true); try{ @SuppressWarnings("rawtypes") Class returnType = field.getType(); field.set(obj, getPropertyValue(returnType, node.getProperty(attribute.value()))); logger.debug("found field {} of type annotated as ListNodes in class {} on node {}", field.getName(), clazz.getName(), node.getName()); }catch(PathNotFoundException e){ logger.debug("the current node dosn't contain {} property",attribute.value()); } catch (Exception e ) { logger.warn("error setting value",e); } } else if (field.isAnnotationPresent(MapAttribute.class)){ //logger.debug("found field {} of type annotated as MapAttribute in class {}", field.getName(), clazz.getName()); field.setAccessible(true); String exclude = field.getAnnotation(MapAttribute.class).excludeStartWith(); Map mapToset = new HashMap(); PropertyIterator iterator = node.getProperties(); if (iterator!=null) { while (iterator.hasNext()){ Property prop = iterator.nextProperty(); if (!exclude.isEmpty() && prop.getName().startsWith(exclude)) continue; try{ mapToset.put(prop.getName(), getPropertyValue(prop)); }catch(PathNotFoundException e){ logger.debug("the property [] is not mapped",prop.getName()); } catch (Exception e ) { logger.warn("error setting value",e); } } } field.set(obj, mapToset); } else if (field.isAnnotationPresent(ListNodes.class)){ logger.debug("found field {} of type annotated as ListNodes in class {} on node {}", field.getName(), clazz.getName(), node.getName()); field.setAccessible(true); String exclude = field.getAnnotation(ListNodes.class).excludeTypeStartWith(); String include = field.getAnnotation(ListNodes.class).includeTypeStartWith(); Class listType = field.getAnnotation(ListNodes.class).listClass(); Map subTypesMap = Collections.emptyMap(); if (!typeToSubtypeMap.containsKey(listType)) { Configuration config = new ConfigurationBuilder().forPackages(listType.getPackage().getName()); Reflections reflections = new Reflections(config); Set subTypes = reflections.getSubTypesOf(listType); if (subTypes.size()>0) { subTypesMap = new HashMap<>(); for (Class subtype: subTypes) if (subtype.isAnnotationPresent(AttributeRootNode.class)) { AttributeRootNode attributeRootNode = (AttributeRootNode)subtype.getAnnotation(AttributeRootNode.class); subTypesMap.put(attributeRootNode.value(), subtype); } } else logger.debug("no subtypes found for {}",listType.getName()); typeToSubtypeMap.put(listType, subTypesMap); } else { logger.info("subtypes already found in cache"); subTypesMap = typeToSubtypeMap.get(listType); } List toSetList = new ArrayList<>(); NodeIterator iterator = node.getNodes(); while (iterator.hasNext()){ Node currentNode = iterator.nextNode(); String primaryType = currentNode.getPrimaryNodeType().getName(); logger.debug("the current node {} has a list",currentNode.getName()); if (!include.isEmpty() && !primaryType.startsWith(include)) continue; if (!exclude.isEmpty() && primaryType.startsWith(exclude)) continue; if (subTypesMap.containsKey(primaryType)) toSetList.add(iterateNodeAttributeFields(subTypesMap.get(primaryType), currentNode)); else toSetList.add(iterateNodeAttributeFields(listType, currentNode)); } if (toSetList.size()!=0) field.set(obj, toSetList); } } return obj; } @SuppressWarnings({ "rawtypes", "unchecked" }) private static Object getPropertyValue(Class returnType, Property prop) throws Exception{ if (returnType.equals(String.class)) return prop.getString(); if (returnType.isEnum()) return Enum.valueOf(returnType, prop.getString()); if (returnType.equals(Calendar.class)) return prop.getDate(); if (returnType.equals(Boolean.class) || returnType.equals(boolean.class)) return prop.getBoolean(); if (returnType.equals(Long.class) || returnType.equals(long.class)) return prop.getLong(); if (returnType.equals(Integer.class) || returnType.equals(int.class)) return prop.getLong(); if (returnType.isArray()) { if (prop.getType()==PropertyType.BINARY) { byte[] bytes = new byte[32000]; try (InputStream stream = prop.getBinary().getStream()){ stream.read(bytes); } return bytes; } else { Object[] ret= getArrayValue(prop); return Arrays.copyOf(ret, ret.length, returnType); } } throw new Exception(String.format("class %s not recognized",returnType.getName())); } @SuppressWarnings({ "rawtypes" }) private static Value getObjectValue(Class returnType, Object value) throws Exception{ if (returnType.equals(String.class)) return new StringValue((String) value); if (returnType.isEnum()) return new StringValue(((Enum) value).toString()); if (returnType.equals(Calendar.class)) return new DateValue((Calendar) value); if (returnType.equals(Boolean.class) || returnType.equals(boolean.class)) return new BooleanValue((Boolean) value); if (returnType.equals(Long.class) || returnType.equals(long.class)) return new LongValue((Long) value); if (returnType.equals(Integer.class) || returnType.equals(int.class)) return new LongValue((Long) value); if (returnType.isArray()) { if (returnType.getComponentType().equals(Byte.class) || returnType.getComponentType().equals(byte.class)) return new BinaryValue((byte[]) value); } throw new Exception(String.format("class %s not recognized",returnType.getName())); } private static Object[] getArrayValue(Property prop) throws Exception{ Object[] values = new Object[prop.getValues().length]; int i = 0; for (Value value : prop.getValues()) values[i++] = getSingleValue(value); return values; } private static Object getPropertyValue(Property prop) throws Exception{ if (prop.isMultiple()){ Object[] values = new Object[prop.getValues().length]; int i = 0; for (Value value : prop.getValues()) values[i++] = getSingleValue(value); return values; } else return getSingleValue(prop.getValue()); } private static Object getSingleValue(Value value) throws Exception{ switch (value.getType()) { case PropertyType.DATE: return value.getDate(); case PropertyType.BOOLEAN: return value.getBoolean(); case PropertyType.LONG: return value.getDate(); default: return value.getString(); } } private static Set retrieveAllFields(Class clazz){ Set fields = new HashSet(); Class currentClass = clazz; do{ List fieldsFound = Arrays.asList(currentClass.getDeclaredFields()); fields.addAll(fieldsFound); }while ((currentClass =currentClass.getSuperclass())!=null); return fields; } public static Node createNodeFromItem(Session session, Node parentNode, T item){ try { //TODO: must understand this place is for name or title String primaryType= classHandler.getNodeType(item.getClass()); Node newNode = parentNode.addNode(item.getTitle(), primaryType); //newNode.setPrimaryType(primaryType); for (Field field : retrieveAllFields(item.getClass())){ if (field.isAnnotationPresent(Attribute.class)){ Attribute attribute = field.getAnnotation(Attribute.class); field.setAccessible(true); try{ //Class returnType = field.getType(); newNode.setProperty(attribute.value(), getObjectValue(field.getType(), field.get(item))); } catch (Exception e ) { logger.warn("error setting value for attribute "+attribute.value(),e); } } else if (field.isAnnotationPresent(NodeAttribute.class)){ NodeAttribute nodeAttribute = field.getAnnotation(NodeAttribute.class); if (nodeAttribute.isReadOnly()) continue; String nodeName = nodeAttribute.value(); logger.debug("retrieving field node "+field.getName()); field.setAccessible(true); try{ iterateItemNodeAttributeFields(field.get(item), newNode, nodeName); } catch (Exception e ) { logger.warn("error setting value",e); } } } return newNode; } catch (RepositoryException e) { logger.error("error writing repository",e); throw new RuntimeException(e); } } private static void iterateItemNodeAttributeFields(Object object, Node parentNode, String nodeName) throws Exception{ AttributeRootNode attributeRootNode = object.getClass().getAnnotation(AttributeRootNode.class); Node newNode = parentNode.addNode(nodeName, attributeRootNode.value()); //newNode.setPrimaryType(attributeRootNode.value()); for (Field field : retrieveAllFields(object.getClass())){ if (field.isAnnotationPresent(Attribute.class)){ Attribute attribute = field.getAnnotation(Attribute.class); field.setAccessible(true); try{ @SuppressWarnings("rawtypes") Class returnType = field.getType(); newNode.setProperty(attribute.value(), getObjectValue(returnType, field.get(object))); } catch (Exception e ) { logger.warn("error setting value",e); } } else if (field.isAnnotationPresent(MapAttribute.class)){ //logger.debug("found field {} of type annotated as MapAttribute in class {}", field.getName(), clazz.getName()); field.setAccessible(true); Map mapToset = (Map)field.get(object); for (Entry entry : mapToset.entrySet()) try{ newNode.setProperty(entry.getKey(), getObjectValue(entry.getValue().getClass(), entry.getValue())); } catch (Exception e ) { logger.warn("error setting value",e); } } else if (field.isAnnotationPresent(ListNodes.class)){ logger.debug("found field {} of type annotated as ListNodes in class {} on node {}", field.getName(), object.getClass().getName(), newNode.getName()); field.setAccessible(true); List toSetList = (List) field.get(object); int i = 0; for (Object obj: toSetList){ logger.debug("the current node {} has a list",newNode.getName()); iterateItemNodeAttributeFields(obj,newNode, field.getName()+(i++)); } } } } }