/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at * * http://www.dspace.org/license/ */ 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.browse.ItemCountException; import org.dspace.browse.ItemCounter; 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 hierarchy */ 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); while (!stack.empty()) { TreeNode node = stack.pop(); validity.add(node.getDSO()); // If we are configured to use collection strengths (i.e. item counts) then include that number in the validity. boolean useCache = ConfigurationManager.getBooleanProperty("webui.strengths.cache"); if (useCache) { try { //try to determine Collection size (i.e. # of items) int size = new ItemCounter(context).getCount(node.getDSO()); validity.add("size:"+size); } catch(ItemCountException e) { /* ignore */ } } 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 references 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"); java.util.List<TreeNode> rootNodes = root.getChildrenOfType(Constants.COMMUNITY); for (TreeNode node : rootNodes) { buildReferenceSet(referenceSet,node); } } else { List list = division.addList("comunity-browser"); java.util.List<TreeNode> rootNodes = root.getChildrenOfType(Constants.COMMUNITY); for (TreeNode node : rootNodes) { buildList(list,node); } } } /** * Recursively build an includeset of the community / collection hierarchy based upon * the given NodeTree. * * @param referenceSet The include set * @param node The current node of the hierarchy. */ public void buildReferenceSet(ReferenceSet referenceSet, TreeNode node) throws WingException { DSpaceObject dso = node.getDSO(); Reference objectInclude = referenceSet.addReference(dso); // Add all the sub-collections; java.util.List<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 java.util.List<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 hierarchy based upon * the given NodeTree. * * @param list The parent list * @param node The current node of the hierarchy. */ 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; java.util.List<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 java.util.List<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 hierarchy are cached so calling it multiple times 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 hierarchy that this node is at. */ private int level; /** All children of this node */ private java.util.List<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 hierarchy of this node. */ public int getLevel() { return this.level; } /** * @return All children */ public java.util.List<TreeNode> getChildren() { return children; } /** * @return All children of the given @type. */ public java.util.List<TreeNode> getChildrenOfType(int type) { java.util.List<TreeNode> results = new ArrayList<TreeNode>(); for (TreeNode node : children) { if (node.dso.getType() == type) { results.add(node); } } return results; } } }