org.openspotlight.persist.support.SimplePersistImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.openspotlight.persist.support.SimplePersistImpl.java

Source

/**
 * OpenSpotLight - Open Source IT Governance Platform
 *
 * Copyright (c) 2009, CARAVELATECH CONSULTORIA E TECNOLOGIA EM INFORMATICA LTDA
 * or third-party contributors as indicated by the @author tags or express
 * copyright attribution statements applied by the authors.  All third-party
 * contributions are distributed under license by CARAVELATECH CONSULTORIA E
 * TECNOLOGIA EM INFORMATICA LTDA.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU Lesser General Public License  for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 *
 ***********************************************************************
 * OpenSpotLight - Plataforma de Governana de TI de Cdigo Aberto
 *
 * Direitos Autorais Reservados (c) 2009, CARAVELATECH CONSULTORIA E TECNOLOGIA
 * EM INFORMATICA LTDA ou como contribuidores terceiros indicados pela etiqueta
 * @author ou por expressa atribuio de direito autoral declarada e atribuda pelo autor.
 * Todas as contribuies de terceiros esto distribudas sob licena da
 * CARAVELATECH CONSULTORIA E TECNOLOGIA EM INFORMATICA LTDA.
 *
 * Este programa  software livre; voc pode redistribu-lo e/ou modific-lo sob os
 * termos da Licena Pblica Geral Menor do GNU conforme publicada pela Free Software
 * Foundation.
 *
 * Este programa  distribudo na expectativa de que seja til, porm, SEM NENHUMA
 * GARANTIA; nem mesmo a garantia implcita de COMERCIABILIDADE OU ADEQUAO A UMA
 * FINALIDADE ESPEC?FICA. Consulte a Licena Pblica Geral Menor do GNU para mais detalhes.
 *
 * Voc deve ter recebido uma cpia da Licena Pblica Geral Menor do GNU junto com este
 * programa; se no, escreva para:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */

package org.openspotlight.persist.support;

import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newLinkedList;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
import static java.lang.Class.forName;
import static java.text.MessageFormat.format;
import static java.util.Collections.reverse;
import static java.util.Collections.sort;
import static org.apache.commons.beanutils.PropertyUtils.getPropertyDescriptors;
import static org.openspotlight.common.util.Assertions.checkCondition;
import static org.openspotlight.common.util.Assertions.checkNotNull;
import static org.openspotlight.common.util.Exceptions.logAndReturnNew;
import static org.openspotlight.common.util.Reflection.unwrapCollectionFromMethodReturn;
import static org.openspotlight.common.util.Reflection.unwrapMapFromMethodReturn;
import static org.openspotlight.common.util.SLCollections.iterableToList;

import java.beans.PropertyDescriptor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.SerializationUtils;
import org.openspotlight.common.Pair;
import org.openspotlight.common.collection.IteratorBuilder;
import org.openspotlight.common.exception.SLRuntimeException;
import org.openspotlight.common.util.Conversion;
import org.openspotlight.common.util.Reflection;
import org.openspotlight.common.util.Wrapper;
import org.openspotlight.persist.annotation.IndexedProperty;
import org.openspotlight.persist.annotation.KeyProperty;
import org.openspotlight.persist.annotation.Name;
import org.openspotlight.persist.annotation.ParentProperty;
import org.openspotlight.persist.annotation.PersistPropertyAsStream;
import org.openspotlight.persist.annotation.SimpleNodeType;
import org.openspotlight.persist.annotation.TransientProperty;
import org.openspotlight.persist.internal.LazyProperty;
import org.openspotlight.persist.internal.StreamPropertyWithParent;
import org.openspotlight.storage.NodeCriteria.NodeCriteriaBuilder;
import org.openspotlight.storage.Partition;
import org.openspotlight.storage.StorageSession;
import org.openspotlight.storage.domain.NodeFactory;
import org.openspotlight.storage.domain.StorageNode;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.inject.Singleton;

/**
 * Created by IntelliJ IDEA. User: feuteston Date: 05/04/2010 Time: 13:19:32 To change this template use File | Settings | File
 * Templates.
 */
@Singleton
public class SimplePersistImpl implements SimplePersistCapable<StorageNode, StorageSession> {

    private static class BeanToNodeChildData {

        final Collection<SimpleNodeType> childrenToSave;

        final boolean multiple;

        final Class<?> nodeType;

        final String propertyName;

        private BeanToNodeChildData(final String propertyName, final boolean multiple, final Class<?> nodeType) {
            this.propertyName = propertyName;
            this.nodeType = nodeType;
            childrenToSave = newHashSet();
            this.multiple = multiple;
        }

    }

    private static class ConversionToBeanContext {
        final Wrapper<SimpleNodeType> beanReference = Wrapper.createMutable();

        final Map<StorageNode, Object> beansConverted = newHashMap();
        final StorageNode node;

        private ConversionToBeanContext(final StorageNode node) {
            this.node = node;
        }
    }

    private static class ConversionToNodeContext {
        final Set<StorageNode> allNodes = newHashSet();

        final SimpleNodeType bean;
        final Wrapper<StorageNode> nodeReference = Wrapper.createMutable();
        final Map<Object, StorageNode> nodesConverted = newHashMap();

        ConversionToNodeContext(final SimpleNodeType bean) {
            this.bean = bean;
        }

    }

    private static class Descriptors {
        private List<Pair<String, SimpleNodeType>> rootObjects = null;

        final SimpleNodeType bean;
        final Class<?> beanType;
        final List<PropertyDescriptor> childrenPropertiesDescriptor;
        final List<PropertyDescriptor> keyPropertiesDescriptor;
        final List<PropertyDescriptor> lazyPropertiesDescriptor;
        final List<PropertyDescriptor> parentPropertiesDescriptor;
        final Wrapper<PropertyDescriptor> parentPropertyDescriptor;
        final List<PropertyDescriptor> simplePropertiesDescriptor;
        final List<PropertyDescriptor> streamPropertiesDescriptor;

