org.apereo.portal.layout.dlm.DistributedLayoutManager.java Source code

Java tutorial

Introduction

Here is the source code for org.apereo.portal.layout.dlm.DistributedLayoutManager.java

Source

/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apereo.portal.layout.dlm;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.Vector;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.xpath.XPathConstants;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apereo.portal.IUserIdentityStore;
import org.apereo.portal.IUserProfile;
import org.apereo.portal.PortalException;
import org.apereo.portal.events.IPortalLayoutEventFactory;
import org.apereo.portal.layout.IUserLayout;
import org.apereo.portal.layout.IUserLayoutManager;
import org.apereo.portal.layout.IUserLayoutStore;
import org.apereo.portal.layout.PortletSubscribeIdResolver;
import org.apereo.portal.layout.node.IUserLayoutChannelDescription;
import org.apereo.portal.layout.node.IUserLayoutFolderDescription;
import org.apereo.portal.layout.node.IUserLayoutNodeDescription;
import org.apereo.portal.layout.node.IUserLayoutNodeDescription.LayoutNodeType;
import org.apereo.portal.layout.node.UserLayoutFolderDescription;
import org.apereo.portal.layout.simple.SimpleLayout;
import org.apereo.portal.portlet.om.IPortletDefinition;
import org.apereo.portal.portlet.om.IPortletDefinitionParameter;
import org.apereo.portal.portlet.registry.IPortletDefinitionRegistry;
import org.apereo.portal.security.AdminEvaluator;
import org.apereo.portal.security.IAuthorizationPrincipal;
import org.apereo.portal.security.IAuthorizationService;
import org.apereo.portal.security.IPerson;
import org.apereo.portal.security.PersonFactory;
import org.apereo.portal.spring.locator.PortletDefinitionRegistryLocator;
import org.apereo.portal.spring.locator.UserIdentityStoreLocator;
import org.apereo.portal.xml.XmlUtilities;
import org.apereo.portal.xml.xpath.XPathOperations;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * A layout manager that provides layout control through
 * layout fragments that are derived from regular portal user accounts.
 *
 * @author Mark Boyd
 * @since uPortal 2.5
 */
public class DistributedLayoutManager implements IUserLayoutManager, InitializingBean {

    private static final Log LOG = LogFactory.getLog(DistributedLayoutManager.class);

    private XmlUtilities xmlUtilities;
    private ILayoutCachingService layoutCachingService;
    private IUserLayoutStore distributedLayoutStore;
    private XPathOperations xpathOperations;
    private IPortalLayoutEventFactory portalEventFactory;
    private IAuthorizationService authorizationService;

    protected final IPerson owner;
    protected final IUserProfile profile;

    /**
     * Holds the bean name of the configured folder label policy if any that is 
     * defined in the dlm context configuration.  
     */
    static final String FOLDER_LABEL_POLICY = "FolderLabelPolicy";

    protected final static Random rnd = new Random();
    protected String cacheKey = null; // Must be "updated" prior to use
    protected String rootNodeId = null;

    private boolean channelsAdded = false;
    private boolean isFragmentOwner = false;

    public DistributedLayoutManager(IPerson owner, IUserProfile profile) throws PortalException {

        if (owner == null) {
            throw new PortalException(
                    "Unable to instantiate DistributedLayoutManager. " + "A non-null owner must to be specified.");
        }
        if (profile == null) {
            throw new PortalException("Unable to instantiate DistributedLayoutManager for "
                    + owner.getAttribute(IPerson.USERNAME) + ". A " + "non-null profile must to be specified.");
        }

        // cache the relatively lightweight userprofile for use in layout PLF loading
        owner.setAttribute(IUserProfile.USER_PROFILE, profile);

        this.owner = owner;
        this.profile = profile;

        // Must always initialize the cacheKey to a generated value
        this.updateCacheKey();
    }

    @Autowired
    public void setAuthorizationService(IAuthorizationService authorizationService) {
        this.authorizationService = authorizationService;
    }

    @Autowired
    public void setXpathOperations(XPathOperations xpathOperations) {
        this.xpathOperations = xpathOperations;
    }

    @Autowired
    public void setXmlUtilities(XmlUtilities xmlUtilities) {
        this.xmlUtilities = xmlUtilities;
    }

    @Autowired
    public void setLayoutCachingService(ILayoutCachingService layoutCachingService) {
        this.layoutCachingService = layoutCachingService;
    }

    @Autowired
    public void setDistributedLayoutStore(IUserLayoutStore distributedLayoutStore) {
        this.distributedLayoutStore = distributedLayoutStore;
    }

    @Autowired
    public void setPortalEventFactory(IPortalLayoutEventFactory portalEventFactory) {
        this.portalEventFactory = portalEventFactory;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // Ensure a new layout gets loaded whenever a user logs in except for guest users
        if (!owner.isGuest()) {
            this.layoutCachingService.removeCachedLayout(owner, profile);
        }

        this.loadUserLayout();

        // verify that we have the minimum layout necessary to render the
        // portal and reset it if we do not.
        this.getRootFolderId();

        // This listener determines if one or more channels have been
        // added, and sets a state variable which is reset when the
        // layout saved event is triggered.
        //        this.addLayoutEventListener(new LayoutEventListenerAdapter()
        //        {
        //            @Override
        //            public void channelAdded(LayoutEvent ev) {
        //                channelsAdded = true;
        //            }
        //            @Override
        //            public void layoutSaved() {
        //                channelsAdded = false;
        //            }
        //        });
    }

    private void setUserLayoutDOM(DistributedUserLayout userLayout) {

        this.layoutCachingService.cacheLayout(owner, profile, userLayout);
        this.updateCacheKey();

        // determine if this is a layout fragment by looking at the root node
        // for a cp:fragment attribute.
        Element layout = userLayout.getLayout().getDocumentElement();
        Node attr = layout.getAttributeNodeNS(Constants.NS_URI, Constants.LCL_FRAGMENT_NAME);
        this.isFragmentOwner = attr != null;
    }

    @SuppressWarnings("deprecation")
    @Deprecated
    @Override
    public Document getUserLayoutDOM() {
        final DistributedUserLayout userLayout = getDistributedUserLayout();
        return userLayout.getLayout();
    }

    protected DistributedUserLayout getDistributedUserLayout() {
        DistributedUserLayout userLayout = this.layoutCachingService.getCachedLayout(owner, profile);
        if (null == userLayout) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Load from store for " + owner.getAttribute(IPerson.USERNAME));
            }
            userLayout = this.distributedLayoutStore.getUserLayout(this.owner, this.profile);

