Package org.jasig.portal.layout.dlm

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

/**
* 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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Vector;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.portal.ChannelRegistryStoreFactory;
import org.jasig.portal.IChannelRegistryStore;
import org.jasig.portal.IUserIdentityStore;
import org.jasig.portal.PortalException;
import org.jasig.portal.UserIdentityStoreFactory;
import org.jasig.portal.UserPreferences;
import org.jasig.portal.UserProfile;
import org.jasig.portal.channel.IChannelDefinition;
import org.jasig.portal.channel.IChannelParameter;
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.LayoutEvent;
import org.jasig.portal.layout.LayoutEventListener;
import org.jasig.portal.layout.LayoutMoveEvent;
import org.jasig.portal.layout.dlm.processing.ProcessingPipe;
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.UserLayoutFolderDescription;
import org.jasig.portal.layout.simple.SimpleLayout;
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.PortalApplicationContextLocator;
import org.jasig.portal.utils.DocumentFactory;
import org.jasig.portal.utils.XML;
import org.springframework.context.ApplicationContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ContentHandler;

/**
* 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: 21847 $ $Date: 2010-10-01 15:25:47 -0500 (Fri, 01 Oct 2010) $
* @since uPortal 2.5
*/
public class DistributedLayoutManager implements IUserLayoutManager,
IFolderLocalNameResolver
{
    public static final String RCS_ID = "@(#) $Header$";
    private static final Log LOG = LogFactory.getLog(DistributedLayoutManager.class);

    protected final IPerson owner;
    protected final UserProfile profile;
    protected final ILayoutCachingService layoutCachingService;
    protected RDBMDistributedLayoutStore store=null;
    protected Set<LayoutEventListener> listeners=new HashSet<LayoutEventListener>();

    /**
     * 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 static Random rnd=new Random();
    protected String cacheKey="initialKey";
    protected String rootNodeId = null;

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

    /**
     * Holder of dlm context configuration bean factory. This is implemented
     * using the thread safe initialization-on-demand-holder idiom.
     */
    static class ContextHolder
    {
        public static IFolderLabelPolicy getLabelPolicy() {
            final ApplicationContext applicationContext = PortalApplicationContextLocator.getApplicationContext();
            if (applicationContext.containsBean(FOLDER_LABEL_POLICY)) {
                final IFolderLabelPolicy folderLabelPolicy = (IFolderLabelPolicy)applicationContext.getBean(FOLDER_LABEL_POLICY, IFolderLabelPolicy.class);
                return folderLabelPolicy;
            }

            return null;
        }
    }

    /**
     * The following variable contains the configured processing pipe which
     * conceptually sits between any class calling processLayoutParemeters() a
     * and getUserLayout() enabling pluggable URL handlers for layout activities
     * of custome renderign theme stylesheets.
     *
     * @see org.jasig.portal.layout.dlm.processing.ProcessingPipe
     */
    private ProcessingPipe processingPipe = null;

    public DistributedLayoutManager(IPerson owner, UserProfile profile,
            IUserLayoutStore store) 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.");
        }
       
        final ApplicationContext applicationContext = PortalApplicationContextLocator.getApplicationContext();
        this.layoutCachingService = (ILayoutCachingService)applicationContext.getBean("layoutCachingService", ILayoutCachingService.class);
       
        // Ensure a new layout gets loaded whenever a user logs in except for guest users
        if (!owner.isGuest()) {
            this.layoutCachingService.removeCachedLayout(owner, profile);
        }

        // cache the relatively lightwieght userprofile for use in
        // in layout PLF loading
        owner.setAttribute(UserProfile.USER_PROFILE, profile);
        try
        {

            this.owner = owner;
            this.profile = profile;
            this.setLayoutStore(store);
            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 LayoutEventListener()
            {
                public void channelAdded(LayoutEvent ev)
                {
                    channelsAdded = true;
                }

                public void channelUpdated(LayoutEvent ev)
                {
                    // ignore
                }

                public void channelMoved(LayoutMoveEvent ev)
                {
                    // ignore
                }

                public void channelDeleted(LayoutMoveEvent ev)
                {
                    // ignore
                }

                public void folderAdded(LayoutEvent ev)
                {
                    // ignore
                }

                public void folderUpdated(LayoutEvent ev)
                {
                    // ignore
                }

                public void folderMoved(LayoutMoveEvent ev)
                {
                    // ignore
                }

                public void folderDeleted(LayoutMoveEvent ev)
                {
                    // ignore
                }

                public void layoutLoaded()
                {
                    // ignore
                }

                public void layoutSaved()
                {
                    channelsAdded = false;
                }
            });
        } catch (Throwable e)
        {
            throw new PortalException(
                    "Unable to instantiate DistributedLayoutManager for "
                        + owner.getAttribute(IPerson.USERNAME)+".", e);
        }
    }

    private void setUserLayoutDOM(Document doc) {

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

        // determine if this is a layout fragment by looking at the root node
        // for a cp:fragment attribute.
        Element layout = (Element) doc.getDocumentElement();
        Node attr = layout.getAttributeNodeNS( Constants.NS_URI,
                                               Constants.LCL_FRAGMENT_NAME );
        this.isFragmentOwner = attr != null;
        /*
         * Handle inline migration of user layout folder labels into an I18N
         * store if an I18N label policy is in place.
         */
        final IFolderLabelPolicy labelPolicy = ContextHolder.getLabelPolicy();
        if (labelPolicy != null) {
            labelPolicy.coordinateFolderLabels(owner.getID(), isFragmentOwner, doc);
        }
       
    }
    private int domRequests = 0;

    /**
     * @deprecated
     * @return
     * @throws PortalException
     */
    @Deprecated
    public Document getUserLayoutDOM()
        throws PortalException
    {
        try
        {
            if (LOG.isDebugEnabled())
            {
                LOG.debug("domRequest: " + (domRequests++));
            }
            Document userLayoutDocument = this.layoutCachingService.getCachedLayout(owner, profile);
            if ( null == userLayoutDocument )
            {
                IUserLayoutStore layoutStore = getLayoutStore();
               
                if (LOG.isDebugEnabled())
                {
                    LOG.debug("Load from store for " +
                        owner.getAttribute(IPerson.USERNAME));
                }
                userLayoutDocument = layoutStore.getUserLayout(this.owner,this.profile);

                // DistributedLayoutManager shall gracefully remove channels
                // that the user isn't authorized to render from folders of type
                // 'header' and 'footer'.
                IAuthorizationService authServ = AuthorizationImpl.singleton();
                IAuthorizationPrincipal principal = authServ.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"))) {
                    // 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 {
                        int chanId = Integer.parseInt(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( userLayoutDocument );
            }
            return userLayoutDocument;
        }
        catch ( Exception ex )
        {
            throw new PortalException( ex );
        }
    }

    /**
     * Loads instances of handlers to assist with layout parameter processing
     * and layout rendering for the current user's layout.
     *
     */
    private void loadProcessingPipe()
    {
        final ApplicationContext applicationContext = PortalApplicationContextLocator.getApplicationContext();
        processingPipe = (ProcessingPipe)applicationContext.getBean(ProcessingPipe.PROCESSING_PIPE_BEAN_ID, ProcessingPipe.class);
        processingPipe.setResources(owner, this);
    }
   
    public void getUserLayout(ContentHandler ch) throws PortalException {
        Document ul=this.getUserLayoutDOM();
        if(ul==null) {
            throw new PortalException("User layout has not been initialized for "
                        + owner.getAttribute(IPerson.USERNAME)+".");
        } else {
            getUserLayout(ul,ch);
        }
    }

    public void getUserLayout(String nodeId, ContentHandler ch) throws PortalException {
        Document ul=this.getUserLayoutDOM();

        if(ul==null) {
            throw new PortalException("User layout has not been initialized for "
                        + owner.getAttribute(IPerson.USERNAME)+".");
        }
            Node rootNode=ul.getElementById(nodeId);
            if(rootNode==null) {
                throw new PortalException("A requested root node (with id=\""
                        + nodeId + "\") is not in the user layout for "
                        + owner.getAttribute(IPerson.USERNAME)+".");
        }
        getUserLayout(rootNode,ch);
    }

    protected void getUserLayout(Node n,ContentHandler ch) throws PortalException {
        if (LOG.isDebugEnabled())
        {
            LOG.debug("Layout event stream for "
                    + owner.getAttribute(IPerson.USERNAME)
                    + " starting.");
        }
        // do a DOM2SAX transformation
        Transformer xfrmr = null;
        xfrmr = getEmptyTransformer();

        try
        {
            ProcessingPipe pipe = getProcessingPipe();
            ch = pipe.getContentHandler(ch);
            xfrmr.transform(new DOMSource(n), new SAXResult(ch));
        }
        catch (Exception e)
        {
            if (LOG.isDebugEnabled())
            {
                LOG.debug("Layout event stream for "
                        + owner.getAttribute(IPerson.USERNAME)
                        + " incurred exception.", e);
            }
            // if exception occurs during rendering then the state held in the
            // pipe's processors will be messed up for the next rendering.
            // So force reloading of pipe.
            processingPipe = null;
            throw new PortalException("Unable to output user layout for "
                    + owner.getAttribute(IPerson.USERNAME)
                    + ". Resetting processing pipe.",e);
        }
        if (LOG.isDebugEnabled())
        {
            LOG.debug("Layout event stream for "
                    + owner.getAttribute(IPerson.USERNAME)
                    + " finished.");
        }
    }

    /**
     * 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 void setLayoutStore(IUserLayoutStore store) {
        this.store=(RDBMDistributedLayoutStore) store;
    }

    protected IUserLayoutStore getLayoutStore() {
        return this.store;
    }

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

    public synchronized void loadUserLayout(boolean reload) throws PortalException {
        IUserLayoutStore layoutStore = getLayoutStore();

        if(layoutStore==null) {
            throw new PortalException("Store implementation has not been "
                    + "set for "
                    + owner.getAttribute(IPerson.USERNAME) + ".");
        }
        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() + "\"");
        }
        try {
            if(uli!=null) {
                // inform listeners
                for(Iterator i=listeners.iterator();i.hasNext();) {
                    LayoutEventListener lel=(LayoutEventListener)i.next();
                    lel.layoutLoaded();
                }
            }
        } catch (Exception e) {
               throw new PortalException("Exception encountered contacting " +
                       "layout listeners of layout for userId=" +
                       this.owner.getID() + ", profileId=" +
                       this.profile.getProfileId() ,e);
        }
    }

    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) + ".");
        }
        IUserLayoutStore layoutStore = getLayoutStore();

        if(layoutStore==null) {
            throw new PortalException("Store implementation has not been set for "
                + owner.getAttribute(IPerson.USERNAME) + ".");
        }
        try {
            layoutStore.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);
        }

        try // inform listeners
        {
            for(Iterator i=listeners.iterator();i.hasNext();) {
                LayoutEventListener lel=(LayoutEventListener)i.next();
                lel.layoutSaved();
            }
        } catch (Exception e) {
            throw new PortalException("Exception encountered contacting " +
                    "layout listeners of layout for userId=" +
                    this.owner.getID() + ", profileId=" +
                    this.profile.getProfileId() ,e);
        }
    }

    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 = store.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
            IUserLayoutStore layoutStore = getLayoutStore();

            if(layoutStore==null) {
                throw new PortalException("Store implementation has not been set for "
                    + owner.getAttribute(IPerson.USERNAME) + ".");
            }
            try {
                    if(node instanceof IUserLayoutChannelDescription) {
                        isChannel=true;
                        node.setId(layoutStore.generateNewChannelSubscribeId(owner));
                    } else {
                        node.setId(layoutStore.generateNewFolderId(owner));

                    final IFolderLabelPolicy labelPolicy = ContextHolder.getLabelPolicy();
                    if (labelPolicy != null)
                    {
                        labelPolicy.addNodeLabel(node.getId(),
                                parentId, getUserLayoutDOM(), owner.getID(),
                                isFragmentOwner, node.getName());
                    }
                }
            } 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 );

            // inform the listeners
            LayoutEvent ev=new LayoutEvent(this,parent,node);
            for(Iterator i=listeners.iterator();i.hasNext();) {
                LayoutEventListener lel=(LayoutEventListener)i.next();
                if(isChannel) {
                    lel.channelAdded(ev);
                } else {
                    lel.folderAdded(ev);
                }
            }
            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 );
            // inform the listeners
            boolean isChannel=false;
            if(node instanceof IUserLayoutChannelDescription) {
                isChannel=true;
            }
            final IUserLayoutNodeDescription oldParentNode = this.getNode(oldParentNodeId);
            LayoutMoveEvent ev=new LayoutMoveEvent(this, parent, node, oldParentNode);
            for(Iterator i=listeners.iterator();i.hasNext();) {
                LayoutEventListener lel=(LayoutEventListener)i.next();
                if(isChannel) {
                    lel.channelMoved(ev);
                } else {
                    lel.folderMoved(ev);
                }
            }
            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
            boolean isChannel=false;
            if(nodeDescription instanceof IUserLayoutChannelDescription) {
                isChannel=true;
            }
            final IUserLayoutNodeDescription parentNode = this.getNode(parentNodeId);
            LayoutMoveEvent ev=new LayoutMoveEvent(this, null, nodeDescription, parentNode);
            for(Iterator i=listeners.iterator();i.hasNext();) {
                LayoutEventListener lel=(LayoutEventListener)i.next();
                if(isChannel) {
                    lel.channelDeleted(ev);
                } else {
                    lel.folderDeleted(ev);
                }
            }

            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);

                // inform the listeners
                final String parentNodeId = this.getParentId(nodeId);
                final IUserLayoutNodeDescription parentNode = this.getNode(parentNodeId);
                LayoutEvent ev = new LayoutEvent(this, parentNode, node);
                for (Iterator i = listeners.iterator(); i.hasNext();)
                {
                    LayoutEventListener lel = (LayoutEventListener) i.next();
                    lel.channelUpdated(ev);
                }
            }
            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);

                    // inform the listeners
                    final String parentNodeId = this.getParentId(nodeId);
                    final IUserLayoutNodeDescription parentNode = this.getNode(parentNodeId);
                    LayoutEvent ev=new LayoutEvent(this, parentNode, node);
                    for(Iterator i=listeners.iterator();i.hasNext();) {
                        LayoutEventListener lel=(LayoutEventListener)i.next();
                        lel.folderUpdated(ev);
                    }
                }
            }
            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 = store.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 = store.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))
            {
                /*
                 * changing value, is it allowed by the channel and by the
                 * fragment if this came from a fragment?
                 */
                if (!oldChanDesc.canOverrideParameter(name))
                    throw new PortalException("This instance of "
                            + oldChanDesc.getTitle()
                            + " does not allow overriding parameter "
                            + name);
                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
                         */
                        IChannelParameter cp =
                            (IChannelParameter) 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.
                     */
                    IChannelParameter cp =
                        (IChannelParameter) 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
        {
            IChannelRegistryStore crs = ChannelRegistryStoreFactory
                .getChannelRegistryStoreImpl();
            int pubId = Integer.parseInt(channelPublishId);
            IChannelDefinition def = crs.getChannelDefinition(pubId);
            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() {
        String compositeKey = getProcessingPipe().getCacheKey() + ":"
        + this.cacheKey;
        return compositeKey;
    }

    /**
     * Gets the current processing pipe instance or instantiates one if not
     * found due to starting up or occurrence of exceptions.
     * @return
     */
    private ProcessingPipe getProcessingPipe()
    {
        if (processingPipe == null)
        {
            loadProcessingPipe();
        }
        return processingPipe;
    }

    /**
     * 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.
     */
    public String getSubscribeId(String fname) {
        try
        {
                String expression = "//channel[@fname=\'"+fname+"\']";
                XPathFactory fac = XPathFactory.newInstance();
                XPath xpath = fac.newXPath();
                Element fnameNode = (Element) xpath.evaluate(expression, this
                        .getUserLayoutDOM(), XPathConstants.NODE);
                if(fnameNode!=null) {
                    return fnameNode.getAttribute("ID");
                }
        } catch (XPathExpressionException e)
        {
            LOG.error("Encountered exception while trying to identify " +
                    "subscribe channel id for the fname=\""+fname+"\"" +
                            " in layout of"
                            + owner.getAttribute(IPerson.USERNAME) + ".", e);
        }
        return null;
    }

    public boolean addLayoutEventListener(LayoutEventListener l) {
        return listeners.add(l);
    }
    public boolean removeLayoutEventListener(LayoutEventListener l) {
        return listeners.remove(l);
    }

    /* (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(String.valueOf(profile.getLayoutId()), this.getUserLayoutDOM());
    }

    /* (non-Javadoc)
     * @see org.jasig.portal.layout.IUserLayoutManager#setUserLayout(org.jasig.portal.layout.IUserLayout)
     */
    public void setUserLayout(IUserLayout userLayout) throws PortalException
    {
        // Temporary until we use IUserLayout for real
        Document doc = DocumentFactory.getNewDocument();
        try {
            userLayout.writeTo(doc);
        } catch (PortalException pe) {
        }
        //this.markedUserLayout=null;
        this.updateCacheKey();
        this.layoutCachingService.cacheLayout(owner, profile, doc);
    }

    /* 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 = RootLocator.getRootElement(layout);
            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: "
                        + XML.serializeNode(layout));
                resetLayout((String) null);
                rootNode = RootLocator.getRootElement(getUserLayoutDOM());
                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 IUserLayoutNodeDescription.CHANNEL.
     *
     * @see org.jasig.portal.layout.IUserLayoutManager#createNodeDescription(int)
     */
    public IUserLayoutNodeDescription createNodeDescription(int nodeType) throws PortalException
    {
        if (nodeType == IUserLayoutNodeDescription.FOLDER)
        {
            return new UserLayoutFolderDescription();
        }
        return new ChannelDescription();
    }

    /**
     * Handle layout specific parameters posted with the request.
     */
    public void processLayoutParameters(IPerson person,
            UserPreferences userPrefs, HttpServletRequest req)
            throws PortalException
    {
        try
        {
            getProcessingPipe().processParameters(userPrefs, req);
        } catch (Exception e)
        {
            throw new PortalException(e);
        }
    }

    /**
     * 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 = UserIdentityStoreFactory
                        .getUserIdentityStoreImpl();
                    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)
    {
        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 (store.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 = UserIdentityStoreFactory
            .getUserIdentityStoreImpl();

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

            final IFolderLabelPolicy labelPolicy = ContextHolder.getLabelPolicy();
            if (labelPolicy != null)
            {
                labelPolicy.purgeFolderLabels(person.getID(), isFragmentOwner);
            }
           
            // 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
        }
   
        final IFolderLabelPolicy labelPolicy = ContextHolder.getLabelPolicy();
        if (labelPolicy != null)
        {
            label = labelPolicy.getNodeLabel(nodeId,
                    plfId,
                    editAllowed,
                    this.owner.getID(),
                    this.isFragmentOwner,
                    label);
        }
        return label;
    }
}
TOP

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

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.