        private Descriptors(final Class<?> beanType, final SimpleNodeType bean,
                final Wrapper<PropertyDescriptor> parentPropertyDescriptor,
                final List<PropertyDescriptor> simplePropertiesDescriptor,
                final List<PropertyDescriptor> keyPropertiesDescriptor,
                final List<PropertyDescriptor> streamPropertiesDescriptor,
                final List<PropertyDescriptor> childrenPropertiesDescriptor,
                final List<PropertyDescriptor> parentPropertiesDescriptor,
                final List<PropertyDescriptor> lazyPropertiesDescriptor) {
            this.beanType = beanType;
            this.bean = bean;
            this.parentPropertyDescriptor = parentPropertyDescriptor;
            this.simplePropertiesDescriptor = simplePropertiesDescriptor;
            this.keyPropertiesDescriptor = keyPropertiesDescriptor;
            this.streamPropertiesDescriptor = streamPropertiesDescriptor;
            this.childrenPropertiesDescriptor = childrenPropertiesDescriptor;
            this.parentPropertiesDescriptor = parentPropertiesDescriptor;
            this.lazyPropertiesDescriptor = lazyPropertiesDescriptor;
        }

        static <T extends SimpleNodeType> Descriptors fillDescriptors(final Class<?> beanType) throws Exception {
            return fillDescriptors(null, beanType);
        }

        static <T extends SimpleNodeType> Descriptors fillDescriptors(final Object bean) throws Exception {
            return fillDescriptors(bean, bean.getClass());

        }

        static <T extends SimpleNodeType> Descriptors fillDescriptors(final Object bean, final Class<?> beanType)
                throws Exception {
            final Descriptors descriptors = Descriptors.createMutable(beanType, bean);
            for (final PropertyDescriptor descriptor : getPropertyDescriptors(beanType)) {
                if (descriptor.getName().equals("class")) {
                    continue;// Object#getClass
                }
                final Method readMethod = descriptor.getReadMethod();
                if (readMethod.isAnnotationPresent(TransientProperty.class)) {
                    continue;
                }
                final Class<?> returnType = readMethod.getReturnType();
                if (readMethod.isAnnotationPresent(ParentProperty.class)) {
                    final Object value = bean != null ? readMethod.invoke(bean) : null;
                    if (value != null && descriptors.parentPropertyDescriptor != null
                            && descriptors.parentPropertyDescriptor.getWrapped() != null) {
                        throw new IllegalStateException("only one parent property is allowed");
                    }
                    if (value != null) {
                        descriptors.parentPropertyDescriptor.setWrapped(descriptor);
                    }
                    descriptors.parentPropertiesDescriptor.add(descriptor);
                } else if (readMethod.isAnnotationPresent(KeyProperty.class)) {
                    descriptors.keyPropertiesDescriptor.add(descriptor);
                } else if (readMethod.isAnnotationPresent(PersistPropertyAsStream.class)) {
                    descriptors.streamPropertiesDescriptor.add(descriptor);
                } else if (returnType.isAssignableFrom(InputStream.class)) {
                    descriptors.streamPropertiesDescriptor.add(descriptor);
                } else if (SimpleNodeType.class.isAssignableFrom(returnType)) {
                    descriptors.childrenPropertiesDescriptor.add(descriptor);
                } else if (Collection.class.isAssignableFrom(returnType)) {
                    final Reflection.UnwrappedCollectionTypeFromMethodReturn<Object> methodInformation = unwrapCollectionFromMethodReturn(
                            readMethod);
                    if (SimpleNodeType.class.isAssignableFrom(methodInformation.getItemType())) {
                        descriptors.childrenPropertiesDescriptor.add(descriptor);
                    } else {
                        descriptors.streamPropertiesDescriptor.add(descriptor);
                    }
                } else if (Map.class.isAssignableFrom(returnType)) {
                    final Reflection.UnwrappedMapTypeFromMethodReturn<Object, Object> methodInformation = unwrapMapFromMethodReturn(
                            readMethod);
                    if (SimpleNodeType.class.isAssignableFrom(methodInformation.getItemType().getK2())) {
                        descriptors.childrenPropertiesDescriptor.add(descriptor);
                    } else {
                        descriptors.streamPropertiesDescriptor.add(descriptor);
                    }
                } else if (LazyProperty.class.isAssignableFrom(returnType)) {
                    descriptors.lazyPropertiesDescriptor.add(descriptor);
                } else {
                    descriptors.simplePropertiesDescriptor.add(descriptor);
                }
            }
            return Descriptors.createImmutableFrom(descriptors);
        }

        public static Descriptors createImmutableFrom(final Descriptors from) {
            return new Descriptors(from.beanType, from.bean,
                    Wrapper.<PropertyDescriptor>createImmutable(from.parentPropertyDescriptor.getWrapped()),
                    ImmutableList.copyOf(from.simplePropertiesDescriptor),
                    ImmutableList.copyOf(from.keyPropertiesDescriptor),
                    ImmutableList.copyOf(from.streamPropertiesDescriptor),
                    ImmutableList.copyOf(from.childrenPropertiesDescriptor),
                    ImmutableList.copyOf(from.parentPropertiesDescriptor),
                    ImmutableList.copyOf(from.lazyPropertiesDescriptor));
        }

        public static Descriptors createMutable(final Class<?> beanType, final Object bean) {
            return new Descriptors(beanType, (SimpleNodeType) bean, Wrapper.<PropertyDescriptor>createMutable(),
                    Lists.<PropertyDescriptor>newLinkedList(), Lists.<PropertyDescriptor>newLinkedList(),
                    Lists.<PropertyDescriptor>newLinkedList(), Lists.<PropertyDescriptor>newLinkedList(),
                    Lists.<PropertyDescriptor>newLinkedList(), Lists.<PropertyDescriptor>newLinkedList());
        }

