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

Java tutorial

Introduction

Here is the source code for com.flexive.ejb.beans.TreeEngineBean.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.storage.StorageManager;
import com.flexive.core.storage.TreeStorage;
import com.flexive.shared.CacheAdmin;
import com.flexive.shared.EJBLookup;
import com.flexive.shared.FxContext;
import com.flexive.shared.FxLanguage;
import com.flexive.shared.configuration.SystemParameters;
import com.flexive.shared.content.FxContent;
import com.flexive.shared.content.FxContentSecurityInfo;
import com.flexive.shared.content.FxPK;
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.UserTicket;
import com.flexive.shared.structure.FxPropertyAssignment;
import com.flexive.shared.structure.FxType;
import com.flexive.shared.tree.FxTreeMode;
import com.flexive.shared.tree.FxTreeNode;
import com.flexive.shared.tree.FxTreeNodeEdit;
import com.flexive.shared.tree.FxTreeRemoveOp;
import com.flexive.shared.value.FxReference;
import com.flexive.shared.value.FxString;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
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.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static com.flexive.shared.tree.FxTreeMode.Live;

/**
 * Flexive Tree implementation
 *
 * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 */
@Stateless(name = "TreeEngine", mappedName = "TreeEngine")
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
@TransactionManagement(TransactionManagementType.CONTAINER)
public class TreeEngineBean implements TreeEngine, TreeEngineLocal {
    private static final Log LOG = LogFactory.getLog(TreeEngineBean.class);

    @Resource
    javax.ejb.SessionContext ctx;

    @EJB
    LanguageEngineLocal languageEngine;
    @EJB
    ContentEngineLocal contentEngine;
    @EJB
    SequencerEngineLocal seq;
    @EJB
    private ScriptingEngineLocal scripting;

    public TreeEngineBean() {
        // nothing to do
    }

    private static long lastCheck = System.currentTimeMillis();
    private static long cachedTreeCaptionProperty = -1L;
    private final static long CHECK_INTERVAL = 60000; //check every 60 sec

    /**
     * Get the caption property id
     *
     * @return caption property id
     * @throws FxApplicationException on errors
     */
    private long getCaptionPropertyId() throws FxApplicationException {
        if (cachedTreeCaptionProperty == -1L || (System.currentTimeMillis() - CHECK_INTERVAL) > lastCheck) {
            //            System.out.println("Checking treeCaptionProperty at " + System.currentTimeMillis() + " last check:" + lastCheck+" delta: "+(System.currentTimeMillis()-lastCheck));
            cachedTreeCaptionProperty = EJBLookup.getConfigurationEngine()
                    .get(SystemParameters.TREE_CAPTION_PROPERTY);
            lastCheck = System.currentTimeMillis();
        }
        return cachedTreeCaptionProperty;
    }

