Package org.jasig.portal.layout.dlm

Source Code of org.jasig.portal.layout.dlm.DistributedLayoutManager

/**
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig 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:
*
* 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.jasig.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.jasig.portal.IUserIdentityStore;
import org.jasig.portal.IUserProfile;
import org.jasig.portal.PortalException;
import org.jasig.portal.events.IPortalLayoutEventFactory;
import org.jasig.portal.layout.IFolderLocalNameResolver;
import org.jasig.portal.layout.IUserLayout;
import org.jasig.portal.layout.IUserLayoutManager;
import org.jasig.portal.layout.IUserLayoutStore;
import org.jasig.portal.layout.PortletSubscribeIdResolver;
import org.jasig.portal.layout.node.IUserLayoutChannelDescription;
import org.jasig.portal.layout.node.IUserLayoutFolderDescription;
import org.jasig.portal.layout.node.IUserLayoutNodeDescription;
import org.jasig.portal.layout.node.IUserLayoutNodeDescription.LayoutNodeType;
import org.jasig.portal.layout.node.UserLayoutFolderDescription;
import org.jasig.portal.layout.simple.SimpleLayout;
import org.jasig.portal.portlet.om.IPortletDefinition;
import org.jasig.portal.portlet.om.IPortletDefinitionParameter;
import org.jasig.portal.portlet.registry.IPortletDefinitionRegistry;
import org.jasig.portal.security.AdminEvaluator;
import org.jasig.portal.security.IAuthorizationPrincipal;
import org.jasig.portal.security.IAuthorizationService;
import org.jasig.portal.security.IPerson;
import org.jasig.portal.security.PersonFactory;
import org.jasig.portal.security.provider.AuthorizationImpl;
import org.jasig.portal.spring.locator.PortletDefinitionRegistryLocator;
import org.jasig.portal.spring.locator.UserIdentityStoreLocator;
import org.jasig.portal.xml.XmlUtilities;
import org.jasig.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
* @version 1.0  $Revision$ $Date$
* @since uPortal 2.5
*/
public class DistributedLayoutManager implements IUserLayoutManager, IFolderLocalNameResolver, InitializingBean
{
    public static final String RCS_ID = "@(#) $Header$";
    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="initialKey";
    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 lightwieght userprofile for use in
        // in layout PLF loading
        owner.setAttribute(IUserProfile.USER_PROFILE, profile);
       
        this.owner = owner;
        this.profile = profile;
    }
   
    @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 exceot 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) + ".");
            }
        }

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

        if ( parent instanceof IUserLayoutFolderDescription &&
             ! ( (IUserLayoutFolderDescription) parent).isAddChildAllowed() )
            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 ( ! 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
    {
        // are we moving to a new parent?
        if ( ! getParentId( node.getId() ).equals( parent.getId() ) )
            return 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 ( ! 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 ( ! 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;

        return 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();
    }

    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 uniqly to the composition and the sturcture of the tree.
     * Here we just return a different key wheneever 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.jasig.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.jasig.portal.layout.IUserLayoutManager#getRootFolderId()
     * @see org.jasig.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.jasig.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.jasig.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_USERNAME.equals(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.jasig.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;
    }
    /**
     * Returns a resolver for local names. This layout manager supports this
     * feature itself and hence returns itself as the interface.
     *
     * @return
     */
    public IFolderLocalNameResolver getFolderNameResolver()
    {
       return this;
    }
   
    /**
     * Returns the localized name of a folder node or null if none is available.
     * This method also implements enforcement of user label overrides to
     * fragment folders purging those overrides if they are no longer allowed
     * or needed.
     */
    public String getFolderLabel(String nodeId)
    {
        IUserLayoutNodeDescription ndesc = getNode(nodeId);
        if (!(ndesc instanceof IUserLayoutFolderDescription))
            return null;
       
        IUserLayoutFolderDescription desc
            = (IUserLayoutFolderDescription) ndesc;
        boolean editAllowed = desc.isEditAllowed();
        String label = desc.getName();
        // assume user owned to begin with which means plfId equals nodeId
        String plfId = nodeId;

        if (nodeId.startsWith(
                org.jasig.portal.layout.dlm.Constants.FRAGMENT_ID_USER_PREFIX))
        {
            Document plf = RDBMDistributedLayoutStore.getPLF( owner );
            Element plfNode = plf.getElementById( nodeId );
            if (plfNode != null)
                plfId = plfNode.getAttribute(Constants.ATT_PLF_ID);
            else
                plfId = null; // no user mods exist for this node
        }
   
        return label;
    }
}
TOP

Related Classes of org.jasig.portal.layout.dlm.DistributedLayoutManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.