        private List<Pair<String, SimpleNodeType>> loadRootObjects() throws Exception {
            final List<Pair<String, SimpleNodeType>> resultInReverseOrder = newLinkedList();
            Descriptors currentDescriptors = this;
            Object parent = bean;
            Object oldParent = null;

            do {
                final PropertyDescriptor descriptor = currentDescriptors.parentPropertyDescriptor.getWrapped();
                oldParent = parent;
                final Class<?> oldType = oldParent != null ? oldParent.getClass() : null;
                parent = descriptor != null ? descriptor.getReadMethod().invoke(parent) : null;
                currentDescriptors = parent != null ? Descriptors.fillDescriptors(parent) : null;
                String oldParentName = null;
                if (oldParent != null && currentDescriptors != null) {
                    lookingForNames: for (final PropertyDescriptor childDescriptor : currentDescriptors.childrenPropertiesDescriptor) {
                        if (childDescriptor.getPropertyType().equals(oldType)) {
                            oldParentName = childDescriptor.getName();
                            break lookingForNames;
                        }
                    }
                }
                resultInReverseOrder.add(Pair.newPair(oldParentName, (SimpleNodeType) oldParent));
            } while (currentDescriptors != null && oldParent != null);
            reverse(resultInReverseOrder);
            return ImmutableList.copyOf(resultInReverseOrder);

        }

        public List<Pair<String, SimpleNodeType>> getRootObjects() throws Exception {
            if (rootObjects == null) {
                rootObjects = loadRootObjects();
            }
            return rootObjects;
        }
    }

    private class InternalMethodsImpl implements InternalMethods {
        @Override
        public Object beforeUnConvert(final SimpleNodeType bean, final Serializable value,
                final Method readMethod) {
            try {
                if (value instanceof Collection) {
                    boolean mayBeStreamPropertyWithParent = true;
                    if (readMethod != null) {
                        final Reflection.UnwrappedCollectionTypeFromMethodReturn<Object> methodDescription = unwrapCollectionFromMethodReturn(
                                readMethod);
                        mayBeStreamPropertyWithParent = StreamPropertyWithParent.class
                                .isAssignableFrom(methodDescription.getItemType());
                    }
                    if (mayBeStreamPropertyWithParent) {
                        final Collection<?> valueAsCollection = (Collection<?>) value;
                        for (final Object o : valueAsCollection) {
                            if (o instanceof StreamPropertyWithParent) {
                                ((StreamPropertyWithParent) o).setParent(bean);
                            }
                        }
                    }
                } else if (value instanceof Map) {
                    boolean mayBeStreamPropertyWithParent = true;
                    if (readMethod != null) {
                        final Reflection.UnwrappedMapTypeFromMethodReturn<Object, Object> methodDescription = unwrapMapFromMethodReturn(
                                readMethod);
                        mayBeStreamPropertyWithParent = StreamPropertyWithParent.class
                                .isAssignableFrom(methodDescription.getItemType().getK2());

                    }

                    if (mayBeStreamPropertyWithParent) {
                        final Map<?, ?> valueAsMap = (Map<?, ?>) value;
                        for (final Map.Entry<?, ?> entry : valueAsMap.entrySet()) {
                            if (entry.getValue() instanceof StreamPropertyWithParent) {
                                ((StreamPropertyWithParent) entry.getValue()).setParent(bean);
                            }
                        }
                    }
                } else if (value instanceof StreamPropertyWithParent) {
                    ((StreamPropertyWithParent) value).setParent(bean);
                }
                return value;
            } catch (final Exception e) {
                throw logAndReturnNew(e, SLRuntimeException.class);

            }

        }

        @Override
        public String getNodeName(final Class<?> nodeType) {
            return internalGetNodeName(nodeType);
        }

    }

    private static final String[] EMPTY_NAMES = new String[] {};

    private static final Object[] EMPTY_VALUES = new Object[] {};

    private static final String NODE_ENTRY_TYPE = "internal-node-entry-type";

    private static final String NODE_PROPERTY_NAME = "internal-node-proeprty-name";

    private static final String SHA1_PROPERTY_NAME = "internal-{0}-sha1";

    private final Partition currentPartition;

    private final StorageSession currentSession;

    private final InternalMethods internalMethods = new InternalMethodsImpl();

    public SimplePersistImpl(final StorageSession currentSession, final Partition currentPartition) {
        this.currentSession = currentSession;
        this.currentPartition = currentPartition;
    }

    private static <T extends Serializable> T asObject(final InputStream is) throws Exception {
        if (is == null) {
            return null;
        }
        final ObjectInputStream ois = new ObjectInputStream(is);
        final T result = (T) ois.readObject();
        return result;
    }

    private static <T extends Serializable> InputStream asStream(final T o) throws Exception {
        if (o == null) {
            return null;
        }
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final ObjectOutputStream ois = new ObjectOutputStream(baos);
        ois.writeObject(o);
        ois.flush();
        return new ByteArrayInputStream(baos.toByteArray());
    }

    private Serializable beforeSerializeList(final List<? extends Serializable> value, final Method readMethod)
            throws Exception {
        if (readMethod != null) {

            final Reflection.UnwrappedCollectionTypeFromMethodReturn<Object> methodDescription = unwrapCollectionFromMethodReturn(
                    readMethod);

            if (StreamPropertyWithParent.class.isAssignableFrom(methodDescription.getItemType())) {
                final LinkedList<Serializable> newCollection = newLinkedList();
                for (final Serializable o : value) {
                    newCollection.add(beforeSerializeSerializable(o));
                }
                return newCollection;
            }
            return (Serializable) value;
        } else {
            final LinkedList<Serializable> newCollection = newLinkedList();
            for (final Serializable o : value) {
                newCollection.add(beforeSerializeSerializable(o));
            }
            return newCollection;
        }

    }

