com.flexive.ejb.beans.ContentEngineBean.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.ejb.beans.ContentEngineBean.java

Source

/***************************************************************
 *  This file is part of the [fleXive](R) framework.
 *
 *  Copyright (c) 1999-2014
 *  UCS - unique computing solutions gmbh (http://www.ucs.at)
 *  All rights reserved
 *
 *  The [fleXive](R) project is free software; you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser General Public
 *  License version 2.1 or higher as published by the Free Software Foundation.
 *
 *  The GNU Lesser General Public License can be found at
 *  http://www.gnu.org/licenses/lgpl.html.
 *  A copy is found in the textfile LGPL.txt and important notices to the
 *  license from the author are found in LICENSE.txt distributed with
 *  these libraries.
 *
 *  This library 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 General Public License for more details.
 *
 *  For further information about UCS - unique computing solutions gmbh,
 *  please see the company website: http://www.ucs.at
 *
 *  For further information about [fleXive](R), please see the
 *  project website: http://www.flexive.org
 *
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package com.flexive.ejb.beans;

import com.flexive.core.Database;
import com.flexive.core.LifeCycleInfoImpl;
import com.flexive.core.conversion.ConversionEngine;
import com.flexive.core.flatstorage.FxFlatStorageManager;
import com.flexive.core.storage.ContentStorage;
import com.flexive.core.storage.LockStorage;
import com.flexive.core.storage.StorageManager;
import com.flexive.core.storage.binary.FxBinaryUtils;
import com.flexive.shared.*;
import com.flexive.shared.configuration.SystemParameters;
import com.flexive.shared.content.*;
import com.flexive.shared.exceptions.*;
import com.flexive.shared.interfaces.*;
import com.flexive.shared.scripting.FxScriptBinding;
import com.flexive.shared.scripting.FxScriptEvent;
import com.flexive.shared.security.ACLCategory;
import com.flexive.shared.security.ACLPermission;
import com.flexive.shared.security.UserTicket;
import com.flexive.shared.structure.*;
import com.flexive.shared.value.BinaryDescriptor;
import com.flexive.shared.value.FxBinary;
import com.flexive.shared.value.FxLargeNumber;
import com.flexive.shared.workflow.Step;
import com.thoughtworks.xstream.converters.ConversionException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.annotation.Resource;
import javax.ejb.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;

/**
 * Content Engine implementation
 *
 * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 */
@SuppressWarnings("UnusedDeclaration")
@Stateless(name = "ContentEngine", mappedName = "ContentEngine")
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
@TransactionManagement(TransactionManagementType.CONTAINER)
public class ContentEngineBean implements ContentEngine, ContentEngineLocal {

    private static final Log LOG = LogFactory.getLog(ContentEngineBean.class);
    @Resource
    javax.ejb.SessionContext ctx;

    @EJB
    SequencerEngineLocal seq;