    final static boolean PARTIAL_LOADING = true;

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxTreeNode getTree(FxTreeMode mode, long nodeId, int depth) throws FxApplicationException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return StorageManager.getTreeStorage().getTree(con, contentEngine, mode, nodeId, depth, PARTIAL_LOADING,
                    FxContext.getUserTicket().getLanguage());
        } catch (FxApplicationException fx) {
            throw fx;
        } catch (Throwable t) {
            throw new FxLoadException(LOG, t, "ex.tree.load.failed.node", nodeId, mode, t.getMessage());
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public long getIdByPath(FxTreeMode mode, String path) throws FxApplicationException {
        return getIdByFQNPath(mode, FxTreeNode.ROOT_NODE, path);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public long getIdByFQNPath(FxTreeMode mode, long startNode, String path) throws FxApplicationException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return StorageManager.getTreeStorage().getIdByFQNPath(con, mode, startNode, path);
        } catch (FxApplicationException ae) {
            throw ae;
        } catch (Throwable t) {
            throw new FxTreeException(LOG, t, "ex.tree.getIdByPath.failed", path, mode);
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public long getIdByLabelPath(FxTreeMode mode, long startNode, String path) throws FxApplicationException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return StorageManager.getTreeStorage().getIdByLabelPath(con, mode, startNode, path);
        } catch (FxApplicationException ae) {
            throw ae;
        } catch (Throwable t) {
            throw new FxTreeException(LOG, t, "ex.tree.getIdByPath.failed", path, mode);
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public String getPathById(FxTreeMode mode, long nodeId) throws FxApplicationException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return StorageManager.getTreeStorage().getPathById(con, mode, nodeId);
        } catch (FxApplicationException ae) {
            throw ae;
        } catch (Throwable t) {
            throw new FxTreeException(LOG, t, "ex.tree.getPathById.failed", nodeId, mode);
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public long[] getIdChain(FxTreeMode mode, long nodeId) throws FxApplicationException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return StorageManager.getTreeStorage().getIdChain(con, mode, nodeId);
        } catch (FxApplicationException ae) {
            throw ae;
        } catch (Throwable t) {
            throw new FxTreeException(LOG, t, "ex.tree.getIdChain.failed", nodeId, mode);
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * Create a new node
     *
     * @param mode         tree mode
     * @param parentNodeId id of the parent node
     * @param name         name (will only be used if no FQN property is available in the reference)
     * @param label        label for Caption property (only used if new reference is created)
     * @param position     position
     * @param reference    referenced content id
     * @param template     optional template to assign @return id of the created node
     * @return id of the node created
     * @throws FxApplicationException on errors
     */
    private long createNode(long parentNodeId, String name, FxString label, int position, FxPK reference,
            String template, FxTreeMode mode) throws FxApplicationException {
        Connection con = null;
        if (mode == FxTreeMode.Live)
            throw new FxCreateException("ex.tree.create.live");
        try {
            con = Database.getDbConnection();
            boolean ok = false;
            try {
                long nodeId = StorageManager.getTreeStorage().createNode(con, seq, contentEngine, mode, -1,
                        parentNodeId, name, label, position, reference, template, true);
                ok = true;
                return nodeId;
            } finally {
                if (ok && !ctx.getRollbackOnly()) {
                    StorageManager.getTreeStorage().checkTreeIfEnabled(con, mode);
                    FxContext.get().setTreeWasModified();
                }
            }
        } catch (FxApplicationException ae) {
            throw ae;
        } catch (Throwable t) {
            throw new FxCreateException(LOG, t, "ex.tree.create.failed", name);
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public long[] createNodes(FxTreeMode mode, long parentNodeId, int position, String path)
            throws FxApplicationException {
        Connection con = null;
        if (mode == FxTreeMode.Live)
            throw new FxCreateException("ex.tree.create.live");
        try {
            con = Database.getDbConnection();
            boolean ok = false;
            try {
                long[] nodes = StorageManager.getTreeStorage().createNodes(con, seq, contentEngine, mode,
                        parentNodeId, path, position, true);
                // call scripts
                final List<Long> scriptIds = scripting.getByScriptEvent(FxScriptEvent.AfterTreeNodeAdded);
                if (scriptIds.size() == 0) {
                    ok = true;
                    return nodes;
                }
                final FxScriptBinding binding = new FxScriptBinding();
                for (long node : nodes) {
                    binding.setVariable("node", getNode(mode, node));
                    for (long scriptId : scriptIds)
                        scripting.runScript(scriptId, binding);
                }
                ok = true;
                return nodes;
            } finally {
                if (ok && !ctx.getRollbackOnly()) {
                    FxContext.get().setTreeWasModified();
                    StorageManager.getTreeStorage().checkTreeIfEnabled(con, mode);
                }
            }
        } catch (FxApplicationException ae) {
            EJBUtils.rollback(ctx);
            throw ae;
        } catch (Throwable t) {
            EJBUtils.rollback(ctx);
            throw new FxCreateException(LOG, t, "ex.tree.create.failed", path);
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void clear(FxTreeMode mode) throws FxApplicationException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            UserTicket ticket = FxContext.getUserTicket();
            if (FxContext.get().getRunAsSystem() || ticket.isGlobalSupervisor())
                StorageManager.getTreeStorage().clearTree(con, contentEngine, mode);
            else
                throw new FxApplicationException("ex.tree.clear.notAllowed", mode.name());
            FxContext.get().setTreeWasModified();
        } catch (FxApplicationException ae) {
            EJBUtils.rollback(ctx);
            throw ae;
        } catch (Throwable t) {
            EJBUtils.rollback(ctx);
            throw new FxCreateException(LOG, t, "FxTree.err.clear.failed", mode);
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * Rename a node
     *
     * @param nodeId id of the node
     * @param mode   tree mode
     * @param name   new name
     * @param label  new label
     * @throws FxApplicationException on errors
     */
    private void renameNode(long nodeId, FxTreeMode mode, String name, FxString label)
            throws FxApplicationException {
        Connection con = null;
        //        boolean setInContent = false;
        try {
            con = Database.getDbConnection();
            FxTreeNode node = getNode(mode, nodeId);
            boolean nameChanged = false;
            if (!node.getName().equals(name) && !StringUtils.isEmpty(name)) {
                StorageManager.getTreeStorage().updateName(con, mode, contentEngine, nodeId, name);
                nameChanged = true;
            }

            if (label != null) {
                // Load the content and its type
                FxPK pk = new FxPK(node.getReference().getId(), mode == Live ? FxPK.LIVE : FxPK.MAX);
                FxContent co = contentEngine.load(pk);
                FxType type = CacheAdmin.getEnvironment().getType(co.getTypeId());
                //noinspection LoopStatementThatDoesntLoop
                for (FxPropertyAssignment pa : type.getAssignmentsForProperty(getCaptionPropertyId())) {
                    if (pa.isMultiLang() == label.isMultiLanguage()) {
                        co.setValue(pa.getXPath(), label);
                    } else {
                        if (pa.isMultiLang() && !label.isMultiLanguage()) {
                            FxString org = (FxString) co.getValue(pa.getXPath());
                            org.setDefaultTranslation(label.getDefaultTranslation());
                            co.setValue(pa.getXPath(), org);
                        } else if (!pa.isMultiLang() && label.isMultiLanguage()) {
                            co.setValue(pa.getXPath(), new FxString(false, label.getBestTranslation()));
                        }
                    }
                    contentEngine.save(co);
                    FxContext.get().setTreeWasModified();
                    return; //just change first occurance
                }
                //if we reach this, no caption property exists
                if (!nameChanged && !label.equals(node.getLabel())) {
                    StorageManager.getTreeStorage().updateName(con, mode, contentEngine, nodeId,
                            label.getBestTranslation());
                }
            }
            StorageManager.getTreeStorage().checkTreeIfEnabled(con, mode);
            FxContext.get().setTreeWasModified();
        } catch (Exception t) {
            EJBUtils.rollback(ctx);
            throw new FxUpdateException(LOG, t, "ex.tree.rename.failed", nodeId);
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void remove(FxTreeNode node, FxTreeRemoveOp removeOp, boolean removeChildren)
            throws FxApplicationException {
        remove(node.getMode(), node.getId(), removeOp, removeChildren);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void remove(FxTreeMode mode, long nodeId, FxTreeRemoveOp removeOp, boolean removeChildren)
            throws FxApplicationException {
        Connection con = null;
        if (nodeId < 0) {
            throw new FxInvalidParameterException("nodeId", "ex.tree.delete.nodeId");
        }
        try {
            con = Database.getDbConnection();
            final FxTreeNode subTree;
            final TreeStorage treeStorage = StorageManager.getTreeStorage();

            if (removeOp == FxTreeRemoveOp.UnfileAll) {
                subTree = treeStorage.getNode(con, mode, nodeId);
                FxPK refPK = subTree.getReference();
                List<FxTreeNode> nodes = treeStorage.getNodesWithReference(con, mode, refPK.getId());
                for (FxTreeNode node : nodes) {
                    try {
                        treeStorage.removeNode(con, mode, contentEngine, node.getId(), removeChildren);
                    } catch (FxApplicationException e) {
                        LOG.warn("Could not unfile node " + node, e);
                    }
                }
                treeStorage.checkTreeIfEnabled(con, mode);
                FxContext.get().setTreeWasModified();
                return;
            }

            if (removeOp != FxTreeRemoveOp.Unfile) {
                subTree = removeChildren ? treeStorage.getTree(con, contentEngine, mode, nodeId, Integer.MAX_VALUE,
                        PARTIAL_LOADING, FxContext.getUserTicket().getLanguage())
                        : treeStorage.getNode(con, mode, nodeId);
            } else {
                subTree = null;
            }

            treeStorage.removeNode(con, mode, contentEngine, nodeId, removeChildren);
            if (removeOp != FxTreeRemoveOp.Unfile) {
                for (FxTreeNode currNode : subTree) {
                    if (currNode.getReference() != null) {
                        long refCount = 0;
                        if (removeOp == FxTreeRemoveOp.RemoveSingleFiled) {
                            //reference count it only relevant if removeOp is RemoveSingleFiled
                            refCount = contentEngine.getReferencedContentCount(currNode.getReference());
                            if (currNode.getId() == nodeId || removeChildren)
                                refCount++; //include the reference from the already removed node
                        }
                        if (removeOp == FxTreeRemoveOp.Remove
                                || (removeOp == FxTreeRemoveOp.RemoveSingleFiled && refCount == 1)) {
                            // remove only contents that are not referenced elsewhere
                            contentEngine.remove(currNode.getReference());
                        }
                    }
                }
            }
            treeStorage.checkTreeIfEnabled(con, mode);
            FxContext.get().setTreeWasModified();
        } catch (FxNotFoundException nf) {
            // Node does not exist and we wanted to delete it anyway .. so this is no error
        } catch (FxApplicationException ae) {
            EJBUtils.rollback(ctx);
            throw ae;
        } catch (Throwable t) {
            EJBUtils.rollback(ctx);
            throw new FxRemoveException(LOG, t, "ex.tree.delete.failed", nodeId, t.getMessage());
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * Update a nodes reference
     *
     * @param node node to update
     * @throws FxTreeException on errors
     */
    private void updateReference(FxTreeNodeEdit node) throws FxTreeException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            StorageManager.getTreeStorage().updateReference(con, node.getMode(), node.getId(),
                    node.getReference().getId());
        } catch (Throwable t) {
            EJBUtils.rollback(ctx);
            throw new FxTreeException(LOG, t, "ex.tree.update.reference.failed", node.getId(),
                    node.getMode().name(), t.getMessage());
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void move(FxTreeMode mode, long nodeId, long destinationId, int newPosition)
            throws FxApplicationException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            StorageManager.getTreeStorage().move(con, seq, mode, nodeId, destinationId, newPosition);
            StorageManager.getTreeStorage().checkTreeIfEnabled(con, mode);
            FxContext.get().setTreeWasModified();
        } catch (FxApplicationException ae) {
            EJBUtils.rollback(ctx);
            throw ae;
        } catch (Throwable t) {
            EJBUtils.rollback(ctx);
            throw new FxUpdateException(LOG, t, "ex.tree.move.failed", nodeId, destinationId, newPosition);
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public long copy(FxTreeMode mode, long source, long destination, int destinationPosition,
            boolean deepReferenceCopy) throws FxApplicationException {
        Connection con = null;
        boolean success = false;
        try {
            con = Database.getDbConnection();
            long nodeId = StorageManager.getTreeStorage().copy(con, seq, mode, source, destination,
                    destinationPosition, deepReferenceCopy, "CopyOf_");
            // call scripts
            final List<Long> scriptIds = scripting.getByScriptEvent(FxScriptEvent.AfterTreeNodeAdded);
            if (scriptIds.size() == 0) {
                success = true;
                return nodeId;
            }
            FxTreeNode parent = getTree(mode, nodeId, 1000);
            final FxScriptBinding binding = new FxScriptBinding();
            for (long scriptId : scriptIds)
                executeScript(scriptId, parent, binding);
            success = true;
            return nodeId;
        } catch (FxApplicationException ae) {
            EJBUtils.rollback(ctx);
            throw ae;
        } catch (Throwable t) {
            EJBUtils.rollback(ctx);
            throw new FxUpdateException(LOG, t, "ex.tree.copy.failed", source, destination, destinationPosition);
        } finally {
            if (success) {
                StorageManager.getTreeStorage().checkTreeIfEnabled(con, mode);
                FxContext.get().setTreeWasModified();
            }
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * Execute a script for a tree node and all its children
     *
     * @param scriptId id of the script to execute
     * @param node     node
     * @param binding  binding
     * @throws FxApplicationException on errors
     */
    private void executeScript(long scriptId, FxTreeNode node, FxScriptBinding binding)
            throws FxApplicationException {
        binding.setVariable("node", node);
        scripting.runScript(scriptId, binding);
        if (node.getChildren() != null && node.getChildren().size() > 0) {
            for (FxTreeNode child : node.getChildren())
                executeScript(scriptId, child, binding);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void activate(FxTreeMode mode, long nodeId, boolean includeChildren, boolean activateContents)
            throws FxApplicationException {
        Connection con = null;
        try {
            if (mode == FxTreeMode.Live)
                return;
            con = Database.getDbConnection();
            final FxTreeNode srcNode = StorageManager.getTreeStorage().getNode(con, mode, nodeId);
            //            if( !contentEngine.getContentVersionInfo(srcNode.getReference()).hasLiveVersion() )
            //                throw new FxTreeException("ex.tree.activate.failed.noLiveContent", srcNode.getPath());
            if (!includeChildren) {
                //if the node is a leaf node, always activate with children to propagate removed subnodes
                if (srcNode.isLeaf())
                    includeChildren = true;
            }

            final List<Long> scriptBeforeIds = scripting.getByScriptEvent(FxScriptEvent.BeforeTreeNodeActivated);
            final List<Long> scriptAfterIds = scripting.getByScriptEvent(FxScriptEvent.AfterTreeNodeActivated);

            if (includeChildren) {
                if (scriptBeforeIds.size() > 0) {
                    final FxScriptBinding binding = new FxScriptBinding();
                    FxTreeNode parent = getTree(FxTreeMode.Edit, nodeId, 1000);
                    for (long scriptId : scriptBeforeIds)
                        executeScript(scriptId, parent, binding);
                }
                StorageManager.getTreeStorage().activateSubtree(con, seq, contentEngine, mode, nodeId,
                        activateContents);
                if (scriptAfterIds.size() > 0) {
                    final FxScriptBinding binding = new FxScriptBinding();
                    FxTreeNode parent = getTree(FxTreeMode.Live, nodeId, 1000);
                    for (long scriptId : scriptAfterIds)
                        executeScript(scriptId, parent, binding);
                }
            } else {
                if (scriptBeforeIds.size() > 0) {
                    final FxScriptBinding binding = new FxScriptBinding();
                    for (long scriptId : scriptBeforeIds) {
                        binding.setVariable("node", getNode(FxTreeMode.Edit, nodeId));
                        scripting.runScript(scriptId, binding);
                    }
                }
                StorageManager.getTreeStorage().activateNode(con, seq, contentEngine, mode, nodeId,
                        activateContents);
                if (scriptAfterIds.size() > 0) {
                    final FxScriptBinding binding = new FxScriptBinding();
                    for (long scriptId : scriptAfterIds) {
                        binding.setVariable("node", getNode(FxTreeMode.Live, nodeId));
                        scripting.runScript(scriptId, binding);
                    }
                }
            }
            StorageManager.getTreeStorage().checkTreeIfEnabled(con, mode);
            StorageManager.getTreeStorage().checkTreeIfEnabled(con, FxTreeMode.Live);
            FxContext.get().setTreeWasModified();
        } catch (FxApplicationException ae) {
            EJBUtils.rollback(ctx);
            throw ae;
        } catch (Throwable t) {
            EJBUtils.rollback(ctx);
            throw new FxUpdateException(LOG, t, "ex.tree.activate.failed", nodeId, includeChildren, t.getMessage());
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public List<FxTreeNode> getNodesWithReference(FxTreeMode mode, long reference) throws FxApplicationException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return StorageManager.getTreeStorage().getNodesWithReference(con, mode, reference);
        } catch (FxApplicationException ae) {
            throw ae;
        } catch (Throwable t) {
            throw new FxUpdateException(LOG, t, "ex.tree.getNodesWithReference.failed", mode, reference);
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxTreeNode getNode(FxTreeMode mode, long id) throws FxApplicationException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return StorageManager.getTreeStorage().getNode(con, mode, id);
        } catch (FxApplicationException fx) {
            throw fx;
        } catch (Throwable t) {
            throw new FxLoadException(LOG, t, "ex.tree.load.failed.node", id, mode, t.getMessage());
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public List<String> getPaths(FxTreeMode mode, long... ids) throws FxApplicationException {
        if (ids == null || ids.length == 0)
            return new ArrayList<String>(0);
        Connection con = null;
        try {
            con = Database.getDbConnection();
            List<String> res = new ArrayList<String>(ids.length);
            TreeStorage tree = StorageManager.getTreeStorage();
            for (long id : ids)
                res.add(tree.getPathById(con, mode, id));
            return res;
        } catch (FxApplicationException ae) {
            throw ae;
        } catch (Throwable t) {
            throw new FxTreeException(LOG, t, "ex.tree.getPaths.failed", Arrays.toString(ids), mode);
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public List<String> getLabels(FxTreeMode mode, long... ids) throws FxApplicationException {
        return getLabels(mode, FxContext.getUserTicket().getLanguage(), ids);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public List<String> getLabels(FxTreeMode mode, FxLanguage language, long... ids) throws FxApplicationException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return StorageManager.getTreeStorage().getLabels(con, mode, getCaptionPropertyId(), language, true,
                    ids);
        } catch (FxApplicationException ae) {
            throw ae;
        } catch (Throwable t) {
            throw new FxUpdateException(LOG, t, "ex.tree.getLabels.failed", mode, language.getIso2digit(),
                    t.getMessage());
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public boolean exist(FxTreeMode mode, long id) throws FxApplicationException {
        Connection con = null;
        try {
            con = Database.getDbConnection();
            return StorageManager.getTreeStorage().exists(con, mode, id);
        } catch (FxApplicationException ae) {
            throw ae;
        } catch (Throwable t) {
            throw new FxLoadException(LOG, t, "ex.tree.exist.failed", id, mode, t.getMessage());
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public String[] getDatas(FxTreeMode mode, long id) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public long[] getReverseIdChain(FxTreeMode mode, long id) throws FxApplicationException {
        long[] chain = getIdChain(mode, id);
        ArrayUtils.reverse(chain);
        return chain;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void setData(FxTreeMode mode, long nodeId, String data) {
        throw new UnsupportedOperationException("Not yet implemented");
        // provisional implementation
        /*
        Connection con = null;
        try {
        con = Database.getDbConnection();
        StorageManager.getTreeStorage().setData(con, mode, nodeId, data);
        FxContext.get().setTreeWasModified();
        } catch (FxApplicationException e) {
        EJBUtils.rollback(ctx);
        throw e;
        } catch (Throwable t) {
        EJBUtils.rollback(ctx);
        throw new FxUpdateException(LOG, t, "ex.tree.setData.failed", data, nodeId, t.getMessage());
        } finally {
        Database.closeObjects(TreeEngineBean.class, con, null);
        }
        */
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxTreeNode findChild(FxTreeMode mode, long nodeId, String name) throws FxApplicationException {
        for (FxTreeNode node : getTree(mode, nodeId, 1).getChildren()) {
            if (node.getName().equals(name)) {
                return node;
            }
        }
        throw new FxNotFoundException("ex.tree.nodeNotFound.name", name, mode, nodeId);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxTreeNode findChild(FxTreeMode mode, long nodeId, long referenceId) throws FxApplicationException {
        for (FxTreeNode node : getTree(mode, nodeId, 1).getChildren())
            if (node.getReference().getId() == referenceId)
                return node;
        throw new FxNotFoundException("ex.tree.nodeNotFound.reference", referenceId, mode, nodeId);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxTreeNode findChild(FxTreeMode mode, long nodeId, FxPK pk) throws FxApplicationException {
        return findChild(mode, nodeId, pk.getId());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public FxTreeNode findChild(FxTreeMode mode, long nodeId, FxReference reference) throws FxApplicationException {
        return findChild(mode, nodeId, reference.getBestTranslation().getId());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public long save(FxTreeNodeEdit node) throws FxApplicationException {
        if (node.isPartialLoaded())
            throw new FxTreeException("ex.tree.partialnode.notAllowed", node.getMode(), node.getId());
        if (node.isNew()) {
            long nodeId = createNode(node.getParentNodeId(), node.getName(), node.getLabel(), node.getPosition(),
                    node.getReference(), node.getData(), node.getMode());
            if ((node.getReference() == null || node.getReference().isNew())
                    && !Arrays.asList(ACLCategory.INSTANCE.getDefaultId()).equals(node.getACLIds())) {
                //requested a non-default ACL for a folder
                FxTreeNode created = getNode(node.getMode(), nodeId);
                FxContent co = contentEngine.load(created.getReference());
                co.setAclIds(node.getACLIds());
                contentEngine.save(co);
            }
            // call scripts
            final List<Long> scriptIds = scripting.getByScriptEvent(FxScriptEvent.AfterTreeNodeAdded);
            if (scriptIds.size() == 0)
                return nodeId;
            final FxScriptBinding binding = new FxScriptBinding();
            binding.setVariable("node", getNode(node.getMode(), nodeId));
            for (long scriptId : scriptIds)
                scripting.runScript(scriptId, binding);
            return nodeId;
        } else {
            FxTreeNode old = getNode(node.getOriginalMode(), node.getId());
            if (node.getReference().getId() != old.getReference().getId()) {
                updateReference(node);
                FxContentSecurityInfo si = contentEngine.getContentSecurityInfo(old.getReference());
                if (si.getTypeId() == CacheAdmin.getEnvironment().getType(FxType.FOLDER).getId()) {
                    if (contentEngine.getReferencedContentCount(old.getReference()) == 0)
                        contentEngine.remove(old.getReference());
                }
                //need refresh with new reference data
                old = getNode(node.getOriginalMode(), node.getId());
            }
            if (!old.getName().equals(node.getName()) || !old.getLabel().equals(node.getLabel()))
                renameNode(node.getId(), node.getOriginalMode(), node.getName(), node.getLabel());
            if (old.getParentNodeId() != node.getParentNodeId() || old.getPosition() != node.getPosition())
                move(node.getMode(), node.getId(), node.getParentNodeId(), node.getPosition());
            if (node.isActivate() && node.getMode() != FxTreeMode.Live)
                activate(FxTreeMode.Edit, node.getId(), node.isActivateWithChildren(), true);
            if (!node.getACLIds().equals(old.getACLIds())) {
                FxContent co = contentEngine.load(node.getReference());
                co.setAclIds(node.getACLIds());
                contentEngine.save(co);
            }
        }
        return node.getId();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void populate(FxTreeMode mode) throws FxApplicationException {
        final int maxNodeChildren = 3; // "default" of 3 data population nodes
        populate(mode, maxNodeChildren);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void populate(FxTreeMode mode, int maxNodeChildren) throws FxApplicationException {

        Connection con = null;
        try {
            con = Database.getDbConnection();
            StorageManager.getTreeStorage().populate(con, seq, contentEngine, mode, maxNodeChildren);
            FxContext.get().setTreeWasModified();
        } catch (FxApplicationException ae) {
            EJBUtils.rollback(ctx);
            throw ae;
        } catch (Throwable t) {
            EJBUtils.rollback(ctx);
            throw new FxLoadException(LOG, t, "ex.tree.populate", mode, t.getMessage());
        } finally {
            Database.closeObjects(TreeEngineBean.class, con, null);
        }
    }
}