    private Serializable beforeSerializeMap(final Map<? extends Serializable, ? extends Serializable> value,
            final Method readMethod) throws Exception {
        if (readMethod != null) {
            final Reflection.UnwrappedMapTypeFromMethodReturn<Object, Object> methodDescription = unwrapMapFromMethodReturn(
                    readMethod);

            if (StreamPropertyWithParent.class.isAssignableFrom(methodDescription.getItemType().getK2())) {
                final HashMap<Serializable, Serializable> newMap = newHashMap();
                for (final Map.Entry<? extends Serializable, ? extends Serializable> entry : value.entrySet()) {
                    newMap.put(entry.getKey(), beforeSerializeSerializable(entry.getValue()));
                }
                return newMap;
            }
            return (Serializable) value;
        } else {
            final HashMap<Serializable, Serializable> newMap = newHashMap();
            for (final Map.Entry<? extends Serializable, ? extends Serializable> entry : value.entrySet()) {
                newMap.put(entry.getKey(), beforeSerializeSerializable(entry.getValue()));
            }
            return newMap;

        }
    }

    private Serializable beforeSerializeSerializable(final Serializable value) {
        if (value instanceof StreamPropertyWithParent) {
            final StreamPropertyWithParent typedValue = (StreamPropertyWithParent) value;

            final SimpleNodeType oldParent = typedValue.getParent();
            typedValue.setParent(null);
            final StreamPropertyWithParent newValue = (StreamPropertyWithParent) SerializationUtils.clone(value);
            typedValue.setParent(oldParent);
            return newValue;
        }
        return value;
    }

    private Serializable beforeSerializeSet(final Set<? extends Serializable> value, final Method readMethod)
            throws Exception {
        if (readMethod != null) {
            final Reflection.UnwrappedCollectionTypeFromMethodReturn<Object> methodDescription = unwrapCollectionFromMethodReturn(
                    readMethod);

            if (StreamPropertyWithParent.class.isAssignableFrom(methodDescription.getItemType())) {
                final HashSet<Serializable> newCollection = newHashSet();
                for (final Serializable o : value) {
                    newCollection.add(beforeSerializeSerializable(o));
                }
                return newCollection;
            }
            return (Serializable) value;
        } else {
            final HashSet<Serializable> newCollection = newHashSet();
            for (final Serializable o : value) {
                newCollection.add(beforeSerializeSerializable(o));
            }
            return newCollection;
        }

    }

    private Map<String, PropertyDescriptor> createMapWith(final PropertyDescriptor[] propertyDescriptors) {
        final ImmutableMap.Builder<String, PropertyDescriptor> builder = ImmutableMap
                .<String, PropertyDescriptor>builder();
        for (final PropertyDescriptor d : propertyDescriptors) {
            builder.put(d.getName(), d);

        }
        return builder.build();
    }

    private <T> StorageNode createNewNode(final ConversionToNodeContext context, final StorageNode parentNode,
            final Descriptors descriptors, final String propertyName) throws Exception {
        final String name = internalGetNodeName(descriptors.bean);
        final NodeFactory.NodeBuilder builder = currentSession.withPartition(currentPartition)
                .createNodeWithType(name);

        if (parentNode != null) {
            builder.withParent(parentNode);
        }
        for (final PropertyDescriptor descriptor : descriptors.keyPropertiesDescriptor) {
            builder.withSimpleKey(descriptor.getName(),
                    Conversion.convert(descriptor.getReadMethod().invoke(descriptors.bean), String.class));
        }
        if (propertyName != null) {
            builder.withSimpleKey(NODE_PROPERTY_NAME, propertyName);
        }
        final StorageNode newNode = builder.andCreate();
        newNode.setIndexedProperty(currentSession, NODE_ENTRY_TYPE, internalGetNodeType(descriptors.bean));
        return newNode;
    }

    private <T> void fillBeanChildren(final ConversionToBeanContext context, final StorageNode node, final T bean,
            final List<PropertyDescriptor> childrenPropertiesDescriptor) throws Exception {
        for (final PropertyDescriptor descriptor : childrenPropertiesDescriptor) {
            final Class<?> propertyType = descriptor.getPropertyType();
            Class<?> nodeType = null;
            final boolean isMultiple = Collection.class.isAssignableFrom(propertyType);
            final Method readMethod = descriptor.getReadMethod();
            if (isMultiple) {
                final Reflection.UnwrappedCollectionTypeFromMethodReturn<Object> methodDescription = unwrapCollectionFromMethodReturn(
                        readMethod);
                if (List.class.isAssignableFrom(propertyType)) {
                    descriptor.getWriteMethod().invoke(bean, newLinkedList());
                } else if (Set.class.isAssignableFrom(propertyType)) {
                    descriptor.getWriteMethod().invoke(bean, newHashSet());
                } else {
                    throw new IllegalStateException("wrong child type");
                }
                nodeType = methodDescription.getItemType();
            } else if (SimpleNodeType.class.isAssignableFrom(propertyType)) {
                nodeType = propertyType;
            } else {
                throw new IllegalStateException("wrong child type");
            }

            if (!SimpleNodeType.class.isAssignableFrom(nodeType)) {
                throw new IllegalStateException("wrong child type");
            }
            final String childrenName = internalGetNodeName(nodeType);
            Iterable<StorageNode> children = iterableToList(
                    node.getChildren(currentPartition, currentSession, childrenName));
            children = filterChildrenWithProperty(children, descriptor.getName());
            final List<Object> childrenAsBeans = newLinkedList();
            for (final StorageNode child : children) {
                childrenAsBeans.add(internalConvertNodeToBean(context, child, bean));
            }
            if (isMultiple) {
                final Collection c = (Collection) readMethod.invoke(bean);
                for (final Object o : childrenAsBeans) {
                    c.add(o);
                }
                if (Comparable.class.isAssignableFrom(nodeType) && c instanceof List) {
                    sort((List) c);
                }
            } else if (childrenAsBeans.size() > 0) {
                final Object value = childrenAsBeans.iterator().next();
                descriptor.getWriteMethod().invoke(bean, value);
            }
        }
    }

