/*
* CommunityBrowser.java
*
* Version: $Revision: 3705 $
*
* Date: $Date: 2009-04-11 17:02:24 +0000 (Sat, 11 Apr 2009) $
*
* Copyright (c) 2002, Hewlett-Packard Company and Massachusetts
* Institute of Technology. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - 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.
*
* - Neither the name of the Hewlett-Packard Company nor the name of the
* Massachusetts Institute of Technology nor the names of their
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS 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 COPYRIGHT
* HOLDERS OR 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.dspace.app.xmlui.aspect.artifactbrowser;
import java.io.IOException;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Stack;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.util.HashUtil;
import org.apache.excalibur.source.SourceValidity;
import org.apache.log4j.Logger;
import org.dspace.app.xmlui.cocoon.AbstractDSpaceTransformer;
import org.dspace.app.xmlui.utils.DSpaceValidity;
import org.dspace.app.xmlui.utils.UIException;
import org.dspace.app.xmlui.wing.Message;
import org.dspace.app.xmlui.wing.WingException;
import org.dspace.app.xmlui.wing.element.Body;
import org.dspace.app.xmlui.wing.element.Division;
import org.dspace.app.xmlui.wing.element.ReferenceSet;
import org.dspace.app.xmlui.wing.element.List;
import org.dspace.app.xmlui.wing.element.Reference;
import org.dspace.app.xmlui.wing.element.PageMeta;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.DSpaceObject;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
import org.dspace.core.LogManager;
import org.xml.sax.SAXException;
/**
* Display a list of Communities and collections.
*
* This item may be configured so that it will only display to a specific depth,
* and may include or exclude collections from the tree.
*
* The configuration option available: <depth exclude-collections="true">999</depth>
*
* @author Scott Phillips
*/
public class CommunityBrowser extends AbstractDSpaceTransformer implements CacheableProcessingComponent
{
private static Logger log = Logger.getLogger(CommunityBrowser.class);
/** Language Strings */
public static final Message T_dspace_home =
message("xmlui.general.dspace_home");
public static final Message T_title =
message("xmlui.ArtifactBrowser.CommunityBrowser.title");
public static final Message T_trail =
message("xmlui.ArtifactBrowser.CommunityBrowser.trail");
public static final Message T_head =
message("xmlui.ArtifactBrowser.CommunityBrowser.head");
public static final Message T_select =
message("xmlui.ArtifactBrowser.CommunityBrowser.select");
/** Should collections be excluded from the list */
protected boolean excludeCollections = false;
/** The default depth if one is not provided by the sitemap */
private static final int DEFAULT_DEPTH = 999;
/** What depth is the maximum depth of the tree */
protected int depth = DEFAULT_DEPTH;
/** Cached version the community / collection hierarch */
protected TreeNode root;
/** cached validity object */
private SourceValidity validity;
/**
* Set the component up, pulling any configuration values from the sitemap
* parameters.
*/
public void setup(SourceResolver resolver, Map objectModel, String src,
Parameters parameters) throws ProcessingException, SAXException,
IOException
{
super.setup(resolver, objectModel, src, parameters);
depth = parameters.getParameterAsInteger("depth", DEFAULT_DEPTH);
excludeCollections = parameters.getParameterAsBoolean(
"exclude-collections", false);
}
/**
* Generate the unique caching key.
* This key must be unique inside the space of this component.
*/
public Serializable getKey()
{
boolean full = ConfigurationManager.getBooleanProperty("xmlui.community-list.render.full", true);
return HashUtil.hash(depth + "-" + excludeCollections + "-" + (full ? "true" : "false"));
}
/**
* Generate the cache validity object.
*
* The validity object will include a list of all communities
* & collection being browsed along with there logo bitstreams.
*/
public SourceValidity getValidity()
{
if (validity == null)
{
try {
DSpaceValidity validity = new DSpaceValidity();
TreeNode root = buildTree(Community.findAllTop(context));
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.push(root);
int objectCount = 0;
while (!stack.empty())
{
objectCount++;
TreeNode node = stack.pop();
validity.add(node.getDSO());
for (TreeNode child : node.getChildren())
{
stack.push(child);
}
}
// Check if we are configured to assume validity.
String assumeCacheValidity = ConfigurationManager.getProperty("xmlui.community-list.cache");
if (assumeCacheValidity != null)
validity.setAssumedValidityDelay(assumeCacheValidity);
this.validity = validity.complete();
}
catch (SQLException sqle)
{
// ignore all errors and return an invalid cache.
}
log.info(LogManager.getHeader(context, "view_community_list", ""));
}
return this.validity;
}
/**
* Add a page title and trail links.
*/
public void addPageMeta(PageMeta pageMeta) throws SAXException,
WingException, UIException, SQLException, IOException,
AuthorizeException
{
// Set the page title
pageMeta.addMetadata("title").addContent(T_title);
pageMeta.addTrailLink(contextPath + "/",T_dspace_home);
pageMeta.addTrail().addContent(T_trail);
}
/**
* Add a community-browser division that includes refrences to community and
* collection metadata.
*/
public void addBody(Body body) throws SAXException, WingException,
UIException, SQLException, IOException, AuthorizeException
{
Division division = body.addDivision("comunity-browser", "primary");
division.setHead(T_head);
division.addPara(T_select);
TreeNode root = buildTree(Community.findAllTop(context));
boolean full = ConfigurationManager.getBooleanProperty("xmlui.community-list.render.full", true);
if (full)
{
ReferenceSet referenceSet = division.addReferenceSet("community-browser",
ReferenceSet.TYPE_SUMMARY_LIST,null,"hierarchy");
ArrayList<TreeNode> rootNodes = root.getChildrenOfType(Constants.COMMUNITY);
for (TreeNode node : rootNodes)
{
buildReferenceSet(referenceSet,node);
}
}
else
{
List list = division.addList("comunity-browser");
ArrayList<TreeNode> rootNodes = root.getChildrenOfType(Constants.COMMUNITY);
for (TreeNode node : rootNodes)
{
buildList(list,node);
}
}
}
/**
* Recursively build an includeset of the community / collection hierarcher based upon
* the given NodeTree.
*
* @param referenceSet The include set
* @param node The current node of the hierarch.
*/
public void buildReferenceSet(ReferenceSet referenceSet, TreeNode node) throws WingException
{
DSpaceObject dso = node.getDSO();
Reference objectInclude = referenceSet.addReference(dso);
// Add all the sub-collections;
ArrayList<TreeNode> collectionNodes = node.getChildrenOfType(Constants.COLLECTION);
if (collectionNodes != null && collectionNodes.size() > 0)
{
ReferenceSet collectionSet = objectInclude.addReferenceSet(ReferenceSet.TYPE_SUMMARY_LIST);
for (TreeNode collectionNode : collectionNodes)
{
collectionSet.addReference(collectionNode.getDSO());
}
}
// Add all the sub-communities
ArrayList<TreeNode> communityNodes = node.getChildrenOfType(Constants.COMMUNITY);
if (communityNodes != null && communityNodes.size() > 0)
{
ReferenceSet communitySet = objectInclude.addReferenceSet(ReferenceSet.TYPE_SUMMARY_LIST);
for (TreeNode communityNode : communityNodes)
{
buildReferenceSet(communitySet,communityNode);
}
}
}
/**
* Recursively build a list of the community / collection hierarchery based upon
* the given NodeTree.
*
* @param List The parent list
* @param node The current node of the hierarch.
*/
public void buildList(List list, TreeNode node) throws WingException
{
DSpaceObject dso = node.getDSO();
String name = null;
if (dso instanceof Community)
name = ((Community) dso).getMetadata("name");
else if (dso instanceof Collection)
name = ((Collection) dso).getMetadata("name");
String url = contextPath + "/handle/"+dso.getHandle();
list.addItem().addHighlight("bold").addXref(url, name);
List subList = null;
// Add all the sub-collections;
ArrayList<TreeNode> collectionNodes = node.getChildrenOfType(Constants.COLLECTION);
if (collectionNodes != null && collectionNodes.size() > 0)
{
subList = list.addList("sub-list-"+dso.getID());
for (TreeNode collectionNode : collectionNodes)
{
String collectionName = ((Collection) collectionNode.getDSO()).getMetadata("name");
String collectionUrl = contextPath + "/handle/"+collectionNode.getDSO().getHandle();
subList.addItemXref(collectionUrl, collectionName);
}
}
// Add all the sub-communities
ArrayList<TreeNode> communityNodes = node.getChildrenOfType(Constants.COMMUNITY);
if (communityNodes != null && communityNodes.size() > 0)
{
if (subList == null)
subList = list.addList("sub-list-"+dso.getID());
for (TreeNode communityNode : communityNodes)
{
buildList(subList,communityNode);
}
}
}
/**
* recycle
*/
public void recycle()
{
this.root = null;
this.validity = null;
super.recycle();
}
/**
* construct a tree structure of communities and collections. The results
* of this hirarchy are cached so calling it multipletimes is acceptable.
*
* @param communities The root level communities
* @return A root level node.
*/
private TreeNode buildTree(Community[] communities) throws SQLException
{
if (root != null)
return root;
TreeNode newRoot = new TreeNode();
// Setup for breath-first traversal
Stack<TreeNode> stack = new Stack<TreeNode>();
for (Community community : communities)
stack.push(newRoot.addChild(community));
while (!stack.empty())
{
TreeNode node = stack.pop();
// Short circuit if we have reached our max depth.
if (node.getLevel() >= this.depth)
continue;
// Only communities nodes are pushed on the stack.
Community community = (Community) node.getDSO();
for (Community subcommunity : community.getSubcommunities())
{
stack.push(node.addChild(subcommunity));
}
// Add any collections to the document.
if (!excludeCollections)
{
for (Collection collection : community.getCollections())
{
node.addChild(collection);
}
}
}
this.root = newRoot;
return root;
}
/**
* Private class to represent the tree structure of communities & collections.
*/
protected static class TreeNode
{
/** The object this node represents */
private DSpaceObject dso;
/** The level in the hirarchy that this node is at. */
private int level;
/** All children of this node */
private ArrayList<TreeNode> children = new ArrayList<TreeNode>();
/**
* Construct a new root level node
*/
public TreeNode()
{
// Root level node is add the zero level.
this.level = 0;
}
/**
* @return The DSpaceObject this node represents
*/
public DSpaceObject getDSO()
{
return this.dso;
}
/**
* Add a child DSpaceObject
*
* @param dso The child
* @return A new TreeNode object attached to the tree structure.
*/
public TreeNode addChild(DSpaceObject dso)
{
TreeNode child = new TreeNode();
child.dso = dso;
child.level = this.level + 1;
children.add(child);
return child;
}
/**
* @return The current level in the hirarchy of this node.
*/
public int getLevel()
{
return this.level;
}
/**
* @return All children
*/
public ArrayList<TreeNode> getChildren()
{
return children;
}
/**
* @return All children of the given @type.
*/
public ArrayList<TreeNode> getChildrenOfType(int type)
{
ArrayList<TreeNode> results = new ArrayList<TreeNode>();
for (TreeNode node : children)
{
if (node.dso.getType() == type)
results.add(node);
}
return results;
}
}
}