    @EJB
    ScriptingEngineLocal scripting;

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxContent initialize(long typeId, long mandatorId, long prefACL, long prefStep, long prefLang)
            throws FxApplicationException {
        UserTicket ticket = FxContext.getUserTicket();
        FxEnvironment environment;
        environment = CacheAdmin.getEnvironment();
        FxPermissionUtils.checkMandatorExistance(mandatorId);
        FxPermissionUtils.checkTypeAvailable(typeId, false);
        FxType type = environment.getType(typeId);
        //security check start
        if (type.isUseTypePermissions() && !ticket.mayCreateACL(type.getACL().getId(), ticket.getUserId()))
            throw new FxNoAccessException("ex.acl.noAccess.create", type.getACL().getName());
        //security check end
        long acl = prefACL;
        try {
            environment.getACL(acl);
        } catch (Exception e) {
            acl = type.hasDefaultInstanceACL() ? type.getDefaultInstanceACL().getId()
                    : ACLCategory.INSTANCE.getDefaultId();
            if (!ticket.isGlobalSupervisor() && type.isUseInstancePermissions()
                    && !(ticket.mayCreateACL(acl, ticket.getUserId()) && ticket.mayReadACL(acl, ticket.getUserId())
                            && ticket.mayEditACL(acl, ticket.getUserId()))) {
                //get best fit if possible
                Long[] acls = ticket.getACLsId(ticket.getUserId(), ACLCategory.INSTANCE, ACLPermission.CREATE,
                        ACLPermission.EDIT, ACLPermission.READ);
                if (acls.length > 0)
                    acl = acls[0];
                else
                    throw new FxNoAccessException("ex.content.noSuitableACL", type.getName());
            }
        }
        long step = -2;
        for (Step check : type.getWorkflow().getSteps()) {
            if (ticket.mayCreateACL(check.getAclId(), ticket.getUserId())
                    && ticket.mayReadACL(check.getAclId(), ticket.getUserId())
                    && ticket.mayEditACL(check.getAclId(), ticket.getUserId())) {
                if (check.getId() == prefStep) {
                    step = check.getId();
                    break;
                } else if (step == -2)
                    step = check.getId(); //first match
            }
        }
        if (step < 0)
            throw new FxInvalidParameterException("STEP", "ex.content.noSuitableStep", type.getName(),
                    type.getWorkflow().getName());
        long lang = prefLang;
        try {
            environment.getLanguage(lang);
        } catch (FxRuntimeException e) {
            lang = ticket.getLanguage().getId();
        }
        FxPK sourcePK = null, destinationPK = null;
        int sourcePos = 0, destinationPos = 0;
        if (type.isRelation()) {
            sourcePK = FxPK.createNewPK();
            destinationPK = FxPK.createNewPK();
            sourcePos = destinationPos = 0;
        }
        FxContent content = new FxContent(FxPK.createNewPK(), FxLock.noLockPK(), type.getId(), type.isRelation(),
                mandatorId, acl, step, 1, environment.getStep(step).isLiveStep() ? 1 : 0, true, lang, sourcePK,
                destinationPK, sourcePos, destinationPos, LifeCycleInfoImpl.createNew(ticket),
                type.createEmptyData(type.buildXPathPrefix(FxPK.createNewPK())), BinaryDescriptor.SYS_UNKNOWN, 1)
                        .initSystemProperties();
        //scripting after start
        FxScriptBinding binding = null;
        long[] scripts = type.getScriptMapping(FxScriptEvent.AfterContentInitialize);
        if (scripts != null)
            for (long script : scripts) {
                if (binding == null)
                    binding = new FxScriptBinding();
                binding.setVariable("content", content);
                scripting.runScript(script, binding);
            }
        //scripting after end
        return content;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxContent initialize(long typeId) throws FxApplicationException {
        FxType type = CacheAdmin.getEnvironment().getType(typeId);
        UserTicket ticket = FxContext.getUserTicket();
        return initialize(type.getId(), ticket.getMandatorId(),
                type.hasDefaultInstanceACL() ? type.getDefaultInstanceACL().getId()
                        : -1 /*invalid ACL will cause a lookup for best-fit ACL*/,
                -1 /*invalid Step will cause a lookup for best-fit Step*/, ticket.getLanguage().getId());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxContent initialize(String typeName) throws FxApplicationException {
        return initialize(CacheAdmin.getEnvironment().getType(typeName).getId());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxContent load(FxPK pk) throws FxApplicationException {
        //        long time = System.currentTimeMillis();
        //        System.out.println("=> Cache check for " + pk);
        FxCachedContent cachedContent = CacheAdmin.getCachedContent(pk);
        FxEnvironment env = CacheAdmin.getEnvironment();
        Connection con = null;
        PreparedStatement ps = null;
        try {
            if (cachedContent == null) {
                ContentStorage storage = StorageManager.getContentStorage(pk.getStorageMode());
                StringBuilder sql = new StringBuilder(2000);
                con = Database.getDbConnection();
                FxContent rawContent = storage.contentLoad(con, pk, env, sql).copy();

                final FxContentSecurityInfo securityInfo = storage.getContentSecurityInfo(con, pk, rawContent);
                rawContent.updateLock(securityInfo.getLock());

                cachedContent = new FxCachedContent(rawContent, securityInfo);
                CacheAdmin.cacheContent(cachedContent, false);
            }
            FxContent content = cachedContent.getContent().copy();

            //security check start
            UserTicket ticket = FxContext.getUserTicket();
            FxType type = env.getType(cachedContent.getContent().getTypeId());
            FxPermissionUtils.checkPermission(ticket, content.getLifeCycleInfo().getCreatorId(), ACLPermission.READ,
                    type, cachedContent.getSecurityInfo().getStepACL(),
                    cachedContent.getSecurityInfo().getContentACLs(), true);
            FxPermissionUtils.checkMandatorExistance(content.getMandatorId());
            FxPermissionUtils.checkTypeAvailable(type.getId(), true);
            if (type.isUsePropertyPermissions() && !ticket.isGlobalSupervisor()) {
                //wrap with FxNoAccess or set to readonly when appropriate
                FxPermissionUtils.wrapNoAccessValues(ticket, cachedContent.getSecurityInfo(), content, type, env);
            }
            //security check end
            //scripting after start
            FxScriptBinding binding = null;
            long[] scripts = env.getType(content.getTypeId()).getScriptMapping(FxScriptEvent.AfterContentLoad);
            if (scripts != null)
                for (long script : scripts) {
                    if (binding == null)
                        binding = new FxScriptBinding();
                    binding.setVariable("content", content);
                    scripting.runScript(script, binding);
                }
            //scripting after end
            content.getRootGroup().removeEmptyEntries();
            content.getRootGroup().compactPositions(true);
            return content;
        } catch (SQLException e) {
            throw new FxLoadException(LOG, e, "ex.db.sqlError", e.getMessage());
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, ps);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxContentContainer loadContainer(long id) throws FxApplicationException {
        FxContentVersionInfo vi = getContentVersionInfo(new FxPK(id));
        List<FxContent> contents = new ArrayList<FxContent>(vi.getVersionCount());
        for (int ver : vi.getVersions())
            contents.add(load(new FxPK(id, ver)));
        return new FxContentContainer(vi, contents);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public FxPK save(FxContent content) throws FxApplicationException {
        content.checkForceSystemPropertyPermissions();
        Connection con = null;
        PreparedStatement ps = null;
        FxPK pk = null;
        FxScriptEvent beforeAssignmentScript, afterAssignmentScript;
        try {
            FxEnvironment env = CacheAdmin.getEnvironment();
            FxPermissionUtils.checkMandatorExistance(content.getMandatorId());
            FxPermissionUtils.checkTypeAvailable(content.getTypeId(), false);
            ContentStorage storage = StorageManager.getContentStorage(content.getPk().getStorageMode());
            con = Database.getDbConnection();

            //security check start
            FxType type = env.getType(content.getTypeId());
            Step step = env.getStep(content.getStepId());
            UserTicket ticket = FxContext.getUserTicket();
            if (content.getPk().isNew()) {
                FxPermissionUtils.checkPermission(ticket, ticket.getUserId(), ACLPermission.CREATE, type,
                        step.getAclId(), content.getAclIds(), true);
                beforeAssignmentScript = FxScriptEvent.BeforeAssignmentDataCreate;
                afterAssignmentScript = FxScriptEvent.AfterAssignmentDataCreate;
            } else {
                FxPermissionUtils.checkPermission(ticket, ACLPermission.EDIT,
                        getContentSecurityInfo(content.getPk()), true);
                beforeAssignmentScript = FxScriptEvent.BeforeAssignmentDataSave;
                afterAssignmentScript = FxScriptEvent.AfterAssignmentDataSave;
            }
            if (type.isUsePropertyPermissions() && !ticket.isGlobalSupervisor() && content.getPk().isNew())
                FxPermissionUtils.checkPropertyPermissions(content, ACLPermission.CREATE);
            //security check end

            content = prepareSave(con, storage, content);

            FxScriptBinding binding = null;
            long[] typeScripts;
            //scripting before start
            //type scripting
            typeScripts = type.getScriptMapping(
                    content.getPk().isNew() ? FxScriptEvent.BeforeContentCreate : FxScriptEvent.BeforeContentSave);
            if (typeScripts != null)
                for (long script : typeScripts) {
                    if (binding == null)
                        binding = new FxScriptBinding();
                    binding.setVariable("content", content);
                    Object result = scripting.runScript(script, binding).getResult();
                    if (result != null && result instanceof FxContent) {
                        content = (FxContent) result;
                    }
                }
            //assignment scripting
            if (type.hasScriptedAssignments()) {
                binding = null;
                for (FxAssignment as : type.getScriptedAssignments(beforeAssignmentScript)) {
                    if (binding == null)
                        binding = new FxScriptBinding();
                    binding.setVariable("assignment", as);
                    for (long script : as.getScriptMapping(beforeAssignmentScript)) {
                        binding.setVariable("content", content);
                        Object result = scripting.runScript(script, binding).getResult();
                        if (result != null && result instanceof FxContent) {
                            content = (FxContent) result;
                        }
                    }
                }
            }
            //scripting before end
            if (content.getPk().isNew() || content.isForcePkOnCreate()) {
                final Long contentId = content.getValue(FxLargeNumber.class, "/ID").getBestTranslation();
                pk = storage.contentCreate(con, env, null,
                        content.isForcePkOnCreate() && contentId != -1 ? contentId
                                : seq.getId(FxSystemSequencer.CONTENT),
                        content);
                if (LOG.isDebugEnabled())
                    LOG.debug("creating new content for type "
                            + CacheAdmin.getEnvironment().getType(content.getTypeId()).getName());
            } else {
                boolean newVersion = false;
                if (type.isAutoVersion() && !FxContext.preventAutoVersioning()) {
                    content.getRootGroup().removeEmptyEntries();
                    content.compact();
                    FxCachedContent cachedContent = CacheAdmin.getCachedContent(content.getPk());
                    FxContent orgContent;
                    if (cachedContent != null)
                        orgContent = cachedContent.getContent();
                    else
                        orgContent = storage.contentLoad(con, content.getPk(), CacheAdmin.getEnvironment(), null);
                    FxDelta delta = FxDelta.processDelta(orgContent, content);
                    newVersion = delta.isDataChanged();
                }
                if (newVersion)
                    pk = storage.contentCreateVersion(con, CacheAdmin.getEnvironment(), null, content);
                else
                    pk = storage.contentSave(con, env, null, content,
                            EJBLookup.getConfigurationEngine().get(SystemParameters.TREE_FQN_PROPERTY));
            }
            //scripting after start
            //assignment scripting
            if (type.hasScriptedAssignments()) {
                binding = new FxScriptBinding();
                binding.setVariable("pk", pk);
                for (FxAssignment as : type.getScriptedAssignments(afterAssignmentScript)) {
                    binding.setVariable("assignment", as);
                    for (long script : as.getScriptMapping(afterAssignmentScript)) {
                        scripting.runScript(script, binding);
                    }
                }
            }
            //type scripting
            typeScripts = env.getType(content.getTypeId()).getScriptMapping(
                    content.getPk().isNew() ? FxScriptEvent.AfterContentCreate : FxScriptEvent.AfterContentSave);
            if (typeScripts != null)
                for (long script : typeScripts) {
                    if (binding == null)
                        binding = new FxScriptBinding();
                    binding.setVariable("pk", pk);
                    scripting.runScript(script, binding);
                }
            //scripting after end
            return pk;
        } catch (FxNotFoundException e) {
            EJBUtils.rollback(ctx);
            throw new FxCreateException(e);
        } catch (SQLException e) {
            EJBUtils.rollback(ctx);
            //            if (Database.isUniqueConstraintViolation(e))
            //                throw new FxEntryExistsException("ex.structure.property.exists", property.getName(), (parentXPath.length() == 0 ? "/" : parentXPath));
            throw new FxCreateException(LOG, e, "ex.db.sqlError", e.getMessage());
        } catch (FxInvalidParameterException e) {
            EJBUtils.rollback(ctx);
            throw new FxCreateException(e);
        } catch (FxCreateException e) {
            EJBUtils.rollback(ctx);
            throw e;
        } catch (FxNoAccessException e) {
            EJBUtils.rollback(ctx);
            throw e;
        } catch (Throwable t) {
            EJBUtils.rollback(ctx);
            if (t instanceof FxApplicationException)
                throw new FxCreateException(t); //no logging
            else
                throw new FxCreateException(LOG, t);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, ps);
            if (ctx.getRollbackOnly()) {
                FxBinaryUtils.removeTXFiles();
            } else
                FxBinaryUtils.resetTXFiles();
            if (!ctx.getRollbackOnly() && pk != null)
                CacheAdmin.expireCachedContent(pk.getId());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public FxPK createNewVersion(FxContent content) throws FxApplicationException {
        content.checkForceSystemPropertyPermissions();
        Connection con = null;
        PreparedStatement ps = null;
        if (content.getPk().isNew())
            return save(content);
        try {
            //security check start
            FxEnvironment env = CacheAdmin.getEnvironment();
            FxType type = env.getType(content.getTypeId());
            Step step = env.getStep(content.getStepId());
            UserTicket ticket = FxContext.getUserTicket();
            FxPermissionUtils.checkMandatorExistance(content.getMandatorId());
            FxPermissionUtils.checkTypeAvailable(type.getId(), false);

            con = Database.getDbConnection();
            //check edit permission on current version
            final ContentStorage storage = StorageManager.getContentStorage(content.getPk().getStorageMode());

            FxPermissionUtils.checkPermission(ticket, ticket.getUserId(), ACLPermission.CREATE, type,
                    step.getAclId(), content.getAclIds(), true);
            FxContent prepared = prepareSave(con, storage, content);
            //security check end

            FxScriptBinding binding = null;
            long[] typeScripts;
            //scripting before start
            //type scripting
            typeScripts = type.getScriptMapping(
                    content.getPk().isNew() ? FxScriptEvent.BeforeContentCreate : FxScriptEvent.BeforeContentSave);
            if (typeScripts != null)
                for (long script : typeScripts) {
                    if (binding == null)
                        binding = new FxScriptBinding();
                    binding.setVariable("content", content);
                    Object result = scripting.runScript(script, binding).getResult();
                    if (result != null && result instanceof FxContent) {
                        content = (FxContent) result;
                    }
                }
            //assignment scripting
            if (type.hasScriptedAssignments()) {
                binding = null;
                for (FxAssignment as : type.getScriptedAssignments(FxScriptEvent.BeforeAssignmentDataSave)) {
                    if (binding == null)
                        binding = new FxScriptBinding();
                    binding.setVariable("assignment", as);
                    for (long script : as.getScriptMapping(FxScriptEvent.BeforeAssignmentDataSave)) {
                        binding.setVariable("content", content);
                        Object result = scripting.runScript(script, binding).getResult();
                        if (result != null && result instanceof FxContent) {
                            content = (FxContent) result;
                        }
                    }
                }
            }
            //scripting before end

            FxPK pk = storage.contentCreateVersion(con, CacheAdmin.getEnvironment(), null, prepared);

            //scripting after start
            //assignment scripting
            if (type.hasScriptedAssignments()) {
                binding = new FxScriptBinding();
                binding.setVariable("pk", pk);
                for (FxAssignment as : type.getScriptedAssignments(FxScriptEvent.AfterAssignmentDataSave)) {
                    binding.setVariable("assignment", as);
                    for (long script : as.getScriptMapping(FxScriptEvent.AfterAssignmentDataSave)) {
                        scripting.runScript(script, binding);
                    }
                }
            }
            //type scripting
            typeScripts = env.getType(content.getTypeId()).getScriptMapping(FxScriptEvent.AfterContentSave);
            if (typeScripts != null)
                for (long script : typeScripts) {
                    if (binding == null)
                        binding = new FxScriptBinding();
                    binding.setVariable("pk", pk);
                    scripting.runScript(script, binding);
                }
            //scripting after end

            return pk;
        } catch (SQLException e) {
            EJBUtils.rollback(ctx);
            throw new FxCreateException(LOG, e, "ex.db.sqlError", e.getMessage());
        } catch (FxNotFoundException e) {
            EJBUtils.rollback(ctx);
            throw e;
        } catch (FxApplicationException e) {
            EJBUtils.rollback(ctx);
            throw new FxCreateException(e);
        } catch (Exception e) {
            EJBUtils.rollback(ctx);
            throw new FxCreateException(LOG, e);
        } catch (Throwable t) {
            EJBUtils.rollback(ctx);
            throw new FxCreateException(LOG, t);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, ps);
            if (!ctx.getRollbackOnly())
                CacheAdmin.expireCachedContent(content.getId());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public FxContent prepareSave(FxContent content) throws FxApplicationException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return prepareSave(con, StorageManager.getContentStorage(content.getPk().getStorageMode()), content);
        } catch (FxNotFoundException e) {
            EJBUtils.rollback(ctx);
            throw new FxCreateException(e);
        } catch (SQLException e) {
            EJBUtils.rollback(ctx);
            throw new FxCreateException(LOG, e, "ex.db.sqlError", e.getMessage());
        } catch (FxInvalidParameterException e) {
            EJBUtils.rollback(ctx);
            throw new FxCreateException(e);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * Prepare a content for a save or create operation (resolves binaries for script processing, etc.).
     *
     * @param con     an open and valid connection
     * @param storage content storage to use
     * @param content the content to prepare
     * @return the prepared content
     * @throws FxApplicationException on errors
     * @throws SQLException           on errors
     */
    protected FxContent prepareSave(Connection con, ContentStorage storage, FxContent content)
            throws FxApplicationException, SQLException {
        storage.prepareSave(con, content);
        FxScriptBinding binding = null;
        long[] scripts;
        //scripting before start
        scripts = CacheAdmin.getEnvironment().getType(content.getTypeId()).getScriptMapping(
                content.getPk().isNew() ? FxScriptEvent.PrepareContentCreate : FxScriptEvent.PrepareContentSave);
        if (scripts != null)
            for (long script : scripts) {
                if (binding == null)
                    binding = new FxScriptBinding();
                binding.setVariable("content", content);
                Object result = scripting.runScript(script, binding).getResult();
                if (result != null && result instanceof FxContent) {
                    //                        System.out.println("setting result");
                    content = (FxContent) result;
                }
            }
        //scripting before end
        return content;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void remove(FxPK pk) throws FxApplicationException {
        Connection con = null;
        PreparedStatement ps = null;
        try {
            ContentStorage storage = StorageManager.getContentStorage(pk.getStorageMode());
            con = Database.getDbConnection();
            final FxEnvironment env = CacheAdmin.getEnvironment();
            //security check start
            if (!(FxContext.get().getRunAsSystem() || FxContext.getUserTicket().isGlobalSupervisor())) {
                FxContentVersionInfo cvi = storage.getContentVersionInfo(con, pk.getId());
                FxPK currPK;
                for (int currVer : cvi.getVersions()) {
                    currPK = new FxPK(pk.getId(), currVer);
                    FxContentSecurityInfo si = storage.getContentSecurityInfo(con, currPK, null);
                    FxPermissionUtils.checkPermission(FxContext.getUserTicket(), ACLPermission.DELETE, si, true);
                }
            }
            //security check end
            FxScriptBinding binding = null;
            long[] scripts;
            FxContentSecurityInfo securityInfo = storage.getContentSecurityInfo(con, pk, null);
            try {
                FxPermissionUtils.checkMandatorExistance(securityInfo.getMandatorId());
                FxPermissionUtils.checkTypeAvailable(securityInfo.getTypeId(), false);
            } catch (FxNotFoundException e) {
                throw new FxRemoveException(e);
            }
            long typeId = securityInfo.getTypeId();
            FxType type = env.getType(typeId);

            //scripting before start
            //type scripting
            scripts = type.getScriptMapping(FxScriptEvent.BeforeContentRemove);
            if (scripts != null)
                for (long script : scripts) {
                    if (binding == null)
                        binding = new FxScriptBinding();
                    binding.setVariable("pk", pk);
                    if (!binding.getProperties().containsKey("securityInfo"))
                        binding.setVariable("securityInfo", securityInfo);
                    scripting.runScript(script, binding);
                }
            //assignment scripting
            if (type.hasScriptedAssignments()) {
                binding = new FxScriptBinding();
                binding.setVariable("pk", pk);
                for (FxAssignment as : type.getScriptedAssignments(FxScriptEvent.BeforeAssignmentDataDelete)) {
                    binding.setVariable("assignment", as);
                    for (long script : as.getScriptMapping(FxScriptEvent.BeforeAssignmentDataDelete)) {
                        scripting.runScript(script, binding);
                    }
                }
            }
            //scripting before end
            storage.contentRemove(con, type, pk);
            //scripting after start
            //assignment scripting
            if (type.hasScriptedAssignments()) {
                binding = new FxScriptBinding();
                binding.setVariable("pk", pk);
                for (FxAssignment as : type.getScriptedAssignments(FxScriptEvent.AfterAssignmentDataDelete)) {
                    binding.setVariable("assignment", as);
                    for (long script : as.getScriptMapping(FxScriptEvent.AfterAssignmentDataDelete)) {
                        scripting.runScript(script, binding);
                    }
                }
            }
            //type scripting
            scripts = type.getScriptMapping(FxScriptEvent.AfterContentRemove);
            if (scripts != null)
                for (long script : scripts) {
                    if (binding == null)
                        binding = new FxScriptBinding();
                    binding.setVariable("pk", pk);
                    if (!binding.getProperties().containsKey("securityInfo"))
                        binding.setVariable("securityInfo", securityInfo);
                    scripting.runScript(script, binding);
                }
            //scripting after end
        } catch (FxRemoveException e) {
            EJBUtils.rollback(ctx);
            throw e;
        } catch (FxNotFoundException e) {
            //not found is ok for removed instances
        } catch (SQLException e) {
            EJBUtils.rollback(ctx);
            throw new FxRemoveException(LOG, e, "ex.db.sqlError", e.getMessage());
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, ps);
            if (!ctx.getRollbackOnly())
                CacheAdmin.expireCachedContent(pk.getId());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void removeVersion(FxPK pk) throws FxApplicationException {
        Connection con = null;
        PreparedStatement ps = null;
        try {
            ContentStorage storage = StorageManager.getContentStorage(pk.getStorageMode());
            con = Database.getDbConnection();
            //security check start
            FxContentSecurityInfo si = StorageManager.getContentStorage(pk.getStorageMode())
                    .getContentSecurityInfo(con, pk, null);
            FxType type = CacheAdmin.getEnvironment().getType(si.getTypeId());
            FxPermissionUtils.checkMandatorExistance(si.getMandatorId());
            FxPermissionUtils.checkTypeAvailable(type.getId(), false);
            FxPermissionUtils.checkPermission(FxContext.getUserTicket(), ACLPermission.DELETE, si, true);
            //security check end

            storage.contentRemoveVersion(con, type, pk);
        } catch (FxNotFoundException e) {
            EJBUtils.rollback(ctx);
            throw e;
        } catch (SQLException e) {
            EJBUtils.rollback(ctx);
            throw new FxRemoveException(LOG, e, "ex.db.sqlError", e.getMessage());
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, ps);
            if (!ctx.getRollbackOnly())
                CacheAdmin.expireCachedContent(pk.getId());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public int removeForType(long typeId) throws FxApplicationException {
        Connection con = null;
        try {
            FxType type = CacheAdmin.getEnvironment().getType(typeId);
            FxPermissionUtils.checkTypeAvailable(typeId, false);
            ContentStorage storage = StorageManager.getContentStorage(type.getStorageMode());
            con = Database.getDbConnection();
            long[] scriptsBefore = null;
            long[] scriptsAfter = null;

            FxScriptBinding binding = null;
            UserTicket ticket = FxContext.getUserTicket();
            if (CacheAdmin.getEnvironment().getType(typeId)
                    .getScriptMapping(FxScriptEvent.BeforeContentRemove) != null)
                scriptsBefore = CacheAdmin.getEnvironment().getType(typeId)
                        .getScriptMapping(FxScriptEvent.BeforeContentRemove);
            if (CacheAdmin.getEnvironment().getType(typeId)
                    .getScriptMapping(FxScriptEvent.AfterContentRemove) != null)
                scriptsAfter = CacheAdmin.getEnvironment().getType(typeId)
                        .getScriptMapping(FxScriptEvent.AfterContentRemove);
            List<FxPK> pks = null;
            if (scriptsBefore != null || scriptsAfter != null || !ticket.isGlobalSupervisor()
                    || type.isTrackHistory()) {
                binding = new FxScriptBinding();
                pks = storage.getPKsForType(con, type, false);
                if (!ticket.isGlobalSupervisor() || scriptsBefore != null) {
                    for (FxPK pk : pks) {
                        //security check start
                        FxContentSecurityInfo si = storage.getContentSecurityInfo(con, pk, null);
                        if (!ticket.isGlobalSupervisor())
                            FxPermissionUtils.checkPermission(FxContext.getUserTicket(), ACLPermission.DELETE, si,
                                    true);
                        //note: instances of deactivated mandators are silently ignored and erased on purposed!
                        //security check end
                        //scripting before start
                        //type scripting
                        if (scriptsBefore != null) {
                            binding.setVariable("pk", pk);
                            binding.setVariable("securityInfo", si);
                            for (long script : scriptsBefore)
                                scripting.runScript(script, binding);
                        }
                        //assignment scripting
                        if (type.hasScriptedAssignments()) {
                            binding = new FxScriptBinding();
                            binding.setVariable("pk", pk);
                            for (FxAssignment as : type
                                    .getScriptedAssignments(FxScriptEvent.BeforeAssignmentDataDelete)) {
                                binding.setVariable("assignment", as);
                                for (long script : as.getScriptMapping(FxScriptEvent.BeforeAssignmentDataDelete)) {
                                    scripting.runScript(script, binding);
                                }
                            }
                        }
                        //scripting before end
                    }
                }
            }
            int removed = storage.contentRemoveForType(con, type);
            if (scriptsAfter != null) {
                if (pks == null)
                    pks = storage.getPKsForType(con, type, false);
                for (FxPK pk : pks) {
                    binding.getProperties().remove("securityInfo");
                    //scripting after start
                    //assignment scripting
                    if (type.hasScriptedAssignments()) {
                        binding = new FxScriptBinding();
                        binding.setVariable("pk", pk);
                        for (FxAssignment as : type
                                .getScriptedAssignments(FxScriptEvent.AfterAssignmentDataDelete)) {
                            binding.setVariable("assignment", as);
                            for (long script : as.getScriptMapping(FxScriptEvent.AfterAssignmentDataDelete)) {
                                scripting.runScript(script, binding);
                            }
                        }
                    }
                    //type scripting
                    if (scriptsBefore != null) {
                        binding.setVariable("pk", pk);
                        for (long script : scriptsAfter)
                            scripting.runScript(script, binding);
                    }
                    //scripting after end
                }
            }
            if (type.isTrackHistory()) {
                HistoryTrackerEngine trackerEngine = EJBLookup.getHistoryTrackerEngine();
                for (FxPK pk : pks)
                    trackerEngine.track(type, pk, null, "history.content.removed");
            }
            return removed;
        } catch (FxNotFoundException e) {
            EJBUtils.rollback(ctx);
            throw new FxRemoveException(e);
        } catch (SQLException e) {
            EJBUtils.rollback(ctx);
            throw new FxRemoveException(LOG, e, "ex.db.sqlError", e.getMessage());
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
            if (!ctx.getRollbackOnly())
                CacheAdmin.expireCachedContents();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public FxContentSecurityInfo getContentSecurityInfo(FxPK pk) throws FxApplicationException {
        FxSharedUtils.checkParameterNull(pk, "pk");
        Connection con = null;
        try {
            ContentStorage storage = StorageManager.getContentStorage(pk.getStorageMode());
            con = Database.getDbConnection();
            return storage.getContentSecurityInfo(con, pk, null);
        } catch (FxNotFoundException e) {
            throw new FxLoadException(e);
        } catch (SQLException e) {
            throw new FxLoadException(LOG, e, "ex.db.sqlError", e.getMessage());
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public FxContentVersionInfo getContentVersionInfo(FxPK pk) throws FxApplicationException {
        FxSharedUtils.checkParameterNull(pk, "pk");
        Connection con = null;
        try {
            if (pk.isNew())
                return new FxContentVersionInfo(); //return an "empty" version info
            ContentStorage storage = StorageManager.getContentStorage(pk.getStorageMode());
            con = Database.getDbConnection();
            return storage.getContentVersionInfo(con, pk.getId());
        } catch (FxNotFoundException e) {
            throw new FxLoadException(e);
        } catch (SQLException e) {
            throw new FxLoadException(LOG, e, "ex.db.sqlError", e.getMessage());
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public int getReferencedContentCount(FxPK pk) throws FxApplicationException {
        FxSharedUtils.checkParameterNull(pk, "pk");
        Connection con = null;
        try {
            ContentStorage storage = StorageManager.getContentStorage(pk.getStorageMode());
            con = Database.getDbConnection();
            return storage.getReferencedContentCount(con, pk.getId())
                    + (int) FxFlatStorageManager.getInstance().getReferencedContentCount(con, pk.getId());
        } catch (FxNotFoundException e) {
            throw new FxLoadException(e);
        } catch (SQLException e) {
            throw new FxLoadException(LOG, e, "ex.db.sqlError", e.getMessage());
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public List<FxPK> getPKsForType(long typeId, boolean onePkPerInstance) throws FxApplicationException {
        if (!FxContext.getUserTicket().isGlobalSupervisor()) {
            throw new FxNoAccessException("ex.content.type.getAll.noPermission");
        }
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return StorageManager.getContentStorage(TypeStorageMode.Hierarchical).getPKsForType(con,
                    CacheAdmin.getEnvironment().getType(typeId), onePkPerInstance);
        } catch (Exception e) {
            throw new FxLoadException(LOG, e, "ex.db.sqlError", e.getMessage());
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public long getBinaryId(FxPK pk, String xpath, FxLanguage language, boolean fallbackToDefault)
            throws FxApplicationException {
        FxSharedUtils.checkParameterNull(pk, "pk");
        FxSharedUtils.checkParameterNull(xpath, "xpath");
        Connection con = null;
        try {
            ContentStorage storage = StorageManager.getContentStorage(pk.getStorageMode());
            con = Database.getDbConnection();
            FxContent co = load(pk);
            UserTicket ticket = FxContext.getUserTicket();
            if (xpath.equals("/")) {
                if (!ticket.isGlobalSupervisor()) {
                    FxType type = CacheAdmin.getEnvironment().getType(co.getTypeId());
                    if (type.isUsePermissions()) {
                        FxContentSecurityInfo si = storage.getContentSecurityInfo(con, pk, null);
                        FxPermissionUtils.checkPermission(ticket, ACLPermission.READ, si, true);
                    }
                }
                return co.getBinaryPreviewId();
            }
            FxPropertyData pd = co.getPropertyData(xpath);
            if (!pd.getValue().isEmpty() && pd.getValue() instanceof FxBinary) {
                final FxBinary bin = (FxBinary) pd.getValue();
                if (language == null)
                    return bin.getBestTranslation(ticket.getLanguage()).getId();
                if (fallbackToDefault)
                    return bin.getBestTranslation(language).getId();
                if (!bin.translationExists(language.getId()))
                    throw new FxInvalidParameterException("language", "ex.content.value.notTranslated", language);
                return bin.getTranslation(language).getId();
            }
            throw new FxInvalidParameterException("XPATH", "ex.content.binary.xpath.invalid", xpath);
        } catch (FxNotFoundException e) {
            throw new FxLoadException(e);
        } catch (SQLException e) {
            throw new FxLoadException(LOG, e, "ex.db.sqlError", e.getMessage());
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public long getBinaryId(FxPK pk, String xpath, FxLanguage language) throws FxApplicationException {
        return getBinaryId(pk, xpath, language, false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public String getBinaryMetaData(long id) {
        if (!FxContext.getUserTicket().isGlobalSupervisor())
            return "";
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return StorageManager.getContentStorage(TypeStorageMode.Hierarchical).getBinaryMetaData(con, id);
        } catch (FxNotFoundException e) {
            LOG.error(e);
        } catch (SQLException e) {
            LOG.error(e);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
        return "";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public BinaryDescriptor getBinaryDescriptor(long id) throws FxApplicationException {
        if (!FxContext.getUserTicket().isGlobalSupervisor()) {
            throw new FxNoAccessException("ex.content.type.getAll.noPermission");
        }
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return StorageManager.getContentStorage(TypeStorageMode.Hierarchical).getBinaryDescriptor(con, id);
        } catch (SQLException e) {
            throw new FxLoadException(LOG, e);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxContent importContent(String xml, boolean newInstance) throws FxApplicationException {
        FxContent co;
        try {
            co = (FxContent) ConversionEngine.getXStream().fromXML(xml);
        } catch (ConversionException e) {
            String key;
            Iterator i = e.keys();
            String path = "unknown";
            String line = "unknown";
            while (i.hasNext()) {
                key = (String) i.next();
                if ("path".equals(key))
                    path = e.get(key);
                else if ("line number".equals(key))
                    line = e.get(key);
                //                System.out.println("Key ["+key+"] -> "+e.get(key));
            }
            throw new FxApplicationException("ex.content.import.conversionError", path, line, e.getShortMessage());
        } catch (Exception e) {
            throw new FxApplicationException("ex.content.import.error", e.getMessage());
        }
        return co;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public String exportContent(FxContent content) throws FxApplicationException {
        return ConversionEngine.getXStream().toXML(content);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public FxLock lock(FxLockType lockType, FxPK pk) throws FxLockException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            FxLock lock = StorageManager.getLockStorage().lock(con, lockType, pk);
            FxCachedContent cachedContent = CacheAdmin.getCachedContent(pk);
            if (cachedContent != null) {
                cachedContent.updateLock(lock);
                CacheAdmin.cacheContent(cachedContent);
            }
            return lock;
        } catch (SQLException e) {
            throw new FxLockException(e, "ex.db.sqlError", e.getMessage());
        } catch (FxNotFoundException e) {
            throw new FxLockException(e);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public FxLock lock(FxLockType lockType, FxPK pk, long duration) throws FxLockException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            FxLock lock = StorageManager.getLockStorage().lock(con, lockType, pk, duration);
            FxCachedContent cachedContent = CacheAdmin.getCachedContent(pk);
            if (cachedContent != null) {
                cachedContent.updateLock(lock);
                CacheAdmin.cacheContent(cachedContent);
            }
            return lock;
        } catch (SQLException e) {
            throw new FxLockException(e, "ex.db.sqlError", e.getMessage());
        } catch (FxNotFoundException e) {
            throw new FxLockException(e);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public FxLock takeOverLock(FxLock lock) throws FxLockException {
        if (!lock.isContentLock())
            return lock;
        Connection con = null;
        try {
            con = Database.getDbConnection();
            FxLock newLock = StorageManager.getLockStorage().takeOver(con, lock);
            FxCachedContent cachedContent = CacheAdmin.getCachedContent(lock.getLockedPK());
            if (cachedContent != null) {
                cachedContent.updateLock(newLock);
                CacheAdmin.cacheContent(cachedContent);
            }
            return newLock;
        } catch (SQLException e) {
            throw new FxLockException(e, "ex.db.sqlError", e.getMessage());
        } catch (FxNotFoundException e) {
            throw new FxLockException(e);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public FxLock takeOverLock(FxPK pk) throws FxLockException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            final LockStorage lockStorage = StorageManager.getLockStorage();
            final FxLock lock = lockStorage.getLock(con, pk);
            FxLock newLock;
            if (lock.isLocked())
                newLock = lockStorage.takeOver(con, lock);
            else
                newLock = lockStorage.lock(con, FxLockType.Loose, pk);
            FxCachedContent cachedContent = CacheAdmin.getCachedContent(pk);
            if (cachedContent != null) {
                cachedContent.updateLock(newLock);
                CacheAdmin.cacheContent(cachedContent);
            }
            return newLock;
        } catch (SQLException e) {
            throw new FxLockException(e, "ex.db.sqlError", e.getMessage());
        } catch (FxNotFoundException e) {
            throw new FxLockException(e);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public FxLock extendLock(FxLock lock, long duration) throws FxLockException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            FxLock newLock = StorageManager.getLockStorage().extend(con, lock, duration);
            FxCachedContent cachedContent = CacheAdmin.getCachedContent(lock.getLockedPK());
            if (cachedContent != null) {
                cachedContent.updateLock(newLock);
                CacheAdmin.cacheContent(cachedContent);
            }
            return newLock;
        } catch (SQLException e) {
            throw new FxLockException(e, "ex.db.sqlError", e.getMessage());
        } catch (FxNotFoundException e) {
            throw new FxLockException(e);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public FxLock extendLock(FxPK pk, long duration) throws FxLockException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            final LockStorage lockStorage = StorageManager.getLockStorage();
            final FxLock lock = lockStorage.getLock(con, pk);
            FxLock newLock;
            if (lock.isLocked())
                newLock = lockStorage.extend(con, lock, duration);
            else
                newLock = lockStorage.lock(con, FxLockType.Loose, pk, duration);
            FxCachedContent cachedContent = CacheAdmin.getCachedContent(pk);
            if (cachedContent != null) {
                cachedContent.updateLock(newLock);
                CacheAdmin.cacheContent(cachedContent);
            }
            return newLock;
        } catch (SQLException e) {
            throw new FxLockException(e, "ex.db.sqlError", e.getMessage());
        } catch (FxNotFoundException e) {
            throw new FxLockException(e);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxLock getLock(FxPK pk) {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            FxCachedContent cachedContent = CacheAdmin.getCachedContent(pk);
            if (cachedContent != null)
                return cachedContent.getContent().getLock();
            return StorageManager.getLockStorage().getLock(con, pk);
        } catch (SQLException e) {
            //noinspection ThrowableInstanceNeverThrown
            throw new FxLockException(e, "ex.db.sqlError", e.getMessage()).asRuntimeException();
        } catch (FxNotFoundException e) {
            throw e.asRuntimeException();
        } catch (FxLockException e) {
            throw e.asRuntimeException();
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void unlock(FxPK pk) throws FxLockException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            final LockStorage lockStorage = StorageManager.getLockStorage();
            final FxLock lock = lockStorage.getLock(con, pk);
            if (lock.isLocked())
                lockStorage.unlock(con, pk);
            FxCachedContent cachedContent = CacheAdmin.getCachedContent(pk);
            if (cachedContent != null) {
                cachedContent.updateLock(FxLock.noLockPK());
                CacheAdmin.cacheContent(cachedContent);
            }
        } catch (SQLException e) {
            throw new FxLockException(e, "ex.db.sqlError", e.getMessage());
        } catch (FxNotFoundException e) {
            throw new FxLockException(e);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public List<FxLock> getLocks(FxLockType lockType, long userId, long typeId, String resource)
            throws FxLockException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            final LockStorage lockStorage = StorageManager.getLockStorage();
            return lockStorage.getLocks(con, lockType, userId, typeId, resource);
        } catch (SQLException e) {
            throw new FxLockException(e, "ex.db.sqlError", e.getMessage());
        } catch (FxNotFoundException e) {
            throw new FxLockException(e);
        } finally {
            Database.closeObjects(ContentEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void convertContentType(FxPK contentPK, long destinationTypeId, boolean allowLossy, boolean allVersions)
            throws FxApplicationException {
        final FxContent content = load(contentPK);
        final FxType sourceType = CacheAdmin.getEnvironment().getType(content.getTypeId());
        FxPermissionUtils.checkTypeAvailable(destinationTypeId, false);
        final FxType destinationType = CacheAdmin.getEnvironment().getType(destinationTypeId);
        final UserTicket ticket = FxContext.getUserTicket();
        Connection con = null;
        final long sourceTypeId = sourceType.getId();
        try {
            if (sourceTypeId == destinationTypeId)
                throw new FxContentTypeConversionException("ex.content.typeconversion.desteqSourceError");

            // only a supervisor or a user whose ACL both belongs to the current content, to the current type and the destination type may
            // move the content
            boolean userPermitted = false;
            if (ticket.isGlobalSupervisor() || ticket.isMandatorSupervisor())
                userPermitted = true;
            if (!userPermitted) {
                if (!FxPermissionUtils.currentUserInACLList(ticket, Arrays.asList(sourceType.getACL().getId())))
                    throw new FxContentTypeConversionException("ex.content.typeconversion.sourceTypeACLError");
                if (!FxPermissionUtils.currentUserInACLList(ticket,
                        Arrays.asList(destinationType.getACL().getId())))
                    throw new FxContentTypeConversionException("ex.content.typeconversion.destinationTypeACLError");
                if (!FxPermissionUtils.currentUserInACLList(ticket, content.getAclIds()))
                    throw new FxContentTypeConversionException("ex.content.typeconversion.notInContentACL");
            }

            // either destination ist derived from source or vice versa, or both types have to have the same supertype
            if (sourceType.getParent() == null && destinationType.getParent() == null)
                throw new FxContentTypeConversionException("ex.content.typeconversion.derivedTypeError");
            if (!sourceType.isDerivedFrom(destinationTypeId) && !destinationType.isDerivedFrom(sourceTypeId)) {
                // check for an indirect parentage (same supertype)
                FxType o1 = null;
                FxType o2 = null;
                for (FxType t = sourceType; t != null; t = t.getParent())
                    o1 = t;
                for (FxType t = destinationType; t != null; t = t.getParent())
                    o2 = t;

                if (o1.getId() != o2.getId())
                    throw new FxContentTypeConversionException("ex.content.typeconversion.derivedTypeError");
            }

            /**
             * Inner class to convert a List of FxAssignments to a Map of ids / XPaths w/o leading types
             * The method omits systeminternal fields
             */
            final class XPathConverter {
                Map<Long, String> convertNoTypesAsMap(List<FxAssignment> ass, boolean propAssignsOnly) {
                    final Map<Long, String> out = new HashMap<Long, String>(ass.size());
                    for (FxAssignment a : ass) {
                        if (!a.isSystemInternal()) {
                            if (propAssignsOnly && a instanceof FxPropertyAssignment)
                                out.put(a.getId(), XPathElement.stripType(a.getXPath()));
                            else if (!propAssignsOnly)
                                out.put(a.getId(), XPathElement.stripType(a.getXPath()));
                        }
                    }
                    return out;
                }
            }

            final List<FxAssignment> sourceTypeAssignments = sourceType.getAllAssignments();
            final List<FxAssignment> destinationTypeAssignments = destinationType.getAllAssignments();
            final XPathConverter xPathConverter = new XPathConverter();
            // determine outcome for lossy / lossless conversion - evaluation is based on XPaths (their names, resp)
            final Map<Long, String> sourcePathsMap = xPathConverter.convertNoTypesAsMap(sourceTypeAssignments,
                    true);
            final Map<Long, String> destPathsMap = xPathConverter.convertNoTypesAsMap(destinationTypeAssignments,
                    true);
            final List<String> sourcePaths = new ArrayList<String>(sourcePathsMap.values());
            Collections.sort(sourcePaths);
            final List<String> destPaths = new ArrayList<String>(destPathsMap.values());
            Collections.sort(destPaths);

            final FxDiff diff = new FxDiff(sourcePaths, destPaths);
            final List<FxDiff.Difference> diffList = diff.diff();
            // a map which holds assignment ids / xpaths which will be removed from the source content (lossy conversion)
            final Map<Long, String> sourceRemoveMap = new HashMap<Long, String>(2);
            if (diffList.size() > 0) { // check if the differences have an impact on !lossy conversion
                for (FxDiff.Difference d : diffList) {
                    if (d.getDeletedStart() == d.getDeletedEnd() && d.getAddedStart() == d.getAddedEnd()) {
                        throw new FxContentTypeConversionException("ex.content.typeconversion.xpathsDiffError");
                    } else if (d.getDeletedStart() == d.getDeletedEnd() && d.getAddedStart() != d.getAddedEnd()) {
                        // lossy conversion check
                        if (!allowLossy) {
                            throw new FxContentTypeConversionException("ex.content.typeconversion.sourceLossError");
                        } else { // add to list of xPaths / assignments t.b. removed
                            // sourcePathsMap.
                            final String removePath = sourcePaths.get(d.getDeletedStart());
                            for (Map.Entry<Long, String> pathEntry : sourcePathsMap.entrySet()) {
                                if (pathEntry.getValue().equals(removePath))
                                    sourceRemoveMap.put(pathEntry.getKey(), removePath);
                            }
                        }
                    } // else: del start != del end (del end is neg.) and add start & end the same, then dest has an add. prop
                }
            }

            // a map for all sourceTypePropAssignmentIds vs. destinationTypePropAssignmentIds,
            // where null values mean that the destination assignments do not exist (data will be lost)
            final Map<Long, Long> assignmentMap = new HashMap<Long, Long>(5);
            // a List containing all destination assignments which are in the flatstore
            final List<Long> flatStoreAssignments = new ArrayList<Long>(5);
            final List<Long> nonFlatSourceAssignments = new ArrayList<Long>(5);
            final List<Long> nonFlatDestinationAssignments = new ArrayList<Long>(5);

            for (FxAssignment a : sourceTypeAssignments) {
                if (a.isSystemInternal() || !(a instanceof FxPropertyAssignment))
                    continue;
                FxPropertyAssignment pa = (FxPropertyAssignment) a;
                final String sourceXPath = XPathElement.stripType(pa.getXPath());
                // get the corresponding assignment from the destination type
                final String destinationXPath = destinationType.getName() + sourceXPath;
                if (destPaths.contains(sourceXPath)
                        && CacheAdmin.getEnvironment().assignmentExists(destinationXPath)) {
                    // if the dest assignment exists, test that the datatype is the same as for the source
                    final FxAssignment destAssignment = CacheAdmin.getEnvironment().getAssignment(destinationXPath);
                    if (!(destAssignment instanceof FxPropertyAssignment))
                        throw new FxContentTypeConversionException("ex.content.typeconversion.destneqprop",
                                destAssignment.getId());

                    final FxDataType sourceDT = pa.getProperty().getDataType();
                    final FxDataType destDT = ((FxPropertyAssignment) destAssignment).getProperty().getDataType();
                    if (sourceDT != destDT)
                        throw new FxContentTypeConversionException("ex.content.typeconversion.destDTneqsourceDT",
                                destinationXPath);
                    if (((FxPropertyAssignment) destAssignment).isFlatStorageEntry()) {
                        flatStoreAssignments.add(destAssignment.getId());
                    } else {
                        nonFlatDestinationAssignments.add(destAssignment.getId());
                    }
                    if (!pa.isFlatStorageEntry()) {
                        nonFlatSourceAssignments.add(pa.getId());
                    }
                    assignmentMap.put(pa.getId(), destAssignment.getId());
                } else {
                    assignmentMap.put(pa.getId(), null);
                }
            }

            // check if we have any mandatory assignments in our destination type and if we've got data for it
            for (FxAssignment a : destinationTypeAssignments) {
                if (a instanceof FxPropertyAssignment && !a.isSystemInternal()) {
                    final int minMult = a.getMultiplicity().getMin();
                    if (minMult > 0) {
                        final String destXPath = XPathElement.stripType(a.getXPath());
                        if (content.getData(destXPath).size() < minMult)
                            throw new FxContentTypeConversionException(
                                    "ex.content.typeconversion.destMultiplicityError", destXPath, a.getId(),
                                    minMult);
                    }
                }
            }

            // passed all tests, start moving the content after retrieving all versions
            con = Database.getDbConnection();
            ContentStorage storage = StorageManager.getContentStorage(contentPK.getStorageMode());
            FxEnvironment env = CacheAdmin.getEnvironment();
            storage.convertContentType(con, contentPK, sourceTypeId, destinationTypeId, allVersions, assignmentMap,
                    flatStoreAssignments, nonFlatSourceAssignments, nonFlatDestinationAssignments, sourcePathsMap,
                    destPathsMap, sourceRemoveMap, env);

        } catch (FxApplicationException e) {
            EJBUtils.rollback(ctx);
            throw new FxContentTypeConversionException("ex.content.typeconversion.error", e.getMessage(), contentPK,
                    sourceTypeId, destinationType.getId());
        } catch (SQLException e) {
            EJBUtils.rollback(ctx);
            LOG.error(e);
        } finally { // flush cache after update was completed
            Database.closeObjects(ContentEngineBean.class, con, null);
            CacheAdmin.expireCachedContent(contentPK.getId());
        }
    }
}