    private void fillBeanLazyProperties(final StorageNode cached, final Object bean,
            final List<PropertyDescriptor> lazyPropertiesDescriptors) throws Exception {
        for (final PropertyDescriptor property : lazyPropertiesDescriptors) {

            final String propertyName = property.getName();
            final LazyProperty<?> lazyProperty = (LazyProperty<?>) property.getReadMethod().invoke(bean);
            lazyProperty.getMetadata().setParentKey(cached.getKey());
            lazyProperty.getMetadata().setSavedNode(cached);
            lazyProperty.getMetadata().setPropertyName(propertyName);
            final String sha1 = cached.getPropertyValueAsString(currentSession,
                    format(SHA1_PROPERTY_NAME, propertyName));
            lazyProperty.getMetadata().internalSetSha1(sha1);
        }

    }

    private void fillBeanParent(final List<PropertyDescriptor> parentPropertyDescriptors, final Object bean,
            final Object beanParent) throws Exception {
        if (beanParent != null) {
            final Class<?> parentType = beanParent.getClass();
            for (final PropertyDescriptor descriptor : parentPropertyDescriptors) {
                if (descriptor.getPropertyType().isAssignableFrom(parentType)) {
                    descriptor.getWriteMethod().invoke(bean, beanParent);
                    break;
                }
            }
        }
    }

    private <T> void fillBeanSimpleProperties(final StorageNode node, final T bean,
            final List<PropertyDescriptor> simplePropertiesDescriptor) throws Exception {
        for (final PropertyDescriptor descriptor : simplePropertiesDescriptor) {
            final String value = node.getPropertyValueAsString(currentSession, descriptor.getName());
            if (value == null && descriptor.getPropertyType().isPrimitive()) {
                continue;
            }
            descriptor.getWriteMethod().invoke(bean, Conversion.convert(value, descriptor.getPropertyType()));
        }
    }

    private <T> void fillBeanStreamProperties(final StorageNode node, final T bean,
            final List<PropertyDescriptor> streamPropertiesDescriptor) throws Exception {
        for (final PropertyDescriptor descriptor : streamPropertiesDescriptor) {
            final Class<?> propertyType = descriptor.getPropertyType();
            if (InputStream.class.isAssignableFrom(propertyType)) {
                final InputStream value = node.getPropertyValueAsStream(currentSession, descriptor.getName());
                descriptor.getWriteMethod().invoke(bean, value);
            } else if (Serializable.class.isAssignableFrom(propertyType)
                    || Collection.class.isAssignableFrom(propertyType)
                    || Map.class.isAssignableFrom(propertyType)) {
                final Serializable value = asObject(
                        node.getPropertyValueAsStream(currentSession, descriptor.getName()));
                descriptor.getWriteMethod().invoke(bean, getInternalMethods().beforeUnConvert((SimpleNodeType) bean,
                        value, descriptor.getReadMethod()));
            } else {
                throw new IllegalStateException("wrong type");
            }
        }
    }

    private <T> void fillNodeChildrenProperties(final ConversionToNodeContext context, final T bean,
            final List<PropertyDescriptor> childrenPropertiesDescriptor, final StorageNode newNodeEntry)
            throws Exception {

        final Map<String, BeanToNodeChildData> nodesToConvert = newHashMap();
        for (final PropertyDescriptor property : childrenPropertiesDescriptor) {
            final String propertyName = property.getName();
            BeanToNodeChildData data = nodesToConvert.get(propertyName);
            final Class<?> propertyType = property.getPropertyType();

            final Object value = property.getReadMethod().invoke(bean);
            if (SimpleNodeType.class.isAssignableFrom(propertyType)) {
                if (data == null) {
                    data = new BeanToNodeChildData(propertyName, Collection.class.isAssignableFrom(propertyType),
                            propertyType);
                    nodesToConvert.put(propertyName, data);
                }
                data.childrenToSave.add((SimpleNodeType) value);
            } else if (Collection.class.isAssignableFrom(propertyType)) {
                final Reflection.UnwrappedCollectionTypeFromMethodReturn<Object> methodInformation = unwrapCollectionFromMethodReturn(
                        property.getReadMethod());
                if (data == null) {
                    data = new BeanToNodeChildData(propertyName, Collection.class.isAssignableFrom(propertyType),
                            methodInformation.getItemType());
                    nodesToConvert.put(propertyName, data);
                }
                if (List.class.isAssignableFrom(methodInformation.getCollectionType())) {
                    for (final SimpleNodeType t : (List<SimpleNodeType>) value) {
                        data.childrenToSave.add(t);
                    }
                } else if (Set.class.isAssignableFrom(methodInformation.getCollectionType())) {
                    for (final SimpleNodeType t : (Set<SimpleNodeType>) value) {
                        data.childrenToSave.add(t);
                    }
                } else {
                    throw new IllegalStateException("invalid collection type");
                }

            } else {
                throw new IllegalStateException("invalid type:" + property.getPropertyType());
            }
        }
        for (final BeanToNodeChildData data : nodesToConvert.values()) {
            if (!data.multiple && data.childrenToSave.size() > 1) {
                throw new IllegalStateException("single property with more than one child");
            }
            for (final SimpleNodeType beanBeenSaved : data.childrenToSave) {
                internalConvertBeanToNode(context, data.propertyName, beanBeenSaved, newNodeEntry);
            }
            context.allNodes.addAll(iterableToList(newNodeEntry.getChildren(currentPartition, currentSession,
                    internalGetNodeName(data.nodeType))));
        }

    }

