org.jspresso.framework.application.backend.AbstractBackendController.java Source code

Java tutorial

Introduction

Here is the source code for org.jspresso.framework.application.backend.AbstractBackendController.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.application.backend;

import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;

import javax.security.auth.Subject;

import org.apache.commons.collections4.map.LRUMap;
import org.apache.commons.lang3.LocaleUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;

import org.jspresso.framework.action.ActionBusinessException;
import org.jspresso.framework.action.ActionContextConstants;
import org.jspresso.framework.action.ActionException;
import org.jspresso.framework.action.IAction;
import org.jspresso.framework.application.AbstractController;
import org.jspresso.framework.application.backend.action.Asynchronous;
import org.jspresso.framework.application.backend.action.Transactional;
import org.jspresso.framework.application.backend.async.AsyncActionExecutor;
import org.jspresso.framework.application.backend.entity.ControllerAwareProxyEntityFactory;
import org.jspresso.framework.application.backend.session.EMergeMode;
import org.jspresso.framework.application.backend.session.IApplicationSession;
import org.jspresso.framework.application.backend.session.IEntityUnitOfWork;
import org.jspresso.framework.application.backend.session.basic.BasicEntityUnitOfWork;
import org.jspresso.framework.application.i18n.ITranslationPlugin;
import org.jspresso.framework.application.model.Module;
import org.jspresso.framework.application.model.Workspace;
import org.jspresso.framework.application.model.descriptor.ModuleDescriptor;
import org.jspresso.framework.application.model.descriptor.WorkspaceDescriptor;
import org.jspresso.framework.application.security.ISecurityPlugin;
import org.jspresso.framework.application.security.SecurityContextBuilder;
import org.jspresso.framework.application.security.SecurityContextConstants;
import org.jspresso.framework.binding.IValueConnector;
import org.jspresso.framework.binding.model.IModelConnectorFactory;
import org.jspresso.framework.model.component.IComponent;
import org.jspresso.framework.model.component.IComponentCollectionFactory;
import org.jspresso.framework.model.component.ILifecycleCapable;
import org.jspresso.framework.model.datatransfer.ComponentTransferStructure;
import org.jspresso.framework.model.descriptor.ICollectionPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IComponentDescriptor;
import org.jspresso.framework.model.descriptor.IModelDescriptor;
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.entity.IEntity;
import org.jspresso.framework.model.entity.IEntityCloneFactory;
import org.jspresso.framework.model.entity.IEntityFactory;
import org.jspresso.framework.model.entity.IEntityRegistry;
import org.jspresso.framework.model.entity.basic.BasicEntityRegistry;
import org.jspresso.framework.security.ISecurable;
import org.jspresso.framework.security.ISecurityContextBuilder;
import org.jspresso.framework.security.SecurityHelper;
import org.jspresso.framework.security.UserPrincipal;
import org.jspresso.framework.util.accessor.IAccessorFactory;
import org.jspresso.framework.util.i18n.ITranslationProvider;
import org.jspresso.framework.util.preferences.IPreferencesStore;

/**
 * Base class for backend application controllers. Backend controllers are
 * responsible for :
 * <ul>
 * <li>keeping a reference to the application session</li>
 * <li>keeping a reference to the application workspaces and their state</li>
 * <li>keeping a reference to the application clipboard</li>
 * <li>keeping a reference to the entity registry that guarantees the in-memory
 * entity reference unicity in the user session</li>
 * <li>keeping a reference to the entity dirt recorder that keeps track of
 * entity changes to afterwards optimize the ORM operations</li>
 * <li>keeping a reference to the Spring transaction template and its peer
 * &quot;Unit of Work&quot; -aka UOW- that is responsible to manage application
 * transactions and adapt the underlying transaction system (Hibernate, JTA,
 * ...)</li>
 * </ul>
 * Moreover, the backend controller will provide several model related factories
 * that can be configured to customize default, built-in behaviour. Most of
 * these configured properties will be accessible using the corresponding
 * getters. Those getters should be used by the service layer.
 *
 * @author Vincent Vandenschrick
 */
public abstract class AbstractBackendController extends AbstractController implements IBackendController {

    private static final Logger LOG = LoggerFactory.getLogger(AbstractBackendController.class);
    private final IEntityUnitOfWork unitOfWork;
    private final IEntityUnitOfWork sessionUnitOfWork;
    private final LRUMap<Module, IValueConnector> moduleConnectors;
    private final ISecurityContextBuilder securityContextBuilder;
    private final Set<AsyncActionExecutor> asyncExecutors;
    private ThreadGroup asyncActionsThreadGroup;
    private ThreadGroup controllerAsyncActionsThreadGroup;
    private IApplicationSession applicationSession;
    private IEntityCloneFactory carbonEntityCloneFactory;
    private IComponentCollectionFactory collectionFactory;
    private IEntityFactory entityFactory;
    private IModelConnectorFactory modelConnectorFactory;
    private TransactionTemplate transactionTemplate;
    private ComponentTransferStructure<IComponent> transferStructure;
    private Map<String, IValueConnector> workspaceConnectors;
    private IPreferencesStore userPreferencesStore;
    private ITranslationProvider translationProvider;
    private ISecurityPlugin customSecurityPlugin;
    private ITranslationPlugin customTranslationPlugin;
    private TimeZone referenceTimeZone;
    private TimeZone clientTimeZone;
    private boolean throwExceptionOnBadUsage;
    private IBackendControllerFactory slaveControllerFactory;
    private int asyncExecutorsMaxCount;
    private IBackendController masterController;