            final Document userLayoutDocument = userLayout.getLayout();

            // DistributedLayoutManager shall gracefully remove channels 
            // that the user isn't authorized to render from folders of type 
            // 'header' and 'footer'.
            IAuthorizationPrincipal principal = authorizationService.newPrincipal(owner.getUserName(),
                    IPerson.class);
            NodeList nodes = userLayoutDocument.getElementsByTagName("folder");
            for (int i = 0; i < nodes.getLength(); i++) {
                Element fd = (Element) nodes.item(i);
                String type = fd.getAttribute("type");
                if (type != null && (type.equals("header") || type.equals("footer") || type.equals("sidebar"))) {
                    // Here's where we do the work...
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("RDBMUserLayoutStore examining the '" + type + "' folder of user '"
                                + owner.getUserName() + "' for non-authorized channels.");
                    }
                    NodeList channels = fd.getElementsByTagName("channel");
                    for (int j = 0; j < channels.getLength(); j++) {
                        Element ch = (Element) channels.item(j);
                        try {
                            String chanId = ch.getAttribute("chanID");
                            if (!principal.canRender(chanId)) {
                                fd.removeChild(ch);
                                if (LOG.isDebugEnabled()) {
                                    LOG.debug("RDBMUserLayoutStore removing channel '" + ch.getAttribute("fname")
                                            + "' from the header or footer of user '" + owner.getUserName()
                                            + "' because he/she isn't authorized to render it.");
                                }
                            }
                        } catch (Throwable t) {
                            // Log this...
                            LOG.warn("RDBMUserLayoutStore was unable to analyze channel element with Id="
                                    + ch.getAttribute("chanID"), t);
                        }
                    }
                }
            }

            setUserLayoutDOM(userLayout);
        }
        return userLayout;
    }

    @Override
    public XMLEventReader getUserLayoutReader() {
        Document ul = this.getUserLayoutDOM();
        if (ul == null) {
            throw new PortalException(
                    "User layout has not been initialized for " + owner.getAttribute(IPerson.USERNAME));
        }

        final XMLInputFactory xmlInputFactory = this.xmlUtilities.getXmlInputFactory();

        final DOMSource layoutSoure = new DOMSource(ul);
        try {
            return xmlInputFactory.createXMLEventReader(layoutSoure);
        } catch (XMLStreamException e) {
            throw new RuntimeException(
                    "Failed to create Layout XMLStreamReader for user: " + owner.getAttribute(IPerson.USERNAME), e);
        }
    }

    /**
     * Instantiates an empty transformer to generate SAX events for the layout.
     * 
     * @return Transformer
     * @throws PortalException
     */
    private Transformer getEmptyTransformer() throws PortalException {
        Transformer xfrmr = null;
        try {
            xfrmr = TransformerFactory.newInstance().newTransformer();
        } catch (Exception e) {
            throw new PortalException("Unable to instantiate transformer.", e);
        }
        return xfrmr;
    }

    public synchronized void loadUserLayout() throws PortalException {
        this.loadUserLayout(false);
    }

    public synchronized void loadUserLayout(boolean reload) throws PortalException {
        Document uli = null;
        try {
            //Clear the loaded document first if this is a forced reload
            if (reload) {
                this.layoutCachingService.removeCachedLayout(owner, profile);
            }

            uli = getUserLayoutDOM();
        } catch (Exception e) {
            throw new PortalException("Exception encountered while " + "reading a layout for userId="
                    + this.owner.getID() + ", profileId=" + this.profile.getProfileId(), e);
        }
        if (uli == null) {
            throw new PortalException(
                    "Null user layout returned " + "for ownerId=\"" + owner.getID() + "\", profileId=\""
                            + profile.getProfileId() + "\", layoutId=\"" + profile.getLayoutId() + "\"");
        }
    }

    public synchronized void saveUserLayout() throws PortalException {
        Document uld = this.getUserLayoutDOM();

        if (uld == null) {
            throw new PortalException(
                    "UserLayout has not been initialized for " + owner.getAttribute(IPerson.USERNAME) + ".");
        }
        try {
            this.distributedLayoutStore.setUserLayout(this.owner, this.profile, uld, channelsAdded);
        } catch (Exception e) {
            throw new PortalException("Exception encountered while " + "saving layout for userId="
                    + this.owner.getID() + ", profileId=" + this.profile.getProfileId(), e);
        }

        this.channelsAdded = false;
    }

    @Override
    public Set<String> getAllSubscribedChannels() {
        final Document uld = this.getUserLayoutDOM();

        if (uld == null) {
            throw new PortalException(
                    "UserLayout has not been initialized for " + owner.getAttribute(IPerson.USERNAME));
        }

        final NodeList channelElements = uld.getElementsByTagName(CHANNEL);

        final Set<String> allSubscribedChannels = new LinkedHashSet<String>(channelElements.getLength());
        for (int nodeIndex = 0; nodeIndex < channelElements.getLength(); nodeIndex++) {
            final Element channelElement = (Element) channelElements.item(nodeIndex);
            final String subscribeId = channelElement.getAttribute("ID");
            allSubscribedChannels.add(subscribeId);
        }

        return allSubscribedChannels;
    }

    public IUserLayoutNodeDescription getNode(String nodeId) throws PortalException {
        if (nodeId == null)
            return null;

        Document uld = this.getUserLayoutDOM();

        if (uld == null)
            throw new PortalException(
                    "UserLayout has not been initialized for " + owner.getAttribute(IPerson.USERNAME) + ".");

        // find an element with a given id
        Element element = uld.getElementById(nodeId);
        if (element == null) {
            throw new PortalException("Element with ID=\"" + nodeId + "\" doesn't exist for "
                    + owner.getAttribute(IPerson.USERNAME) + ".");
        }
        // instantiate the node description
        IUserLayoutNodeDescription desc = createNodeDescription(element);
        if (nodeId.startsWith(Constants.FRAGMENT_ID_USER_PREFIX) && desc instanceof ChannelDescription) {
            FragmentChannelInfo info = this.distributedLayoutStore.getFragmentChannelInfo(nodeId);
            ((ChannelDescription) desc).setFragmentChannelInfo(info);
        }
        return desc;
    }

    public IUserLayoutNodeDescription addNode(IUserLayoutNodeDescription node, String parentId,
            String nextSiblingId) throws PortalException {
        boolean isChannel = false;
        IUserLayoutNodeDescription parent = this.getNode(parentId);
        if (canAddNode(node, parent, nextSiblingId)) {
            // assign new Id
            try {
                if (node instanceof IUserLayoutChannelDescription) {
                    isChannel = true;
                    node.setId(this.distributedLayoutStore.generateNewChannelSubscribeId(owner));
                } else {
                    node.setId(this.distributedLayoutStore.generateNewFolderId(owner));
                }
            } catch (Exception e) {
                throw new PortalException("Exception encountered while "
                        + "generating new user layout node Id for  for " + owner.getAttribute(IPerson.USERNAME), e);
            }

            Document uld = getUserLayoutDOM();
            Element childElement = node.getXML(uld);
            Element parentElement = uld.getElementById(parentId);
            if (nextSiblingId == null) {
                parentElement.appendChild(childElement);
            } else {
                Node nextSibling = uld.getElementById(nextSiblingId);
                parentElement.insertBefore(childElement, nextSibling);
            }
            // register element id
            childElement.setIdAttribute(Constants.ATT_ID, true);
            childElement.setAttribute(Constants.ATT_ID, node.getId());
            this.updateCacheKey();

            // push into the user's real layout that gets persisted.
            HandlerUtils.createPlfNodeAndPath(childElement, isChannel, owner);

            // fire event
            final int layoutId = this.getLayoutId();
            if (isChannel) {
                this.channelsAdded = true;
                final String fname = ((IUserLayoutChannelDescription) node).getFunctionalName();
                this.portalEventFactory.publishPortletAddedToLayoutPortalEvent(this, this.owner, layoutId,
                        parent.getId(), fname);
            } else {
                this.portalEventFactory.publishFolderAddedToLayoutPortalEvent(this, this.owner, layoutId,
                        node.getId());
            }

            return node;
        }
        return null;
    }

    public boolean moveNode(String nodeId, String parentId, String nextSiblingId) throws PortalException {
        IUserLayoutNodeDescription parent = this.getNode(parentId);
        IUserLayoutNodeDescription node = this.getNode(nodeId);
        String oldParentNodeId = getParentId(nodeId);
        if (canMoveNode(node, parent, nextSiblingId)) {
            // must be a folder
            Document uld = this.getUserLayoutDOM();
            Element childElement = uld.getElementById(nodeId);
            Element parentElement = uld.getElementById(parentId);
            if (nextSiblingId == null) {
                parentElement.appendChild(childElement);
            } else {
                Node nextSibling = uld.getElementById(nextSiblingId);
                parentElement.insertBefore(childElement, nextSibling);
            }
            this.updateCacheKey();

            // propagate the change into the PLF
            Element oldParent = uld.getElementById(oldParentNodeId);
            TabColumnPrefsHandler.moveElement(childElement, oldParent, owner);
            // fire event
            final int layoutId = this.getLayoutId();
            if (node instanceof IUserLayoutChannelDescription) {
                this.channelsAdded = true;
                final String fname = ((IUserLayoutChannelDescription) node).getFunctionalName();
                this.portalEventFactory.publishPortletMovedInLayoutPortalEvent(this, this.owner, layoutId,
                        oldParentNodeId, parent.getId(), fname);
            } else {
                this.portalEventFactory.publishFolderMovedInLayoutPortalEvent(this, this.owner, layoutId,
                        oldParentNodeId, parent.getId());
            }
            return true;
        }
        return false;
    }

    public boolean deleteNode(String nodeId) throws PortalException {
        if (canDeleteNode(nodeId)) {
            IUserLayoutNodeDescription nodeDescription = this.getNode(nodeId);
            String parentNodeId = this.getParentId(nodeId);

            Document uld = this.getUserLayoutDOM();
            Element ilfNode = uld.getElementById(nodeId);
            Node parent = ilfNode.getParentNode();
            if (parent != null) {
                parent.removeChild(ilfNode);
            } else {
                throw new PortalException("Node \"" + nodeId + "\" has a NULL parent for layout of "
                        + owner.getAttribute(IPerson.USERNAME) + ".");
            }
            this.updateCacheKey();

            // now push into the PLF
            TabColumnPrefsHandler.deleteNode(ilfNode, (Element) parent, owner);
            // inform the listeners
            final int layoutId = this.getLayoutId();
            if (nodeDescription instanceof IUserLayoutChannelDescription) {
                final IUserLayoutChannelDescription userLayoutChannelDescription = (IUserLayoutChannelDescription) nodeDescription;
                this.portalEventFactory.publishPortletDeletedFromLayoutPortalEvent(this, this.owner, layoutId,
                        parentNodeId, userLayoutChannelDescription.getFunctionalName());
            } else {
                this.portalEventFactory.publishFolderDeletedFromLayoutPortalEvent(this, this.owner, layoutId,
                        parentNodeId, nodeDescription.getId(), nodeDescription.getName());
            }

            return true;
        }
        return false;
    }

    /**
     * Handles pushing changes made to the passed-in node into the user's layout.
     * If the node is an ILF node then the change is recorded via directives in
     * the PLF if such changes are allowed by the owning fragment. If the node
     * is a user owned node then the changes are applied directly to the corresponding node
     * in the PLF. 
     */
    public synchronized boolean updateNode(IUserLayoutNodeDescription node) throws PortalException {
        if (canUpdateNode(node)) {
            String nodeId = node.getId();
            IUserLayoutNodeDescription oldNode = getNode(nodeId);

            if (oldNode instanceof IUserLayoutChannelDescription) {
                IUserLayoutChannelDescription oldChanDesc = (IUserLayoutChannelDescription) oldNode;
                if (!(node instanceof IUserLayoutChannelDescription)) {
                    throw new PortalException(
                            "Change channel to folder is " + "not allowed by updateNode() method! Occurred "
                                    + "in layout for " + owner.getAttribute(IPerson.USERNAME) + ".");
                }
                IUserLayoutChannelDescription newChanDesc = (IUserLayoutChannelDescription) node;
                updateChannelNode(nodeId, newChanDesc, oldChanDesc);
            } else {
                // must be a folder
                IUserLayoutFolderDescription oldFolderDesc = (IUserLayoutFolderDescription) oldNode;
                if (oldFolderDesc.getId().equals(getRootFolderId()))
                    throw new PortalException("Update of root node is not currently allowed!");

                if (node instanceof IUserLayoutFolderDescription) {
                    IUserLayoutFolderDescription newFolderDesc = (IUserLayoutFolderDescription) node;
                    updateFolderNode(nodeId, newFolderDesc, oldFolderDesc);
                }
            }
            this.updateCacheKey();
            return true;
        }
        return false;
    }

    /**
     * Compares the new folder description object with the old folder 
     * description object to determine what items were changed and if those
     * changes are allowed. Once all changes are verified as being allowed
     * changes then they are pushed into both the ILF and the PLF as
     * appropriate. No changes are made until we determine that all changes are
     * allowed.
     * 
     * @param nodeId
     * @param newFolderDesc
     * @param oldFolderDesc
     * @throws PortalException
     */
    private void updateFolderNode(String nodeId, IUserLayoutFolderDescription newFolderDesc,
            IUserLayoutFolderDescription oldFolderDesc) throws PortalException {
        Element ilfNode = (Element) getUserLayoutDOM().getElementById(nodeId);
        List<ILayoutProcessingAction> pendingActions = new ArrayList<ILayoutProcessingAction>();

        /*
         * see what structure attributes changed if any and see if allowed.
         * 
         * CHANNEL ATTRIBUTES that currently can be EDITED in DLM are:
         * name - in both fragments and regular layouts
         * dlm:moveAllowed - only on fragments 
         * dlm:editAllowed - only on fragments 
         * dlm:deleteAllowed - only on fragments 
         * dlm:addChildAllowed - only on fragments
         */

        // ATT: DLM Restrictions
        if (isFragmentOwner && (newFolderDesc.isDeleteAllowed() != oldFolderDesc.isDeleteAllowed()
                || newFolderDesc.isEditAllowed() != oldFolderDesc.isEditAllowed()
                || newFolderDesc.isAddChildAllowed() != oldFolderDesc.isAddChildAllowed()
                || newFolderDesc.isMoveAllowed() != oldFolderDesc.isMoveAllowed())) {
            pendingActions.add(new LPAEditRestriction(owner, ilfNode, newFolderDesc.isMoveAllowed(),
                    newFolderDesc.isDeleteAllowed(), newFolderDesc.isEditAllowed(),
                    newFolderDesc.isAddChildAllowed()));
        }

        // ATT: Name
        updateNodeAttribute(ilfNode, nodeId, Constants.ATT_NAME, newFolderDesc.getName(), oldFolderDesc.getName(),
                pendingActions);

        /*
         * if we make it to this point then all edits made are allowed so
         * process the actions to push the edits into the layout
         */
        for (Iterator itr = pendingActions.iterator(); itr.hasNext();) {
            ILayoutProcessingAction action = (ILayoutProcessingAction) itr.next();
            action.perform();
        }
    }

    /**
     * Handles checking for updates to a named attribute, verifying such change
     * is allowed, and generates an action object to make that change.
     * 
     * @param ilfNode the node in the viewed layout
     * @param nodeId the id of the ilfNode
     * @param attName the attribute to be checked
     * @param newVal the attribute's new value
     * @param oldVal the attribute's old value
     * @param pendingActions the set of actions for adding an action 
     * @throws PortalException if the change is not allowed
     */
    private void updateNodeAttribute(Element ilfNode, String nodeId, String attName, String newVal, String oldVal,
            List<ILayoutProcessingAction> pendingActions) throws PortalException {
        if (newVal == null && oldVal != null || newVal != null && oldVal == null
                || (newVal != null && oldVal != null && !newVal.equals(oldVal))) {
            boolean isIncorporated = nodeId.startsWith(Constants.FRAGMENT_ID_USER_PREFIX);
            if (isIncorporated) {
                /*
                 * Is a change to this attribute allowed?
                 */
                FragmentNodeInfo fragNodeInf = this.distributedLayoutStore.getFragmentNodeInfo(nodeId);
                if (fragNodeInf == null) {
                    /*
                     * null should only happen if a node was deleted in the
                     * fragment and a user happened to already be logged in and
                     * edited an attribute on that node.
                     */
                    pendingActions
                            .add(new LPAChangeAttribute(nodeId, attName, newVal, owner, ilfNode, isFragmentOwner));
                } else if (!fragNodeInf.canOverrideAttributes()) {
                    /*
                     * It isn't overrideable.
                     */
                    throw new PortalException("Layout element '" + fragNodeInf.getAttributeValue(attName)
                            + "' does not allow overriding attribute '" + attName + "'.");
                } else if (!fragNodeInf.getAttributeValue(attName).equals(newVal)) {
                    /*
                     * If we get here we can override and the value is 
                     * different than that in the fragment so make the change.
                     */
                    pendingActions
                            .add(new LPAChangeAttribute(nodeId, attName, newVal, owner, ilfNode, isFragmentOwner));
                } else {
                    /*
                     * The new value matches that in the fragment. 
                     */
                    pendingActions.add(new LPAResetAttribute(nodeId, attName,
                            fragNodeInf.getAttributeValue(attName), owner, ilfNode));
                }
            } else {
                /*
                 * Node owned by user so no checking needed. Just change it.
                 */
                pendingActions
                        .add(new LPAChangeAttribute(nodeId, attName, newVal, owner, ilfNode, isFragmentOwner));
            }
        }
    }

    /**
     * Compares the new channel description object with the old channel
     * description object to determine what items were changed and if those
     * changes are allowed. Once all changes are verified as being allowed
     * changes then they are pushed into both the ILF and the PLF as
     * appropriate. No changes are made until we determine that all changes are
     * allowed.
     * 
     * @param nodeId
     * @param newChanDesc
     * @param oldChanDesc
     * @throws PortalException
     */
    private void updateChannelNode(String nodeId, IUserLayoutChannelDescription newChanDesc,
            IUserLayoutChannelDescription oldChanDesc) throws PortalException {
        Element ilfNode = (Element) getUserLayoutDOM().getElementById(nodeId);
        List<ILayoutProcessingAction> pendingActions = new ArrayList<ILayoutProcessingAction>();
        boolean isIncorporated = nodeId.startsWith(Constants.FRAGMENT_ID_USER_PREFIX);

        /*
         * see what structure attributes changed if any and see if allowed.
         * 
         * CHANNEL ATTRIBUTES that currently can be EDITED in DLM are:
         * dlm:moveAllowed - only on fragments 
         * dlm:editAllowed - only on fragments 
         * dlm:deleteAllowed - only on fragments 
         */

        // ATT: DLM Restrictions
        if (isFragmentOwner && (newChanDesc.isDeleteAllowed() != oldChanDesc.isDeleteAllowed()
                || newChanDesc.isEditAllowed() != oldChanDesc.isEditAllowed()
                || newChanDesc.isMoveAllowed() != oldChanDesc.isMoveAllowed())) {
            pendingActions.add(new LPAEditRestriction(owner, ilfNode, newChanDesc.isMoveAllowed(),
                    newChanDesc.isDeleteAllowed(), newChanDesc.isEditAllowed(), newChanDesc.isAddChildAllowed()));
        }

        // ATT: other? if other attributes should be editable in DLM on channels
        // we can add calls like this to enable such support.
        //  updateNodeAttribute(ilfNode, nodeId, "hidden", 
        //     newChanDesc.getName(), oldChanDesc.getName(), pendingActions);

        /*
         * now we loop through all parameters in the new channel description and
         * see if there is a corresponding parameter in the old channel
         * description and see if the change is allowed. For each allowed change
         * we add an object that will make such a change once all changes have
         * been approved. As we find matches in the old channel description we
         * remove those parameters. Then any left there after processing those
         * of the new channel description indicate parameters that were removed.
         */
        FragmentChannelInfo fragChanInf = null;
        Map pubParms = getPublishedChannelParametersMap(newChanDesc.getChannelPublishId());

        if (isIncorporated)
            fragChanInf = this.distributedLayoutStore.getFragmentChannelInfo(nodeId);
        Map oldParms = new HashMap(oldChanDesc.getParameterMap());
        for (Iterator itr = newChanDesc.getParameterMap().entrySet().iterator(); itr.hasNext();) {
            Map.Entry e = (Entry) itr.next();
            String name = (String) e.getKey();
            String newVal = (String) e.getValue();
            String oldVal = (String) oldParms.remove(name);

            if (oldVal == null) {
                /*
                 * not in old description so this is a new ad-hoc parameter
                 */
                pendingActions.add(new LPAAddParameter(nodeId, name, newVal, owner, ilfNode));
            } else if (!oldVal.equals(newVal)) {
                if (isIncorporated) {
                    /*
                     * if the fragment does not have a value for this parm then
                     * this is an ad-hoc value and we need a directive to
                     * persist the user's desired value. if the frament does
                     * have a value and it is the same as the new value then we
                     * can remove the override since it won't accomplish
                     * anything. if the fragment does have a value and it is
                     * different then we need the directive to persist the
                     * user's desired value.
                     */
                    String fragValue = fragChanInf.getParameterValue(name);

                    if (fragValue == null) {
                        /*
                         * so fragment doesn't override. See if the value
                         * specified matches that of the channel definition
                         */
                        IPortletDefinitionParameter cp = (IPortletDefinitionParameter) pubParms.get(name);

                        if (cp != null && cp.getValue().equals(newVal))
                            /*
                             * new value matches that of published channel to
                             * remove any user parameter spec since not needed
                             */
                            pendingActions.add(new LPARemoveParameter(nodeId, name, owner, ilfNode));
                        else
                            /*
                             * value doesn't match that of published chanel so
                             * we need change any existing parameter spec or add
                             * a new one if it doesn't exist.
                             */
                            pendingActions.add(new LPAChangeParameter(nodeId, name, newVal, owner, ilfNode));
                    } else if (!fragValue.equals(newVal)) {
                        /*
                         * so fragment does specify and user value is different
                         * so change any existing parameter spec or add a new
                         * one if it doesn't exist.
                         */
                        pendingActions.add(new LPAChangeParameter(nodeId, name, newVal, owner, ilfNode));
                    } else {
                        /*
                         * new val same as fragment value so don't persist.
                         * remove any parameter spec if it exists.
                         */
                        pendingActions.add(new LPAResetParameter(nodeId, name, fragValue, owner, ilfNode));
                    }
                } else // not incorporated from a fragment 
                {
                    /*
                     * see if the value specified matches that of the channel
                     * definition.
                     */
                    IPortletDefinitionParameter cp = (IPortletDefinitionParameter) pubParms.get(name);

                    if (cp != null && cp.getValue().equals(newVal))
                        pendingActions.add(new LPARemoveParameter(nodeId, name, owner, ilfNode));
                    else
                        pendingActions.add(new LPAChangeParameter(nodeId, name, newVal, owner, ilfNode));
                }
            }
        }
        /*
         * So any parameters remaining in the oldParms map at this point didn't
         * match those in the new channel description which means that they were
         * removed. So remove any parameter spec if it exists.
         */
        for (Iterator itr = oldParms.entrySet().iterator(); itr.hasNext();) {
            Map.Entry e = (Entry) itr.next();
            String name = (String) e.getKey();
            pendingActions.add(new LPARemoveParameter(nodeId, name, owner, ilfNode));
        }
        /*
         * if we make it to this point then all edits made are allowed so
         * process the actions to push the edits into the layout
         */
        for (Iterator itr = pendingActions.iterator(); itr.hasNext();) {
            ILayoutProcessingAction action = (ILayoutProcessingAction) itr.next();
            action.perform();
        }
    }

    /**
     * Return a map parameter names to channel parameter objects representing
     * the parameters specified at publish time for the channel with the
     * passed-in publish id.
     * 
     * @param channelPublishId
     * @return
     * @throws PortalException
     */
    private Map getPublishedChannelParametersMap(String channelPublishId) throws PortalException {
        try {
            IPortletDefinitionRegistry registry = PortletDefinitionRegistryLocator.getPortletDefinitionRegistry();
            IPortletDefinition def = registry.getPortletDefinition(channelPublishId);
            return def.getParametersAsUnmodifiableMap();
        } catch (Exception e) {
            throw new PortalException("Unable to acquire channel definition.", e);
        }
    }

    public boolean canAddNode(IUserLayoutNodeDescription node, String parentId, String nextSiblingId)
            throws PortalException {
        return this.canAddNode(node, this.getNode(parentId), nextSiblingId);
    }

    protected boolean canAddNode(IUserLayoutNodeDescription node, IUserLayoutNodeDescription parent,
            String nextSiblingId) throws PortalException {
        // make sure sibling exists and is a child of nodeId
        if (nextSiblingId != null && !nextSiblingId.equals("")) {
            IUserLayoutNodeDescription sibling = getNode(nextSiblingId);
            if (sibling == null) {
                throw new PortalException("Unable to find a sibling node " + "with id=\"" + nextSiblingId
                        + "\".  Occurred " + "in layout for " + owner.getAttribute(IPerson.USERNAME) + ".");
            }
            if (!parent.getId().equals(getParentId(nextSiblingId))) {
                throw new PortalException("Given sibling (\"" + nextSiblingId
                        + "\") is not a child of a given parentId (\"" + parent.getId() + "\"). Occurred "
                        + "in layout for " + owner.getAttribute(IPerson.USERNAME) + ".");
            }
        }

        // todo if isFragmentOwner should probably verify both node and parent are part of the
        // same layout fragment as the fragment owner to insure a misbehaving front-end doesn't
        // do an improper operation.

        if (parent == null || !(node.isMoveAllowed() || isFragmentOwner))
            return false;

        if (parent instanceof IUserLayoutFolderDescription
                && !(((IUserLayoutFolderDescription) parent).isAddChildAllowed()) && !isFragmentOwner)
            return false;

        if (nextSiblingId == null || nextSiblingId.equals("")) // end of list targeted
            return true;

        // so lets see if we can place it at the end of the sibling list and
        // hop left until we get into the correct position.

        Enumeration sibIds = getVisibleChildIds(parent.getId());
        List sibs = Collections.list(sibIds);

        if (sibs.size() == 0) // last node in list so should be ok
            return true;

        // reverse scan so that as changes are made the order of the, as yet,
        // unprocessed nodes is not altered.
        for (int idx = sibs.size() - 1; idx >= 0; idx--) {
            IUserLayoutNodeDescription prev = getNode((String) sibs.get(idx));

            if (!isFragmentOwner && !MovementRules.canHopLeft(node, prev))
                return false;
            if (prev.getId().equals(nextSiblingId))
                return true;
        }
        return false; // oops never found the sib
    }

    public boolean canMoveNode(String nodeId, String parentId, String nextSiblingId) throws PortalException {
        return this.canMoveNode(this.getNode(nodeId), this.getNode(parentId), nextSiblingId);
    }

    protected boolean canMoveNode(IUserLayoutNodeDescription node, IUserLayoutNodeDescription parent,
            String nextSiblingId) throws PortalException {
        // todo if isFragmentOwner should probably verify both node and parent are part of the
        // same layout fragment as the fragment owner to insure a misbehaving front-end doesn't
        // do an improper operation.

        // are we moving to a new parent?
        if (!getParentId(node.getId()).equals(parent.getId()))
            return (isFragmentOwner || node.isMoveAllowed()) && canAddNode(node, parent, nextSiblingId);

        // same parent. which direction are we moving?
        Document uld = this.getUserLayoutDOM();
        Element parentE = uld.getElementById(parent.getId());
        Element child = (Element) parentE.getFirstChild();
        int idx = 0;
        int nodeIdx = -1;
        int sibIdx = -1;

        while (child != null) {
            String id = child.getAttribute(Constants.ATT_ID);
            if (id.equals(node.getId()))
                nodeIdx = idx;
            if (id.equals(nextSiblingId))
                sibIdx = idx;
            idx++;
            child = (Element) child.getNextSibling();
        }
        if (nodeIdx == -1 || // couldn't find node
                (nextSiblingId != null && sibIdx == -1)) // couldn't find sibling
            return false;

        if (nodeIdx < sibIdx || // moving right
                sibIdx == -1) // appending to end
            return canMoveRight(node.getId(), nextSiblingId);
        return canMoveLeft(node.getId(), nextSiblingId);
    }

    private boolean canMoveRight(String nodeId, String targetNextSibId) throws PortalException {
        IUserLayoutNodeDescription node = getNode(nodeId);
        Enumeration sibIds = getVisibleChildIds(getParentId(nodeId));
        List sibs = Collections.list(sibIds);

        for (int idx = sibs.indexOf(nodeId) + 1; idx > 0 && idx < sibs.size(); idx++) {
            String nextSibId = (String) sibs.get(idx);
            IUserLayoutNodeDescription next = getNode(nextSibId);

            if (nextSibId != null && next.getId().equals(targetNextSibId))
                return true;
            else if (!isFragmentOwner && !MovementRules.canHopRight(node, next))
                return false;
        }

        if (targetNextSibId == null) // made it to end of sib list and
            return true; // that is the desired location
        return false; // oops never found the sib. Should never happen.
    }

    private boolean canMoveLeft(String nodeId, String targetNextSibId) throws PortalException {
        IUserLayoutNodeDescription node = getNode(nodeId);
        Enumeration sibIds = getVisibleChildIds(getParentId(nodeId));
        List sibs = Collections.list(sibIds);

        for (int idx = sibs.indexOf(nodeId) - 1; idx >= 0; idx--) {
            String prevSibId = (String) sibs.get(idx);
            IUserLayoutNodeDescription prev = getNode(prevSibId);

            if (!isFragmentOwner && !MovementRules.canHopLeft(node, prev))
                return false;
            if (targetNextSibId != null && prev.getId().equals(targetNextSibId))
                return true;
        }
        return false; // oops never found the sib
    }

    public boolean canDeleteNode(String nodeId) throws PortalException {
        return canDeleteNode(this.getNode(nodeId));
    }

    /**
       Returns true if the node exists in the underlying
       DOM model and it does not contain a 'deleteAllowed' attribute with a
       value of 'false'.
     */
    protected boolean canDeleteNode(IUserLayoutNodeDescription node) throws PortalException {
        if (node == null)
            return false;

        // todo if isFragmentOwner should probably verify node is part of the
        // same layout fragment as the fragment owner to insure a misbehaving front-end doesn't
        // do an improper operation.

        return isFragmentOwner || node.isDeleteAllowed();
    }

    public boolean canUpdateNode(String nodeId) throws PortalException {
        return canUpdateNode(this.getNode(nodeId));
    }

    /**
     * Returns true if we are dealing with a fragment layout or if editing of
     * attributes is allowed, or the node is a channel since ad-hoc parameters
     * can always be added.
     */
    public boolean canUpdateNode(IUserLayoutNodeDescription node) {
        if (node == null)
            return false;

        return isFragmentOwner || node.isEditAllowed() || node instanceof IUserLayoutChannelDescription;
    }

    /**
     * Unsupported operation in DLM. This feature is handled by pluggable 
     * processors in the DLM processing pipe. See properties/dlmContext.xml.
     */
    public void markAddTargets(IUserLayoutNodeDescription node) {
        throw new UnsupportedOperationException("Use an appropriate " + "processor for adding targets.");
    }

    /**
     * Unsupported operation in DLM. This feature is handled by pluggable 
     * processors in the DLM processing pipe. See properties/dlmContext.xml.
     */
    public void markMoveTargets(String nodeId) throws PortalException {
        throw new UnsupportedOperationException("Use an appropriate " + "processor for adding targets.");
    }

    public String getParentId(String nodeId) throws PortalException {
        Document uld = this.getUserLayoutDOM();
        Element nelement = uld.getElementById(nodeId);
        if (nelement != null) {
            Node parent = nelement.getParentNode();
            if (parent != null) {
                if (parent.getNodeType() != Node.ELEMENT_NODE) {
                    throw new PortalException(
                            "Node with id=\"" + nodeId + "\" is attached to something other then an element node.");
                }
                Element e = (Element) parent;
                return e.getAttribute("ID");
            }
            return null;
        }
        throw new PortalException("Node with id=\"" + nodeId + "\" doesn't exist. Occurred in layout for "
                + owner.getAttribute(IPerson.USERNAME) + ".");
    }

    public String getNextSiblingId(String nodeId) throws PortalException {
        Document uld = this.getUserLayoutDOM();
        Element nelement = uld.getElementById(nodeId);
        if (nelement != null) {
            Node nsibling = nelement.getNextSibling();
            // scroll to the next element node
            while (nsibling != null && nsibling.getNodeType() != Node.ELEMENT_NODE) {
                nsibling = nsibling.getNextSibling();
            }
            if (nsibling != null) {
                Element e = (Element) nsibling;
                return e.getAttribute("ID");
            }
            return null;
        }
        throw new PortalException("Node with id=\"" + nodeId + "\" doesn't exist. Occurred " + "in layout for "
                + owner.getAttribute(IPerson.USERNAME) + ".");
    }

    public String getPreviousSiblingId(String nodeId) throws PortalException {
        Document uld = this.getUserLayoutDOM();
        Element nelement = uld.getElementById(nodeId);
        if (nelement != null) {
            Node nsibling = nelement.getPreviousSibling();
            // scroll to the next element node
            while (nsibling != null && nsibling.getNodeType() != Node.ELEMENT_NODE) {
                nsibling = nsibling.getNextSibling();
            }
            if (nsibling != null) {
                Element e = (Element) nsibling;
                return e.getAttribute("ID");
            }
            return null;
        }
        throw new PortalException("Node with id=\"" + nodeId + "\" doesn't exist. Occurred in layout for "
                + owner.getAttribute(IPerson.USERNAME) + ".");
    }

    public Enumeration<String> getChildIds(String nodeId) throws PortalException {
        return getChildIds(nodeId, false);
    }

    private Enumeration<String> getVisibleChildIds(String nodeId) throws PortalException {
        return getChildIds(nodeId, true);
    }

    private Enumeration<String> getChildIds(String nodeId, boolean visibleOnly) throws PortalException {
        Vector<String> v = new Vector<String>();
        IUserLayoutNodeDescription node = getNode(nodeId);
        if (node instanceof IUserLayoutFolderDescription) {
            Document uld = this.getUserLayoutDOM();
            Element felement = uld.getElementById(nodeId);
            for (Node n = felement.getFirstChild(); n != null; n = n.getNextSibling()) {
                if (n.getNodeType() == Node.ELEMENT_NODE && (visibleOnly == false || (visibleOnly == true
                        && ((Element) n).getAttribute(Constants.ATT_HIDDEN).equals("false")))) {
                    Element e = (Element) n;
                    if (e.getAttribute("ID") != null) {
                        v.add(e.getAttribute("ID"));
                    }
                }
            }
        }
        return v.elements();
    }

    @Override
    public String getCacheKey() {
        return this.cacheKey;
    }

    /**
     * This is outright cheating! We're supposed to analyze the user layout tree
     * and return a key that corresponds uniquely to the composition and the structure of the tree.
     * Here we just return a different key whenever anything changes. So if one was to move a
     * node back and forth, the key would always never (almost) come back to the original value,
     * even though the changes to the user layout are cyclic.
     *
     */
    private void updateCacheKey() {
        this.cacheKey = Long.toString(rnd.nextLong());
    }

    public int getLayoutId() {
        return profile.getLayoutId();
    }

    /**
     * Returns the subscribe ID of a channel having the passed in functional
     * name or null if it can't find such a channel in the layout.
     */
    @Override
    public String getSubscribeId(String fname) {
        final Document userLayout = this.getUserLayoutDOM();
        return new PortletSubscribeIdResolver(fname).traverseDocument(userLayout);
    }

    public String getSubscribeId(String parentFolderId, String fname) {
        final Map<String, String> variables = new HashMap<String, String>();
        variables.put("parentFolderId", parentFolderId);
        variables.put("fname", fname);

        final Document userLayout = this.getUserLayoutDOM();
        final Element fnameNode = this.xpathOperations.evaluate(
                "//folder[@ID=$parentFolderId]/descendant::channel[@fname=$fname]", variables, userLayout,
                XPathConstants.NODE);
        if (fnameNode != null) {
            return fnameNode.getAttribute("ID");
        }

        return null;
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.layout.IUserLayoutManager#getUserLayout()
     */
    public IUserLayout getUserLayout() throws PortalException {
        // Copied from SimpleLayoutManager since our layouts are regular
        // simple layouts, ie Documents.
        return new SimpleLayout(this.getDistributedUserLayout(), String.valueOf(profile.getLayoutId()),
                this.cacheKey);
    }

    /* Returns the ID attribute of the root folder of the layout. This folder 
     * is defined to be the single child of the top most "layout" Element.
     * 
     * @see org.apereo.portal.layout.IUserLayoutManager#getRootFolderId()
     * @see org.apereo.portal.layout.dlm.RootLocator
     */
    public String getRootFolderId() {
        if (rootNodeId == null) {
            Document layout = getUserLayoutDOM();

            Element rootNode = this.xpathOperations.evaluate("//layout/folder", layout, XPathConstants.NODE);
            if (rootNode == null || !rootNode.getAttribute(Constants.ATT_TYPE).equals(Constants.ROOT_FOLDER_ID)) {
                LOG.error("Unable to locate root node in layout of " + owner.getAttribute(IPerson.USERNAME)
                        + ". Resetting corrupted layout.");
                resetLayout((String) null);

                rootNode = this.xpathOperations.evaluate("//layout/folder", layout, XPathConstants.NODE);
                if (rootNode == null
                        || !rootNode.getAttribute(Constants.ATT_TYPE).equals(Constants.ROOT_FOLDER_ID)) {
                    throw new PortalException("Corrupted layout detected for "
                            + owner.getAttribute(IPerson.USERNAME) + " and resetting layout failed.");
                }
            }
            rootNodeId = rootNode.getAttribute("ID");
        }
        return rootNodeId;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apereo.portal.layout.IUserLayoutManager#getDepth(java.lang.String)
     */
    public int getDepth(String nodeId) throws PortalException {
        // can't see what it calling this anywhere so ignoring for now.
        // TODO waiting to hear back from peter/michael
        return 0;
    }

    /* Return an implementation of IUserLayoutNodeDescription appropriate for
     * the type of node indicated. Currently, the only two types supported are
     * IUserLayoutNodeDescription.FOLDER and LayoutNodeType.PORTLET.
     * 
     * @see org.apereo.portal.layout.IUserLayoutManager#createNodeDescription(int)
     */
    @Override
    public IUserLayoutNodeDescription createNodeDescription(LayoutNodeType nodeType) throws PortalException {
        if (nodeType == LayoutNodeType.FOLDER) {
            return new UserLayoutFolderDescription();
        }
        return new ChannelDescription();
    }

    /**
     * Resets the layout of the user with the specified user id if the current
     * user is an administrator or a member of any administrative sub-group.
     * Has no effect if these requirements are not met.
     * 
     * @return true if layout was reset, false otherwise.
     * 
     * @param loginId
     */
    public boolean resetLayout(String loginId) {
        boolean resetSuccess = false;
        boolean resetCurrentUserLayout = (null == loginId);

        if (resetCurrentUserLayout || (!resetCurrentUserLayout && AdminEvaluator.isAdmin(owner))) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Reset layout requested for user with id " + loginId + ".");
            }
            int portalID = IPerson.UNDEFINED_ID;
            IPerson person = null;

            if (resetCurrentUserLayout || loginId.equals(owner.getAttribute(IPerson.USERNAME))) {
                person = owner;
                portalID = owner.getID();
            } else {
                // need to get the portal id
                person = PersonFactory.createPerson();
                person.setAttribute(IPerson.USERNAME, loginId);

                try {
                    IUserIdentityStore userStore = UserIdentityStoreLocator.getUserIdentityStore();
                    portalID = userStore.getPortalUID(person);
                    person.setID(portalID);
                } catch (Exception e) {
                    // ignore since the store will log the problem
                }
            }
            if (portalID != IPerson.UNDEFINED_ID) {
                resetSuccess = resetLayout(person);
            }
        } else {
            LOG.error("Layout reset requested for user " + loginId + " by " + owner.getID()
                    + " who is not an administrative user.");
        }
        return resetSuccess;
    }

    /**
     * Resets the layout of the specified user.
     */
    private boolean resetLayout(IPerson person) {
        final String userName = person.getUserName();
        if (PersonFactory.GUEST_USERNAMES.contains(userName)) {
            throw new IllegalArgumentException("CANNOT RESET LAYOUT FOR A GUEST USER: " + person);
        }
        LOG.warn("Resetting user layout for: " + userName, new Throwable());

        boolean layoutWasReset = false;

        /*
         * is the person being reset a fragment owner? Can't use the
         * isFramentOwner variable in this class since we could be resetting 
         * another user's layout.
         */
        if (this.distributedLayoutStore.isFragmentOwner(person)) {
            // set template user override so reload of layout comes from
            // fragment template user
            person.setAttribute(org.apereo.portal.Constants.TEMPLATE_USER_NAME_ATT,
                    FragmentDefinition.getDefaultLayoutOwnerId());
        }
        IUserIdentityStore userStore = UserIdentityStoreLocator.getUserIdentityStore();

        try {
            userStore.removePortalUID(person.getID());
            userStore.getPortalUID(person, true);

            // see if the current user was the one to reset their layout and if
            // so we need to refresh our local copy of their layout
            if (person == owner) {
                this.layoutCachingService.removeCachedLayout(person, profile);
                updateCacheKey();
                getUserLayoutDOM();
            }
            //if (isFragmentOwner)
            //{
            //    
            //    store.updateOwnerLayout(person);
            //}
            layoutWasReset = true;
        } catch (Exception e) {
            LOG.error("Unable to reset layout for " + person.getAttribute(IPerson.USERNAME) + ".", e);
        }
        return layoutWasReset;
    }

    public IUserLayoutNodeDescription createNodeDescription(Element node) throws PortalException {
        String type = node.getNodeName();
        if (type.equals(Constants.ELM_CHANNEL)) {
            return new ChannelDescription(node);
        } else if (type.equals(Constants.ELM_FOLDER)) {
            return new UserLayoutFolderDescription(node);
        } else {
            throw new PortalException("Given XML Element is not a channel!");
        }
    }

    /**
     * Return a map of channel identifiers to functional names, for those 
     * channels that have functional names.
     */
    public Map getChannelFunctionalNameMap() throws PortalException {
        Document layout = getUserLayoutDOM();

        /*
         * NodeLists are known not to be thread safe but the layout is 
         * hierarchical and this is the simples way to obtain all of the nested
         * channels. Furthermore, since this method is only called by jndi
         * initialization once in a user's session and hence should be just
         * fine. Furthermore, this NodeList is not that of the children of
         * a node in the layout so it is unlikely that it will change.
         */
        NodeList channelNodes = layout.getElementsByTagName("channel");
        Map<String, String> map = new HashMap<String, String>();

        // Parse through the channels and populate the set
        for (int i = 0; i < channelNodes.getLength(); i++) {
            // Attempt to get the fname and instance ID from the channel
            Element chan = (Element) channelNodes.item(i);
            String id = chan.getAttribute("ID");
            String fname = chan.getAttribute("fname");
            if (!id.equals("") && !fname.equals("")) {
                map.put(id, fname);
            }
        }
        return map;
    }

    /**
     * Returns the IPerson that is the owner of this layout manager instance.
     * @return IPerson object
     */
    IPerson getOwner() {
        return owner;
    }

}