    private void fillNodeLazyProperties(final ConversionToNodeContext context, final SimpleNodeType bean,
            final List<PropertyDescriptor> lazyPropertiesDescriptor, final StorageNode nodeEntry) throws Exception {
        for (final PropertyDescriptor descriptor : lazyPropertiesDescriptor) {
            final LazyProperty<?> property = (LazyProperty<?>) descriptor.getReadMethod().invoke(bean);
            if (property != null && property.getMetadata().needsSave()) {
                final String propertyName = descriptor.getName();
                final Object value = property.getMetadata().getTransient();
                if (value instanceof InputStream) {
                    nodeEntry.setSimpleProperty(currentSession, propertyName, (InputStream) value);
                } else if (value instanceof Set) {
                    nodeEntry.setSimpleProperty(currentSession, propertyName,
                            asStream(beforeSerializeSet((Set<? extends Serializable>) value, null)));
                } else if (value instanceof List) {
                    nodeEntry.setSimpleProperty(currentSession, propertyName,
                            asStream(beforeSerializeList((List<? extends Serializable>) value, null)));
                } else if (value instanceof Map) {
                    nodeEntry.setSimpleProperty(currentSession, propertyName, asStream(
                            beforeSerializeMap((Map<? extends Serializable, ? extends Serializable>) value, null)));

                } else {// Serializable
                    nodeEntry.setSimpleProperty(currentSession, propertyName,
                            asStream(beforeSerializeSerializable((Serializable) value)));

                }
                nodeEntry.setSimpleProperty(currentSession, format(SHA1_PROPERTY_NAME, propertyName),
                        property.getMetadata().getSha1());
                property.getMetadata().markAsSaved();
            }
            ;
        }

    }

    private <T> void fillNodeSimpleProperties(final T bean,
            final List<PropertyDescriptor> simplePropertiesDescriptor, final StorageNode newNodeEntry)
            throws Exception {
        for (final PropertyDescriptor property : simplePropertiesDescriptor) {
            if (property.getReadMethod().isAnnotationPresent(IndexedProperty.class)) {
                newNodeEntry.setIndexedProperty(currentSession, property.getName(),
                        Conversion.convert(property.getReadMethod().invoke(bean), String.class));
            } else {
                newNodeEntry.setSimpleProperty(currentSession, property.getName(),
                        Conversion.convert(property.getReadMethod().invoke(bean), String.class));
            }
        }
    }

    private <T> void fillNodeStreamProperties(final T bean,
            final List<PropertyDescriptor> streamPropertiesDescriptor, final StorageNode newNodeEntry)
            throws Exception {
        for (final PropertyDescriptor property : streamPropertiesDescriptor) {
            final Class<?> propertyType = property.getPropertyType();
            final Method readMethod = property.getReadMethod();
            final Object value = readMethod.invoke(bean);
            if (InputStream.class.isAssignableFrom(propertyType)) {
                newNodeEntry.setSimpleProperty(currentSession, property.getName(), (InputStream) value);
            } else if (Collection.class.isAssignableFrom(propertyType)) {
                final Reflection.UnwrappedCollectionTypeFromMethodReturn<Object> methodInformation = unwrapCollectionFromMethodReturn(
                        property.getReadMethod());
                if (List.class.isAssignableFrom(methodInformation.getCollectionType())) {
                    newNodeEntry.setSimpleProperty(currentSession, property.getName(),
                            asStream(beforeSerializeList((List<? extends Serializable>) value, readMethod)));
                } else if (Set.class.isAssignableFrom(methodInformation.getCollectionType())) {
                    newNodeEntry.setSimpleProperty(currentSession, property.getName(),
                            asStream(beforeSerializeSet((Set<? extends Serializable>) value, readMethod)));
                } else {
                    throw new IllegalStateException("invalid collection type");
                }

            } else if (Map.class.isAssignableFrom(propertyType)) {
                newNodeEntry.setSimpleProperty(currentSession, property.getName(),
                        asStream(beforeSerializeMap((Map<? extends Serializable, ? extends Serializable>) value,
                                readMethod)));

            } else if (Serializable.class.isAssignableFrom(propertyType)) {
                if (propertyType.equals(String.class) || Number.class.isAssignableFrom(propertyType)
                        || propertyType.isPrimitive() || Boolean.class.equals(propertyType)
                        || Character.class.equals(propertyType) || Date.class.equals(propertyType)) {
                    newNodeEntry.setSimpleProperty(currentSession, property.getName(),
                            Conversion.convert(value, String.class));
                } else {
                    newNodeEntry.setSimpleProperty(currentSession, property.getName(),
                            asStream(beforeSerializeSerializable((Serializable) value)));
                }

            } else {
                throw new IllegalStateException("invalid type");
            }

        }
    }

    private Iterable<StorageNode> filterChildrenWithProperty(final Iterable<StorageNode> children,
            final String name) {
        if (name == null) {
            return children;
        }
        final List<StorageNode> filtered = newLinkedList();
        for (final StorageNode e : children) {
            final String propertyValue = e.getPropertyValueAsString(currentSession, NODE_PROPERTY_NAME);
            if (name.equals(propertyValue)) {
                filtered.add(e);
            }
        }
        return filtered;
    }

    private Class<?> findClassFromNode(final StorageNode nodeEntry) throws Exception {
        return forName(nodeEntry.getPropertyValueAsString(currentSession, NODE_ENTRY_TYPE));
    }

