/** * Copyright © 2002 The JA-SIG Collaborative. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by the JA-SIG Collaborative * (http://www.jasig.org/)." * * THIS SOFTWARE IS PROVIDED BY THE JA-SIG COLLABORATIVE "AS IS" AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE JA-SIG COLLABORATIVE OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * */ package org.jasig.portal.layout; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Enumeration; import java.util.Random; import java.util.Set; import java.util.Vector; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXResult; import org.apache.xpath.XPathAPI; import org.jasig.portal.IUserLayoutStore; import org.jasig.portal.PortalException; import org.jasig.portal.UserProfile; import org.jasig.portal.EntityIdentifier; import org.jasig.portal.security.IPerson; import org.jasig.portal.security.IAuthorizationPrincipal; import org.jasig.portal.services.AuthorizationService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jasig.portal.utils.DocumentFactory; import org.jasig.portal.utils.IPortalDocument; import org.jasig.portal.utils.XSLT; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.ContentHandler; /** * An implementation of a user layout manager that uses 2.0-release store implementations. * * @author Peter Kharchenko * @version $Revision: 1.32 $ */ public class SimpleUserLayoutManager implements IUserLayoutManager { private static final Log log = LogFactory.getLog(SimpleUserLayoutManager.class); protected final IPerson owner; protected final UserProfile profile; protected IUserLayoutStore store=null; protected Set listeners=new HashSet(); protected IUserLayout userLayout = null; protected Document userLayoutDocument=null; protected Document markedUserLayout=null; protected static Random rnd=new Random(); protected String cacheKey="initialKey"; protected String rootNodeId; private boolean dirtyState=false; // marking mode variables private String markingMode=null; // null means markings are turned off private String markingNode; // The names for marking nodes private static final String ADD_COMMAND = "add"; private static final String MOVE_COMMAND = "move"; // marking stylesheet private static final String MARKING_XSLT_URI="/org/jasig/portal/layout/MarkUserLayout.xsl"; public SimpleUserLayoutManager(IPerson owner, UserProfile profile, IUserLayoutStore store) throws PortalException { if(owner==null) { throw new PortalException("A non-null owner needs to be specified."); } if(profile==null) { throw new PortalException("A non-null profile needs to be specified."); } this.owner=owner; this.profile=profile; this.rootNodeId = null; this.setLayoutStore(store); this.loadUserLayout(); this.markingMode=null; this.markingNode=null; } public IUserLayout getUserLayout() throws PortalException { // Temporary until we use IUserLayout for real return new SimpleLayout(String.valueOf(profile.getLayoutId()), this.userLayoutDocument); } 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.userLayoutDocument=doc; this.markedUserLayout=null; this.updateCacheKey(); } private void setUserLayoutDOM(Document doc) { this.userLayoutDocument=doc; this.markedUserLayout=null; this.updateCacheKey(); } public Document getUserLayoutDOM() { return this.userLayoutDocument; } public void getUserLayout(ContentHandler ch) throws PortalException { Document ulm=this.getUserLayoutDOM(); if(ulm==null) { throw new PortalException("User layout has not been initialized"); } else { getUserLayout(ulm,ch); } } public void getUserLayout(String nodeId, ContentHandler ch) throws PortalException { Document ulm=this.getUserLayoutDOM(); if(ulm==null) { throw new PortalException("User layout has not been initialized"); } else { Node rootNode=ulm.getElementById(nodeId); if(rootNode==null) { throw new PortalException("A requested root node (with id=\""+nodeId+"\") is not in the user layout."); } else { getUserLayout(rootNode,ch); } } } protected void getUserLayout(Node n,ContentHandler ch) throws PortalException { // do a DOM2SAX transformation, invoking marking transformation if necessary try { if(markingMode!=null) { Hashtable stylesheetParams=new Hashtable(1); stylesheetParams.put("operation",markingMode); if(markingNode!=null) { stylesheetParams.put("targetId",markingNode); } XSLT xslt = XSLT.getTransformer(this); xslt.setXML(n); xslt.setTarget(ch); xslt.setStylesheetParameters(stylesheetParams); xslt.setXSL(MARKING_XSLT_URI); xslt.transform(); } else { Transformer emptyt=TransformerFactory.newInstance().newTransformer(); emptyt.transform(new DOMSource(n), new SAXResult(ch)); } } catch (Exception e) { throw new PortalException("Encountered an exception trying to output user layout",e); } } public void setLayoutStore(IUserLayoutStore store) { this.store=store; } protected IUserLayoutStore getLayoutStore() { return this.store; } public void loadUserLayout() throws PortalException { if(this.getLayoutStore()==null) { throw new PortalException("Store implementation has not been set."); } else { try { Document uli=this.getLayoutStore().getUserLayout(this.owner,this.profile); if(uli!=null) { this.setUserLayoutDOM(uli); clearDirtyFlag(); // inform listeners for(Iterator i=listeners.iterator();i.hasNext();) { LayoutEventListener lel=(LayoutEventListener)i.next(); lel.layoutLoaded(); } updateCacheKey(); } else { throw new PortalException("Null user layout returned for ownerId=\""+owner.getID()+"\", profileId=\""+profile.getProfileId()+"\", layoutId=\""+profile.getLayoutId()+"\""); } } catch (PortalException pe) { throw pe; } catch (Exception e) { throw new PortalException("Exception encountered while reading a layout for userId="+this.owner.getID()+", profileId="+this.profile.getProfileId(),e); } } } public void saveUserLayout() throws PortalException{ if(isLayoutDirty()) { Document ulm=this.getUserLayoutDOM(); if(ulm==null) { throw new PortalException("UserLayout has not been initialized."); } else { if(this.getLayoutStore()==null) { throw new PortalException("Store implementation has not been set."); } else { try { this.getLayoutStore().setUserLayout(this.owner,this.profile,ulm,true); // inform listeners for(Iterator i=listeners.iterator();i.hasNext();) { LayoutEventListener lel=(LayoutEventListener)i.next(); lel.layoutSaved(); } } catch (PortalException pe) { throw pe; } catch (Exception e) { throw new PortalException("Exception encountered while trying to save a layout for userId="+this.owner.getID()+", profileId="+this.profile.getProfileId(),e); } } } } } public IUserLayoutNodeDescription getNode(String nodeId) throws PortalException { Document ulm=this.getUserLayoutDOM(); if(ulm==null) { throw new PortalException("UserLayout has not been initialized."); } // find an element with a given id Element element = (Element) ulm.getElementById(nodeId); if(element==null) { throw new PortalException("Element with ID=\""+nodeId+"\" doesn't exist."); } return UserLayoutNodeDescription.createUserLayoutNodeDescription(element); } /** * Returns the depth of a node in the layout tree. * * @param nodeId a String value * @return a depth value * @exception PortalException if an error occurs */ public int getDepth(String nodeId) throws PortalException { int depth = 0; for (String parentId = getParentId(nodeId); parentId != null; parentId = getParentId(parentId), depth++); return depth; } 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 if(this.getLayoutStore()==null) { throw new PortalException("Store implementation has not been set."); } else { try { if(node instanceof IUserLayoutChannelDescription) { isChannel=true; node.setId(this.getLayoutStore().generateNewChannelSubscribeId(owner)); } else { node.setId(this.getLayoutStore().generateNewFolderId(owner)); } } catch (PortalException pe) { throw pe; } catch (Exception e) { throw new PortalException("Exception encountered while generating new usre layout node Id for userId="+this.owner.getID()); } } Document ulm=this.getUserLayoutDOM(); Element childElement=node.getXML(ulm); // If channel is editable, check if user has permission to edit this channel if (node instanceof IUserLayoutChannelDescription) { if (childElement.getAttribute("editable") == "true") { EntityIdentifier ei = owner.getEntityIdentifier(); IAuthorizationPrincipal ap = AuthorizationService.instance().newPrincipal(ei.getKey(),ei.getType()); // Remove edit option if user doesn't have access if (!ap.canEdit(Integer.parseInt(childElement.getAttribute("chanID")))) { childElement.setAttribute("editable", "false"); } } } Element parentElement=(Element)ulm.getElementById(parentId); if(nextSiblingId==null) { parentElement.appendChild(childElement); } else { Node nextSibling=ulm.getElementById(nextSiblingId); parentElement.insertBefore(childElement,nextSibling); } markLayoutDirty(); // register element id if (ulm instanceof IPortalDocument) { ((IPortalDocument)ulm).putIdentifier( node.getId(),childElement); } else { StringBuffer msg = new StringBuffer(128); msg.append("SimpleUserLayoutManager::addNode() : "); msg.append("User Layout does not implement IPortalDocument, "); msg.append("so element caching cannot be performed."); log.error( msg.toString()); } this.updateCacheKey(); this.clearMarkings(); // inform the listeners LayoutEvent ev=new LayoutEvent(this,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 void clearMarkings() { markingMode=null; markingNode=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 ulm=this.getUserLayoutDOM(); Element childElement=(Element)ulm.getElementById(nodeId); Element parentElement=(Element)ulm.getElementById(parentId); if(nextSiblingId==null) { parentElement.appendChild(childElement); } else { Node nextSibling=ulm.getElementById(nextSiblingId); parentElement.insertBefore(childElement,nextSibling); } markLayoutDirty(); clearMarkings(); updateCacheKey(); // inform the listeners boolean isChannel=false; if(node instanceof IUserLayoutChannelDescription) { isChannel=true; } LayoutMoveEvent ev=new LayoutMoveEvent(this,node,oldParentNodeId); for(Iterator i=listeners.iterator();i.hasNext();) { LayoutEventListener lel=(LayoutEventListener)i.next(); if(isChannel) { lel.channelMoved(ev); } else { lel.folderMoved(ev); } } return true; } else { return false; } } public boolean deleteNode(String nodeId) throws PortalException { if(canDeleteNode(nodeId)) { IUserLayoutNodeDescription nodeDescription=this.getNode(nodeId); String parentNodeId=this.getParentId(nodeId); Document ulm=this.getUserLayoutDOM(); Element childElement=(Element)ulm.getElementById(nodeId); Node parent=childElement.getParentNode(); if(parent!=null) { parent.removeChild(childElement); } else { throw new PortalException("Node \""+nodeId+"\" has a NULL parent !"); } markLayoutDirty(); // clearMarkings(); // this one is questionable this.updateCacheKey(); // inform the listeners boolean isChannel=false; if(nodeDescription instanceof IUserLayoutChannelDescription) { isChannel=true; } LayoutMoveEvent ev=new LayoutMoveEvent(this,nodeDescription,parentNodeId); for(Iterator i=listeners.iterator();i.hasNext();) { LayoutEventListener lel=(LayoutEventListener)i.next(); if(isChannel) { lel.channelDeleted(ev); } else { lel.folderDeleted(ev); } } return true; } else { return false; } } public String getRootFolderId() { try { if ( rootNodeId == null ) { Element rootNode = (Element) XPathAPI.selectSingleNode(this.getUserLayoutDOM(),"//layout/folder"); rootNodeId = rootNode.getAttribute("ID"); } } catch ( Exception e ) { log.error( e); } return rootNodeId; } public synchronized boolean updateNode(IUserLayoutNodeDescription node) throws PortalException { boolean isChannel=false; if(canUpdateNode(node)) { // normally here, one would determine what has changed // but we'll just make sure that the node type has not // changed and then regenerate the node Element from scratch, // and attach any children it might have had to it. String nodeId=node.getId(); String nextSiblingId=getNextSiblingId(nodeId); Element nextSibling=null; if(nextSiblingId!=null) { Document ulm=this.getUserLayoutDOM(); nextSibling=ulm.getElementById(nextSiblingId); } IUserLayoutNodeDescription oldNode=getNode(nodeId); if(oldNode instanceof IUserLayoutChannelDescription) { IUserLayoutChannelDescription oldChannel=(IUserLayoutChannelDescription) oldNode; if(node instanceof IUserLayoutChannelDescription) { isChannel=true; Document ulm=this.getUserLayoutDOM(); // generate new XML Element Element newChannelElement=node.getXML(ulm); // If channel is editable, check if user has permission to edit this channel if (newChannelElement.getAttribute("editable") == "true") { EntityIdentifier ei = owner.getEntityIdentifier(); IAuthorizationPrincipal ap = AuthorizationService.instance().newPrincipal(ei.getKey(),ei.getType()); // Remove edit attribute if user doesn't have access if (!ap.canEdit(Integer.parseInt(newChannelElement.getAttribute("chanID")))) { newChannelElement.setAttribute("editable", "false"); } } Element oldChannelElement=(Element)ulm.getElementById(nodeId); Node parent=oldChannelElement.getParentNode(); parent.removeChild(oldChannelElement); parent.insertBefore(newChannelElement,nextSibling); // register new child instead if (ulm instanceof IPortalDocument) { ((IPortalDocument)ulm).putIdentifier( node.getId(),newChannelElement); } else { StringBuffer msg = new StringBuffer(128); msg.append("SimpleUserLayoutManager::updateNode() : "); msg.append("User Layout does not implement "); msg.append("IPortalDocument, "); msg.append("so element caching cannot be performed."); log.error( msg.toString()); } // inform the listeners LayoutEvent ev=new LayoutEvent(this,node); for(Iterator i=listeners.iterator();i.hasNext();) { LayoutEventListener lel=(LayoutEventListener)i.next(); lel.channelUpdated(ev); } } else { throw new PortalException("Change channel to folder is not allowed by updateNode() method!"); } } else { // must be a folder IUserLayoutFolderDescription oldFolder=(IUserLayoutFolderDescription) oldNode; if(node instanceof IUserLayoutFolderDescription) { Document ulm=this.getUserLayoutDOM(); // generate new XML Element Element newFolderElement=node.getXML(ulm); Element oldFolderElement=(Element)ulm.getElementById(nodeId); Node parent=oldFolderElement.getParentNode(); // move children Vector children=new Vector(); for(Node n=oldFolderElement.getFirstChild(); n!=null;n=n.getNextSibling()) { children.add(n); } for(int i=0;iString subscription id. */ public String getSubscribeId(String fname) throws PortalException { try { Element fnameNode = (Element) XPathAPI.selectSingleNode(this.getUserLayoutDOM(),"//channel[@fname=\'"+fname+"\']"); if(fnameNode!=null) { return fnameNode.getAttribute("ID"); } else { return null; } } catch (Exception e) { log.error( "SimpleUserLayoutManager::getSubcribeId() : encountered the following exception, while trying to identify subscribe channel id for the fname=\""+fname+"\" : "+e); e.printStackTrace(); return null; } } public boolean addLayoutEventListener(LayoutEventListener l) { return listeners.add(l); } public boolean removeLayoutEventListener(LayoutEventListener l) { return listeners.remove(l); } /** * A factory method to create an empty IUserLayoutNodeDescription instance * * @param nodeType a node type value * @return an IUserLayoutNodeDescription instance * @exception PortalException if the error occurs. */ public IUserLayoutNodeDescription createNodeDescription( int nodeType ) throws PortalException { switch ( nodeType ) { case IUserLayoutNodeDescription.FOLDER: return new UserLayoutFolderDescription(); case IUserLayoutNodeDescription.CHANNEL: return new UserLayoutChannelDescription(); default: return null; } } protected boolean isLayoutDirty() { return dirtyState; } private void markLayoutDirty() { dirtyState=true; } private void clearDirtyFlag() { dirtyState=false; } }