/**
* 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; }
}