    private <T> StorageNode internalConvertBeanToNode(final ConversionToNodeContext context,
            final String propertyName, final SimpleNodeType bean, final StorageNode parentNode) throws Exception {
        final boolean firstInvocation = bean == null;
        if (firstInvocation) {
            StorageNode currentParentNode = parentNode;
            final Descriptors currentBeanDescriptors = Descriptors.fillDescriptors(context.bean);
            final List<Pair<String, SimpleNodeType>> rootObjects = currentBeanDescriptors.getRootObjects();
            for (final Pair<String, SimpleNodeType> pair : rootObjects) {
                currentParentNode = internalConvertBeanToNode(context, pair.getK1(), pair.getK2(),
                        currentParentNode);
            }
            context.allNodes.removeAll(context.nodesConverted.values());
            for (final StorageNode unusedNode : context.allNodes) {
                currentSession.removeNode(unusedNode);
            }
            return context.nodeReference.getWrapped();
        } else {
            StorageNode cached = context.nodesConverted.get(bean);
            if (cached == null) {
                final Descriptors parentDescriptors = Descriptors.fillDescriptors(bean);
                cached = createNewNode(context, parentNode, parentDescriptors, propertyName);
                context.nodesConverted.put(bean, cached);
                fillNodeSimpleProperties(bean, parentDescriptors.simplePropertiesDescriptor, cached);
                fillNodeStreamProperties(bean, parentDescriptors.streamPropertiesDescriptor, cached);
                fillNodeChildrenProperties(context, bean, parentDescriptors.childrenPropertiesDescriptor, cached);
                fillNodeLazyProperties(context, bean, parentDescriptors.lazyPropertiesDescriptor, cached);
                if (bean == context.bean) {
                    context.nodeReference.setWrapped(cached);
                }
            }
            return cached;
        }

    }

    private <T> StorageNode internalConvertBeanToNode(final StorageNode parent, final T bean) throws Exception {
        final ConversionToNodeContext context = new ConversionToNodeContext((SimpleNodeType) bean);
        internalConvertBeanToNode(context, null, null, parent);
        final StorageNode result = context.nodeReference.getWrapped();
        checkNotNull("result", result);
        return result;
    }

    private <T> Iterable<T> internalConvertNodesToBeans(final Iterable<StorageNode> nodes) throws Exception {
        final IteratorBuilder.SimpleIteratorBuilder<T, StorageNode> b = IteratorBuilder.createIteratorBuilder();
        b.withConverter(new IteratorBuilder.Converter<T, StorageNode>() {
            @Override
            public T convert(final StorageNode nodeEntry) throws Exception {
                return (T) convertNodeToBean(nodeEntry);
            }
        });
        final Iterable<T> result = b.withItems(nodes).andBuild();
        return result;

    }

    private Object internalConvertNodeToBean(final ConversionToBeanContext context, final StorageNode nodeEntry,
            final Object beanParent) throws Exception {

        if (nodeEntry == null) {
            final List<StorageNode> parents = newLinkedList();
            StorageNode currentParent = context.node;
            while (currentParent != null && isSimpleNode(currentParent)) {
                parents.add(currentParent);
                currentParent = currentParent.getParent(currentSession);
            }
            reverse(parents);
            Object currentParentAsBean = null;
            for (final StorageNode parentEntry : parents) {
                currentParentAsBean = internalConvertNodeToBean(context, parentEntry, currentParentAsBean);
            }
            return context.beanReference.getWrapped();
        } else {
            Object cached = context.beansConverted.get(nodeEntry);
            if (cached == null) {
                final Class<?> beanType = findClassFromNode(nodeEntry);
                cached = beanType.newInstance();
                context.beansConverted.put(nodeEntry, cached);
                final Descriptors descriptors = Descriptors.fillDescriptors(beanType);
                fillBeanParent(descriptors.parentPropertiesDescriptor, cached, beanParent);
                fillBeanSimpleProperties(nodeEntry, cached, descriptors.keyPropertiesDescriptor);
                fillBeanSimpleProperties(nodeEntry, cached, descriptors.simplePropertiesDescriptor);
                fillBeanStreamProperties(nodeEntry, cached, descriptors.streamPropertiesDescriptor);
                fillBeanLazyProperties(nodeEntry, cached, descriptors.lazyPropertiesDescriptor);
                fillBeanChildren(context, nodeEntry, cached, descriptors.childrenPropertiesDescriptor);
                if (nodeEntry.equals(context.node)) {
                    context.beanReference.setWrapped((SimpleNodeType) cached);
                }
            }
            return cached;
        }

    }

    private <T> String internalGetNodeName(final Class<T> beanType) {
        final Name annotation = beanType.getAnnotation(Name.class);
        return annotation != null ? annotation.value() : beanType.getName();
    }

    private <T> String internalGetNodeName(final T bean) {
        return this.<T>internalGetNodeName((Class<T>) bean.getClass());
    }

    private <T> String internalGetNodeType(final Class<T> beanType) {
        return beanType.getName();
    }

    private String internalGetNodeType(final StorageNode node) {
        return node.getType();
    }

    private <T> String internalGetNodeType(final T bean) {
        return this.<T>internalGetNodeType((Class<T>) bean.getClass());
    }

    private boolean isSimpleNode(final StorageNode currentParent) {
        return currentParent.getPropertyValueAsString(currentSession, NODE_ENTRY_TYPE) != null;
    }

    @Override
    public void closeResources() {
        currentSession.closeResources();
    }

    @Override
    public <T> Iterable<StorageNode> convertBeansToNodes(final Iterable<T> beans) {
        return convertBeansToNodes(null, beans);
    }

    @Override
    public <T> Iterable<StorageNode> convertBeansToNodes(final StorageNode parent, final Iterable<T> beans) {
        try {
            final List<StorageNode> itemsConverted = newArrayList();
            for (final T bean : beans) {
                itemsConverted.add(convertBeanToNode(parent, bean));
            }
            return itemsConverted;
        } catch (final Exception e) {
            throw logAndReturnNew(e, SLRuntimeException.class);

        }

    }

