org.jspresso.framework.model.component.basic.AbstractComponentInvocationHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.jspresso.framework.model.component.basic.AbstractComponentInvocationHandler.java

Source

/*
 * Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved.
 *
 *  This file is part of the Jspresso framework.
 *
 *  Jspresso is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Jspresso 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 Jspresso.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.jspresso.framework.model.component.basic;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeListenerProxy;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import gnu.trove.map.hash.THashMap;
import gnu.trove.set.hash.THashSet;
import gnu.trove.set.hash.TLinkedHashSet;
import org.apache.commons.beanutils.MethodUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.jspresso.framework.model.component.ComponentException;
import org.jspresso.framework.model.component.IComponent;
import org.jspresso.framework.model.component.IComponentCollectionFactory;
import org.jspresso.framework.model.component.IComponentExtension;
import org.jspresso.framework.model.component.IComponentExtensionFactory;
import org.jspresso.framework.model.component.IComponentFactory;
import org.jspresso.framework.model.component.IComponentFactoryAware;
import org.jspresso.framework.model.component.ILifecycleCapable;
import org.jspresso.framework.model.component.IPropertyTranslation;
import org.jspresso.framework.model.component.service.AbstractComponentServiceDelegate;
import org.jspresso.framework.model.component.service.DependsOnHelper;
import org.jspresso.framework.model.component.service.IComponentService;
import org.jspresso.framework.model.component.service.ILifecycleInterceptor;
import org.jspresso.framework.model.descriptor.IBooleanPropertyDescriptor;
import org.jspresso.framework.model.descriptor.ICollectionPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IComponentDescriptor;
import org.jspresso.framework.model.descriptor.IModelDescriptorAware;
import org.jspresso.framework.model.descriptor.IPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IReferencePropertyDescriptor;
import org.jspresso.framework.model.descriptor.IRelationshipEndPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IStringPropertyDescriptor;
import org.jspresso.framework.model.descriptor.MandatoryPropertyException;
import org.jspresso.framework.model.descriptor.basic.AbstractComponentDescriptor;
import org.jspresso.framework.model.entity.EntityHelper;
import org.jspresso.framework.model.entity.IEntity;
import org.jspresso.framework.model.entity.IEntityFactory;
import org.jspresso.framework.model.entity.IEntityLifecycleHandler;
import org.jspresso.framework.security.UserPrincipal;
import org.jspresso.framework.util.accessor.IAccessor;
import org.jspresso.framework.util.accessor.IAccessorFactory;
import org.jspresso.framework.util.accessor.ICollectionAccessor;
import org.jspresso.framework.util.bean.AccessorInfo;
import org.jspresso.framework.util.bean.BeanPropertyChangeRecorder;
import org.jspresso.framework.util.bean.EAccessorType;
import org.jspresso.framework.util.bean.IPropertyChangeCapable;
import org.jspresso.framework.util.bean.SinglePropertyChangeSupport;
import org.jspresso.framework.util.bean.SingleWeakPropertyChangeSupport;
import org.jspresso.framework.util.collection.CollectionHelper;
import org.jspresso.framework.util.lang.ObjectUtils;

/**
 * This is the core implementation of all components in the application.
 * Instances of this class serve as handlers for proxies representing the
 * components.
 *
 * @author Vincent Vandenschrick
 */
public abstract class AbstractComponentInvocationHandler implements InvocationHandler, Serializable {

    // @formatter:off
    private static final Logger LOG = LoggerFactory.getLogger(AbstractComponentInvocationHandler.class);
    // @formatter:on

    private static final long serialVersionUID = -8332414648339056836L;

    private final IAccessorFactory accessorFactory;

    private SinglePropertyChangeSupport propertyChangeSupport;
    private SingleWeakPropertyChangeSupport weakPropertyChangeSupport;
    private List<PropertyChangeEvent> delayedEvents;

    private IComponentCollectionFactory collectionFactory;
    private final IComponentDescriptor<? extends IComponent> componentDescriptor;
    private Map<Class<IComponentExtension<IComponent>>, IComponentExtension<IComponent>> componentExtensions;
    private final IComponentExtensionFactory extensionFactory;
    private final IComponentFactory inlineComponentFactory;
    private Set<String> modifierMonitors;

    private boolean propertyProcessorsEnabled;
    private boolean propertyChangeEnabled;
    private boolean collectionSortEnabled;

    private Map<String, NestedReferenceTracker> referenceTrackers;

    private Map<String, Object> computedPropertiesCache;

    private static final Collection<String> LIFECYCLE_METHOD_NAMES;
    private IComponent owningComponent;
    private IPropertyDescriptor owningPropertyDescriptor;
    private Map<String, Set<String>> fakePclAttachements;
    private Map<String, Set<String>> delayedFakePclAttachements;
    // Fake PCL cannot be static, because there must be 1 registration on
    // referent per owning instance, i.e. 2 different instances must not share
    // the same fake PCL that will be removed when the referent is detached.
    private PropertyChangeListener fakePcl;

    static {
        Collection<String> methodNames = new THashSet<>(6);
        for (Method m : ILifecycleCapable.class.getMethods()) {
            methodNames.add(m.getName());
        }
        LIFECYCLE_METHOD_NAMES = methodNames;
    }

    /**
     * Constructs a new {@code BasicComponentInvocationHandler} instance.
     *
     * @param componentDescriptor
     *     The descriptor of the proxy component.
     * @param inlineComponentFactory
     *     the factory used to create inline components.
     * @param collectionFactory
     *     The factory used to create empty component collections from     collection getters.
     * @param accessorFactory
     *     The factory used to access proxy properties.
     * @param extensionFactory
     *     The factory used to create component extensions based on their     classes.
     */
    protected AbstractComponentInvocationHandler(IComponentDescriptor<? extends IComponent> componentDescriptor,
            IComponentFactory inlineComponentFactory, IComponentCollectionFactory collectionFactory,
            IAccessorFactory accessorFactory, IComponentExtensionFactory extensionFactory) {
        this.componentDescriptor = componentDescriptor;
        this.inlineComponentFactory = inlineComponentFactory;
        this.collectionFactory = collectionFactory;
        this.accessorFactory = accessorFactory;
        this.extensionFactory = extensionFactory;
        this.propertyProcessorsEnabled = true;
        this.propertyChangeEnabled = true;
        this.collectionSortEnabled = true;
    }

    /**
     * Gets the interface class being the contract of this component.
     *
     * @return the component interface contract.
     */
    public Class<?> getComponentContract() {
        return componentDescriptor.getComponentContract();
    }