    /**
     * Constructs a new {@code AbstractBackendController} instance.
     */
    @SuppressWarnings("unchecked")
    protected AbstractBackendController() {
        unitOfWork = createUnitOfWork();
        sessionUnitOfWork = createUnitOfWork();
        sessionUnitOfWork.begin();
        moduleConnectors = new LRUMap<>(20);
        securityContextBuilder = new SecurityContextBuilder();
        throwExceptionOnBadUsage = true;
        asyncExecutors = new LinkedHashSet<>();
        setAsyncExecutorsMaxCount(10);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void joinTransaction() {
        joinTransaction(false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void joinTransaction(boolean nested) {
        TransactionSynchronizationManager.registerSynchronization(this);
        if (!isUnitOfWorkActive()) {
            beginUnitOfWork();
        } else if (nested) {
            beginNestedUnitOfWork();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void performPendingOperations() {
        Collection<IEntity> entitiesToUpdate = sessionUnitOfWork.getEntitiesRegisteredForUpdate();
        Collection<IEntity> entitiesToDelete = sessionUnitOfWork.getEntitiesRegisteredForDeletion();
        if (entitiesToUpdate != null) {
            List<IEntity> uowEntitiesToUpdate = cloneInUnitOfWork(new ArrayList<>(entitiesToUpdate));
            for (IEntity uowEntity : uowEntitiesToUpdate) {
                // Must force insertion
                registerForUpdate(uowEntity);
            }
        }
        if (entitiesToDelete != null) {
            List<IEntity> uowEntitiesToDelete = cloneInUnitOfWork(new ArrayList<>(entitiesToDelete));
            for (IEntity uowEntity : uowEntitiesToDelete) {
                // Must force deletion
                registerForDeletion(uowEntity);
            }
        }
        clearPendingOperations();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void beginUnitOfWork() {
        if (isUnitOfWorkActive()) {
            throw new BackendException("Cannot begin a new unit of work. Another one is already active.");
        }
        doBeginUnitOfWork();
    }

    /**
     * Performs actual UOW begin.
     */
    protected void doBeginUnitOfWork() {
        unitOfWork.begin();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void beginNestedUnitOfWork() {
        doBeginNestedUnitOfWork();
    }

    /**
     * Performs actual UOW begin.
     */
    protected void doBeginNestedUnitOfWork() {
        unitOfWork.beginNested();
    }

    /**
     * Clears the pending operations.
     * <p/>
     * {@inheritDoc}
     */
    @Override
    public void clearPendingOperations() {
        sessionUnitOfWork.clearPendingOperations();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final <E extends IEntity> E cloneInUnitOfWork(E entity) {
        return cloneInUnitOfWork(Collections.singletonList(entity)).get(0);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <E extends IEntity> List<E> cloneInUnitOfWork(List<E> entities) {
        if (!isUnitOfWorkActive()) {
            throw new BackendException("Cannot use a unit of work that has not begun.");
        }
        List<E> uowEntities = new ArrayList<>();
        IEntityRegistry alreadyCloned = getUowEntityRegistry();
        Set<IEntity> eventsToRelease = new LinkedHashSet<>();
        boolean wasDirtyTrackingEnabled = isDirtyTrackingEnabled();
        try {
            setDirtyTrackingEnabled(false);
            for (E entity : entities) {
                uowEntities.add(cloneInUnitOfWork(entity, alreadyCloned, eventsToRelease));
            }
        } finally {
            for (IEntity entity : eventsToRelease) {
                try {
                    entity.releaseEvents();
                } catch (Throwable t) {
                    LOG.error("An unexpected exception occurred when releasing events after a merge", t);
                }
            }
            setDirtyTrackingEnabled(wasDirtyTrackingEnabled);
        }
        return uowEntities;
    }

    /**
     * Gets uow entity registry.
     *
     * @return the uow entity registry
     */
    protected IEntityRegistry getUowEntityRegistry() {
        Map<Class<? extends IEntity>, Map<Serializable, IEntity>> uowExistingEntities = unitOfWork
                .getRegisteredEntities();
        return createEntityRegistry("cloneInUnitOfWork", uowExistingEntities);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void commitUnitOfWork() {
        if (!isUnitOfWorkActive()) {
            throw new BackendException("Cannot commit a unit of work that has not begun.");
        }
        if (unitOfWork.hasNested()) {
            unitOfWork.commit();
        } else {
            doCommitUnitOfWork();
        }
    }

    /**
     * Performs actual UOW commit.
     */
    protected void doCommitUnitOfWork() {
        try {
            Collection<IEntity> uowEntitiesRegisteredForDeletion = unitOfWork.getEntitiesRegisteredForDeletion();
            if (uowEntitiesRegisteredForDeletion != null) {
                // manually trigger the fact that deleted entities are synchronized since flush interceptor does not do it.
                for (IEntity uowEntityRegisteredForDeletion : uowEntitiesRegisteredForDeletion) {
                    recordAsSynchronized(uowEntityRegisteredForDeletion);
                }
            }
            Collection<IEntity> flushedEntities = new LinkedHashSet<>();
            Collection<IEntity> updatedEntities = unitOfWork.getUpdatedEntities();
            if (updatedEntities != null) {
                flushedEntities.addAll(updatedEntities);
            }
            Collection<IEntity> deletedEntities = unitOfWork.getDeletedEntities();
            if (deletedEntities != null) {
                flushedEntities.addAll(deletedEntities);
            }
            mergeBackFlushedEntities(flushedEntities);
        } finally {
            unitOfWork.clearPendingOperations();
            unitOfWork.commit();
        }
    }

    /**
     * Merge back flushed entities.
     *
     * @param updatedEntities the updated entities
     */
    protected void mergeBackFlushedEntities(Collection<IEntity> updatedEntities) {
        if (updatedEntities != null) {
            List<IEntity> mergedEntities = merge(new ArrayList<>(updatedEntities), EMergeMode.MERGE_CLEAN_LAZY);
            if (recordedMergedEntities != null) {
                recordedMergedEntities.addAll(mergedEntities);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IValueConnector createModelConnector(String id, IModelDescriptor modelDescriptor) {
        return modelConnectorFactory.createModelConnector(id, modelDescriptor, this);
    }

    /**
     * Directly delegates execution to the action after having completed its
     * execution context with the controller's initial context.
     * <p/>
     * {@inheritDoc}
     */
    @Override
    public boolean execute(IAction action, Map<String, Object> context) {
        if (action == null) {
            return true;
        }
        if (!action.isBackend()) {
            throw new ActionException(
                    "The backend controller is executing a frontend action. Please check the action chaining : "
                            + action.toString());
        }
        // Should be handled before getting there.
        // checkAccess(action);
        final Map<String, Object> actionContext = getInitialActionContext();
        if (context != null) {
            context.putAll(actionContext);
        }
        if (action.getClass().isAnnotationPresent(Asynchronous.class)) {
            int currentExecutorsCount = getRunningExecutors().size();
            int maxExecutorsCount = getAsyncExecutorsMaxCount(context);
            if (maxExecutorsCount >= 0 && currentExecutorsCount >= maxExecutorsCount) {
                throw new ActionBusinessException(
                        "The number of concurrent asynchronous actions has exceeded the allowed max value : "
                                + currentExecutorsCount,
                        "async.count.exceeded", currentExecutorsCount);
            }
            executeAsynchronously(action, context);
            return true;
        }
        if (action.getClass().isAnnotationPresent(Transactional.class)) {
            return executeTransactionally(action, context);
        }
        return executeBackend(action, context);
    }

    /**
     * Executes a backend action.
     * @param action the action to execute.
     * @param context the action context
     * @return true if the action chain should continue, false otherwise.
     */
    protected boolean executeBackend(IAction action, Map<String, Object> context) {
        return action.execute(this, context);
    }

    /**
     * Executes an action asynchronously, i.e. when
     * the action is annotated with {@link org.jspresso.framework.application.backend.action.Asynchronous}.
     *
     * @param action
     *     the action to execute.
     * @param context
     *     the context
     * @return the slave thread executing the action.
     */
    public AsyncActionExecutor executeAsynchronously(IAction action, Map<String, Object> context) {
        AbstractBackendController slaveBackendController = createBackendController();
        AsyncActionExecutor slaveExecutor = new AsyncActionExecutor(action, context,
                getControllerAsyncActionsThreadGroup(), slaveBackendController);
        asyncExecutors.add(slaveExecutor);
        Set<AsyncActionExecutor> oldRunningExecutors = new LinkedHashSet<>(getRunningExecutors());
        firePropertyChange("runningExecutors", oldRunningExecutors, getRunningExecutors());
        slaveExecutor.start();
        if (LOG.isDebugEnabled()) {
            LOG.debug("List of running executors :");
            for (AsyncActionExecutor executor : getRunningExecutors()) {
                LOG.debug("  --> Executor {} has completed {}", executor.getName(),
                        NumberFormat.getPercentInstance().format(executor.getProgress()));
            }
        }
        return slaveExecutor;
    }

    private synchronized ThreadGroup getControllerAsyncActionsThreadGroup() {
        if (controllerAsyncActionsThreadGroup == null || controllerAsyncActionsThreadGroup.isDestroyed()) {
            controllerAsyncActionsThreadGroup = new ThreadGroup(asyncActionsThreadGroup, toString());
        }
        return controllerAsyncActionsThreadGroup;
    }

    private synchronized void cleanupControllerAsyncActionsThreadGroup() {
        if (controllerAsyncActionsThreadGroup != null && controllerAsyncActionsThreadGroup.activeCount() == 0
                && !controllerAsyncActionsThreadGroup.isDestroyed()) {
            controllerAsyncActionsThreadGroup.destroy();
        }
    }

    /**
     * Creates a slave backend controller, starts it and assign it the same
     * application session.
     * <p/>
     * {@inheritDoc}
     */
    @Override
    public AbstractBackendController createBackendController() {
        AbstractBackendController slaveBackendController = (AbstractBackendController) getSlaveControllerFactory()
                .createBackendController();
        // Start the slave controller
        slaveBackendController.start(getLocale(), getClientTimeZone());
        // Use the same application session
        slaveBackendController.setApplicationSession(getApplicationSession());
        slaveBackendController.masterController = this;
        return slaveBackendController;
    }

    /**
     * Executes an action transactionally, e.g. when the @Transactional annotation
     * is present.
     *
     * @param action
     *     the action to execute.
     * @param context
     *     the context
     * @return the action outcome
     */
    public boolean executeTransactionally(final IAction action, final Map<String, Object> context) {
        Boolean ret = getTransactionTemplate().execute(new TransactionCallback<Boolean>() {

            @Override
            public Boolean doInTransaction(TransactionStatus status) {
                boolean executionStatus = executeBackend(action, context);
                if (!executionStatus) {
                    status.setRollbackOnly();
                }
                return executionStatus;
            }
        });
        return ret;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAccessorFactory getAccessorFactory() {
        return modelConnectorFactory.getAccessorFactory();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IApplicationSession getApplicationSession() {
        return applicationSession;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map<String, Object> getDirtyProperties(IEntity entity) {
        return getDirtyProperties(entity, true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map<String, Object> getDirtyProperties(IEntity entity, boolean includeComputed) {
        Map<String, Object> dirtyProperties;
        if (isUnitOfWorkActive()) {
            dirtyProperties = unitOfWork.getDirtyProperties(entity);
        } else {
            dirtyProperties = sessionUnitOfWork.getDirtyProperties(entity);
        }
        if (dirtyProperties != null) {
            for (Iterator<Map.Entry<String, Object>> ite = dirtyProperties.entrySet().iterator(); ite.hasNext();) {
                Map.Entry<String, Object> property = ite.next();
                boolean include = true;
                if (!includeComputed) {
                    IComponentDescriptor<?> entityDescriptor = getEntityFactory()
                            .getComponentDescriptor(getComponentContract(entity));
                    IPropertyDescriptor propertyDescriptor = entityDescriptor
                            .getPropertyDescriptor(property.getKey());
                    include = (propertyDescriptor != null && !propertyDescriptor.isComputed());
                }
                if (include) {
                    Object propertyValue = property.getValue();
                    Object currentProperty = entity.straightGetProperty(property.getKey());
                    if ((currentProperty != null && !(currentProperty instanceof Collection<?>)
                            && areEqualWithoutInitializing(currentProperty, property.getValue()))
                            || (currentProperty == null && propertyValue == null)) {
                        // Unfortunately, we cannot ignore collections that have been
                        // changed but reset to their original state. This prevents the
                        // entity to be merged back into the session while the session state
                        // might be wrong.
                        clearPropertyDirtyState(currentProperty);
                        ite.remove(); // actually removes the mapping from the map.
                    }
                } else {
                    ite.remove();
                }
            }
        }
        return dirtyProperties;
    }

    private boolean areEqualWithoutInitializing(Object obj1, Object obj2) {
        if (obj1 == obj2) {
            return true;
        }
        if (obj1 == null || obj2 == null) {
            return false;
        }
        if (obj1 instanceof IEntity) {
            // To prevent lazy initialization.
            if (obj2 instanceof IEntity) {
                return ((IEntity) obj1).getId().equals(((IEntity) obj2).getId());
            }
            return false;
        }
        return obj1.equals(obj2);
    }

    /**
     * Resets the property technical dirty state. Gives a chance to subclasses to
     * reset technical dirty state. Useful in Hibernate for resetting collection
     * dirty states when their state is identical to the original one after
     * several modifications.
     *
     * @param property
     *     the property to reset the dirty state for.
     */
    protected void clearPropertyDirtyState(Object property) {
        // NO-OP
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IEntityFactory getEntityFactory() {
        return entityFactory;
    }

    /**
     * Contains the current backend controller.
     * <p/>
     * {@inheritDoc}
     */
    @Override
    public Map<String, Object> getInitialActionContext() {
        Map<String, Object> initialActionContext = new HashMap<>();
        initialActionContext.put(ActionContextConstants.BACK_CONTROLLER, this);
        return initialActionContext;
    }

    /**
     * Gets the locale used by this controller. The locale is actually held by the
     * session.
     *
     * @return locale used by this controller.
     */
    @Override
    public Locale getLocale() {
        return applicationSession.getLocale();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IEntity getRegisteredEntity(Class<? extends IEntity> entityContract, Serializable entityId) {
        return sessionUnitOfWork.getRegisteredEntity(entityContract, entityId);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map<Class<? extends IEntity>, Map<Serializable, IEntity>> getUnitOfWorkEntities() {
        if (!isUnitOfWorkActive()) {
            throw new BackendException("Cannot query a unit of work that has not begun.");
        }
        return unitOfWork.getRegisteredEntities();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IEntity getUnitOfWorkEntity(Class<? extends IEntity> entityContract, Serializable entityId) {
        if (!isUnitOfWorkActive()) {
            throw new BackendException("Cannot query a unit of work that has not begun.");
        }
        return unitOfWork.getRegisteredEntity(entityContract, entityId);
    }

    /**
     * Gets unit of work or registered entity.
     *
     * @param entityType
     *     the entity type
     * @param id
     *     the id
     * @return the unit of work or registered entity
     */
    @Override
    public IEntity getUnitOfWorkOrRegisteredEntity(Class<? extends IEntity> entityType, Serializable id) {
        IEntity entity;
        if (isUnitOfWorkActive()) {
            entity = getUnitOfWorkEntity(entityType, id);
        } else {
            entity = getRegisteredEntity(entityType, id);
        }
        return entity;
    }

    /**
     * Gets the transactionTemplate.
     *
     * @return the transactionTemplate.
     */
    @Override
    public TransactionTemplate getTransactionTemplate() {
        return transactionTemplate;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IValueConnector getWorkspaceConnector(String workspaceName) {
        return workspaceConnectors.get(workspaceName);
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public IValueConnector getModuleConnector(Module module) {
        if (module == null) {
            return null;
        }
        // we must rehash entries in case in case modules hashcode have changed and
        // still preserve LRU order.
        Map<Module, IValueConnector> buff = new LinkedHashMap<>();
        buff.putAll(moduleConnectors);
        moduleConnectors.clear();
        moduleConnectors.putAll(buff);
        IValueConnector moduleConnector = moduleConnectors.get(module);
        if (moduleConnector == null) {
            moduleConnector = createModelConnector(module.getName(), ModuleDescriptor.MODULE_DESCRIPTOR);
            moduleConnectors.put(module, moduleConnector);
        }
        moduleConnector.setConnectorValue(module);
        return moduleConnector;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public abstract void initializePropertyIfNeeded(IComponent componentOrEntity, String propertyName);

    /**
     * Sets the model controller workspaces. These workspaces are not kept as-is.
     * Their connectors are.
     *
     * @param workspaces
     *     A map containing the workspaces indexed by a well-known key used
     *     to bind them with their views.
     */
    @Override
    public void installWorkspaces(Map<String, Workspace> workspaces) {
        workspaceConnectors = new HashMap<>();
        for (Map.Entry<String, Workspace> workspaceEntry : workspaces.entrySet()) {
            String workspaceName = workspaceEntry.getKey();
            Workspace workspace = workspaceEntry.getValue();
            IModelDescriptor workspaceDescriptor;
            workspaceDescriptor = WorkspaceDescriptor.WORKSPACE_DESCRIPTOR;
            IValueConnector nextWorkspaceConnector = modelConnectorFactory.createModelConnector(workspaceName,
                    workspaceDescriptor, this);
            nextWorkspaceConnector.setConnectorValue(workspace);
            workspaceConnectors.put(workspaceName, nextWorkspaceConnector);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isAnyDirtyInDepth(Collection<?> elements) {
        return isAnyDirtyInDepth(elements, false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isAnyDirtyInDepth(Collection<?> elements, boolean includeComputed) {
        IEntityRegistry alreadyTraversed = createEntityRegistry("isAnyDirtyInDepth");
        if (elements != null) {
            for (Object element : elements) {
                if (element instanceof IEntity) {
                    if (isDirtyInDepth((IEntity) element, includeComputed, alreadyTraversed)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDirtyInDepth(IEntity entity) {
        return isDirtyInDepth(entity, false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDirtyInDepth(IEntity entity, boolean includeComputed) {
        return isAnyDirtyInDepth(Collections.singleton(entity), includeComputed);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isEntityRegisteredForDeletion(IEntity entity) {
        if (isUnitOfWorkActive()) {
            return unitOfWork.isEntityRegisteredForDeletion(entity);
        } else {
            return sessionUnitOfWork.isEntityRegisteredForDeletion(entity);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isEntityRegisteredForUpdate(IEntity entity) {
        if (isUnitOfWorkActive()) {
            return unitOfWork.isEntityRegisteredForUpdate(entity);
        } else {
            return sessionUnitOfWork.isEntityRegisteredForUpdate(entity);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public abstract boolean isInitialized(Object objectOrProxy);

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isUnitOfWorkActive() {
        return unitOfWork.isActive();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isUpdatedInUnitOfWork(IEntity entity) {
        if (!isUnitOfWorkActive()) {
            throw new BackendException("Cannot access unit of work.");
        }
        return unitOfWork.isUpdated(entity);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <E extends IEntity> E merge(E entity, EMergeMode mergeMode) {
        return merge(Collections.singletonList(entity), mergeMode).get(0);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <E extends IEntity> List<E> merge(List<E> entities, EMergeMode mergeMode) {
        IEntityRegistry alreadyMerged = createEntityRegistry("merge");
        Set<IEntity> eventsToRelease = new LinkedHashSet<>();
        List<E> mergedList = new ArrayList<>();
        boolean wasDirtyTrackingEnabled = isDirtyTrackingEnabled();
        try {
            setDirtyTrackingEnabled(false);
            for (E entity : entities) {
                mergedList.add(merge(entity, mergeMode, alreadyMerged, eventsToRelease));
            }
        } finally {
            boolean suspendUnitOfWork = isUnitOfWorkActive();
            try {
                if (suspendUnitOfWork) {
                    suspendUnitOfWork();
                }
                for (IEntity entity : eventsToRelease) {
                    try {
                        entity.releaseEvents();
                    } catch (Throwable t) {
                        LOG.error("An unexpected exception occurred when releasing events after a merge", t);
                    }
                }
            } finally {
                if (suspendUnitOfWork) {
                    resumeUnitOfWork();
                }
                setDirtyTrackingEnabled(wasDirtyTrackingEnabled);
            }
        }
        return mergedList;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void recordAsSynchronized(IEntity flushedEntity) {
        if (isUnitOfWorkActive()) {
            Map<String, Object> hasActuallyBeenFlushed = getDirtyProperties(flushedEntity, false);
            if (hasActuallyBeenFlushed == null) {
                LOG.error(
                        "*BAD UOW USAGE* You are flushing an entity ({})[{}] that you have not cloned before in the UOW.\n"
                                + "You should only work on entities copies you obtain using the "
                                + "backendController.cloneInUnitOfWork(...) method.",
                        new Object[] { flushedEntity, flushedEntity.getComponentContract().getSimpleName() });
                throw new BackendException(
                        "An entity has been flushed to the persistent store without being first registered"
                                + " in the UOW : " + flushedEntity);
            }
            unitOfWork.clearDirtyState(flushedEntity);
            if (isEntityRegisteredForDeletion(flushedEntity)) {
                if (IEntity.DELETED_VERSION.equals(flushedEntity.getVersion())) {
                    unitOfWork.addDeletedEntity(flushedEntity);
                } else {
                    // To support in-memory TX deletions
                    sessionUnitOfWork.registerForDeletion(flushedEntity);
                }
            } else if (!hasActuallyBeenFlushed.isEmpty()) {
                unitOfWork.addUpdatedEntity(flushedEntity);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void registerEntity(IEntity entity) {
        Map<String, Object> initialDirtyProperties = null;
        if (!entity.isPersistent()) {
            initialDirtyProperties = new HashMap<>();
            for (Map.Entry<String, Object> property : entity.straightGetProperties().entrySet()) {
                String propertyName = property.getKey();
                Object propertyValue = property.getValue();
                if (propertyValue != null && !(propertyValue instanceof Collection<?>
                        && ((Collection<?>) property.getValue()).isEmpty())) {
                    initialDirtyProperties.put(propertyName, null);
                }
            }
        }
        if (isUnitOfWorkActive()) {
            unitOfWork.register(entity, initialDirtyProperties);
        } else {
            sessionUnitOfWork.register(entity, initialDirtyProperties);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void registerForDeletion(IEntity entity) {
        if (entity == null) {
            throw new IllegalArgumentException("Passed entity cannot be null");
        }
        if (isUnitOfWorkActive()) {
            unitOfWork.registerForDeletion(entity);
        } else {
            sessionUnitOfWork.registerForDeletion(entity);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void registerForUpdate(IEntity entity) {
        if (entity == null) {
            throw new IllegalArgumentException("Passed entity cannot be null");
        }
        if (isUnitOfWorkActive()) {
            unitOfWork.registerForUpdate(entity);
        } else {
            sessionUnitOfWork.registerForUpdate(entity);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ComponentTransferStructure<IComponent> retrieveComponents() {
        return transferStructure;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void rollbackUnitOfWork() {
        if (!isUnitOfWorkActive()) {
            throw new BackendException("Cannot rollback a unit of work that has not begun.");
        }
        if (unitOfWork.hasNested()) {
            unitOfWork.rollback();
        } else {
            doRollbackUnitOfWork();
        }
    }

    /**
     * Performs actual UOW rollback.
     */
    protected void doRollbackUnitOfWork() {
        unitOfWork.rollback();
    }

    /**
     * Assigns the application session to this backend controller. This property
     * can only be set once and should only be used by the DI container. It will
     * rarely be changed from built-in defaults unless you need to specify a
     * custom implementation instance to be used.
     *
     * @param applicationSession
     *     the applicationSession to set.
     */
    public void setApplicationSession(IApplicationSession applicationSession) {
        this.applicationSession = applicationSession;
    }

    /**
     * Configures the entity clone factory used to carbon-copy entities. An entity
     * carbon-copy is an technical copy of an entity, including id and version but
     * excluding relationship properties. This mechanism is used by the controller
     * when duplicating entities into the UOW to allow for memory state aware
     * transactions. This property should only be used by the DI container. It
     * will rarely be changed from built-in defaults unless you need to specify a
     * custom implementation instance to be used.
     *
     * @param carbonEntityCloneFactory
     *     the carbonEntityCloneFactory to set.
     */
    public void setCarbonEntityCloneFactory(IEntityCloneFactory carbonEntityCloneFactory) {
        this.carbonEntityCloneFactory = carbonEntityCloneFactory;
    }

    /**
     * Configures the factory responsible for creating entities (or components)
     * collections that are held by domain relationship properties. This property
     * should only be used by the DI container. It will rarely be changed from
     * built-in defaults unless you need to specify a custom implementation
     * instance to be used.
     *
     * @param collectionFactory
     *     the collectionFactory to set.
     */
    public void setCollectionFactory(IComponentCollectionFactory collectionFactory) {
        this.collectionFactory = collectionFactory;
    }

    /**
     * Configures the entity factory to use to create new entities. Backend
     * controllers only accept instances of
     * {@code ControllerAwareProxyEntityFactory} or a subclass. This is
     * because the backend controller must keep track of created entities.
     * Jspresso entity implementations also use the controller from which they
     * were created behind the scene.
     *
     * @param entityFactory
     *     the entityFactory to set.
     */
    public void setEntityFactory(IEntityFactory entityFactory) {
        if (entityFactory != null && !(entityFactory instanceof ControllerAwareProxyEntityFactory)) {
            throw new IllegalArgumentException(
                    "entityFactory must be a " + ControllerAwareProxyEntityFactory.class.getSimpleName());
        }
        this.entityFactory = entityFactory;
    }

    /**
     * Configures the model connector factory to use to create new model
     * connectors. Connectors are adapters used by the binding layer to access
     * domain model values.
     *
     * @param modelConnectorFactory
     *     the modelConnectorFactory to set.
     */
    public void setModelConnectorFactory(IModelConnectorFactory modelConnectorFactory) {
        this.modelConnectorFactory = modelConnectorFactory;
    }

    /**
     * Assigns the Spring transaction template to this backend controller. This
     * property can only be set once and should only be used by the DI container.
     * It will rarely be changed from built-in defaults unless you need to specify
     * a custom implementation instance to be used.
     * <p/>
     * The configured instance is the one that will be returned by the
     * controller's {@code getTransactionTemplate()} method that should be
     * used by the service layer for transaction management.
     *
     * @param transactionTemplate
     *     the transactionTemplate to set.
     */
    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        if (this.transactionTemplate != null) {
            throw new IllegalArgumentException("Spring transaction template can only be configured once.");
        }
        if (transactionTemplate != null && !(transactionTemplate instanceof ControllerAwareTransactionTemplate)) {
            throw new IllegalArgumentException(
                    "You have configured a transaction template that is not a controller "
                            + "aware transaction template. This is not legal since this prevents "
                            + "the Unit of Work to be synchronized with the current transaction.");
        }
        this.transactionTemplate = transactionTemplate;
    }

    /**
     * Creates a &quot;Unit of Work&quot; to be used by this controller.
     *
     * @return the created UOW.
     */
    protected IEntityUnitOfWork createUnitOfWork() {
        return new BasicEntityUnitOfWork();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean start(Locale startingLocale, TimeZone theClientTimeZone) {
        applicationSession.setLocale(startingLocale);
        setClientTimeZone(theClientTimeZone);
        return true;
    }

    /**
     * Sets client time zone.
     *
     * @param clientTimeZone
     *     the client time zone
     */
    @Override
    public void setClientTimeZone(TimeZone clientTimeZone) {
        this.clientTimeZone = clientTimeZone;
    }

    /**
     * Sets reference time zone id.
     *
     * @param referenceTimeZoneId
     *     the reference time zone id
     */
    public void setReferenceTimeZoneId(String referenceTimeZoneId) {
        if (referenceTimeZoneId != null) {
            this.referenceTimeZone = TimeZone.getTimeZone(referenceTimeZoneId);
        } else {
            this.referenceTimeZone = null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean stop() {
        // The application session can now be shared across async slave
        // controllers.
        // if (applicationSession != null) {
        // applicationSession.clear();
        // }
        if (getUserPreferencesStore() != null) {
            getUserPreferencesStore().setStorePath(IPreferencesStore.GLOBAL_STORE);
        }
        if (sessionUnitOfWork != null) {
            sessionUnitOfWork.clear();
            sessionUnitOfWork.begin();
        }
        if (unitOfWork != null) {
            unitOfWork.clear();
        }
        if (workspaceConnectors != null) {
            workspaceConnectors.clear();
        }
        if (moduleConnectors != null) {
            moduleConnectors.clear();
        }
        transferStructure = null;
        cleanupControllerAsyncActionsThreadGroup();
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void storeComponents(ComponentTransferStructure<IComponent> components) {
        this.transferStructure = components;
    }

    /**
     * Creates a transient collection instance, in respect to the type of
     * collection passed as parameter.
     *
     * @param <E>  the type parameter
     * @param collection      the collection to take the type from (List, Set, ...)
     * @return a transient collection instance with the same interface type as the
     * parameter.
     */
    protected <E> Collection<E> createTransientEntityCollection(Collection<E> collection) {
        Collection<E> uowEntityCollection = null;
        if (collection instanceof Set<?>) {
            uowEntityCollection = collectionFactory.createComponentCollection(Set.class);
        } else if (collection instanceof List<?>) {
            uowEntityCollection = collectionFactory.createComponentCollection(List.class);
        }
        return uowEntityCollection;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDirty(IEntity entity) {
        return isDirty(entity, false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDirty(IEntity entity, boolean includeComputed) {
        if (entity == null) {
            return false;
        }
        Map<String, Object> entityDirtyProperties = getDirtyProperties(entity, includeComputed);
        if (entityDirtyProperties != null) {
            entityDirtyProperties.remove(IEntity.VERSION);
        }
        return entityDirtyProperties != null && !entityDirtyProperties.isEmpty();
    }

    /**
     * Gets whether the entity property is dirty (has changes that need to be
     * updated to the persistent store).
     *
     * @param entity
     *     the entity to test.
     * @param propertyName
     *     the entity property to test.
     * @return true if the entity is dirty.
     */
    @Override
    public boolean isDirty(IEntity entity, String propertyName) {
        if (entity == null) {
            return false;
        }
        Map<String, Object> entityDirtyProperties = getDirtyProperties(entity);
        if (entityDirtyProperties != null && entityDirtyProperties.containsKey(propertyName)) {
            IPropertyDescriptor propertyDescriptor = getEntityFactory()
                    .getComponentDescriptor(getComponentContract(entity)).getPropertyDescriptor(propertyName);
            return propertyDescriptor != null && !propertyDescriptor.isComputed();
        }
        return false;
    }

    /**
     * Gives a chance to the session to wrap a collection before making it part of
     * the unit of work.
     *
     * @param <E>  the type parameter
     * @param owner      the entity the collection belongs to.
     * @param detachedCollection      the transient collection to make part of the unit of work.
     * @param snapshotCollection      the original collection state as reported by the dirt recorder.
     * @param role      the name of the property represented by the collection in its
     *     owner.
     * @return the wrapped collection if any (it may be the collection itself as
     * in this implementation).
     */
    protected <E> Collection<E> wrapDetachedCollection(IEntity owner, Collection<E> detachedCollection,
            Collection<E> snapshotCollection, String role) {
        return detachedCollection;
    }

    private IComponent cloneComponentInUnitOfWork(IComponent component, IEntityRegistry alreadyCloned,
            Set<IEntity> eventsToRelease) {
        IComponent uowComponent = carbonEntityCloneFactory.cloneComponent(component, entityFactory);
        Map<String, Object> componentProperties = component.straightGetProperties();
        for (Map.Entry<String, Object> property : componentProperties.entrySet()) {
            String propertyName = property.getKey();
            Object propertyValue = property.getValue();
            if (propertyValue instanceof IEntity) {
                if (isInitialized(propertyValue)) {
                    uowComponent.straightSetProperty(propertyName,
                            cloneInUnitOfWork((IEntity) propertyValue, alreadyCloned, eventsToRelease));
                } else {
                    uowComponent.straightSetProperty(propertyName,
                            cloneUninitializedProperty(uowComponent, propertyValue));
                }
            } else if (propertyValue instanceof IComponent) {
                uowComponent.straightSetProperty(propertyName,
                        cloneComponentInUnitOfWork((IComponent) propertyValue, alreadyCloned, eventsToRelease));
            }
        }
        IComponent owningComponent = component.getOwningComponent();
        if (owningComponent != null) {
            IComponent uowOwningComponent;
            if (owningComponent instanceof IEntity) {
                uowOwningComponent = cloneInUnitOfWork((IEntity) owningComponent, alreadyCloned, eventsToRelease);
            } else {
                uowOwningComponent = cloneComponentInUnitOfWork(owningComponent, alreadyCloned, eventsToRelease);
            }
            uowComponent.setOwningComponent(uowOwningComponent, owningComponent.getOwningPropertyDescriptor());
        }
        return uowComponent;
    }

    @SuppressWarnings({ "unchecked", "ConstantConditions" })
    private <E extends IEntity> E cloneInUnitOfWork(E entity, IEntityRegistry alreadyCloned,
            Set<IEntity> eventsToRelease) {
        if (entity == null) {
            return null;
        }
        Class<? extends IEntity> entityContract = getComponentContract(entity);
        IComponentDescriptor<?> entityDescriptor = getEntityFactory().getComponentDescriptor(entityContract);
        E uowEntity = (E) alreadyCloned.get(entityContract, entity.getId());
        if (uowEntity != null) {
            return uowEntity;
        }
        uowEntity = performUowEntityCloning(entity);
        boolean eventsBlocked = false;
        try {
            if (isInitialized(uowEntity)) {
                eventsBlocked = uowEntity.blockEvents();
            }
            Map<String, Object> dirtyProperties;
            if (isInitialized(entity)) {
                dirtyProperties = unitOfWork.getParentDirtyProperties(entity, sessionUnitOfWork);
                if (dirtyProperties == null) {
                    dirtyProperties = new HashMap<>();
                }
            } else {
                dirtyProperties = new HashMap<>();
            }
            alreadyCloned.register(entityContract, entity.getId(), uowEntity);
            if (isInitialized(entity)) {
                Map<String, Object> entityProperties = entity.straightGetProperties();
                for (Map.Entry<String, Object> property : entityProperties.entrySet()) {
                    String propertyName = property.getKey();
                    Object propertyValue = property.getValue();
                    IPropertyDescriptor propertyDescriptor = entityDescriptor.getPropertyDescriptor(propertyName);
                    if (propertyValue instanceof IEntity) {
                        if (isInitialized(propertyValue)) {
                            uowEntity.straightSetProperty(propertyName,
                                    cloneInUnitOfWork((IEntity) propertyValue, alreadyCloned, eventsToRelease));
                        } else {
                            uowEntity.straightSetProperty(propertyName,
                                    cloneUninitializedProperty(uowEntity, propertyValue));
                        }
                    } else if (propertyValue instanceof Collection<?>
                            // to support collections stored as java serializable blob.
                            // and detachedEntities (see bug # 1130)
                            && (propertyDescriptor == null
                                    || propertyDescriptor instanceof ICollectionPropertyDescriptor<?>)) {
                        if (isInitialized(propertyValue)) {
                            Collection<Object> uowCollection = createTransientEntityCollection(
                                    (Collection<Object>) property.getValue());
                            for (Object collectionElement : (Collection<?>) property.getValue()) {
                                if (collectionElement != null) {
                                    if (collectionElement instanceof IEntity) {
                                        uowCollection.add(cloneInUnitOfWork((IEntity) collectionElement,
                                                alreadyCloned, eventsToRelease));
                                    } else if (collectionElement instanceof IComponent) {
                                        uowCollection.add(cloneComponentInUnitOfWork((IComponent) collectionElement,
                                                alreadyCloned, eventsToRelease));
                                    } else {
                                        uowCollection.add(collectionElement);
                                    }
                                } else {
                                    uowCollection.add(null);
                                }
                            }
                            if (propertyDescriptor == null || !propertyDescriptor.isComputed()) {
                                Collection<Object> snapshotCollection = null;
                                Object originalProperty = dirtyProperties.get(propertyName);
                                // Workaround bug #1148
                                if (originalProperty != null && originalProperty instanceof Collection<?>) {
                                    snapshotCollection = (Collection<Object>) originalProperty;
                                    Collection<Object> clonedSnapshotCollection = createTransientEntityCollection(
                                            snapshotCollection);
                                    for (Object snapshotCollectionElement : snapshotCollection) {
                                        if (snapshotCollectionElement != null) {
                                            if (snapshotCollectionElement instanceof IEntity) {
                                                clonedSnapshotCollection
                                                        .add(cloneInUnitOfWork((IEntity) snapshotCollectionElement,
                                                                alreadyCloned, eventsToRelease));
                                            } else if (snapshotCollectionElement instanceof IComponent) {
                                                clonedSnapshotCollection.add(cloneComponentInUnitOfWork(
                                                        (IComponent) snapshotCollectionElement, alreadyCloned,
                                                        eventsToRelease));
                                            } else {
                                                clonedSnapshotCollection.add(snapshotCollectionElement);
                                            }
                                        } else {
                                            clonedSnapshotCollection.add(null);
                                        }
                                    }
                                    snapshotCollection = clonedSnapshotCollection;
                                }
                                if (entity.isPersistent()) {
                                    uowCollection = wrapDetachedCollection(entity, uowCollection,
                                            snapshotCollection, propertyName);
                                }
                            }
                            uowEntity.straightSetProperty(propertyName, uowCollection);
                        } else {
                            uowEntity.straightSetProperty(propertyName,
                                    cloneUninitializedProperty(uowEntity, propertyValue));
                        }
                    } else if (propertyValue instanceof IEntity[]) {
                        IEntity[] uowArray = new IEntity[((IEntity[]) propertyValue).length];
                        for (int i = 0; i < uowArray.length; i++) {
                            uowArray[i] = cloneInUnitOfWork(((IEntity[]) propertyValue)[i], alreadyCloned,
                                    eventsToRelease);
                        }
                        uowEntity.straightSetProperty(propertyName, uowArray);
                    } else if (propertyValue instanceof IComponent[]) {
                        IComponent[] uowArray = new IComponent[((IComponent[]) property.getValue()).length];
                        for (int i = 0; i < uowArray.length; i++) {
                            uowArray[i] = cloneComponentInUnitOfWork(((IComponent[]) propertyValue)[i],
                                    alreadyCloned, eventsToRelease);
                        }
                        uowEntity.straightSetProperty(propertyName, uowArray);
                    } else if (propertyValue instanceof IComponent) {
                        uowEntity.straightSetProperty(propertyName, cloneComponentInUnitOfWork(
                                (IComponent) propertyValue, alreadyCloned, eventsToRelease));
                    }
                }
                if (eventsBlocked && uowEntity != null && isInitialized(uowEntity)) {
                    eventsToRelease.add(uowEntity);
                }
                unitOfWork.register(uowEntity, new HashMap<>(dirtyProperties));
                if (uowEntity instanceof ILifecycleCapable) {
                    ((ILifecycleCapable) uowEntity).onClone(entity);
                }
            }
        } finally {
            if (eventsBlocked && uowEntity != null && isInitialized(uowEntity)) {
                eventsToRelease.add(uowEntity);
            }
        }
        return uowEntity;
    }

    /**
     * Performs the actual entity cloning in unit of work. Gives a chance to
     * subclasses to override and take a better decision than just a deep carbon
     * copy.
     *
     * @param <E>
     *     the actual entity type.
     * @param entity
     *     the source entity.
     * @return the cloned entity.
     */
    protected <E extends IEntity> E performUowEntityCloning(E entity) {
        E uowEntity = carbonEntityCloneFactory.cloneEntity(entity, entityFactory);
        return uowEntity;
    }

    private boolean isDirtyInDepth(IEntity entity, boolean includeComputed, IEntityRegistry alreadyTraversed) {
        alreadyTraversed.register(getComponentContract(entity), entity.getId(), entity);
        if (isDirty(entity, includeComputed)) {
            return true;
        }
        Map<String, Object> entityProps = entity.straightGetProperties();
        for (Map.Entry<String, Object> property : entityProps.entrySet()) {
            Object propertyValue = property.getValue();
            if (propertyValue instanceof IEntity) {
                if (isInitialized(propertyValue)
                        && alreadyTraversed.get(getComponentContract((IEntity) propertyValue),
                                ((IEntity) propertyValue).getId()) == null) {
                    if (isDirtyInDepth((IEntity) propertyValue, includeComputed, alreadyTraversed)) {
                        return true;
                    }
                }
            } else if (propertyValue instanceof Collection<?>) {
                if (isInitialized(propertyValue)) {
                    for (Object elt : ((Collection<?>) propertyValue)) {
                        if (elt instanceof IEntity && alreadyTraversed.get(getComponentContract((IEntity) elt),
                                ((IEntity) elt).getId()) == null) {
                            if (isDirtyInDepth((IEntity) elt, includeComputed, alreadyTraversed)) {
                                return true;
                            }
                        }
                    }
                }
            }
        }
        return false;
    }

    @SuppressWarnings({ "unchecked", "ConstantConditions" })
    private <E extends IEntity> E merge(E entity, final EMergeMode mergeMode, IEntityRegistry alreadyMerged,
            Set<IEntity> eventsToRelease) {
        if (entity == null) {
            return null;
        }
        Class<? extends IEntity> entityContract = getComponentContract(entity);
        E alreadyMergedEntity = (E) alreadyMerged.get(entityContract, entity.getId());
        if (alreadyMergedEntity != null) {
            return alreadyMergedEntity;
        }
        // Allow for merging eagerly dirty entities
        if (mergeMode != EMergeMode.MERGE_EAGER) {
            checkBadMergeUsage(entity);
        }
        E registeredEntity = null;
        boolean eventsBlocked = false;
        try {
            registeredEntity = (E) getRegisteredEntity(getComponentContract(entity), entity.getId());
            boolean newlyRegistered = false;
            if (registeredEntity == null) {
                if (!isInitialized(entity)) {
                    return mergeUninitializedEntity(entity);
                }
                registeredEntity = carbonEntityCloneFactory.cloneEntity(entity, entityFactory);
                if (mergeMode == EMergeMode.MERGE_EAGER) {
                    sessionUnitOfWork.register(registeredEntity, getDirtyProperties(entity));
                } else {
                    sessionUnitOfWork.register(registeredEntity, null);
                }
                newlyRegistered = true;
            } else if (mergeMode == EMergeMode.MERGE_KEEP
                    || ((mergeMode == EMergeMode.MERGE_LAZY || mergeMode == EMergeMode.MERGE_CLEAN_LAZY)
                            && !isInitialized(entity))) {
                alreadyMerged.register(entityContract, entity.getId(), registeredEntity);
                return registeredEntity;
            } else if (mergeMode == EMergeMode.MERGE_EAGER) {
                sessionUnitOfWork.register(registeredEntity, getDirtyProperties(entity));
            }
            if (isInitialized(registeredEntity)) {
                eventsBlocked = registeredEntity.blockEvents();
            }
            alreadyMerged.register(entityContract, entity.getId(), registeredEntity);
            if (newlyRegistered || (mergeMode != EMergeMode.MERGE_CLEAN_LAZY && mergeMode != EMergeMode.MERGE_LAZY)
                    || registeredEntity.getVersion() == null
                    || !registeredEntity.getVersion().equals(entity.getVersion())) {
                if (mergeMode == EMergeMode.MERGE_CLEAN_EAGER || mergeMode == EMergeMode.MERGE_CLEAN_LAZY
                        || mergeMode == EMergeMode.MERGE_LAZY) {
                    sessionUnitOfWork.clearDirtyState(entity);
                }
                IComponentDescriptor<?> entityDescriptor = getEntityFactory()
                        .getComponentDescriptor(getComponentContract(entity));
                Map<String, Object> entityProperties = entity.straightGetProperties();
                Map<String, Object> registeredEntityProperties = registeredEntity.straightGetProperties();
                Map<String, Object> mergedProperties = new LinkedHashMap<>();
                Set<String> propertiesToSort = new HashSet<>();
                for (Map.Entry<String, Object> property : entityProperties.entrySet()) {
                    String propertyName = property.getKey();
                    Object propertyValue = property.getValue();
                    IPropertyDescriptor propertyDescriptor = entityDescriptor.getPropertyDescriptor(propertyName);
                    if (propertyValue instanceof IEntity) {
                        // Do not take the registeredProperty from the
                        // registeredEntityProperties.
                        // It might be a different ID from the one we are trying to merge
                        // Object registeredProperty = registeredEntityProperties
                        // .get(propertyName);
                        Object registeredProperty = getRegisteredEntity(
                                getComponentContract((IEntity) propertyValue), ((IEntity) propertyValue).getId());
                        if (registeredProperty == null) {
                            mergedProperties.put(propertyName,
                                    merge((IEntity) propertyValue, mergeMode, alreadyMerged, eventsToRelease));
                        } else {
                            if (mergeMode == EMergeMode.MERGE_EAGER || mergeMode == EMergeMode.MERGE_LAZY) {
                                if (isInitialized(propertyValue)) {
                                    initializePropertyIfNeeded(registeredEntity, propertyName);
                                } else if (isInitialized(registeredProperty)) {
                                    initializePropertyIfNeeded(entity, propertyName);
                                }
                            }
                            // Fix for bug #1023 : the referenced entity might have changed
                            // int the TX. Merge must happen unconditionally.
                            // if (isInitialized(registeredProperty)) {
                            mergedProperties.put(propertyName,
                                    merge((IEntity) propertyValue, mergeMode, alreadyMerged, eventsToRelease));
                        }
                    } else if (propertyValue instanceof Collection<?>
                            // to support collections stored as java serializable blob.
                            // and detachedEntities (see bug # 1130)
                            && (propertyDescriptor == null
                                    || propertyDescriptor instanceof ICollectionPropertyDescriptor<?>)) {
                        Collection<Object> registeredCollection = (Collection<Object>) registeredEntityProperties
                                .get(propertyName);
                        if (!newlyRegistered
                                && (mergeMode == EMergeMode.MERGE_EAGER || mergeMode == EMergeMode.MERGE_LAZY)) {
                            if (isInitialized(propertyValue)) {
                                initializePropertyIfNeeded(registeredEntity, propertyName);
                            } else if (isInitialized(registeredCollection)) {
                                initializePropertyIfNeeded(entity, propertyName);
                            }
                        }
                        if (isInitialized(registeredCollection)) {
                            if (newlyRegistered && !isInitialized(propertyValue)) {
                                // Must have another collection instance.
                                // See bug #902
                                registeredCollection = cloneUninitializedProperty(registeredEntity,
                                        (Collection<Object>) propertyValue);
                            } else {
                                if (propertyValue instanceof List) {
                                    registeredCollection = collectionFactory.createComponentCollection(List.class);
                                } else {
                                    registeredCollection = collectionFactory.createComponentCollection(Set.class);
                                }
                                initializePropertyIfNeeded(entity, propertyName);
                                List<?> reusedComponentInstances = null;
                                if (registeredEntityProperties.get(propertyName) != null) {
                                    reusedComponentInstances = new ArrayList<>(
                                            (Collection<?>) registeredEntityProperties.get(propertyName));
                                }
                                int i = 0;
                                for (Object collectionElement : (Collection<?>) propertyValue) {
                                    if (collectionElement instanceof IEntity) {
                                        registeredCollection.add(merge((IEntity) collectionElement, mergeMode,
                                                alreadyMerged, eventsToRelease));
                                    } else if (collectionElement instanceof IComponent) {
                                        IComponent registeredComponent = null;
                                        // We must reuse component instances for binding to operate correctly.
                                        if (reusedComponentInstances != null
                                                && i < reusedComponentInstances.size()) {
                                            registeredComponent = (IComponent) reusedComponentInstances.get(i);
                                        }
                                        registeredCollection.add(mergeComponent((IComponent) collectionElement,
                                                registeredComponent, mergeMode, alreadyMerged, eventsToRelease));
                                        i++;
                                    } else {
                                        registeredCollection.add(collectionElement);
                                    }
                                }
                            }
                            Collection<?> mergedCollection = mergeCollection(propertyName, propertyValue,
                                    registeredEntity, registeredCollection);
                            mergedProperties.put(propertyName, mergedCollection);
                            if (isInitialized(registeredCollection)) {
                                propertiesToSort.add(propertyName);
                            }
                        }
                    } else if (propertyValue instanceof IComponent) {
                        IComponent registeredComponent = (IComponent) registeredEntityProperties.get(propertyName);
                        mergedProperties.put(propertyName, mergeComponent((IComponent) propertyValue,
                                registeredComponent, mergeMode, alreadyMerged, eventsToRelease));
                    } else {
                        mergedProperties.put(propertyName, propertyValue);
                    }
                }
                registeredEntity.straightSetProperties(mergedProperties);
                for (String propertyToSort : propertiesToSort) {
                    getEntityFactory().sortCollectionProperty(registeredEntity, propertyToSort);
                }
            } else if (mergeMode == EMergeMode.MERGE_CLEAN_LAZY) {
                // version has not evolved but we must still reset dirty properties in
                // case only versionControl false properties have changed.
                sessionUnitOfWork.clearDirtyState(entity);
            }
            if (registeredEntity instanceof ILifecycleCapable) {
                ((ILifecycleCapable) registeredEntity).onClone(entity);
            }
            return registeredEntity;
        } finally {
            if (eventsBlocked && registeredEntity != null && isInitialized(registeredEntity)) {
                eventsToRelease.add(registeredEntity);
            }
        }
    }

    /**
     * Merge non initialized entity.
     *
     * @param <E>
     *     the actual entity type
     * @param entity
     *     the entity
     * @return the merged entity
     */
    protected <E extends IEntity> E mergeUninitializedEntity(E entity) {
        return entity;
    }

    /**
     * Merge collection.
     *
     * @param <E>
     *     the actual entity type.
     * @param propertyName
     *     the property name
     * @param propertyValue
     *     the property value
     * @param registeredEntity
     *     the registered entity
     * @param registeredCollection
     *     the registered collection
     * @return the collection
     */
    protected <E extends IEntity> Collection<?> mergeCollection(String propertyName, Object propertyValue,
            E registeredEntity, Collection<?> registeredCollection) {
        return registeredCollection;
    }

    private <E extends IEntity> void checkBadMergeUsage(E entity) {
        if (isUnitOfWorkActive()) {
            if (isInitialized(entity) && entity.isPersistent() && isDirty(entity)) {
                LOG.error(
                        "*BAD MERGE USAGE* An attempt is made to merge a UOW dirty entity ({})[{}] to the application session.\n"
                                + "This will break transaction isolation since, if the transaction is rolled back,"
                                + " the UOW dirty state will be kept.\n"
                                + "Dirty UOW entities will be automatically merged whenever the transaction is committed.",
                        entity, getComponentContract(entity).getSimpleName());
                if (isThrowExceptionOnBadUsage()) {
                    throw new BackendException("A bad usage has been detected on the backend controller."
                            + "This is certainly an application coding problem. Please check the logs.");
                }
            }
        }
    }

    private IComponent mergeComponent(IComponent componentToMerge, IComponent registeredComponent,
            EMergeMode mergeMode, IEntityRegistry alreadyMerged, Set<IEntity> eventsToRelease) {
        IComponent varRegisteredComponent = registeredComponent;
        if (componentToMerge == null) {
            return null;
        }
        if (varRegisteredComponent == null) {
            varRegisteredComponent = carbonEntityCloneFactory.cloneComponent(componentToMerge, entityFactory);
            IComponent owningComponent = componentToMerge.getOwningComponent();
            if (owningComponent != null) {
                IComponent uowOwningComponent;
                if (owningComponent instanceof IEntity) {
                    uowOwningComponent = merge((IEntity) owningComponent, mergeMode, alreadyMerged,
                            eventsToRelease);
                } else {
                    uowOwningComponent = mergeComponent(owningComponent, null, mergeMode, alreadyMerged,
                            eventsToRelease);
                }
                varRegisteredComponent.setOwningComponent(uowOwningComponent,
                        owningComponent.getOwningPropertyDescriptor());
            }
        } else if (mergeMode == EMergeMode.MERGE_KEEP) {
            return varRegisteredComponent;
        }
        Map<String, Object> componentPropertiesToMerge = componentToMerge.straightGetProperties();
        Map<String, Object> registeredComponentProperties = varRegisteredComponent.straightGetProperties();
        Map<String, Object> mergedProperties = new HashMap<>();
        for (Map.Entry<String, Object> property : componentPropertiesToMerge.entrySet()) {
            String propertyName = property.getKey();
            Object propertyValue = property.getValue();
            if (propertyValue instanceof IEntity) {
                // Do not take the registeredProperty from the
                // registeredEntityProperties.
                // It might be a different ID from the one we are trying to merge
                // Object registeredProperty = registeredEntityProperties
                // .get(propertyName);
                Object registeredProperty = getRegisteredEntity(getComponentContract((IEntity) propertyValue),
                        ((IEntity) propertyValue).getId());
                if (registeredProperty == null) {
                    mergedProperties.put(propertyName,
                            merge((IEntity) propertyValue, mergeMode, alreadyMerged, eventsToRelease));
                } else {
                    if (mergeMode == EMergeMode.MERGE_EAGER || mergeMode == EMergeMode.MERGE_LAZY) {
                        if (isInitialized(propertyValue)) {
                            initializePropertyIfNeeded(varRegisteredComponent, propertyName);
                        } else if (isInitialized(registeredProperty)) {
                            initializePropertyIfNeeded(componentToMerge, propertyName);
                        }
                    }
                    // Fix for bug #1023 : the referenced entity might have changed
                    // int the TX. Merge must happen unconditionally.
                    // if (isInitialized(registeredProperty)) {
                    mergedProperties.put(propertyName,
                            merge((IEntity) propertyValue, mergeMode, alreadyMerged, eventsToRelease));
                }
            } else if (propertyValue instanceof IComponent) {
                IComponent registeredSubComponent = (IComponent) registeredComponentProperties.get(propertyName);
                mergedProperties.put(propertyName, mergeComponent((IComponent) propertyValue,
                        registeredSubComponent, mergeMode, alreadyMerged, eventsToRelease));
            } else {
                mergedProperties.put(propertyName, propertyValue);
            }
        }
        varRegisteredComponent.straightSetProperties(mergedProperties);
        return varRegisteredComponent;
    }

    /**
     * Performs necessary cleanings when an entity or component is deleted.
     *
     * @param component
     *     the deleted entity or component.
     * @param dryRun
     *     set to true to simulate before actually doing it.
     * @throws IllegalAccessException
     *     whenever this kind of exception occurs.
     * @throws InvocationTargetException
     *     whenever this kind of exception occurs.
     * @throws NoSuchMethodException
     *     whenever this kind of exception occurs.
     */
    @Override
    public void cleanRelationshipsOnDeletion(IComponent component, boolean dryRun)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        Set<IComponent> clearedEntities = new HashSet<>();
        Map<IComponent, RuntimeException> integrityViolations = new HashMap<>();
        cleanRelationshipsOnDeletion(component, dryRun, clearedEntities, integrityViolations);
        // Throw exceptions for entities that have not been cleared during the
        // process.
        for (Map.Entry<IComponent, RuntimeException> integrityViolation : integrityViolations.entrySet()) {
            if (!(clearedEntities.contains(integrityViolation.getKey()))) {
                throw integrityViolation.getValue();
            }
        }
    }

    @SuppressWarnings({ "unchecked", "ThrowableResultOfMethodCallIgnored", "ConstantConditions",
            "SuspiciousMethodCalls" })
    private void cleanRelationshipsOnDeletion(IComponent componentOrProxy, boolean dryRun,
            Set<IComponent> clearedEntities, Map<IComponent, RuntimeException> integrityViolations)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        if (componentOrProxy == null) {
            return;
        }
        IComponent component;
        component = (IComponent) unwrapProxy(componentOrProxy);
        Class<? extends IComponent> componentContract = getComponentContract(component);
        if (clearedEntities.contains(component)) {
            return;
        }
        clearedEntities.add(component);
        if (!dryRun) {
            if (component instanceof IEntity) {
                registerForDeletion((IEntity) component);
            }
        }
        try {
            component.setPropertyProcessorsEnabled(false);
            IComponentDescriptor<?> componentDescriptor = getEntityFactory()
                    .getComponentDescriptor(componentContract);
            for (Map.Entry<String, Object> property : component.straightGetProperties().entrySet()) {
                String propertyName = property.getKey();
                Object propertyValue = property.getValue();
                if (propertyValue != null) {
                    IPropertyDescriptor propertyDescriptor = componentDescriptor
                            .getPropertyDescriptor(propertyName);
                    if (propertyDescriptor instanceof IRelationshipEndPropertyDescriptor) {
                        // force initialization of relationship property.
                        getAccessorFactory().createPropertyAccessor(propertyName, componentContract)
                                .getValue(component);
                        if (propertyDescriptor instanceof IReferencePropertyDescriptor
                                && propertyValue instanceof IEntity) {
                            if (((IRelationshipEndPropertyDescriptor) propertyDescriptor).isComposition()) {
                                cleanRelationshipsOnDeletion((IEntity) propertyValue, dryRun, clearedEntities,
                                        integrityViolations);
                            } else {
                                if (((IRelationshipEndPropertyDescriptor) propertyDescriptor)
                                        .getReverseRelationEnd() != null) {
                                    IPropertyDescriptor reversePropertyDescriptor = ((IReferencePropertyDescriptor<?>) propertyDescriptor)
                                            .getReverseRelationEnd();
                                    //noinspection SuspiciousMethodCalls
                                    if (!clearedEntities.contains(propertyValue)) {
                                        try {
                                            if (reversePropertyDescriptor instanceof IReferencePropertyDescriptor) {
                                                if (dryRun) {
                                                    // manually trigger reverse relations preprocessors.
                                                    reversePropertyDescriptor.preprocessSetter(propertyValue, null);
                                                } else {
                                                    getAccessorFactory()
                                                            .createPropertyAccessor(
                                                                    reversePropertyDescriptor.getName(),
                                                                    getComponentContract(
                                                                            ((IComponent) propertyValue)))
                                                            .setValue(propertyValue, null);
                                                    // Technically reset to original value to avoid
                                                    // Hibernate not-null checks
                                                    component.straightSetProperty(propertyName, propertyValue);
                                                }
                                            } else if (reversePropertyDescriptor instanceof ICollectionPropertyDescriptor<?>) {
                                                if (dryRun) {
                                                    // manually trigger reverse relations preprocessors.
                                                    Collection<Object> reverseCollection = getAccessorFactory()
                                                            .createPropertyAccessor(
                                                                    reversePropertyDescriptor.getName(),
                                                                    getComponentContract(
                                                                            ((IComponent) propertyValue)))
                                                            .getValue(propertyValue);
                                                    ((ICollectionPropertyDescriptor<?>) reversePropertyDescriptor)
                                                            .preprocessRemover(propertyValue, reverseCollection,
                                                                    component);
                                                } else {
                                                    getAccessorFactory()
                                                            .createCollectionPropertyAccessor(
                                                                    reversePropertyDescriptor.getName(),
                                                                    getComponentContract(
                                                                            ((IComponent) propertyValue)),
                                                                    componentContract)
                                                            .removeFromValue(propertyValue, component);
                                                    // but technically reset to original value to avoid
                                                    // Hibernate not-null checks
                                                    component.straightSetProperty(propertyName, propertyValue);
                                                }
                                            }
                                        } catch (RuntimeException ex) {
                                            integrityViolations.put((IComponent) propertyValue, ex);
                                        }
                                    }
                                }
                            }
                        } else if (propertyDescriptor instanceof ICollectionPropertyDescriptor) {
                            if (((ICollectionPropertyDescriptor<?>) propertyDescriptor).isComposition()) {
                                for (Object composedElement : new ArrayList<>((Collection<?>) propertyValue)) {
                                    if (composedElement instanceof IComponent) {
                                        cleanRelationshipsOnDeletion((IComponent) composedElement, dryRun,
                                                clearedEntities, integrityViolations);
                                    }
                                }
                            } else if (propertyDescriptor.isModifiable()
                                    && !((Collection<?>) propertyValue).isEmpty()) {
                                if (((ICollectionPropertyDescriptor<?>) propertyDescriptor)
                                        .getReverseRelationEnd() != null) {
                                    IPropertyDescriptor reversePropertyDescriptor = ((ICollectionPropertyDescriptor<?>) propertyDescriptor)
                                            .getReverseRelationEnd();
                                    for (Object collectionElement : new ArrayList<>(
                                            (Collection<?>) propertyValue)) {
                                        if (collectionElement instanceof IComponent
                                                && !clearedEntities.contains(collectionElement)) {
                                            try {
                                                if (reversePropertyDescriptor instanceof IReferencePropertyDescriptor) {
                                                    if (dryRun) {
                                                        // manually trigger reverse relations preprocessors.
                                                        reversePropertyDescriptor
                                                                .preprocessSetter(collectionElement, null);
                                                    } else {
                                                        getAccessorFactory()
                                                                .createPropertyAccessor(
                                                                        reversePropertyDescriptor.getName(),
                                                                        getComponentContract(
                                                                                (IComponent) collectionElement))
                                                                .setValue(collectionElement, null);
                                                    }
                                                } else if (reversePropertyDescriptor instanceof ICollectionPropertyDescriptor<?>) {
                                                    if (dryRun) {
                                                        // manually trigger reverse relations preprocessors.
                                                        Collection<Object> reverseCollection = getAccessorFactory()
                                                                .createPropertyAccessor(
                                                                        reversePropertyDescriptor.getName(),
                                                                        getComponentContract(
                                                                                (IComponent) collectionElement))
                                                                .getValue(collectionElement);
                                                        ((ICollectionPropertyDescriptor<?>) reversePropertyDescriptor)
                                                                .preprocessRemover(collectionElement,
                                                                        reverseCollection, component);
                                                    } else {
                                                        getAccessorFactory()
                                                                .createCollectionPropertyAccessor(
                                                                        reversePropertyDescriptor.getName(),
                                                                        getComponentContract(
                                                                                (IComponent) collectionElement),
                                                                        componentContract)
                                                                .removeFromValue(collectionElement, component);
                                                    }
                                                }
                                            } catch (RuntimeException ex) {
                                                integrityViolations.put((IComponent) collectionElement, ex);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } finally {
            component.setPropertyProcessorsEnabled(true);
        }
    }

    /**
     * Unwrap ORM proxy if needed.
     *
     * @param componentOrProxy
     *     the component or proxy.
     * @return the proxy implementation if it's an ORM proxy.
     */
    protected Object unwrapProxy(Object componentOrProxy) {
        return componentOrProxy;
    }

    /**
     * Clones an uninitialized (proxied) property.
     *
     * @param <E>  the type parameter
     * @param owner      the property owner.
     * @param propertyValue      the propertyValue.
     * @return the property clone.
     */
    protected <E> E cloneUninitializedProperty(Object owner, E propertyValue) {
        return propertyValue;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void loggedIn(Subject subject) {
        getApplicationSession().setSubject(subject);

        String userPreferredLanguageCode = (String) getApplicationSession().getPrincipal()
                .getCustomProperty(UserPrincipal.LANGUAGE_PROPERTY);
        if (userPreferredLanguageCode != null) {
            getApplicationSession().setLocale(LocaleUtils.toLocale(userPreferredLanguageCode));
        }
        if (getUserPreferencesStore() != null) {
            getUserPreferencesStore().setStorePath(getApplicationSession().getUsername());
        }
    }

    /**
     * Reads a user preference.
     *
     * @param key
     *     the key under which the preference as been stored.
     * @return the stored preference or null.
     */
    @Override
    public String getUserPreference(String key) {
        if (getUserPreferencesStore() != null) {
            return getUserPreferencesStore().getPreference(key);
        }
        return null;
    }

    /**
     * Stores a user preference.
     *
     * @param key
     *     the key under which the preference as to be stored.
     * @param value
     *     the value of the preference to be stored.
     */
    @Override
    public void putUserPreference(String key, String value) {
        if (getUserPreferencesStore() != null) {
            getUserPreferencesStore().putPreference(key, value);
        }
    }

    /**
     * Deletes a user preference.
     *
     * @param key
     *     the key under which the preference is stored.
     */
    @Override
    public void removeUserPreference(String key) {
        if (getUserPreferencesStore() != null) {
            getUserPreferencesStore().removePreference(key);
        }
    }

    /**
     * Gets the user preferences store.
     *
     * @return the user preferences store.
     */
    protected IPreferencesStore getUserPreferencesStore() {
        return userPreferencesStore;
    }

    /**
     * Sets the user preference store.
     *
     * @param userPreferencesStore
     *     the userPreferenceStore to set.
     */
    public void setUserPreferencesStore(IPreferencesStore userPreferencesStore) {
        this.userPreferencesStore = userPreferencesStore;
    }

    /**
     * Configures the translation provider used to compute internationalized
     * messages and labels.
     *
     * @param translationProvider
     *     the translationProvider to set.
     */
    public void setTranslationProvider(ITranslationProvider translationProvider) {
        this.translationProvider = translationProvider;
    }

    /**
     * Delegates to the translation provider.
     * <p/>
     * {@inheritDoc}
     */
    @Override
    public String getTranslation(String key, Locale locale) {
        if (customTranslationPlugin != null) {
            String translation = customTranslationPlugin.getTranslation(key, locale, getApplicationSession());
            if (translation != null) {
                return translation;
            }
        }
        return translationProvider.getTranslation(key, locale);
    }

    /**
     * Delegates to the translation provider.
     * <p/>
     * {@inheritDoc}
     */
    @Override
    public String getTranslation(String key, Object[] args, Locale locale) {
        if (customTranslationPlugin != null) {
            String translation = customTranslationPlugin.getTranslation(key, args, locale, getApplicationSession());
            if (translation != null) {
                return translation;
            }
        }
        return translationProvider.getTranslation(key, args, locale);
    }

    /**
     * Delegates to the translation provider.
     * <p/>
     * {@inheritDoc}
     */
    @Override
    public String getTranslation(String key, String defaultMessage, Locale locale) {
        if (customTranslationPlugin != null) {
            String translation = customTranslationPlugin.getTranslation(key, locale, getApplicationSession());
            if (translation != null) {
                return translation;
            }
        }
        return translationProvider.getTranslation(key, defaultMessage, locale);
    }

    /**
     * Delegates to the translation provider.
     * <p/>
     * {@inheritDoc}
     */
    @Override
    public String getTranslation(String key, Object[] args, String defaultMessage, Locale locale) {
        if (customTranslationPlugin != null) {
            String translation = customTranslationPlugin.getTranslation(key, args, locale, getApplicationSession());
            if (translation != null) {
                return translation;
            }
        }
        return translationProvider.getTranslation(key, args, defaultMessage, locale);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isAccessGranted(ISecurable securable) {
        if (SecurityHelper.isSubjectGranted(getApplicationSession().getSubject(), securable)) {
            if (customSecurityPlugin != null) {
                try {
                    pushToSecurityContext(securable);
                    Map<String, Object> securityContext = new HashMap<>();
                    if (getApplicationSession() != null && getApplicationSession().getPrincipal() != null) {
                        securityContext.put(SecurityContextConstants.USER_ROLES,
                                SecurityHelper.getRoles(getApplicationSession().getSubject()));
                        securityContext.put(SecurityContextConstants.USER_ID,
                                getApplicationSession().getUsername());
                        Map<String, Object> sessionProperties = getApplicationSession().getCustomValues();
                        sessionProperties.putAll(getApplicationSession().getPrincipal().getCustomProperties());
                        securityContext.put(SecurityContextConstants.SESSION_PROPERTIES, sessionProperties);
                    }
                    securityContext.putAll(getSecurityContext());
                    return customSecurityPlugin.isAccessGranted(securable, securityContext);
                } finally {
                    restoreLastSecurityContextSnapshot();
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Configures a custom security plugin on the controller. The controller
     * itself is a security handler and is used as such across most of the
     * application layers. Before delegating to the custom security handler, the
     * controller will apply role-based security rules that cannot be disabled.
     *
     * @param customSecurityPlugin
     *     the customESecurityHandler to set.
     */
    public void setCustomSecurityPlugin(ISecurityPlugin customSecurityPlugin) {
        this.customSecurityPlugin = customSecurityPlugin;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map<String, Object> getSecurityContext() {
        return securityContextBuilder.getSecurityContext();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ISecurityContextBuilder pushToSecurityContext(Object contextElement) {
        securityContextBuilder.pushToSecurityContext(contextElement);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ISecurityContextBuilder restoreLastSecurityContextSnapshot() {
        securityContextBuilder.restoreLastSecurityContextSnapshot();
        return this;
    }

    /**
     * Configures a custom translation plugin on the controller. The controller
     * itself is a translation provider and is used as such across most of the
     * application layers. The custom translation plugin is used to override the
     * default static, bundle-based, i18n scheme.
     *
     * @param customTranslationPlugin
     *     the customTranslationPlugin to set.
     */
    public void setCustomTranslationPlugin(ITranslationPlugin customTranslationPlugin) {
        this.customTranslationPlugin = customTranslationPlugin;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public TimeZone getReferenceTimeZone() {
        if (referenceTimeZone != null) {
            return referenceTimeZone;
        }
        return TimeZone.getDefault();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public TimeZone getClientTimeZone() {
        if (clientTimeZone != null) {
            return clientTimeZone;
        }
        return TimeZone.getDefault();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void cleanupRequestResources() {
        // Empty implementation
    }

    /**
     * Gets the throwExceptionOnBadUsage.
     *
     * @return the throwExceptionOnBadUsage.
     */
    public boolean isThrowExceptionOnBadUsage() {
        return throwExceptionOnBadUsage;
    }

    /**
     * Configures the backend controller to throw or not an exception whenever a
     * bad usage is detected like manually merging a dirty entity from an ongoing
     * UOW.
     *
     * @param throwExceptionOnBadUsage
     *     the throwExceptionOnBadUsage to set.
     */
    public void setThrowExceptionOnBadUsage(boolean throwExceptionOnBadUsage) {
        this.throwExceptionOnBadUsage = throwExceptionOnBadUsage;
    }

    /**
     * Performs necessary checks in order to ensure isolation on unit of work.
     * <p/>
     * {@inheritDoc}
     */
    @Override
    public Object sanitizeModifierParam(Object target, IPropertyDescriptor propertyDescriptor, Object param) {
        if (propertyDescriptor.isComputed()) {
            // do not perform any check regarding computed properties.
            return param;
        }
        if (param instanceof Collection<?>) {
            for (Object element : (Collection<?>) param) {
                // the return value is not leveraged.
                sanitizeModifierParam(target, propertyDescriptor, element);
            }
            return param;
        }
        Class<?> targetClass;
        IEntity targetEntity = null;
        IEntity sessionTargetEntity = null;
        IEntity paramEntity = null;
        IEntity sessionParamEntity = null;
        if (target instanceof IComponent) {
            targetEntity = refineEntity((IComponent) target);
            if (targetEntity != null) {
                sessionTargetEntity = getRegisteredEntity(getComponentContract(targetEntity), targetEntity.getId());
            }
        }
        if (param instanceof IComponent) {
            paramEntity = refineEntity((IComponent) param);
            if (paramEntity != null) {
                sessionParamEntity = getRegisteredEntity(getComponentContract(paramEntity), paramEntity.getId());
            }
        }

        if (target instanceof IComponent) {
            targetClass = getComponentContract(((IComponent) target));
        } else {
            targetClass = target.getClass();
        }

        if (isUnitOfWorkActive()) {
            if (targetEntity != null && objectEquals(targetEntity, sessionTargetEntity)) {
                // We are modifying on a session entity inside a unit of work. This is
                // not legal.
                LOG.error(
                        "*BAD UOW USAGE* You are modifying a session registered entity ({})[{}] inside an ongoing UOW.\n"
                                + "You should only work on entities copies you obtain using the "
                                + "backendController.cloneInUnitOfWork(...) method.\n"
                                + "The property being modified is [{}].",
                        targetEntity, getComponentContract(targetEntity).getSimpleName(),
                        propertyDescriptor.getName());
                if (isThrowExceptionOnBadUsage()) {
                    throw new BackendException(
                            "An invalid modification on a session entity has been detected while having an active Unit of Work. "
                                    + "Please check the logs.");
                }
            }
            if (paramEntity != null && objectEquals(paramEntity, sessionParamEntity)) {
                // We are linking an entity with a session entity inside a unit of work.
                // This is not legal.
                LOG.error(
                        "*BAD UOW USAGE* You are linking an entity ({})[{}] with a session entity ({})[{}] inside an ongoing UOW.\n"
                                + "You should only work on entities copies you obtain using the "
                                + "backendController.cloneInUnitOfWork(...) method\n"
                                + "The property being modified is [{}].",
                        target, targetClass.getSimpleName(), paramEntity,
                        getComponentContract(paramEntity).getSimpleName(), propertyDescriptor.getName());
                if (isThrowExceptionOnBadUsage()) {
                    throw new BackendException(
                            "An invalid usage of a session entity has been detected while having an active Unit of Work. "
                                    + "Please check the logs.");
                }
            }
        } else {
            if (targetEntity != null && !objectEquals(targetEntity, sessionTargetEntity)) {
                // We are working on an entity that has not been registered in the
                // session. This is not legal.
                LOG.error(
                        "*BAD SESSION USAGE* You are modifying an entity ({})[{}] that has not been previously merged in the "
                                + "session.\n" + "You should 1st merge your entities in the session by using the "
                                + "backendController.merge(...) method.\n" + "The property being modified is [{}].",
                        targetEntity, getComponentContract(targetEntity).getSimpleName(),
                        propertyDescriptor.getName());
                if (isThrowExceptionOnBadUsage()) {
                    throw new BackendException(
                            "An invalid modification of an entity that was not previously registered in the session has been "
                                    + "detected. " + "Please check the logs.");
                }
            }
            if (paramEntity != null && !objectEquals(paramEntity, sessionParamEntity)) {
                // We are linking an entity with another one that has not been
                // registered in the session. This is not legal.
                LOG.error(
                        "*BAD SESSION USAGE* You are linking an entity ({})[{}] with another one ({})[{}] "
                                + "that has not been previously merged in the session.\n"
                                + "You should 1st merge your entities in the session by using the "
                                + "backendController.merge(...) method.\n" + "The property being modified is [{}].",
                        target, targetClass.getSimpleName(), paramEntity,
                        getComponentContract(paramEntity).getSimpleName(), propertyDescriptor.getName());
                if (isThrowExceptionOnBadUsage()) {
                    throw new BackendException(
                            "An invalid usage of an entity that was not previously registered in the session has been detected. "
                                    + "Please check the logs.");
                }
            }
        }
        return param;
    }

    private IEntity refineEntity(IComponent target) {
        return refineEntity(target, new IdentityHashMap<IComponent, Object>());
    }

    private IEntity refineEntity(IComponent target, IdentityHashMap<IComponent, Object> traversed) {
        if (traversed.containsKey(target)) {
            return null;
        }
        traversed.put(target, null);
        if (target instanceof IEntity) {
            return (IEntity) target;
        }
        if (target != null) {
            return refineEntity(target.getOwningComponent(), traversed);
        }
        return null;
    }

    /**
     * Checks object equality between 2 entities ignoring any implementation
     * details like proxy optimisation.
     *
     * @param e1
     *     the 1st entity.
     * @param e2
     *     the 2nd entity.
     * @return true if both entity are object equal.
     */
    protected boolean objectEquals(IEntity e1, IEntity e2) {
        return e1 == e2;
    }

    /**
     * Hook to allow subclasses to determine component contract without
     * initializing it.
     *
     * @param <E>
     *     the actual component type.
     * @param component
     *     the component to get the component contract for.
     * @return the component contract.
     */
    @SuppressWarnings("unchecked")
    protected <E extends IComponent> Class<? extends E> getComponentContract(E component) {
        return (Class<? extends E>) component.getComponentContract();
    }

    /**
     * Gets the slaveControllerFactory.
     *
     * @return the slaveControllerFactory.
     */
    protected IBackendControllerFactory getSlaveControllerFactory() {
        return slaveControllerFactory;
    }

    /**
     * Sets the slaveControllerFactory.
     *
     * @param slaveControllerFactory
     *     the slaveControllerFactory to set.
     */
    public void setSlaveControllerFactory(IBackendControllerFactory slaveControllerFactory) {
        this.slaveControllerFactory = slaveControllerFactory;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<AsyncActionExecutor> getRunningExecutors() {
        if (controllerAsyncActionsThreadGroup != null) {
            int activeCount = controllerAsyncActionsThreadGroup.activeCount();
            AsyncActionExecutor[] activeExecutors = new AsyncActionExecutor[activeCount];
            controllerAsyncActionsThreadGroup.enumerate(activeExecutors);
            return new LinkedHashSet<>(Arrays.asList(activeExecutors));
        }
        return Collections.emptySet();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<AsyncActionExecutor> getCompletedExecutors() {
        Set<AsyncActionExecutor> completedExecutors = new LinkedHashSet<>(asyncExecutors);
        completedExecutors.removeAll(getRunningExecutors());
        return completedExecutors;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void purgeCompletedExecutors() {
        Set<AsyncActionExecutor> oldValue = new LinkedHashSet<>(getCompletedExecutors());
        asyncExecutors.removeAll(getCompletedExecutors());
        firePropertyChange("completedExecutors", oldValue, getCompletedExecutors());
    }

    /**
     * Gets the asyncExecutorsMaxCount.
     *
     * @param context
     *     the action context.
     * @return the asyncExecutorsMaxCount.
     */
    @SuppressWarnings("UnusedParameters")
    protected int getAsyncExecutorsMaxCount(Map<String, Object> context) {
        return asyncExecutorsMaxCount;
    }

    /**
     * Configures the maximum count of concurrent asynchronous action executors.
     * It defaults to {@code 10}.
     *
     * @param asyncExecutorsMaxCount
     *     the asyncExecutorsMaxCount to set.
     */
    public void setAsyncExecutorsMaxCount(int asyncExecutorsMaxCount) {
        this.asyncExecutorsMaxCount = asyncExecutorsMaxCount;
    }

    /**
     * Creates an entity registry.
     *
     * @param name
     *     the entity registry name.
     * @return a new entity registry.
     */
    protected final IEntityRegistry createEntityRegistry(String name) {
        return createEntityRegistry(name, new HashMap<Class<? extends IEntity>, Map<Serializable, IEntity>>());
    }

    /**
     * Creates an entity registry.
     *
     * @param name      the entity registry name.
     * @param backingStore the backing store
     * @return a new entity registry.
     */
    protected IEntityRegistry createEntityRegistry(String name,
            Map<Class<? extends IEntity>, Map<Serializable, IEntity>> backingStore) {
        return new BasicEntityRegistry(name, backingStore);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void suspend() {
        // NO-OP
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void resume() {
        // NO-OP
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void flush() {
        // NO-OP
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void beforeCommit(boolean readOnly) {
        // NO-OP
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void beforeCompletion() {
        // NO-OP
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void afterCommit() {
        // NO-OP
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void afterCompletion(int status) {
        if (status == STATUS_COMMITTED) {
            commitUnitOfWork();
        } else {
            rollbackUnitOfWork();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDirtyTrackingEnabled() {
        // Since both session and UOW dirty tracker are synchronized, we can only query session one.
        return sessionUnitOfWork.isDirtyTrackingEnabled();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setDirtyTrackingEnabled(boolean enabled) {
        // Both session AND UOW dirty tracker should be disabled at the same time. See bug #jspresso-ce-21.
        sessionUnitOfWork.setDirtyTrackingEnabled(enabled);
        if (isUnitOfWorkActive()) {
            unitOfWork.setDirtyTrackingEnabled(enabled);
        }
    }

    /**
     * {@inheritDoc}.
     */
    @Override
    public void addDirtInterceptor(PropertyChangeListener interceptor) {
        if (isUnitOfWorkActive()) {
            unitOfWork.addDirtInterceptor(interceptor);
        } else {
            sessionUnitOfWork.addDirtInterceptor(interceptor);
        }
    }

    /**
     * {@inheritDoc}.
     */
    @Override
    public void removeDirtInterceptor(PropertyChangeListener interceptor) {
        if (isUnitOfWorkActive()) {
            unitOfWork.removeDirtInterceptor(interceptor);
        } else {
            sessionUnitOfWork.removeDirtInterceptor(interceptor);
        }
    }

    /**
     * Suspend unit of work.
     */
    protected void suspendUnitOfWork() {
        unitOfWork.suspend();
    }

    /**
     * Resume unit of work.
     */
    protected void resumeUnitOfWork() {
        unitOfWork.resume();
    }

    /**
     * Delegates to the master controller if any.
     *
     * @param action
     *     the action
     * @param context
     *     the context
     */
    @Override
    public synchronized void executeLater(IAction action, Map<String, Object> context) {
        if (masterController != null) {
            masterController.executeLater(action, context);
        } else {
            super.executeLater(action, context);
        }
    }

    private List<IEntity> recordedMergedEntities;

    /**
     * Record uow merged entities for later reuse.
     */
    public void recordUowMergedEntities() {
        recordedMergedEntities = new ArrayList<>();
    }

    /**
     * Gets recorded uow merged entities.
     *
     * @return the recorded uow merged entities
     */
    public List<IEntity> getRecordedUowMergedEntitiesAndClear() {
        List<IEntity> copy = recordedMergedEntities;
        recordedMergedEntities = null;
        return copy;
    }

    /**
     * Sets async actions thread group.
     *
     * @param asyncActionsThreadGroup
     *     the async actions thread group
     */
    public void setAsyncActionsThreadGroup(ThreadGroup asyncActionsThreadGroup) {
        this.asyncActionsThreadGroup = asyncActionsThreadGroup;
    }
}