    @Override
    public <T> StorageNode convertBeanToNode(final StorageNode parent, final T bean) {
        try {
            return internalConvertBeanToNode(parent, bean);
        } catch (final Exception e) {
            throw logAndReturnNew(e, SLRuntimeException.class);

        }

    }

    @Override
    public <T> StorageNode convertBeanToNode(final T bean) throws Exception {
        return convertBeanToNode(null, bean);
    }

    @Override
    public <T> Iterable<T> convertNodesToBeans(final Iterable<StorageNode> nodes) {
        try {
            return internalConvertNodesToBeans(nodes);
        } catch (final Exception e) {
            throw logAndReturnNew(e, SLRuntimeException.class);

        }

    }

    @Override
    public <T> T convertNodeToBean(final StorageNode node) throws Exception {
        final T bean = (T) internalConvertNodeToBean(new ConversionToBeanContext(node), null, null);
        return bean;
    }

    @Override
    public <T> Iterable<T> findAll(final Class<T> beanType) {
        return findByProperties(beanType, EMPTY_NAMES, EMPTY_VALUES);
    }

    @Override
    public <T> Iterable<T> findAll(final StorageNode parentNode, final Class<T> beanType) {
        return findByProperties(parentNode, beanType, EMPTY_NAMES, EMPTY_VALUES);
    }

    @Override
    public <T> Iterable<T> findByProperties(final Class<T> beanType, final String[] propertyNames,
            final Object[] propertyValues) {
        return findByProperties(null, beanType, propertyNames, propertyValues);
    }

    @Override
    public <T> Iterable<T> findByProperties(final StorageNode parent, final Class<T> beanType,
            final String[] propertyNames, final Object[] propertyValues) {
        try {
            checkNotNull("currentPartition", currentPartition);
            checkNotNull("currentSession", currentSession);
            checkNotNull("beanType", beanType);
            checkNotNull("propertyNames", propertyNames);
            checkNotNull("propertyValues", propertyValues);
            checkCondition("namesAndValues:sameSize", propertyNames.length == propertyValues.length);

            final NodeCriteriaBuilder builder = currentSession.withPartition(currentPartition).createCriteria()
                    .withNodeType(internalGetNodeName(beanType));
            final Map<String, PropertyDescriptor> allDescriptors = createMapWith(
                    PropertyUtils.getPropertyDescriptors(beanType));

            for (int i = 0, size = propertyNames.length; i < size; i++) {

                final PropertyDescriptor descriptor = allDescriptors.get(propertyNames[i]);
                if (descriptor == null) {
                    throw new SLRuntimeException("invalid property:" + propertyNames[i]);
                }
                builder.withProperty(propertyNames[i])
                        .equalsTo(Conversion.convert(propertyValues[i], String.class));
            }
            final Iterable<StorageNode> foundItems = builder.buildCriteria().andSearch(currentSession);

            final IteratorBuilder.SimpleIteratorBuilder<T, StorageNode> b = IteratorBuilder
                    .<T, StorageNode>createIteratorBuilder();
            b.withConverter(new IteratorBuilder.Converter<T, StorageNode>() {
                @Override
                public T convert(final StorageNode nodeEntry) throws Exception {
                    return (T) convertNodeToBean(nodeEntry);
                }
            });
            b.withReferee(new IteratorBuilder.NextItemReferee<StorageNode>() {
                @Override
                public boolean canAcceptAsNewItem(final StorageNode nodeEntry) {
                    final String typeAsString = nodeEntry.getPropertyValueAsString(currentSession, NODE_ENTRY_TYPE);
                    if (typeAsString != null && typeAsString.equals(beanType.getName())) {
                        if (parent != null) {
                            StorageNode parentNode = nodeEntry;
                            while (parentNode != null) {
                                if (parentNode.getKey().equals(parent.getKey())) {
                                    return true;
                                }
                                parentNode = parentNode.getParent(currentSession);
                            }
                            return false;
                        }
                        return true;
                    }
                    return false;
                }
            });
            final Iterable<T> result = b.withItems(foundItems).andBuild();
            return result;

        } catch (final Exception e) {
            throw logAndReturnNew(e, SLRuntimeException.class);

        }

    }

    @Override
    public <T> T findUnique(final Class<T> beanType) {
        return findUniqueByProperties(beanType, EMPTY_NAMES, EMPTY_VALUES);
    }

    @Override
    public <T> T findUnique(final StorageNode parentNode, final Class<T> beanType) {
        return findUniqueByProperties(parentNode, beanType, EMPTY_NAMES, EMPTY_VALUES);
    }

    @Override
    public <T> T findUniqueByProperties(final Class<T> beanType, final String[] propertyNames,
            final Object[] propertyValues) {
        return findUniqueByProperties(null, beanType, propertyNames, propertyValues);
    }

    @Override
    public <T> T findUniqueByProperties(final StorageNode parent, final Class<T> beanType,
            final String[] propertyNames, final Object[] propertyValues) {
        final Iterable<T> result = findByProperties(parent, beanType, propertyNames, propertyValues);
        final Iterator<T> it = result.iterator();
        final T resultAsBean = it.hasNext() ? it.next() : null;
        return resultAsBean;
    }

    @Override
    public Partition getCurrentPartition() {
        return currentPartition;
    }

    @Override
    public StorageSession getCurrentSession() {
        return currentSession;
    }

    @Override
    public InternalMethods getInternalMethods() {
        return internalMethods;

    }

    @Override
    public StorageSession.PartitionMethods getPartitionMethods() {
        return currentSession.withPartition(currentPartition);
    }

}