    /**
     * Handles methods invocations on the component proxy. Either : <li>delegates
     * to one of its extension if the accessed property is registered as being
     * part of an extension <li>handles property access internally
     * <p/>
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    public synchronized Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName()/* .intern() */;
        switch (methodName) {
        case "hashCode":
            return computeHashCode((IComponent) proxy);
        case "equals":
            return computeEquals((IComponent) proxy, args[0]);
        case "toString":
            return toString(proxy);
        case "getComponentContract":
            return componentDescriptor.getComponentContract();
        case "addPropertyChangeListener":
            if (args.length == 1) {
                addPropertyChangeListener(proxy, (PropertyChangeListener) args[0]);
                return null;
            }
            addPropertyChangeListener(proxy, (String) args[0], (PropertyChangeListener) args[1]);
            return null;
        case "addWeakPropertyChangeListener":
            if (args.length == 1) {
                addWeakPropertyChangeListener(proxy, (PropertyChangeListener) args[0]);
                return null;
            }
            addWeakPropertyChangeListener(proxy, (String) args[0], (PropertyChangeListener) args[1]);
            return null;
        case "removePropertyChangeListener":
            if (args.length == 1) {
                removePropertyChangeListener((PropertyChangeListener) args[0]);
                return null;
            }
            removePropertyChangeListener((String) args[0], (PropertyChangeListener) args[1]);
            return null;
        case "hasListeners":
            return hasListeners(proxy, (String) args[0]);
        case "getPropertyChangeListeners":
            if (args != null && args.length > 0) {
                return getPropertyChangeListeners(proxy, (String) args[0]);
            }
            return getPropertyChangeListeners(proxy);
        case "firePropertyChange":
            firePropertyChange(proxy, (String) args[0], args[1], args[2]);
            return null;
        case "blockEvents":
            return blockEvents();
        case "releaseEvents":
            releaseEvents();
            return null;
        case "straightSetProperty":
            straightSetProperty(proxy, (String) args[0], args[1]);
            return null;
        case "straightGetProperty":
            return straightGetProperty(proxy, (String) args[0]);
        case "straightSetProperties":
            straightSetProperties(proxy, (Map<String, Object>) args[0]);
            return null;
        case "straightGetProperties":
            return straightGetProperties(proxy);
        case "setPropertyProcessorsEnabled":
            propertyProcessorsEnabled = (Boolean) args[0];
            return null;
        case "getOwningComponent":
            return owningComponent;
        case "getOwningPropertyDescriptor":
            return owningPropertyDescriptor;
        case "setOwningComponent":
            owningComponent = (IComponent) args[0];
            owningPropertyDescriptor = (IPropertyDescriptor) args[1];
            return null;
        case "checkIntegrity":
            checkIntegrity(proxy);
            return null;
        case "checkMandatoryProperties":
            checkMandatoryProperties(proxy);
            return null;
        default:
            if (isLifecycleMethod(method)) {
                return invokeLifecycleInterceptors(proxy, method, args);
            }
            AccessorInfo accessorInfo = getAccessorFactory().getAccessorInfo(method);
            EAccessorType accessorType = accessorInfo.getAccessorType();
            IPropertyDescriptor propertyDescriptor = null;
            if (accessorType != EAccessorType.NONE) {
                String accessedPropertyName = accessorInfo.getAccessedPropertyName();
                if (accessedPropertyName != null) {
                    propertyDescriptor = componentDescriptor.getPropertyDescriptor(accessedPropertyName);
                }
            }
            if (propertyDescriptor != null) {
                Class<IComponentExtension<IComponent>> extensionClass = (Class<IComponentExtension<IComponent>>) propertyDescriptor
                        .getDelegateClass();
                if (extensionClass != null) {
                    return accessComputedProperty(propertyDescriptor, accessorInfo, extensionClass, proxy, method,
                            args);
                } else if (!propertyDescriptor.isComputed()) {
                    if (accessorInfo.isModifier()) {
                        if (modifierMonitors != null && modifierMonitors.contains(methodName)) {
                            return null;
                        }
                        if (modifierMonitors == null) {
                            modifierMonitors = new THashSet<>(1);
                        }
                        modifierMonitors.add(methodName);
                    }
                    try {
                        Object param;
                        switch (accessorType) {
                        case GETTER:
                            return getProperty(proxy, propertyDescriptor);
                        case SETTER:
                            param = sanitizeModifierParam(proxy, propertyDescriptor, args[0]);
                            setProperty(proxy, propertyDescriptor, param);
                            return null;
                        case ADDER:
                            if (args.length == 2) {
                                param = sanitizeModifierParam(proxy, propertyDescriptor, args[1]);
                                addToProperty(proxy, (ICollectionPropertyDescriptor<?>) propertyDescriptor,
                                        (Integer) args[0], param);
                            } else {
                                param = sanitizeModifierParam(proxy, propertyDescriptor, args[0]);
                                addToProperty(proxy, (ICollectionPropertyDescriptor<?>) propertyDescriptor, param);
                            }
                            return null;
                        case REMOVER:
                            param = sanitizeModifierParam(proxy, propertyDescriptor, args[0]);
                            removeFromProperty(proxy, (ICollectionPropertyDescriptor<?>) propertyDescriptor, param);
                            return null;
                        default:
                            break;
                        }
                    } finally {
                        if (modifierMonitors != null && accessorInfo.isModifier()) {
                            modifierMonitors.remove(methodName);
                        }
                    }
                } else if (propertyDescriptor instanceof IStringPropertyDescriptor
                        && ((IStringPropertyDescriptor) propertyDescriptor).isTranslatable()) {
                    if (accessorInfo.isModifier()) {
                        if (propertyDescriptor.getName().endsWith(IComponentDescriptor.NLS_SUFFIX)) {
                            invokeNlsSetter(proxy, (IStringPropertyDescriptor) propertyDescriptor,
                                    (String) args[0]);
                        } else {
                            invokeNlsOrRawSetter(proxy, (IStringPropertyDescriptor) propertyDescriptor,
                                    (String) args[0]);
                        }
                        return null;
                    } else {
                        if (propertyDescriptor.getName().endsWith(IComponentDescriptor.NLS_SUFFIX)) {
                            return invokeNlsGetter(proxy, (IStringPropertyDescriptor) propertyDescriptor);
                        } else {
                            return invokeNlsOrRawGetter(proxy, (IStringPropertyDescriptor) propertyDescriptor);
                        }
                    }
                } else {
                    try {
                        return invokeServiceMethod(proxy, method, args);
                    } catch (NoSuchMethodException ignored) {
                        // it will fall back in the general case.
                    }
                    throw new ComponentException("The '" + propertyDescriptor.getName()
                            + "' property is described as computed but we couldn't determine a way to compute it,"
                            + " either through an extension or a service delegate on the following component : \n"
                            + componentDescriptor.getComponentContract().getName());
                }
            } else {
                try {
                    return invokeServiceMethod(proxy, method, args);
                } catch (NoSuchMethodException ignored) {
                    // it will fall back in the general case.
                }
            }
            break;
        }
        throw new ComponentException(method.toString() + " is not supported on the component "
                + componentDescriptor.getComponentContract().getName());
    }

    /**
     * Invoke nls getter.
     *
     * @param proxy
     *     the proxy
     * @param propertyDescriptor
     *     the property descriptor
     * @return the translated value
     */
    protected String invokeNlsGetter(Object proxy, IStringPropertyDescriptor propertyDescriptor) {
        return (String) straightGetProperty(proxy, propertyDescriptor.getName() + IComponentDescriptor.RAW_SUFFIX);
    }

    /**
     * Invoke nls or raw getter.
     *
     * @param proxy
     *     the proxy
     * @param propertyDescriptor
     *     the property descriptor
     * @return the translated value or raw if non-existent.
     */
    protected String invokeNlsOrRawGetter(Object proxy, IStringPropertyDescriptor propertyDescriptor) {
        String nlsOrRawValue = invokeNlsGetter(proxy, propertyDescriptor);
        if (nlsOrRawValue == null) {
            nlsOrRawValue = (String) straightGetProperty(proxy,
                    propertyDescriptor.getName() + IComponentDescriptor.RAW_SUFFIX);
        }
        return nlsOrRawValue;
    }

    /**
     * Invoke nls setter.
     *
     * @param proxy
     *     the proxy
     * @param propertyDescriptor
     *     the property descriptor
     * @param translatedValue
     *     the translated value
     */
    protected void invokeNlsSetter(Object proxy, IStringPropertyDescriptor propertyDescriptor,
            String translatedValue) {
        straightSetProperty(proxy, propertyDescriptor.getName() + IComponentDescriptor.RAW_SUFFIX, translatedValue);
    }

    /**
     * Invoke nls or raw setter.
     *
     * @param proxy
     *     the proxy
     * @param propertyDescriptor
     *     the property descriptor
     * @param translatedValue
     *     the translated value
     */
    protected void invokeNlsOrRawSetter(Object proxy, IStringPropertyDescriptor propertyDescriptor,
            String translatedValue) {
        String oldTranslation = invokeNlsOrRawGetter(proxy, propertyDescriptor);
        invokeNlsSetter(proxy, propertyDescriptor, translatedValue);
        String propertyName = propertyDescriptor.getName();
        storeProperty(propertyName, translatedValue);
        firePropertyChange(proxy, propertyName, oldTranslation, translatedValue);
    }

    private boolean isLifecycleMethod(Method method) {
        String methodName = method.getName();
        if (LIFECYCLE_METHOD_NAMES.contains(methodName)) {
            try {
                return ILifecycleCapable.class.getMethod(methodName, method.getParameterTypes()) != null;
            } catch (NoSuchMethodException ignored) {
                // this is certainly normal.
            }
        }
        return false;
    }

    /**
     * Gives chance to subclasses to perform sanity checks and eventually
     * substitute the passed param by an other one when it's technically
     * necessary.
     *
     * @param target
     *     the target being modified.
     * @param propertyDescriptor
     *     the descriptor of the property being modified.
     * @param param
     *     the modifier parameter.
     * @return the parameter to actually pass to the modifier
     */
    protected Object sanitizeModifierParam(Object target, IPropertyDescriptor propertyDescriptor, Object param) {
        return param;
    }

    /**
     * Sets the collectionFactory.
     *
     * @param collectionFactory
     *     the collectionFactory to set.
     */
    public void setCollectionFactory(IComponentCollectionFactory collectionFactory) {
        this.collectionFactory = collectionFactory;
    }

    /**
     * Delegate method to compute object equality.
     *
     * @param proxy
     *     the target component to compute equality of.
     * @param another
     *     the object to compute equality against.
     * @return the computed equality.
     */
    protected abstract boolean computeEquals(IComponent proxy, Object another);

    /**
     * Delegate method to compute hashcode.
     *
     * @param proxy
     *     the target component to compute hashcode for.
     * @return the computed hashcode.
     */
    protected abstract int computeHashCode(IComponent proxy);

    /**
     * Gives a chance to configure created extensions.
     *
     * @param extension
     *     the extension to configure.
     */
    protected void configureExtension(IComponentExtension<IComponent> extension) {
        if (extension instanceof IComponentFactoryAware) {
            ((IComponentFactoryAware) extension).setComponentFactory(getInlineComponentFactory());
        }
        extension.postCreate();
    }

    /**
     * Gives a chance to the implementor to decorate a component reference before
     * returning it when fetching association ends.
     *
     * @param referent
     *     the component reference to decorate.
     * @param referentDescriptor
     *     the component descriptor of the referent.
     * @return the decorated component.
     */
    protected abstract IComponent decorateReferent(IComponent referent,
            IComponentDescriptor<? extends IComponent> referentDescriptor);

    /**
     * An empty hook that gets called whenever an entity is detached from a parent
     * one.
     *
     * @param parent
     *     the parent entity.
     * @param child
     *     the child entity.
     * @param propertyDescriptor
     *     the property descriptor this entity was detached from.
     */
    @SuppressWarnings("UnusedParameters")
    protected void entityDetached(IEntity parent, IEntity child,
            IRelationshipEndPropertyDescriptor propertyDescriptor) {
        // defaults to no-op.
    }

    /**
     * Gets the accessorFactory.
     *
     * @return the accessorFactory.
     */
    protected IAccessorFactory getAccessorFactory() {
        return accessorFactory;
    }

    /**
     * Gets a collection property value.
     *
     * @param proxy
     *     the proxy to get the property of.
     * @param propertyDescriptor
     *     the property descriptor to get the value for.
     * @return the property value.
     */
    @SuppressWarnings({ "unchecked", "ConstantConditions" })
    protected Object getCollectionProperty(Object proxy,
            ICollectionPropertyDescriptor<? extends IComponent> propertyDescriptor) {
        String propertyName = propertyDescriptor.getName();
        try {
            Object property = straightGetProperty(proxy, propertyName);
            if (property == null) {
                property = collectionFactory.createComponentCollection(
                        propertyDescriptor.getReferencedDescriptor().getCollectionInterface());
                storeProperty(propertyName, property);
            }
            if (property instanceof List<?>) {
                List<IComponent> propertyAsList = (List<IComponent>) property;
                for (int i = 0; i < propertyAsList.size(); i++) {
                    IComponent referent = propertyAsList.get(i);
                    IComponent decorated = decorateReferent(referent, propertyDescriptor.getReferencedDescriptor()
                            .getElementDescriptor().getComponentDescriptor());
                    if (decorated != referent) {
                        propertyAsList.set(i, decorated);
                    }
                    if (referent == null) {
                        if (proxy instanceof IEntity) {
                            LOG.warn("A null element was detected in indexed list [{}] on {}, id {} at index {}",
                                    propertyName, ((IEntity) proxy).getComponentContract().getName(),
                                    ((IEntity) proxy).getId(), i);
                            LOG.warn(
                                    "This might be normal but sometimes it reveals a mis-use of indexed collection property accessors.");
                        }
                    } else if (EntityHelper.isInlineComponentReference(
                            propertyDescriptor.getReferencedDescriptor().getElementDescriptor())) {
                        if (decorated != null) {
                            decorated.setOwningComponent((IComponent) proxy, propertyDescriptor);
                        }
                    }
                }
            } else if (property instanceof Set<?>) {
                Set<IComponent> propertyAsSet = (Set<IComponent>) property;
                for (IComponent referent : new THashSet<>(propertyAsSet)) {
                    IComponent decorated = decorateReferent(referent, propertyDescriptor.getReferencedDescriptor()
                            .getElementDescriptor().getComponentDescriptor());
                    if (decorated != referent) {
                        propertyAsSet.add(decorated);
                    }
                    if (EntityHelper.isInlineComponentReference(
                            propertyDescriptor.getReferencedDescriptor().getElementDescriptor())) {
                        if (decorated != null) {
                            decorated.setOwningComponent((IComponent) proxy, propertyDescriptor);
                        }
                    }
                }
            }
            if (isCollectionSortOnReadEnabled() && collectionSortEnabled) {
                inlineComponentFactory.sortCollectionProperty((IComponent) proxy, propertyName);
            }
            if (property instanceof ICollectionWrapper<?>) {
                return property;
            }
            List<Class<?>> implementedInterfaces = new ArrayList<>();
            implementedInterfaces.add(ICollectionWrapper.class);
            implementedInterfaces.addAll(Arrays.asList(property.getClass().getInterfaces()));
            return Proxy.newProxyInstance(AbstractComponentInvocationHandler.class.getClassLoader(),
                    implementedInterfaces.toArray(new Class[implementedInterfaces.size()]),
                    new PersistentCollectionWrapper<>(
                            (Collection<IComponent>) property, (IComponent) proxy, propertyName, propertyDescriptor
                                    .getCollectionDescriptor().getElementDescriptor().getComponentContract(),
                            accessorFactory));
        } catch (RuntimeException re) {
            LOG.error("Error when retrieving [{}] collection property on {}", propertyName, proxy);
            throw (re);
        }
    }

    /**
     * Allow to disable collection property sorting on read.
     *
     * @return true if collection sorting is enabled on read access.
     */
    protected boolean isCollectionSortOnReadEnabled() {
        return true;
    }

    /**
     * Is dirty tracking enabled.
     *
     * @return {@code true} if dirty tracking is enabled.
     */
    protected boolean isDirtyTrackingEnabled() {
        return true;
    }

    /**
     * Sets dirty tracking enabled.
     *
     * @param enabled
     *     {@code true} if enabled, {@code false} otherwise.
     */
    protected void setDirtyTrackingEnabled(boolean enabled) {
        // NO-OP;
    }

    /**
     * Creates and registers an extension instance.
     *
     * @param extensionClass
     *     the extension class.
     * @param proxy
     *     the proxy to register the extension on.
     * @return the component extension.
     */
    protected synchronized IComponentExtension<? extends IComponent> getExtensionInstance(
            Class<IComponentExtension<IComponent>> extensionClass, IComponent proxy) {
        IComponentExtension<IComponent> extension;
        if (componentExtensions == null) {
            componentExtensions = new THashMap<>(1, 1.0f);
            extension = null;
        } else {
            extension = componentExtensions.get(extensionClass);
        }
        if (extension == null) {
            extension = extensionFactory.createComponentExtension(extensionClass,
                    componentDescriptor.getComponentContract(), proxy);
            componentExtensions.put(extensionClass, extension);
            configureExtension(extension);
        }
        return extension;
    }

    /**
     * Gets the inlineComponentFactory.
     *
     * @return the inlineComponentFactory.
     */
    protected IComponentFactory getInlineComponentFactory() {
        return inlineComponentFactory;
    }

    /**
     * Gets a property value.
     *
     * @param proxy
     *     the proxy to get the property of.
     * @param propertyDescriptor
     *     the property descriptor to get the value for.
     * @return the property value.
     */
    @SuppressWarnings("unchecked")
    protected Object getProperty(Object proxy, IPropertyDescriptor propertyDescriptor) {
        if (propertyDescriptor instanceof ICollectionPropertyDescriptor) {
            return getCollectionProperty(proxy,
                    (ICollectionPropertyDescriptor<? extends IComponent>) propertyDescriptor);
        }
        if (propertyDescriptor instanceof IReferencePropertyDescriptor) {
            return getReferenceProperty(proxy, (IReferencePropertyDescriptor<IComponent>) propertyDescriptor);
        }
        Object propertyValue = straightGetProperty(proxy, propertyDescriptor.getName());
        return propertyValue;
    }

    /**
     * Gets a reference property value.
     *
     * @param proxy
     *     the proxy to get the property of.
     * @param propertyDescriptor
     *     the property descriptor to get the value for.
     * @return the property value.
     */
    @SuppressWarnings("unchecked")
    protected Object getReferenceProperty(Object proxy,
            final IReferencePropertyDescriptor<IComponent> propertyDescriptor) {
        String propertyName = propertyDescriptor.getName();
        Object referent = straightGetProperty(proxy, propertyName);
        if (referent instanceof IPropertyChangeCapable) {
            initializeInlineTrackerIfNeeded((IPropertyChangeCapable) referent, propertyName, true);
            Set<String> delayedNestedPropertyListening = null;
            if (delayedFakePclAttachements != null) {
                delayedNestedPropertyListening = delayedFakePclAttachements.remove(propertyName);
            }
            if (delayedNestedPropertyListening != null) {
                Set<String> nestedPropertyListening = null;
                if (fakePclAttachements != null) {
                    nestedPropertyListening = fakePclAttachements.get(propertyName);
                }
                if (nestedPropertyListening == null) {
                    nestedPropertyListening = new THashSet<>(1);
                    if (fakePclAttachements == null) {
                        fakePclAttachements = new THashMap<>(1, 1.0f);
                    }
                    fakePclAttachements.put(propertyName, nestedPropertyListening);
                }
                for (String nestedPropertyName : delayedNestedPropertyListening) {
                    ((IPropertyChangeCapable) referent).addWeakPropertyChangeListener(nestedPropertyName,
                            createOrGetFakePcl());
                    nestedPropertyListening.add(nestedPropertyName);
                }
            }
        }
        IComponentDescriptor<IComponent> referencedDescriptor = (IComponentDescriptor<IComponent>) propertyDescriptor
                .getReferencedDescriptor();
        if (referent == null && EntityHelper.isInlineComponentReference(propertyDescriptor)
                && !propertyDescriptor.isComputed() && propertyDescriptor.isMandatory()) {
            boolean wasDirtyTrackingEnabled = isDirtyTrackingEnabled();
            try {
                setDirtyTrackingEnabled(false);
                referent = inlineComponentFactory
                        .createComponentInstance(referencedDescriptor.getComponentContract());
                storeReferenceProperty(proxy, propertyDescriptor, null, referent);
            } finally {
                setDirtyTrackingEnabled(wasDirtyTrackingEnabled);
            }
        }
        if (referent instanceof IComponent) {
            return decorateReferent((IComponent) referent, referencedDescriptor);
        }
        return referent;
    }

    /**
     * Invokes a service method on the component.
     *
     * @param proxy
     *     the component to invoke the service on.
     * @param method
     *     the method implemented by the component.
     * @param args
     *     the arguments of the method implemented by the component.
     * @return the value returned by the method execution if any.
     *
     * @throws NoSuchMethodException
     *     if no mean could be found to service the method.
     */
    @SuppressWarnings("unchecked")
    protected Object invokeServiceMethod(Object proxy, Method method, Object... args) throws NoSuchMethodException {
        IComponentService service = componentDescriptor.getServiceDelegate(method);
        if (service != null) {
            try {
                if (service instanceof AbstractComponentServiceDelegate<?>) {
                    Method refinedMethod = service.getClass().getMethod(method.getName(),
                            method.getParameterTypes());
                    if (refinedMethod != null) {
                        return ((AbstractComponentServiceDelegate<Object>) service).executeWith(proxy,
                                refinedMethod, args);
                    }
                }
                int signatureSize = method.getParameterTypes().length + 1;
                Class<?>[] parameterTypes = new Class<?>[signatureSize];
                Object[] parameters = new Object[signatureSize];

                parameterTypes[0] = componentDescriptor.getComponentContract();
                parameters[0] = proxy;

                for (int i = 1; i < signatureSize; i++) {
                    parameterTypes[i] = method.getParameterTypes()[i - 1];
                    parameters[i] = args[i - 1];
                }
                return MethodUtils.invokeMethod(service, method.getName(), parameters, parameterTypes);
            } catch (IllegalAccessException | NoSuchMethodException ex) {
                throw new ComponentException(ex);
            } catch (InvocationTargetException ex) {
                if (ex.getCause() instanceof RuntimeException) {
                    throw (RuntimeException) ex.getCause();
                }
                throw new ComponentException(ex.getCause());
            }
        }
        throw new NoSuchMethodException(method.toString());
    }

    /**
     * Whether the object is fully initialized.
     *
     * @param objectOrProxy
     *     the object to test.
     * @return true if the object is fully initialized.
     */
    protected boolean isInitialized(Object objectOrProxy) {
        return true;
    }

    /**
     * An empty hook that gets called when an component is created (still transient).
     *
     * @param proxy
     *     the proxy
     * @param entityFactory
     *     an entity factory instance which can be used to complete the     lifecycle step.
     * @param principal
     *     the principal triggering the action.
     * @param entityLifecycleHandler
     *     entityLifecycleHandler.
     */
    protected void onCreate(Object proxy, IEntityFactory entityFactory, UserPrincipal principal,
            IEntityLifecycleHandler entityLifecycleHandler) {
        registerServicesForwardingListenersIfNecessary(proxy);
    }

    /**
     * An empty hook that gets called whenever an entity is to be persisted.
     *
     * @param proxy
     *     the proxy
     * @param entityFactory
     *     an entity factory instance which can be used to complete the     lifecycle step.
     * @param principal
     *     the principal triggering the action.
     * @param entityLifecycleHandler
     *     entityLifecycleHandler.
     */
    protected void onPersist(Object proxy, IEntityFactory entityFactory, UserPrincipal principal,
            IEntityLifecycleHandler entityLifecycleHandler) {
        // defaults to no-op.
    }

    /**
     * An empty hook that gets called whenever an entity is to be updated.
     *
     * @param proxy
     *     the proxy
     * @param entityFactory
     *     an entity factory instance which can be used to complete the     lifecycle step.
     * @param principal
     *     the principal triggering the action.
     * @param entityLifecycleHandler
     *     entityLifecycleHandler.
     */
    protected void onUpdate(Object proxy, IEntityFactory entityFactory, UserPrincipal principal,
            IEntityLifecycleHandler entityLifecycleHandler) {
        // defaults to no-op.
    }

    /**
     * An empty hook that gets called just before an component is deleted (delete).
     *
     * @param proxy
     *     the proxy
     * @param entityFactory
     *     an entity factory instance which can be used to complete the     lifecycle step.
     * @param principal
     *     the principal triggering the action.
     * @param entityLifecycleHandler
     *     entityLifecycleHandler.
     * @return true if the state of the component has been updated.
     */
    protected boolean onDelete(Object proxy, IEntityFactory entityFactory, UserPrincipal principal,
            IEntityLifecycleHandler entityLifecycleHandler) {
        // defaults to no-op.
        return false;
    }

    /**
     * An empty hook that gets called when an component is loaded from the persistent store or merged back
     * from the unit of work.
     *
     * @param proxy
     *     the proxy
     */
    protected void onLoad(Object proxy) {
        registerServicesForwardingListenersIfNecessary(proxy);
    }

    /**
     * An empty hook that gets called whenever an entity is cloned to the unit of work.
     *
     * @param <E>
     *     tha actual component type.
     * @param proxy
     *     the proxy
     * @param sourceComponent
     *     the component that is the source of the cloning.
     */
    protected <E extends IComponent> void onClone(Object proxy, E sourceComponent) {
        registerServicesForwardingListenersIfNecessary(proxy);
    }

    private boolean servicesForwardingListenersRegistered = false;

    private void registerServicesForwardingListenersIfNecessary(Object proxy) {
        if (!servicesForwardingListenersRegistered) {
            servicesForwardingListenersRegistered = true;
            Collection<Class<?>> serviceContracts = componentDescriptor.getServiceContracts();
            if (serviceContracts != null) {
                for (Class<?> serviceContract : serviceContracts) {
                    DependsOnHelper.registerDependsOnListeners(serviceContract, (IPropertyChangeCapable) proxy,
                            accessorFactory);
                }
            }
        }
    }

    /**
     * Direct read access to the properties map without any other operation. Use
     * with caution only in subclasses.
     *
     * @param propertyName
     *     the property name.
     * @return the property value.
     */
    protected abstract Object retrievePropertyValue(String propertyName);

    /**
     * Refine property to store object.
     *
     * @param propertyValue
     *     the property value
     * @return the object
     */
    protected Object refinePropertyToStore(Object propertyValue) {
        if (propertyValue instanceof ICollectionWrapper<?>) {
            return ((ICollectionWrapper) propertyValue).getWrappedCollection();
        }
        return propertyValue;
    }

    /**
     * Direct write access to the properties map without any other operation. Use
     * with caution only in subclasses.
     *
     * @param propertyName
     *     the property name.
     * @param propertyValue
     *     the property value.
     */
    protected abstract void storeProperty(String propertyName, Object propertyValue);

    /**
     * Store collection property.
     *
     * @param proxy
     *     the proxy
     * @param propertyDescriptor
     *     the property descriptor
     * @param oldPropertyValue
     *     the old property value
     * @param newPropertyValue
     *     the new property value
     */
    @SuppressWarnings("unchecked")
    protected void storeCollectionProperty(Object proxy, ICollectionPropertyDescriptor<?> propertyDescriptor,
            Object oldPropertyValue, Object newPropertyValue) {
        String propertyName = propertyDescriptor.getName();
        if (EntityHelper
                .isInlineComponentReference(propertyDescriptor.getReferencedDescriptor().getElementDescriptor())) {
            if (oldPropertyValue instanceof Collection<?> && isInitialized(oldPropertyValue)) {
                for (IComponent component : (Collection<IComponent>) oldPropertyValue) {
                    if (component != null) {
                        component.setOwningComponent(null, null);
                    }
                }
            }
            if (newPropertyValue instanceof Collection<?> && isInitialized(newPropertyValue)) {
                for (IComponent component : (Collection<IComponent>) newPropertyValue) {
                    if (component != null) {
                        component.setOwningComponent((IComponent) proxy, propertyDescriptor);
                    }
                }
            }
        }
        storeProperty(propertyName, newPropertyValue);
    }

    /**
     * Performs necessary registration on inline components before actually
     * storing them.
     *
     * @param proxy
     *     the proxy to store the reference property for.
     * @param propertyDescriptor
     *     the reference property descriptor.
     * @param oldPropertyValue
     *     the old reference property value.
     * @param newPropertyValue
     *     the new reference property value.
     */
    protected void storeReferenceProperty(Object proxy, IReferencePropertyDescriptor<?> propertyDescriptor,
            Object oldPropertyValue, Object newPropertyValue) {
        String propertyName = propertyDescriptor.getName();

        NestedReferenceTracker referenceTracker = null;
        if (referenceTrackers != null) {
            referenceTracker = referenceTrackers.get(propertyName);
        }

        // Handle owning component.
        if (oldPropertyValue instanceof IComponent && EntityHelper.isInlineComponentReference(propertyDescriptor)
                && isInitialized(oldPropertyValue)) {
            ((IComponent) oldPropertyValue).setOwningComponent(null, null);
        }
        if (newPropertyValue instanceof IComponent && EntityHelper.isInlineComponentReference(propertyDescriptor)
                && isInitialized(newPropertyValue)) {
            ((IComponent) newPropertyValue).setOwningComponent((IComponent) proxy, propertyDescriptor);
        }

        if (oldPropertyValue instanceof IPropertyChangeCapable) {
            if (isInitialized(oldPropertyValue)) {
                if (referenceTracker != null) {
                    ((IPropertyChangeCapable) oldPropertyValue).removePropertyChangeListener(referenceTracker);
                }
                Set<String> nestedPropertyListening = null;
                if (fakePclAttachements != null) {
                    nestedPropertyListening = fakePclAttachements.get(propertyName);
                }
                if (nestedPropertyListening != null) {
                    for (String nestedPropertyName : nestedPropertyListening) {
                        ((IPropertyChangeCapable) oldPropertyValue).removePropertyChangeListener(nestedPropertyName,
                                createOrGetFakePcl());
                    }
                }
                if (delayedFakePclAttachements != null) {
                    delayedFakePclAttachements.remove(propertyName);
                }
            }
        }
        storeProperty(propertyName, newPropertyValue);
        if (newPropertyValue instanceof IPropertyChangeCapable) {
            Set<String> nestedPropertyListening = null;
            if (fakePclAttachements != null) {
                nestedPropertyListening = fakePclAttachements.get(propertyName);
            }
            if (nestedPropertyListening != null) {
                if (isInitialized(newPropertyValue)) {
                    for (String nestedPropertyName : nestedPropertyListening) {
                        ((IPropertyChangeCapable) newPropertyValue)
                                .addWeakPropertyChangeListener(nestedPropertyName, createOrGetFakePcl());
                    }
                } else {
                    if (delayedFakePclAttachements == null) {
                        delayedFakePclAttachements = new THashMap<>(1, 1.0f);
                    }
                    delayedFakePclAttachements.put(propertyName, nestedPropertyListening);
                }
            }
            if (referenceTracker == null) {
                referenceTracker = new NestedReferenceTracker(proxy, propertyName,
                        EntityHelper.isInlineComponentReference(propertyDescriptor)
                                && !propertyDescriptor.isComputed());
                if (referenceTrackers == null) {
                    referenceTrackers = new THashMap<>(1, 1.0f);
                }
                referenceTrackers.put(propertyName, referenceTracker);
            }
            referenceTracker.setInitialized(false);
            initializeInlineTrackerIfNeeded((IPropertyChangeCapable) newPropertyValue, propertyName,
                    // To avoid breaking lazy initialization of oldPropertyValue
                    !isInitialized(oldPropertyValue) || (isInitialized(newPropertyValue)
                            && !ObjectUtils.equals(oldPropertyValue, newPropertyValue)));
        } else if (referenceTracker != null) {
            if (oldPropertyValue instanceof IComponent
                    // To avoid breaking lazy initialization optimisation
                    && isInitialized(oldPropertyValue)) {
                for (Map.Entry<String, Object> property : ((IComponent) oldPropertyValue).straightGetProperties()
                        .entrySet()) {
                    referenceTracker.propertyChange(new PropertyChangeEvent(oldPropertyValue, property.getKey(),
                            property.getValue(), null));
                }
            }
        }
    }

    /**
     * Performs (potentially delayed due to lazy initialization) inline tracker
     * attachment.
     *
     * @param referenceProperty
     *     the reference to link the tracker to.
     * @param propertyName
     *     the property name of the tracker.
     * @param fireNestedPropertyChange
     *     Whenever the initialization is performed, does a first set of
     *     property change events be fired ?
     */
    private void initializeInlineTrackerIfNeeded(IPropertyChangeCapable referenceProperty, String propertyName,
            boolean fireNestedPropertyChange) {
        if (referenceProperty != null && isInitialized(referenceProperty)) {
            NestedReferenceTracker storedTracker = null;
            if (referenceTrackers != null) {
                storedTracker = referenceTrackers.get(propertyName);
            }
            if (storedTracker != null && !storedTracker.isInitialized()) {
                storedTracker.setInitialized(true);
                referenceProperty.addWeakPropertyChangeListener(storedTracker);
                if (fireNestedPropertyChange && referenceProperty instanceof IComponent) {
                    for (Map.Entry<String, Object> property : ((IComponent) referenceProperty)
                            .straightGetProperties().entrySet()) {
                        storedTracker.propertyChange(new PropertyChangeEvent(referenceProperty, property.getKey(),
                                IPropertyChangeCapable.UNKNOWN, property.getValue()));
                    }
                }
            }
        }
    }

    /**
     * Directly gets all property values out of the property store without any
     * other operation.
     *
     * @param proxy
     *     the proxy to straight get the properties from.
     * @return The map of properties.
     */
    protected Map<String, Object> straightGetProperties(Object proxy) {
        Map<String, Object> allProperties = new HashMap<>();
        for (IPropertyDescriptor propertyDescriptor : componentDescriptor.getPropertyDescriptors()) {
            String propertyName = propertyDescriptor.getName();
            if (!(propertyDescriptor.isComputed() && propertyDescriptor.getPersistenceFormula() == null)) {
                allProperties.put(propertyName, straightGetProperty(proxy, propertyName));
            }
        }
        return allProperties;
    }

    /**
     * Directly get a property value out of the property store without any other
     * operation.
     *
     * @param proxy
     *     the proxy to straight get the property from.
     * @param propertyName
     *     the name of the property.
     * @return the property value or null.
     */
    protected Object straightGetProperty(Object proxy, String propertyName) {
        IPropertyDescriptor propertyDescriptor = componentDescriptor.getPropertyDescriptor(propertyName);
        if (propertyDescriptor == null
                || (propertyDescriptor.isComputed() && propertyDescriptor.getPersistenceFormula() == null)) {

            return null;
        }
        Object propertyValue = retrievePropertyValue(propertyName);
        if (propertyValue == null && propertyDescriptor instanceof IBooleanPropertyDescriptor) {
            return Boolean.FALSE;
        }
        return propertyValue;
    }

    /**
     * Directly set a property value to the property store without any other
     * operation.
     *
     * @param proxy
     *     the proxy to straight set the property to.
     * @param propertyName
     *     the name of the property.
     * @param newPropertyValue
     *     the property value or null.
     */
    protected void straightSetProperty(Object proxy, String propertyName, Object newPropertyValue) {
        IPropertyDescriptor propertyDescriptor = componentDescriptor.getPropertyDescriptor(propertyName);
        if (propertyDescriptor == null
                || (propertyDescriptor.isComputed() && propertyDescriptor.getPersistenceFormula() == null)) {
            return;
        }
        Object currentPropertyValue = straightGetProperty(proxy, propertyName);
        if (propertyDescriptor instanceof IReferencePropertyDescriptor) {
            // reference must change sometimes even if entities are equal.
            if (/* !ObjectUtils.equals(currentPropertyValue, newPropertyValue) */currentPropertyValue != newPropertyValue) {
                storeReferenceProperty(proxy, (IReferencePropertyDescriptor<?>) propertyDescriptor,
                        currentPropertyValue, newPropertyValue);
            }
        } else if (propertyDescriptor instanceof ICollectionPropertyDescriptor) {
            storeCollectionProperty(proxy, (ICollectionPropertyDescriptor<?>) propertyDescriptor,
                    currentPropertyValue, newPropertyValue);
            if (currentPropertyValue != null && currentPropertyValue == newPropertyValue
                    && isInitialized(currentPropertyValue)) {
                currentPropertyValue = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                        new Class<?>[] { ((ICollectionPropertyDescriptor<?>) propertyDescriptor)
                                .getReferencedDescriptor().getCollectionInterface() },
                        new NeverEqualsInvocationHandler(
                                CollectionHelper.cloneCollection((Collection<?>) currentPropertyValue)));
            }
        } else {
            storeProperty(propertyName, newPropertyValue);
        }
        doFirePropertyChange(proxy, propertyName, currentPropertyValue, newPropertyValue);
    }

    private synchronized void addPropertyChangeListener(Object proxy, PropertyChangeListener listener) {
        if (listener == null) {
            return;
        }
        if (propertyChangeSupport == null) {
            propertyChangeSupport = new SinglePropertyChangeSupport(proxy);
        }
        propertyChangeSupport.addPropertyChangeListener(listener);
    }

    private synchronized void addWeakPropertyChangeListener(Object proxy, PropertyChangeListener listener) {
        if (listener == null) {
            return;
        }
        if (weakPropertyChangeSupport == null) {
            weakPropertyChangeSupport = new SingleWeakPropertyChangeSupport(proxy);
        }
        weakPropertyChangeSupport.addPropertyChangeListener(listener);
    }

    private synchronized void addPropertyChangeListener(Object proxy, String propertyName,
            PropertyChangeListener listener) {
        if (listener == null) {
            return;
        }
        if (propertyChangeSupport == null) {
            propertyChangeSupport = new SinglePropertyChangeSupport(proxy);
        }
        handleNestedPropertyChangeListening(proxy, propertyName);
        propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
    }

    private synchronized void addWeakPropertyChangeListener(Object proxy, String propertyName,
            PropertyChangeListener listener) {
        if (listener == null) {
            return;
        }
        if (weakPropertyChangeSupport == null) {
            weakPropertyChangeSupport = new SingleWeakPropertyChangeSupport(proxy);
        }
        handleNestedPropertyChangeListening(proxy, propertyName);
        weakPropertyChangeSupport.addPropertyChangeListener(propertyName, listener);
    }

    private void handleNestedPropertyChangeListening(Object proxy, String propertyName) {
        int nestedDelimIndex = propertyName.indexOf(IAccessor.NESTED_DELIM);
        if (nestedDelimIndex >= 0) {
            String rootProperty = propertyName.substring(0, nestedDelimIndex);
            String nestedPropertyName = propertyName.substring(nestedDelimIndex + 1);
            NestedReferenceTracker referenceTracker = null;
            if (referenceTrackers != null) {
                referenceTracker = referenceTrackers.get(rootProperty);
            }
            if (referenceTracker == null) {
                IReferencePropertyDescriptor<?> rootPropertyDescriptor = (IReferencePropertyDescriptor<?>) componentDescriptor
                        .getPropertyDescriptor(rootProperty);
                referenceTracker = new NestedReferenceTracker(proxy, rootProperty,
                        EntityHelper.isInlineComponentReference(rootPropertyDescriptor)
                                && !rootPropertyDescriptor.isComputed());
                if (referenceTrackers == null) {
                    referenceTrackers = new THashMap<>(1, 1.0f);
                }
                referenceTrackers.put(rootProperty, referenceTracker);
            }
            Object currentRootProperty = straightGetProperty(proxy, rootProperty);
            if (currentRootProperty instanceof IPropertyChangeCapable) {
                if (isInitialized(currentRootProperty)) {
                    ((IPropertyChangeCapable) currentRootProperty).addWeakPropertyChangeListener(nestedPropertyName,
                            createOrGetFakePcl());
                    Set<String> nestedPropertyListening = null;
                    if (fakePclAttachements != null) {
                        nestedPropertyListening = fakePclAttachements.get(propertyName);
                    }
                    if (nestedPropertyListening == null) {
                        nestedPropertyListening = new THashSet<>(1);
                        if (fakePclAttachements == null) {
                            fakePclAttachements = new THashMap<>(1, 1.0f);
                        }
                        fakePclAttachements.put(rootProperty, nestedPropertyListening);
                    }
                    nestedPropertyListening.add(nestedPropertyName);
                } else {
                    Set<String> delayedNestedPropertyListening = null;
                    if (delayedFakePclAttachements != null) {
                        delayedNestedPropertyListening = delayedFakePclAttachements.get(propertyName);
                    }
                    if (delayedNestedPropertyListening == null) {
                        delayedNestedPropertyListening = new THashSet<>(1);
                        if (delayedFakePclAttachements == null) {
                            delayedFakePclAttachements = new THashMap<>(1, 1.0f);
                        }
                        delayedFakePclAttachements.put(rootProperty, delayedNestedPropertyListening);
                    }
                    delayedNestedPropertyListening.add(nestedPropertyName);
                }
            }
            referenceTracker.addToTrackedProperties(nestedPropertyName);
        }
    }

    @SuppressWarnings("unchecked")
    protected void addToProperty(Object proxy, ICollectionPropertyDescriptor<?> propertyDescriptor, int index,
            Object value) {
        String propertyName = propertyDescriptor.getName();
        Collection<Object> collectionProperty = (Collection<Object>) straightGetProperty(proxy, propertyName);
        if (value instanceof IEntity && collectionProperty.contains(value)) {
            if (collectionProperty instanceof Set<?>) {
                LOG.warn("You have added twice the same element to the following collection property : {}.{}"
                        + componentDescriptor.getComponentContract().getName(), propertyName);
            } else {
                throw new ComponentException("Collection property does not allow duplicates : "
                        + componentDescriptor.getComponentContract().getName() + "." + propertyName);
            }
        }
        try {
            if (propertyProcessorsEnabled) {
                propertyDescriptor.preprocessAdder(proxy, collectionProperty, value);
            }
            IRelationshipEndPropertyDescriptor reversePropertyDescriptor = propertyDescriptor
                    .getReverseRelationEnd();
            if (reversePropertyDescriptor != null) {
                if (reversePropertyDescriptor instanceof IReferencePropertyDescriptor<?>) {
                    accessorFactory
                            .createPropertyAccessor(reversePropertyDescriptor.getName(), propertyDescriptor
                                    .getReferencedDescriptor().getElementDescriptor().getComponentContract())
                            .setValue(value, proxy);
                } else if (reversePropertyDescriptor instanceof ICollectionPropertyDescriptor<?>) {
                    ICollectionAccessor collectionAccessor = accessorFactory.createCollectionPropertyAccessor(
                            reversePropertyDescriptor.getName(),
                            propertyDescriptor.getReferencedDescriptor().getElementDescriptor()
                                    .getComponentContract(),
                            ((ICollectionPropertyDescriptor<?>) reversePropertyDescriptor).getCollectionDescriptor()
                                    .getElementDescriptor().getComponentContract());
                    if (collectionAccessor instanceof IModelDescriptorAware) {
                        ((IModelDescriptorAware) collectionAccessor).setModelDescriptor(reversePropertyDescriptor);
                    }
                    collectionAccessor.addToValue(value, proxy);
                }
            }
            Collection<?> oldCollectionSnapshot = CollectionHelper
                    .cloneCollection((Collection<?>) collectionProperty);
            boolean inserted;
            if (collectionProperty instanceof List<?> && index >= 0 && index < collectionProperty.size()) {
                ((List<Object>) collectionProperty).add(index, value);
                inserted = true;
            } else {
                inserted = collectionProperty.add(value);
            }
            if (inserted) {
                if (EntityHelper.isInlineComponentReference(
                        propertyDescriptor.getReferencedDescriptor().getElementDescriptor())) {
                    if (value != null) {
                        ((IComponent) value).setOwningComponent((IComponent) proxy, propertyDescriptor);
                    }
                }
                if (collectionSortEnabled) {
                    inlineComponentFactory.sortCollectionProperty((IComponent) proxy, propertyName);
                }
                doFirePropertyChange(proxy, propertyName, oldCollectionSnapshot, collectionProperty);
                if (propertyProcessorsEnabled) {
                    propertyDescriptor.postprocessAdder(proxy, collectionProperty, value);
                }
            }
        } catch (RuntimeException ex) {
            rollbackProperty(proxy, propertyDescriptor, collectionProperty);
            throw ex;
        } catch (InvocationTargetException ex) {
            rollbackProperty(proxy, propertyDescriptor, collectionProperty);
            if (ex.getCause() instanceof RuntimeException) {
                throw (RuntimeException) ex.getCause();
            }
            throw new ComponentException(ex.getCause());
        } catch (IllegalAccessException | NoSuchMethodException ex) {
            throw new ComponentException(ex);
        }
    }

    private void addToProperty(Object proxy, ICollectionPropertyDescriptor<?> propertyDescriptor, Object value) {
        addToProperty(proxy, propertyDescriptor, -1, value);
    }

    private void checkIntegrity(Object proxy) {
        checkMandatoryProperties(proxy);
        if (propertyProcessorsEnabled) {
            for (IPropertyDescriptor propertyDescriptor : componentDescriptor.getPropertyDescriptors()) {
                if (!propertyDescriptor.isComputed()) {
                    propertyDescriptor.preprocessSetter(proxy,
                            straightGetProperty(proxy, propertyDescriptor.getName()));
                }
            }
        }
    }

    private void checkMandatoryProperties(Object proxy) {
        if (propertyProcessorsEnabled) {
            for (IPropertyDescriptor propertyDescriptor : componentDescriptor.getPropertyDescriptors()) {
                if (!propertyDescriptor.isComputed()) {
                    if (propertyDescriptor.isMandatory()) {
                        Object newValue = straightGetProperty(proxy, propertyDescriptor.getName());
                        if (newValue == null || (isInitialized(newValue) && newValue instanceof Collection<?>
                                && ((Collection<?>) newValue).isEmpty())) {
                            throw new MandatoryPropertyException(propertyDescriptor, proxy);
                        }
                    }
                    if (propertyDescriptor instanceof ICollectionPropertyDescriptor<?>
                            && !((ICollectionPropertyDescriptor<?>) propertyDescriptor).getReferencedDescriptor()
                                    .isNullElementAllowed()) {
                        Object newValue = straightGetProperty(proxy, propertyDescriptor.getName());
                        if (isInitialized(newValue) && newValue instanceof Collection<?>) {
                            for (Object element : ((Collection<?>) newValue)) {
                                if (element == null) {
                                    throw new MandatoryPropertyException(propertyDescriptor, proxy);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    @SuppressWarnings({ "unused", "UnusedParameters" })
    private boolean hasListeners(Object proxy, String propertyName) {
        if (computedPropertiesCache != null && computedPropertiesCache.containsKey(propertyName)) {
            // this is necessary in order to force cache re-computation
            computedPropertiesCache.remove(propertyName);
        }
        if (propertyChangeSupport != null && propertyChangeSupport.hasListeners(propertyName)) {
            PropertyChangeListener[] listeners = propertyChangeSupport.getPropertyChangeListeners(propertyName);
            if (listeners != null && listeners.length > 0) {
                return true;
            }
            listeners = propertyChangeSupport.getPropertyChangeListeners();
            for (PropertyChangeListener listener : listeners) {
                // Avoid single property change listeners and dirt trackers
                if (!(listener instanceof PropertyChangeListenerProxy
                        || listener instanceof BeanPropertyChangeRecorder)) {
                    return true;
                }
            }
        }
        if (weakPropertyChangeSupport != null && weakPropertyChangeSupport.hasListeners(propertyName)) {
            PropertyChangeListener[] listeners = weakPropertyChangeSupport.getPropertyChangeListeners(propertyName);
            if (listeners != null && listeners.length > 0) {
                return true;
            }
            listeners = weakPropertyChangeSupport.getPropertyChangeListeners();
            for (PropertyChangeListener listener : listeners) {
                if (listener instanceof NestedReferenceTracker
                        && ((NestedReferenceTracker) listener).source instanceof IComponent) {
                    if (!propertyName.contains(((NestedReferenceTracker) listener).referencePropertyName)
                            && ((IComponent) ((NestedReferenceTracker) listener).source)
                                    .hasListeners(((NestedReferenceTracker) listener).referencePropertyName + "."
                                            + propertyName)) {
                        // Query nested component but prevent
                        // stack overflows with 1-1 relationships
                        return true;
                    }
                } else {
                    return true;
                }
            }
        }
        return false;
    }

    @SuppressWarnings({ "unused", "UnusedParameters" })
    private PropertyChangeListener[] getPropertyChangeListeners(Object proxy) {
        List<PropertyChangeListener> listeners = new ArrayList<>();
        if (propertyChangeSupport != null) {
            for (PropertyChangeListener pcl : propertyChangeSupport.getPropertyChangeListeners()) {
                // Avoid single property change listeners
                if (!(pcl instanceof PropertyChangeListenerProxy)) {
                    listeners.add(pcl);
                }
            }
        }
        if (weakPropertyChangeSupport != null) {
            Collections.addAll(listeners, weakPropertyChangeSupport.getPropertyChangeListeners());
        }
        return listeners.toArray(new PropertyChangeListener[listeners.size()]);
    }

    @SuppressWarnings({ "unused", "UnusedParameters" })
    private PropertyChangeListener[] getPropertyChangeListeners(Object proxy, String propertyName) {
        List<PropertyChangeListener> listeners = new ArrayList<>();
        if (propertyChangeSupport != null) {
            Collections.addAll(listeners, propertyChangeSupport.getPropertyChangeListeners(propertyName));
        }
        if (weakPropertyChangeSupport != null) {
            Collections.addAll(listeners, weakPropertyChangeSupport.getPropertyChangeListeners(propertyName));
        }
        return listeners.toArray(new PropertyChangeListener[listeners.size()]);
    }

    /**
     * Fire property change.
     *
     * @param proxy
     *     the proxy
     * @param propertyName
     *     the property name
     * @param oldValue
     *     the old value
     * @param newValue
     *     the new value
     */
    protected void firePropertyChange(Object proxy, String propertyName, Object oldValue, Object newValue) {
        Object actualNewValue = newValue;
        if (computedPropertiesCache != null && computedPropertiesCache.containsKey(propertyName)
                && oldValue != IPropertyChangeCapable.UNKNOWN) {
            computedPropertiesCache.remove(propertyName);
            actualNewValue = IPropertyChangeCapable.UNKNOWN;
        }
        doFirePropertyChange(proxy, propertyName, oldValue, actualNewValue);
        // This method supports firing nested property changes
        if (propertyName != null) {
            int lastIndexOfDelim = propertyName.lastIndexOf(IAccessor.NESTED_DELIM);
            if (lastIndexOfDelim > 0) {
                Object propertyHolder;
                try {
                    propertyHolder = getAccessorFactory().createPropertyAccessor(
                            propertyName.substring(0, lastIndexOfDelim), getComponentContract()).getValue(proxy);
                    if (propertyHolder != null && propertyHolder instanceof IComponent) {
                        ((IComponent) propertyHolder).firePropertyChange(
                                propertyName.substring(lastIndexOfDelim + 1), oldValue, actualNewValue);
                    }
                } catch (IllegalAccessException | NoSuchMethodException ex) {
                    throw new ComponentException(ex);
                } catch (InvocationTargetException ex) {
                    if (ex.getCause() instanceof RuntimeException) {
                        throw (RuntimeException) ex.getCause();
                    }
                    throw new ComponentException(ex.getCause());
                }
            }
        }
    }

    private void doFirePropertyChange(Object proxy, String propertyName, Object oldValue, Object newValue) {
        if (propertyChangeEnabled) {
            if ((oldValue == null && newValue == null) || (oldValue == newValue)) {
                return;
            }
            if (!isInitialized(oldValue) || !isInitialized(newValue)) {
                doFirePropertyChange(
                        new PropertyChangeEvent(proxy, propertyName, IPropertyChangeCapable.UNKNOWN, newValue));
            } else {
                doFirePropertyChange(new PropertyChangeEvent(proxy, propertyName, oldValue, newValue));
            }
        }
    }

    private void doFirePropertyChange(PropertyChangeEvent evt) {
        if (propertyChangeEnabled) {
            if (delayedEvents != null) {
                if (isDirtyTrackingEnabled()) {
                    delayedEvents.add(evt);
                } else {
                    delayedEvents.add(new DirtyFreePropertyChangeEvent(evt));
                }
            } else {
                if (propertyChangeSupport != null) {
                    propertyChangeSupport.firePropertyChange(evt);
                }
                if (weakPropertyChangeSupport != null) {
                    weakPropertyChangeSupport.firePropertyChange(evt);
                }
            }
        }
    }

    private static class DirtyFreePropertyChangeEvent extends PropertyChangeEvent {

        private static final long serialVersionUID = -3661229785535176973L;

        /**
         * Instantiates a new Dirty free property change event.
         *
         * @param pce
         *     the pce
         */
        public DirtyFreePropertyChangeEvent(PropertyChangeEvent pce) {
            super(pce.getSource(), pce.getPropertyName(), pce.getOldValue(), pce.getNewValue());
        }
    }

    private boolean blockEvents() {
        if (delayedEvents == null) {
            delayedEvents = new ArrayList<>();
            return true;
        }
        return false;
    }

    private void releaseEvents() {
        if (delayedEvents != null) {
            List<PropertyChangeEvent> delayedEventsCopy = new ArrayList<>(delayedEvents);
            delayedEvents = null;
            for (PropertyChangeEvent evt : delayedEventsCopy) {
                boolean wasDirtyTrackingEnabled = isDirtyTrackingEnabled();
                try {
                    if (evt instanceof DirtyFreePropertyChangeEvent) {
                        setDirtyTrackingEnabled(false);
                    }
                    doFirePropertyChange(evt);
                } finally {
                    setDirtyTrackingEnabled(wasDirtyTrackingEnabled);
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    private synchronized Object accessComputedProperty(IPropertyDescriptor propertyDescriptor,
            AccessorInfo accessorInfo, Class<IComponentExtension<IComponent>> extensionClass, Object proxy,
            Method method, Object... args) {
        try {
            String propertyName = propertyDescriptor.getName();
            Object computedPropertyValue = null;
            if (accessorInfo.isModifier()) {
                computedPropertyValue = getAccessorFactory()
                        .createPropertyAccessor(propertyDescriptor.getName(), getComponentContract())
                        .getValue(proxy);
                Object interceptedValue = args[args.length - 1];
                if (propertyProcessorsEnabled) {
                    switch (accessorInfo.getAccessorType()) {
                    case SETTER:
                        interceptedValue = propertyDescriptor.interceptSetter(proxy, interceptedValue);
                        propertyDescriptor.preprocessSetter(proxy, interceptedValue);
                        break;
                    case ADDER:
                        ((ICollectionPropertyDescriptor<?>) propertyDescriptor).preprocessAdder(proxy,
                                (Collection<Object>) computedPropertyValue, interceptedValue);
                        break;
                    case REMOVER:
                        ((ICollectionPropertyDescriptor<?>) propertyDescriptor).preprocessRemover(proxy,
                                (Collection<Object>) computedPropertyValue, interceptedValue);
                        break;
                    default:
                        break;
                    }
                    args[args.length - 1] = interceptedValue;
                }
            } else if (propertyDescriptor.isCacheable()) {
                if (computedPropertiesCache != null && computedPropertiesCache.containsKey(propertyName)) {
                    computedPropertyValue = computedPropertiesCache.get(propertyName);
                    return computedPropertyValue;
                }
            }
            IComponentExtension<? extends IComponent> extensionDelegate = getExtensionInstance(extensionClass,
                    (IComponent) proxy);
            if (accessorInfo.isModifier()) {
                // do not change computed property value
                invokeExtensionMethod(extensionDelegate, method, args);
            } else {
                computedPropertyValue = invokeExtensionMethod(extensionDelegate, method, args);
            }
            if (accessorInfo.isModifier()) {
                Object newComputedPropertyValue = getAccessorFactory()
                        .createPropertyAccessor(propertyDescriptor.getName(), getComponentContract())
                        .getValue(proxy);
                switch (accessorInfo.getAccessorType()) {
                case SETTER:
                    propertyDescriptor.postprocessSetter(proxy, computedPropertyValue, newComputedPropertyValue);
                    break;
                case ADDER:
                    ((ICollectionPropertyDescriptor<?>) propertyDescriptor).postprocessAdder(proxy,
                            (Collection<Object>) newComputedPropertyValue, args[args.length - 1]);
                    break;
                case REMOVER:
                    ((ICollectionPropertyDescriptor<?>) propertyDescriptor).postprocessRemover(proxy,
                            (Collection<Object>) newComputedPropertyValue, args[args.length - 1]);
                    break;
                default:
                    break;
                }
            } else if (propertyDescriptor.isCacheable()) {
                if (computedPropertiesCache == null) {
                    computedPropertiesCache = new THashMap<>(1, 1.0f);
                }
                computedPropertiesCache.put(propertyName, computedPropertyValue);
            }
            return computedPropertyValue;
        } catch (IllegalAccessException | NoSuchMethodException ex) {
            throw new ComponentException(ex);
        } catch (InvocationTargetException ex) {
            if (ex.getCause() instanceof RuntimeException) {
                throw (RuntimeException) ex.getCause();
            }
            throw new ComponentException(ex.getCause());
        }
    }

    private Object invokeExtensionMethod(IComponentExtension<? extends IComponent> componentExtension,
            Method method, Object... args)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        return MethodUtils.invokeMethod(componentExtension, method.getName(), args, method.getParameterTypes());
    }

    @SuppressWarnings({ "unchecked", "ConstantConditions" })
    private boolean invokeLifecycleInterceptors(Object proxy, Method lifecycleMethod, Object... args) {
        String methodName = lifecycleMethod.getName()/* .intern() */;
        if (ILifecycleCapable.ON_CREATE_METHOD_NAME.equals(methodName)) {
            onCreate(proxy, (IEntityFactory) args[0], (UserPrincipal) args[1], (IEntityLifecycleHandler) args[2]);
        } else if (ILifecycleCapable.ON_PERSIST_METHOD_NAME.equals(methodName)) {
            onPersist(proxy, (IEntityFactory) args[0], (UserPrincipal) args[1], (IEntityLifecycleHandler) args[2]);
        } else if (ILifecycleCapable.ON_UPDATE_METHOD_NAME.equals(methodName)) {
            onUpdate(proxy, (IEntityFactory) args[0], (UserPrincipal) args[1], (IEntityLifecycleHandler) args[2]);
        } else if (ILifecycleCapable.ON_LOAD_METHOD_NAME.equals(methodName)) {
            onLoad(proxy);
        } else if (ILifecycleCapable.ON_CLONE_METHOD_NAME.equals(methodName)) {
            onClone(proxy, (IComponent) args[0]);
        } else if (ILifecycleCapable.ON_DELETE_METHOD_NAME.equals(methodName)) {
            onDelete(proxy, (IEntityFactory) args[0], (UserPrincipal) args[1], (IEntityLifecycleHandler) args[2]);
        }
        boolean interceptorResults = false;
        for (ILifecycleInterceptor<?> lifecycleInterceptor : componentDescriptor.getLifecycleInterceptors()) {
            int signatureSize = lifecycleMethod.getParameterTypes().length + 1;
            Class<?>[] parameterTypes = new Class<?>[signatureSize];
            Object[] parameters = new Object[signatureSize];

            parameterTypes[0] = componentDescriptor.getComponentContract();
            parameters[0] = proxy;

            for (int i = 1; i < signatureSize; i++) {
                parameterTypes[i] = lifecycleMethod.getParameterTypes()[i - 1];
                parameters[i] = args[i - 1];
            }
            try {
                Object interceptorResult = MethodUtils.invokeMethod(lifecycleInterceptor, methodName, parameters,
                        parameterTypes);
                if (interceptorResult instanceof Boolean) {
                    interceptorResults = interceptorResults || (Boolean) interceptorResult;
                }
            } catch (IllegalAccessException | NoSuchMethodException ex) {
                throw new ComponentException(ex);
            } catch (InvocationTargetException ex) {
                if (ex.getCause() instanceof RuntimeException) {
                    throw (RuntimeException) ex.getCause();
                }
                throw new ComponentException(ex.getCause());
            }
        }
        // invoke lifecycle method on inline components
        for (IPropertyDescriptor propertyDescriptor : componentDescriptor.getPropertyDescriptors()) {
            if (propertyDescriptor instanceof IReferencePropertyDescriptor<?>
                    && EntityHelper.isInlineComponentReference(
                            (IReferencePropertyDescriptor<IComponent>) propertyDescriptor)
                    && !propertyDescriptor.isComputed()) {
                Object inlineComponent = getProperty(proxy, propertyDescriptor);
                if (inlineComponent instanceof ILifecycleCapable) {
                    try {
                        Object[] compArgs = null;
                        if (args != null) {
                            compArgs = new Object[args.length];
                            for (int j = 0; j < args.length; j++) {
                                // certainly onClone argument
                                if (args[j] != null && ((IComponent) proxy).getComponentContract()
                                        .isAssignableFrom(args[j].getClass())) {
                                    // we must retrieve the original inline component
                                    compArgs[j] = ((IComponent) args[j])
                                            .straightGetProperty(propertyDescriptor.getName());
                                } else {
                                    compArgs[j] = args[j];
                                }
                            }
                        }
                        Object interceptorResult = MethodUtils.invokeMethod(inlineComponent, methodName, compArgs,
                                lifecycleMethod.getParameterTypes());
                        if (interceptorResult instanceof Boolean) {
                            interceptorResults = interceptorResults || (Boolean) interceptorResult;
                        }
                    } catch (NoSuchMethodException | IllegalAccessException ex) {
                        throw new ComponentException(ex);
                    } catch (InvocationTargetException ex) {
                        if (ex.getCause() instanceof RuntimeException) {
                            throw (RuntimeException) ex.getCause();
                        }
                        throw new ComponentException(ex.getCause());
                    }
                }
            }
        }
        switch (methodName) {
        case ILifecycleCapable.ON_PERSIST_METHOD_NAME:
            // Important to check for not null values.
            checkIntegrity(proxy);
            break;
        case ILifecycleCapable.ON_UPDATE_METHOD_NAME:
            // Important to check for not null values
            // since the checking has been delayed until persistence.
            checkMandatoryProperties(proxy);
            break;
        case ILifecycleCapable.ON_DELETE_METHOD_NAME:
            // Performs any necessary operation on internal state to mark the proxy
            // deleted and thus unusable.
            markDeleted(proxy);
            break;
        default:
            break;
        }
        return interceptorResults;
    }

    /**
     * Performs any necessary operation on internal state to mark the proxy
     * deleted and thus unusable.
     *
     * @param proxy
     *     the proxy.
     */
    protected void markDeleted(Object proxy) {
        // NO-OP.
    }

    @SuppressWarnings("unchecked")
    protected void removeFromProperty(Object proxy, ICollectionPropertyDescriptor<?> propertyDescriptor,
            Object value) {
        String propertyName = propertyDescriptor.getName();
        // The following optimization breaks bidirectional N-N relationship persistence
        // if (!isInitialized(straightGetProperty(proxy, propertyName))) {
        // return;
        // }
        Collection<Object> collectionProperty = (Collection<Object>) straightGetProperty(proxy, propertyName);
        try {
            if (propertyProcessorsEnabled) {
                propertyDescriptor.preprocessRemover(proxy, collectionProperty, value);
            }
            if (collectionProperty.contains(value)) {
                IRelationshipEndPropertyDescriptor reversePropertyDescriptor = propertyDescriptor
                        .getReverseRelationEnd();
                if (reversePropertyDescriptor != null) {
                    if (reversePropertyDescriptor instanceof IReferencePropertyDescriptor<?>) {
                        accessorFactory
                                .createPropertyAccessor(reversePropertyDescriptor.getName(), propertyDescriptor
                                        .getReferencedDescriptor().getElementDescriptor().getComponentContract())
                                .setValue(value, null);
                    } else if (reversePropertyDescriptor instanceof ICollectionPropertyDescriptor<?>) {
                        ICollectionAccessor collectionAccessor = accessorFactory.createCollectionPropertyAccessor(
                                reversePropertyDescriptor.getName(),
                                propertyDescriptor.getReferencedDescriptor().getElementDescriptor()
                                        .getComponentContract(),
                                ((ICollectionPropertyDescriptor<?>) reversePropertyDescriptor)
                                        .getCollectionDescriptor().getElementDescriptor().getComponentContract());
                        if (collectionAccessor instanceof IModelDescriptorAware) {
                            ((IModelDescriptorAware) collectionAccessor)
                                    .setModelDescriptor(reversePropertyDescriptor);
                        }
                        collectionAccessor.removeFromValue(value, proxy);
                    }
                }
                Collection<?> oldCollectionSnapshot = CollectionHelper
                        .cloneCollection((Collection<?>) collectionProperty);
                if (collectionProperty.remove(value)) {
                    if (EntityHelper.isInlineComponentReference(
                            propertyDescriptor.getReferencedDescriptor().getElementDescriptor())) {
                        if (value != null) {
                            ((IComponent) value).setOwningComponent(null, null);
                        }
                    }
                    doFirePropertyChange(proxy, propertyName, oldCollectionSnapshot, collectionProperty);
                    if (propertyProcessorsEnabled) {
                        propertyDescriptor.postprocessRemover(proxy, collectionProperty, value);
                    }
                    if (proxy instanceof IEntity && value instanceof IEntity) {
                        entityDetached((IEntity) proxy, (IEntity) value, propertyDescriptor);
                    }
                }
            }
        } catch (RuntimeException ex) {
            rollbackProperty(proxy, propertyDescriptor, collectionProperty);
            throw ex;
        } catch (InvocationTargetException ex) {
            rollbackProperty(proxy, propertyDescriptor, collectionProperty);
            if (ex.getCause() instanceof RuntimeException) {
                throw (RuntimeException) ex.getCause();
            }
            throw new ComponentException(ex.getCause());
        } catch (IllegalAccessException | NoSuchMethodException ex) {
            throw new ComponentException(ex);
        }
    }

    private synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
        if (listener == null) {
            return;
        }
        if (propertyChangeSupport != null) {
            propertyChangeSupport.removePropertyChangeListener(listener);
        }
        if (weakPropertyChangeSupport != null) {
            weakPropertyChangeSupport.removePropertyChangeListener(listener);
        }
    }

    private synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        if (listener == null) {
            return;
        }
        if (propertyChangeSupport != null) {
            propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
        }
        if (weakPropertyChangeSupport != null) {
            weakPropertyChangeSupport.removePropertyChangeListener(propertyName, listener);
        }
    }

    private void rollbackProperty(Object proxy, IPropertyDescriptor propertyDescriptor, Object oldProperty) {
        // boolean wasPropertyProcessorsEnabled = propertyProcessorsEnabled;
        // try {
        // propertyProcessorsEnabled = false;
        // setProperty(proxy, propertyDescriptor, oldProperty);
        // } finally {
        // propertyProcessorsEnabled = wasPropertyProcessorsEnabled;
        // }
        straightSetProperty(proxy, propertyDescriptor.getName(), oldProperty);
    }

    @SuppressWarnings("unchecked")
    private void setProperty(Object proxy, IPropertyDescriptor propertyDescriptor, Object newProperty) {
        String propertyName = propertyDescriptor.getName();

        Object oldProperty;
        try {
            oldProperty = accessorFactory
                    .createPropertyAccessor(propertyName, componentDescriptor.getComponentContract())
                    .getValue(proxy);
        } catch (IllegalAccessException | NoSuchMethodException ex) {
            throw new ComponentException(ex);
        } catch (InvocationTargetException ex) {
            if (ex.getCause() instanceof RuntimeException) {
                throw (RuntimeException) ex.getCause();
            }
            throw new ComponentException(ex.getCause());
        }
        Object actualNewProperty;
        if (propertyProcessorsEnabled) {
            actualNewProperty = propertyDescriptor.interceptSetter(proxy, newProperty);
        } else {
            actualNewProperty = newProperty;
        }
        if (isInitialized(oldProperty) && isInitialized(actualNewProperty)
                && ObjectUtils.equals(oldProperty, actualNewProperty)) {
            return;
        }
        if (propertyProcessorsEnabled) {
            propertyDescriptor.preprocessSetter(proxy, actualNewProperty);
        }
        if (propertyDescriptor instanceof IRelationshipEndPropertyDescriptor) {
            // It's a relation end
            IRelationshipEndPropertyDescriptor reversePropertyDescriptor = ((IRelationshipEndPropertyDescriptor) propertyDescriptor)
                    .getReverseRelationEnd();
            try {
                if (propertyDescriptor instanceof IReferencePropertyDescriptor) {
                    // It's a 'one' relation end
                    storeReferenceProperty(proxy, (IReferencePropertyDescriptor<?>) propertyDescriptor, oldProperty,
                            actualNewProperty);
                    if (reversePropertyDescriptor != null) {
                        // It is bidirectional, so we are going to update the other end.
                        if (reversePropertyDescriptor instanceof IReferencePropertyDescriptor) {
                            // It's a one-to-one relationship
                            if (proxy instanceof IEntity && oldProperty instanceof IEntity) {
                                entityDetached((IEntity) proxy, (IEntity) oldProperty,
                                        ((IRelationshipEndPropertyDescriptor) propertyDescriptor));
                            }
                            IAccessor reversePropertyAccessor = accessorFactory.createPropertyAccessor(
                                    reversePropertyDescriptor.getName(),
                                    ((IReferencePropertyDescriptor<?>) propertyDescriptor).getReferencedDescriptor()
                                            .getComponentContract());
                            if (oldProperty != null) {
                                reversePropertyAccessor.setValue(oldProperty, null);
                            }
                            if (actualNewProperty != null) {
                                reversePropertyAccessor.setValue(actualNewProperty, proxy);
                            }
                        } else if (reversePropertyDescriptor instanceof ICollectionPropertyDescriptor) {
                            // It's a one-to-many relationship
                            ICollectionAccessor reversePropertyAccessor = accessorFactory
                                    .createCollectionPropertyAccessor(reversePropertyDescriptor.getName(),
                                            ((IReferencePropertyDescriptor<?>) propertyDescriptor)
                                                    .getReferencedDescriptor().getComponentContract(),
                                            ((ICollectionPropertyDescriptor<?>) reversePropertyDescriptor)
                                                    .getCollectionDescriptor().getElementDescriptor()
                                                    .getComponentContract());
                            if (reversePropertyAccessor instanceof IModelDescriptorAware) {
                                ((IModelDescriptorAware) reversePropertyAccessor)
                                        .setModelDescriptor(reversePropertyDescriptor);
                            }
                            if (oldProperty != null) {
                                reversePropertyAccessor.removeFromValue(oldProperty, proxy);
                            }
                            if (actualNewProperty != null) {
                                reversePropertyAccessor.addToValue(actualNewProperty, proxy);
                            }
                        }
                    }
                } else if (propertyDescriptor instanceof ICollectionPropertyDescriptor) {
                    Collection<?> oldCollectionSnapshot = CollectionHelper
                            .cloneCollection((Collection<?>) oldProperty);
                    // It's a 'many' relation end
                    Collection<Object> oldPropertyElementsToRemove = new THashSet<>(1);
                    Collection<Object> newPropertyElementsToAdd = new TLinkedHashSet<>(1);
                    Collection<Object> propertyElementsToKeep = new THashSet<>(1);

                    if (oldProperty != null) {
                        oldPropertyElementsToRemove.addAll((Collection<?>) oldProperty);
                        propertyElementsToKeep.addAll((Collection<?>) oldProperty);
                    }
                    if (actualNewProperty != null) {
                        newPropertyElementsToAdd.addAll((Collection<?>) actualNewProperty);
                    }
                    propertyElementsToKeep.retainAll(newPropertyElementsToAdd);
                    oldPropertyElementsToRemove.removeAll(propertyElementsToKeep);
                    newPropertyElementsToAdd.removeAll(propertyElementsToKeep);
                    ICollectionAccessor propertyAccessor = accessorFactory.createCollectionPropertyAccessor(
                            propertyName, componentDescriptor.getComponentContract(),
                            ((ICollectionPropertyDescriptor<?>) propertyDescriptor).getCollectionDescriptor()
                                    .getElementDescriptor().getComponentContract());
                    boolean oldCollectionSortEnabled = collectionSortEnabled;
                    boolean oldPropertyChangeEnabled = propertyChangeEnabled;
                    boolean oldPropertyProcessorsEnabled = propertyProcessorsEnabled;
                    try {
                        // Delay sorting for performance reasons.
                        collectionSortEnabled = false;
                        // Block property changes for performance reasons;
                        propertyChangeEnabled = false;
                        // Block property processors
                        propertyProcessorsEnabled = false;
                        for (Object element : oldPropertyElementsToRemove) {
                            propertyAccessor.removeFromValue(proxy, element);
                        }
                        for (Object element : newPropertyElementsToAdd) {
                            propertyAccessor.addToValue(proxy, element);
                        }
                        inlineComponentFactory.sortCollectionProperty((IComponent) proxy, propertyName);
                    } finally {
                        collectionSortEnabled = oldCollectionSortEnabled;
                        propertyChangeEnabled = oldPropertyChangeEnabled;
                        propertyProcessorsEnabled = oldPropertyProcessorsEnabled;
                    }
                    // if the property is a list we may restore the element order and be
                    // careful not to miss one...
                    if (actualNewProperty instanceof List) {
                        Collection<Object> currentProperty = (Collection<Object>) oldProperty;
                        if (currentProperty instanceof List) {
                            // Just check that only order differs
                            Set<Object> temp = new THashSet<>(currentProperty);
                            temp.removeAll((List<?>) actualNewProperty);
                            if (currentProperty instanceof ICollectionWrapper) {
                                currentProperty = ((ICollectionWrapper) currentProperty).getWrappedCollection();
                            }
                            currentProperty.clear();
                            currentProperty.addAll((List<?>) actualNewProperty);
                            currentProperty.addAll(temp);
                        }
                    }
                    oldProperty = oldCollectionSnapshot;
                }
            } catch (RuntimeException ex) {
                rollbackProperty(proxy, propertyDescriptor, oldProperty);
                throw ex;
            } catch (InvocationTargetException ex) {
                rollbackProperty(proxy, propertyDescriptor, oldProperty);
                if (ex.getCause() instanceof RuntimeException) {
                    throw (RuntimeException) ex.getCause();
                }
                throw new ComponentException(ex.getCause());
            } catch (IllegalAccessException | NoSuchMethodException ex) {
                throw new ComponentException(ex);
            }
        } else {
            storeProperty(propertyName, actualNewProperty);
        }
        doFirePropertyChange(proxy, propertyName, oldProperty, actualNewProperty);
        if (propertyProcessorsEnabled) {
            propertyDescriptor.postprocessSetter(proxy, oldProperty, actualNewProperty);
        }
    }

    private void straightSetProperties(Object proxy, Map<String, Object> backendProperties) {
        boolean eventsBlocked = blockEvents();
        try {
            for (Map.Entry<String, Object> propertyEntry : backendProperties.entrySet()) {
                straightSetProperty(proxy, propertyEntry.getKey(), propertyEntry.getValue());
            }
        } finally {
            if (eventsBlocked) {
                releaseEvents();
            }
        }
    }

    private PropertyChangeListener createOrGetFakePcl() {
        if (fakePcl == null) {
            fakePcl = new PropertyChangeListener() {

                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    // It's fake
                }
            };
        }
        return fakePcl;
    }

    /**
     * To string.
     *
     * @param proxy
     *     the proxy
     * @return the to string
     */
    protected String toString(Object proxy) {
        try {
            String toStringPropertyName = componentDescriptor.getToStringProperty();
            Object toStringValue = accessorFactory
                    .createPropertyAccessor(toStringPropertyName, componentDescriptor.getComponentContract())
                    .getValue(proxy);
            if (toStringValue == null) {
                return "";
            }
            return toStringValue.toString();
        } catch (IllegalAccessException | NoSuchMethodException ex) {
            throw new ComponentException(ex);
        } catch (InvocationTargetException ex) {
            if (ex.getCause() instanceof RuntimeException) {
                throw (RuntimeException) ex.getCause();
            }
            throw new ComponentException(ex.getCause());
        }
    }

    /**
     * Gets component descriptor.
     *
     * @return the component descriptor
     */
    protected IComponentDescriptor<? extends IComponent> getComponentDescriptor() {
        return componentDescriptor;
    }

    /**
     * Gets translated property value.
     *
     * @param proxy
     *     the proxy
     * @param propertyDescriptor
     *     the property descriptor
     * @param locale
     *     the locale
     * @return the translated property value
     */
    @SuppressWarnings("unchecked")
    protected String getNlsPropertyValue(Object proxy, IStringPropertyDescriptor propertyDescriptor,
            Locale locale) {
        if (locale != null) {
            String barePropertyName = propertyDescriptor.getName();
            if (barePropertyName.endsWith(IComponentDescriptor.NLS_SUFFIX)) {
                barePropertyName = barePropertyName.substring(0,
                        barePropertyName.length() - IComponentDescriptor.NLS_SUFFIX.length());
            }
            String nlsPropertyName = barePropertyName + IComponentDescriptor.NLS_SUFFIX;
            Set<IPropertyTranslation> translations = (Set<IPropertyTranslation>) straightGetProperty(proxy,
                    AbstractComponentDescriptor.getComponentTranslationsDescriptorTemplate().getName());
            if (translations != null && isInitialized(translations)) {
                String sessionLanguage = locale.getLanguage();
                if (sessionLanguage != null) {
                    for (IPropertyTranslation translation : translations) {
                        if (sessionLanguage.equalsIgnoreCase(translation.getLanguage())
                                && barePropertyName.equals(translation.getPropertyName())) {
                            return translation.getTranslatedValue();
                        }
                    }
                }
                return null;
            }
            return (String) straightGetProperty(proxy, nlsPropertyName);
        }
        return null;
    }

    /**
     * Sets translated property value.
     *
     * @param proxy
     *     the proxy
     * @param propertyDescriptor
     *     the property descriptor
     * @param translatedValue
     *     the translated value
     * @param entityFactory
     *     the entity factory
     * @param locale
     *     the locale
     */
    @SuppressWarnings("unchecked")
    protected void setNlsPropertyValue(Object proxy, IStringPropertyDescriptor propertyDescriptor,
            String translatedValue, IEntityFactory entityFactory, Locale locale) {
        if (locale != null) {
            String actualTranslatedValue = (String) propertyDescriptor.interceptSetter(proxy, translatedValue);
            // manually trigger interceptors
            propertyDescriptor.preprocessSetter(proxy, translatedValue);
            String barePropertyName = propertyDescriptor.getName();
            if (barePropertyName.endsWith(IComponentDescriptor.NLS_SUFFIX)) {
                barePropertyName = barePropertyName.substring(0,
                        barePropertyName.length() - IComponentDescriptor.NLS_SUFFIX.length());
            }
            String nlsPropertyName = barePropertyName + IComponentDescriptor.NLS_SUFFIX;
            String oldTranslation = invokeNlsGetter(proxy, propertyDescriptor);
            Set<IPropertyTranslation> translations;
            String translationsPropertyName = AbstractComponentDescriptor
                    .getComponentTranslationsDescriptorTemplate().getName();

            Class<? extends IComponent> translationContract = ((ICollectionPropertyDescriptor<IComponent>) getComponentDescriptor()
                    .getPropertyDescriptor(translationsPropertyName)).getReferencedDescriptor()
                            .getElementDescriptor().getComponentContract();
            ICollectionAccessor translationsAccessor = getAccessorFactory().createCollectionPropertyAccessor(
                    translationsPropertyName, getComponentContract(), translationContract);
            try {
                translations = translationsAccessor.getValue(proxy);
            } catch (IllegalAccessException | NoSuchMethodException ex) {
                throw new ComponentException(ex);
            } catch (InvocationTargetException ex) {
                if (ex.getCause() instanceof RuntimeException) {
                    throw (RuntimeException) ex.getCause();
                }
                throw new ComponentException(ex.getCause());
            }
            String sessionLanguage = locale.getLanguage();
            IPropertyTranslation oldSessionTranslation = null;
            if (sessionLanguage != null) {
                for (IPropertyTranslation translation : translations) {
                    if (sessionLanguage.equalsIgnoreCase(translation.getLanguage())
                            && barePropertyName.equals(translation.getPropertyName())) {
                        oldSessionTranslation = translation;
                    }
                }
            }
            // Cannot simply update the old session translation or Hibernate will not manage persistence correctly.
            if (oldSessionTranslation != null) {
                try {
                    translationsAccessor.removeFromValue(proxy, oldSessionTranslation);
                } catch (IllegalAccessException | NoSuchMethodException ex) {
                    throw new ComponentException(ex);
                } catch (InvocationTargetException ex) {
                    if (ex.getCause() instanceof RuntimeException) {
                        throw (RuntimeException) ex.getCause();
                    }
                    throw new ComponentException(ex.getCause());
                }
            }
            IPropertyTranslation sessionTranslation = (IPropertyTranslation) entityFactory
                    .createComponentInstance(translationContract);
            sessionTranslation.setLanguage(locale.getLanguage());
            sessionTranslation.setPropertyName(barePropertyName);
            sessionTranslation.setTranslatedValue(actualTranslatedValue);
            try {
                translationsAccessor.addToValue(proxy, sessionTranslation);
            } catch (IllegalAccessException | NoSuchMethodException ex) {
                throw new ComponentException(ex);
            } catch (InvocationTargetException ex) {
                if (ex.getCause() instanceof RuntimeException) {
                    throw (RuntimeException) ex.getCause();
                }
                throw new ComponentException(ex.getCause());
            }
            firePropertyChange(proxy, nlsPropertyName, oldTranslation, actualTranslatedValue);

            propertyDescriptor.postprocessSetter(proxy, oldTranslation, actualTranslatedValue);
        }
    }

    private class NestedReferenceTracker implements PropertyChangeListener {

        private final Object source;
        private final String referencePropertyName;
        private boolean enabled;
        private boolean initialized;
        private final boolean referencesInlinedComponent;
        private final Set<String> trackedProperties;

        /**
         * Constructs a new {@code InlineReferenceTracker} instance.
         *
         * @param source
         *     the proxy holding the reference tracker.
         * @param referencePropertyName
         *     the name of the component to track the properties.
         * @param referencesInlineComponent
         *     is it tracking an inline component or an entity ref ?
         */
        public NestedReferenceTracker(Object source, String referencePropertyName,
                boolean referencesInlineComponent) {
            this.source = source;
            this.referencePropertyName = referencePropertyName;
            this.referencesInlinedComponent = referencesInlineComponent;
            this.enabled = true;
            this.initialized = false;
            this.trackedProperties = new THashSet<>(1);
        }

        /**
         * Adds a property to the list of tracked nested properties for this nested
         * reference tracker instance.
         *
         * @param nestedPropertyName
         *     the nested property name.
         */
        public void addToTrackedProperties(String nestedPropertyName) {
            trackedProperties.add(nestedPropertyName);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (enabled) {
                try {
                    enabled = false;
                    if (source instanceof IEntity && referencesInlinedComponent) {
                        // for dirtiness notification.
                        // must check if the actual property change does not come from a
                        // nested entity. In that case, the persistent state has not
                        // changed.
                        boolean chainHasEntity = false;
                        String[] chain = evt.getPropertyName().split("\\" + IAccessor.NESTED_DELIM);
                        IComponent sourceComponent = (IComponent) evt.getSource();
                        if (chain.length > 1) {
                            StringBuilder chainPart = new StringBuilder();
                            for (int i = 0; i < chain.length - 1 && !chainHasEntity; i++) {
                                if (chainPart.length() > 0) {
                                    chainPart.append(IAccessor.NESTED_DELIM);
                                }
                                chainPart.append(chain[i]);
                                if (sourceComponent.straightGetProperty(chainPart.toString()) instanceof IEntity) {
                                    chainHasEntity = true;
                                }
                            }
                        }
                        if (!chainHasEntity) {
                            IComponent oldComponentValue = getInlineComponentFactory()
                                    .createComponentInstance(sourceComponent.getComponentContract());
                            oldComponentValue.straightSetProperties(sourceComponent.straightGetProperties());
                            oldComponentValue.straightSetProperty(evt.getPropertyName(), evt.getOldValue());
                            doFirePropertyChange(source, referencePropertyName, oldComponentValue, evt.getSource());
                        }
                    }
                    // for ui notification
                    if (!(evt.getOldValue() instanceof IComponent
                            && (!AbstractComponentInvocationHandler.this.isInitialized(evt.getOldValue())
                                    || ((IComponent) evt.getOldValue()).getOwningComponent() == null))) { // FAKE OLD COMPONENT VALUE
                        for (String trackedProperty : trackedProperties) {
                            if (trackedProperty.equals(evt.getPropertyName())) {
                                doFirePropertyChange(source,
                                        referencePropertyName + IAccessor.NESTED_DELIM + trackedProperty,
                                        evt.getOldValue(), evt.getNewValue());
                            } else if (trackedProperty.startsWith(evt.getPropertyName())) {
                                String remainderProperty = trackedProperty
                                        .substring(evt.getPropertyName().length() + 1);
                                if (remainderProperty.indexOf(IAccessor.NESTED_DELIM) >= 0) {
                                    // If the remainder is a nested property, we have to take
                                    // care of manually firing.
                                    if (evt.getNewValue() != null) {
                                        try {
                                            Object newValue = getAccessorFactory()
                                                    .createPropertyAccessor(remainderProperty,
                                                            evt.getNewValue().getClass())
                                                    .getValue(evt.getNewValue());
                                            doFirePropertyChange(source,
                                                    referencePropertyName + IAccessor.NESTED_DELIM
                                                            + trackedProperty,
                                                    IPropertyChangeCapable.UNKNOWN, newValue);
                                        } catch (IllegalAccessException | NoSuchMethodException ex) {
                                            throw new ComponentException(ex);
                                        } catch (InvocationTargetException ex) {
                                            if (ex.getTargetException() instanceof RuntimeException) {
                                                throw (RuntimeException) ex.getTargetException();
                                            }
                                            throw new ComponentException(ex);
                                        }
                                    } else {
                                        doFirePropertyChange(source,
                                                referencePropertyName + IAccessor.NESTED_DELIM + trackedProperty,
                                                IPropertyChangeCapable.UNKNOWN, null);
                                    }
                                }
                            }
                        }
                    }
                } finally {
                    enabled = true;
                }
            }
        }

        /**
         * Gets the initialized.
         *
         * @return the initialized.
         */
        public boolean isInitialized() {
            return initialized;
        }

        /**
         * Sets the initialized.
         *
         * @param initialized
         *     the initialized to set.
         */
        public void setInitialized(boolean initialized) {
            this.initialized = initialized;
        }
    }

    private static final class NeverEqualsInvocationHandler implements InvocationHandler {

        private final Object delegate;

        private NeverEqualsInvocationHandler(Object delegate) {
            this.delegate = delegate;
        }

        /**
         * Just 'overrides' the equals method to always return false.
         * <p/>
         * {@inheritDoc}
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("equals") && args.length == 1) {
                return false;
            }
            return method.invoke(delegate, args);
        }
    }
}