/* Copyright 2005 The JA-SIG Collaborative. All rights reserved.
* See license distributed with this file and
* available online at http://www.uportal.org/license.html
*/
package org.jasig.portal.layout.dlm;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
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 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.PortalException;
import org.jasig.portal.UserProfile;
import org.jasig.portal.layout.IUserLayout;
import org.jasig.portal.layout.node.IUserLayoutChannelDescription;
import org.jasig.portal.layout.node.IUserLayoutFolderDescription;
import org.jasig.portal.layout.IUserLayoutManager;
import org.jasig.portal.layout.node.IUserLayoutNodeDescription;
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.node.UserLayoutChannelDescription;
import org.jasig.portal.layout.node.UserLayoutFolderDescription;
import org.jasig.portal.layout.node.UserLayoutNodeDescription;
import org.jasig.portal.layout.simple.SimpleLayout;
import org.jasig.portal.security.IPerson;
import org.jasig.portal.utils.DocumentFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
/**
* A layout manager that provides layout control through
* layout fragments that are derived from regular portal user accounts.
*
* @author <a href="mailto:mboyd@sct.com">Mark Boyd</a>
* @version 1.0 $Revision: 1.7.2.1 $ $Date: 2005/09/13 18:31:52 $
* @since uPortal 2.5
*/
public class DistributedLayoutManager implements IUserLayoutManager
{
public static final String RCS_ID = "@(#) $Header: /home/cvs/jasig/portal/source/org/jasig/portal/layout/dlm/DistributedLayoutManager.java,v 1.7.2.1 2005/09/13 18:31:52 bjohnson Exp $";
private static final Log LOG = LogFactory.getLog(DistributedLayoutManager.class);
protected final IPerson owner;
protected final UserProfile profile;
protected IUserLayoutStore store=null;
protected Set listeners=new HashSet();
protected Document userLayoutDocument=null;
protected static Random rnd=new Random();
protected String cacheKey="initialKey";
protected String rootNodeId = null;
private boolean channelsAdded = false;
private boolean isFragment = false;
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.");
}
try
{
this.owner = owner;
this.profile = profile;
this.setLayoutStore(store);
this.loadUserLayout();
// 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.userLayoutDocument = doc;
this.updateCacheKey();
// determine if this is a layout fragment by looking at the layout node
// for a dlm:fragment attribute.
Element layout = this.userLayoutDocument.getDocumentElement();
Node attr = layout.getAttributeNodeNS( Constants.NS_URI,
Constants.LCL_FRAGMENT_NAME );
this.isFragment = attr != null;
}
public Document getUserLayoutDOM() {
return this.userLayoutDocument;
}
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)+".");
} else {
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)+".");
} else {
getUserLayout(rootNode,ch);
}
}
}
protected void getUserLayout(Node n,ContentHandler ch) throws PortalException {
// do a DOM2SAX transformation
try {
Transformer emptyt=TransformerFactory.newInstance().newTransformer();
emptyt.transform(new DOMSource(n), new SAXResult(ch));
} catch (Exception e) {
LOG.error("Encountered an exception trying to output "
+ "user layout for " + owner.getAttribute(IPerson.USERNAME)
+ ".", e);
throw new PortalException("Encountered an exception trying to "
+ "output user layout for "
+ owner.getAttribute(IPerson.USERNAME) + ".",e);
}
}
public void setLayoutStore(IUserLayoutStore store) {
this.store=store;
}
protected IUserLayoutStore getLayoutStore() {
return this.store;
}
public synchronized void loadUserLayout() throws PortalException {
IUserLayoutStore layoutStore = getLayoutStore();
if(layoutStore==null) {
throw new PortalException("Store implementation has not been "
+ "set for "
+ owner.getAttribute(IPerson.USERNAME) + ".");
} else {
Document uli= null;
try {
uli=layoutStore.getUserLayout(this.owner,this.profile);
} 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) {
this.setUserLayoutDOM(uli);
// 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) + ".");
} else {
IUserLayoutStore layoutStore = getLayoutStore();
if(layoutStore==null) {
throw new PortalException("Store implementation has not been set for "
+ owner.getAttribute(IPerson.USERNAME) + ".");
} else {
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 = (Element) uld.getElementById( nodeId );
if( element == null )
{
throw new PortalException("Element with ID=\"" + nodeId +
"\" doesn't exist for "
+ owner.getAttribute(IPerson.USERNAME) + "." );
}
return UserLayoutNodeDescription.createUserLayoutNodeDescription(element);
}
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) + ".");
} else {
try {
if(node instanceof IUserLayoutChannelDescription) {
isChannel=true;
node.setId(layoutStore.generateNewChannelSubscribeId(owner));
} else {
node.setId(layoutStore.generateNewFolderId(owner));
}
} catch (Exception e) {
throw new PortalException("Exception encountered while " +
"generating new user layout node Id for for "
+ owner.getAttribute(IPerson.USERNAME));
}
}
Document uld=this.userLayoutDocument;
Element childElement=node.getXML(this.getUserLayoutDOM());
Element parentElement=(Element)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,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=(Element)uld.getElementById(nodeId);
Element parentElement=(Element)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 = (Element) uld.getElementById(oldParentNodeId);
TabColumnPrefsHandler.moveElement( childElement,
oldParent,
owner );
// 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 uld=this.getUserLayoutDOM();
Element childElement=(Element)uld.getElementById(nodeId);
Node parent=childElement.getParentNode();
if(parent!=null) {
parent.removeChild(childElement);
} 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( childElement, (Element) parent,
owner );
// 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 synchronized boolean updateNode( IUserLayoutNodeDescription node )
throws PortalException
{
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 uld = this.userLayoutDocument;
nextSibling = uld.getElementById( nextSiblingId );
}
IUserLayoutNodeDescription oldNode = getNode( nodeId );
if( oldNode instanceof IUserLayoutChannelDescription )
{
IUserLayoutChannelDescription oldChannel=(IUserLayoutChannelDescription) oldNode;
if( node instanceof IUserLayoutChannelDescription )
{
Document uld = this.userLayoutDocument;
// generate new XML Element
Element newChannelElement = node.getXML(uld);
Element oldChannelElement = (Element) uld.getElementById( nodeId );
Node parent = oldChannelElement.getParentNode();
parent.removeChild( oldChannelElement );
parent.insertBefore( newChannelElement, nextSibling );
// register new child instead
newChannelElement.setIdAttribute(Constants.ATT_ID, true);
// inform the listeners
LayoutEvent ev=new LayoutEvent( this, node);
for( Iterator i=listeners.iterator(); i.hasNext(); )
{
LayoutEventListener lel=(LayoutEventListener)i.next();
lel.channelUpdated( ev );
}
pushChanDiffsIntoPlf( newChannelElement,
(IUserLayoutChannelDescription) node,
oldChannel );
}
else
{
throw new PortalException("Change channel to folder is " +
"not allowed by updateNode() method! Occurred " +
"in layout for "
+ owner.getAttribute(IPerson.USERNAME) + ".");
}
}
else
{
// must be a folder
UserLayoutFolderDescription oldFolder=(UserLayoutFolderDescription) oldNode;
if (oldFolder.getId().equals(getRootFolderId()))
throw new PortalException("Update of root node is not currently allowed!");
if( node instanceof IUserLayoutFolderDescription )
{
Document uld=this.userLayoutDocument;
// generate new XML Element
Element newFolderElement=node.getXML(uld);
Element oldFolderElement=(Element)uld.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; i<children.size(); i++ )
{
newFolderElement.appendChild((Node)children.get(i));
}
// replace the actual node
parent.removeChild(oldFolderElement);
parent.insertBefore(newFolderElement,nextSibling);
// register new child instead
newFolderElement.setIdAttribute(Constants.ATT_ID, true);
// inform the listeners
LayoutEvent ev=new LayoutEvent(this,node);
for(Iterator i=listeners.iterator();i.hasNext();) {
LayoutEventListener lel=(LayoutEventListener)i.next();
lel.folderUpdated(ev);
}
pushFolderDiffsIntoPlf( newFolderElement,
(UserLayoutFolderDescription) node,
oldFolder );
}
}
this.updateCacheKey();
return true;
}
else
{
return false;
}
}
private void pushFolderDiffsIntoPlf( Element element,
UserLayoutFolderDescription newF,
UserLayoutFolderDescription oldF )
throws PortalException
{
// currently the only elements that we expect users to change are:
// deleteAllowed, moveAllowed, editAllowed, and addChildAllowed (for
// fragment owners); name (for regular users).
if ( ! newF.getName().equals( oldF.getName() ) )
TabColumnPrefsHandler.editAttribute( element,
Constants.ATT_NAME,
owner );
if ( newF.isMoveAllowed() != oldF.isMoveAllowed() ||
newF.isEditAllowed() != oldF.isEditAllowed() ||
newF.isAddChildAllowed() != oldF.isAddChildAllowed() ||
newF.isDeleteAllowed() != oldF.isDeleteAllowed() )
TabColumnPrefsHandler.changeRestrictions( element,
newF.isMoveAllowed(),
newF.isEditAllowed(),
newF.isAddChildAllowed(),
newF.isDeleteAllowed(),
owner );
}
private void pushChanDiffsIntoPlf( Element element,
IUserLayoutChannelDescription newChan,
IUserLayoutChannelDescription oldChan )
throws PortalException
{
// currently the only elements that we expect users to change are:
// deleteAllowed and moveAllowed (for fragment owners); and child
// property elements.
if ( newChan.isMoveAllowed() != oldChan.isMoveAllowed() ||
newChan.isDeleteAllowed() != oldChan.isDeleteAllowed() )
TabColumnPrefsHandler.changeRestrictions( element,
newChan.isMoveAllowed(),
false,
false,
newChan.isDeleteAllowed(),
owner );
// now push changed param children into PLF
Element plfChan = TabColumnPrefsHandler.getPlfChannel( element,
owner );
Document root = plfChan.getOwnerDocument();
// TODO figure out how the line below is supposed to take place in
// the latest codebase via the interface rather than the implemention
// class.
UserLayoutChannelDescription newChanDef = (UserLayoutChannelDescription) newChan;
newChanDef.addParameterChildren( plfChan, root );
}
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) {
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 ) // 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 = (Element) 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 );
else
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 the node is a folder node and edits on the folder are
allowed or if the folder is a channel.
*/
public boolean canUpdateNode( IUserLayoutNodeDescription node )
{
if ( node == null )
return false;
if ( node instanceof UserLayoutFolderDescription )
return isFragment ||
( ( UserLayoutFolderDescription ) node ).isEditAllowed();
return true;
}
public void markAddTargets(IUserLayoutNodeDescription node) {
// we use the old prefs channel for now so ignore this.
// TODO add for conversion to integrated modes theme
// get all folders
this.updateCacheKey();
}
public void markMoveTargets(String nodeId) throws PortalException {
this.updateCacheKey();
}
public String getParentId(String nodeId) throws PortalException {
Document uld=this.getUserLayoutDOM();
Element nelement=(Element)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.");
} else {
Element e=(Element) parent;
return e.getAttribute("ID");
}
} else {
return null;
}
} else {
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=(Element)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");
} else {
return null;
}
} else {
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=(Element)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");
} else {
return null;
}
} else {
throw new PortalException("Node with id=\""+nodeId+
"\" doesn't exist. Occurred in layout for "
+ owner.getAttribute(IPerson.USERNAME) + ".");
}
}
public Enumeration getChildIds(String nodeId) throws PortalException {
return getChildIds( nodeId, false );
}
private Enumeration getVisibleChildIds(String nodeId)
throws PortalException
{
return getChildIds( nodeId, true );
}
private Enumeration getChildIds( String nodeId,
boolean visibleOnly)
throws PortalException
{
Vector v=new Vector();
IUserLayoutNodeDescription node=getNode(nodeId);
if(node instanceof UserLayoutFolderDescription) {
Document uld=this.getUserLayoutDOM();
Element felement=(Element)uld.getElementById(nodeId);
for(Node n=felement.getFirstChild(); n!=null;n=n.getNextSibling()) {
if( n.getNodeType()==Node.ELEMENT_NODE &&
( visibleOnly == false ||
( visibleOnly == true &&
((Element) n).getAttribute( Constants.ATT_HIDDEN )
.equals("false") ) ) )
{
Element e=(Element)n;
if(e.getAttribute("ID")!=null)
{
v.add(e.getAttribute("ID"));
}
}
}
}
return v.elements();
}
public String getCacheKey() {
return this.cacheKey;
}
/**
* This is outright cheating ! We're supposed to analyze the user layout tree
* and return a key that corresponds uniqly to the composition and the sturcture of the tree.
* Here we just return a different key wheneever anything changes. So if one was to move a
* node back and forth, the key would always never (almost) come back to the original value,
* even though the changes to the user layout are cyclic.
*
*/
private void updateCacheKey() {
this.cacheKey=Long.toString(rnd.nextLong());
}
public int getLayoutId() {
return profile.getLayoutId();
}
/**
* Returns the subscribe ID of a channel having the passed in functional
* name or null if it can't find such a channel in the layout.
*/
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");
} else {
return null;
}
} 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.userLayoutDocument);
}
/* (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.userLayoutDocument=doc;
//this.markedUserLayout=null;
this.updateCacheKey();
}
/* (non-Javadoc)
* @see org.jasig.portal.layout.IUserLayoutManager#getRootFolderId()
*/
public String getRootFolderId()
{
String expression = "//layout/folder";
try
{
if (rootNodeId == null)
{
XPathFactory fac = XPathFactory.newInstance();
XPath xpath = fac.newXPath();
Element rootNode = (Element) xpath.evaluate(expression, this
.getUserLayoutDOM(), XPathConstants.NODE);
rootNodeId = rootNode.getAttribute("ID");
}
} catch (XPathExpressionException e)
{
LOG.error("Unable to locate node with path " + expression +
" in layout of"
+ owner.getAttribute(IPerson.USERNAME) + ".", e);
}
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();
}
else
{
return new UserLayoutChannelDescription();
}
}
}