/**
* Copyright © 2001 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;
import java.util.Date;
import java.util.Iterator;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.xpath.XPathAPI;
import org.jasig.portal.groups.IEntity;
import org.jasig.portal.groups.IEntityGroup;
import org.jasig.portal.groups.IGroupMember;
import org.jasig.portal.properties.PropertiesManager;
import org.jasig.portal.security.IAuthorizationPrincipal;
import org.jasig.portal.security.IPermission;
import org.jasig.portal.security.IPerson;
import org.jasig.portal.security.IUpdatingPermissionManager;
import org.jasig.portal.services.AuthorizationService;
import org.jasig.portal.services.GroupService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.portal.services.StatsRecorder;
import org.jasig.portal.utils.CommonUtils;
import org.jasig.portal.utils.DocumentFactory;
import org.jasig.portal.utils.ResourceLoader;
import org.jasig.portal.utils.SmartCache;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* Manages the channel registry which is a listing of published channels
* that one can subscribe to (add to their layout).
* Also currently manages the channel types data and CPD documents.
* (maybe these should be managed by another class -Ken)
* @author Ken Weiner, kweiner@unicon.net
* @version $Revision: 1.48 $
*/
public class ChannelRegistryManager {
private static final Log log = LogFactory.getLog(ChannelRegistryManager.class);
protected static final IChannelRegistryStore crs = ChannelRegistryStoreFactory.getChannelRegistryStoreImpl();
// Cache timeout properties
protected static final int registryCacheTimeout = PropertiesManager.getPropertyAsInt("org.jasig.portal.ChannelRegistryManager.channel_registry_cache_timeout");
protected static final int chanTypesCacheTimeout = PropertiesManager.getPropertyAsInt("org.jasig.portal.ChannelRegistryManager.channel_types_cache_timeout");
protected static final int cpdCacheTimeout = PropertiesManager.getPropertyAsInt("org.jasig.portal.ChannelRegistryManager.cpd_cache_timeout");
// I18n propertiy
protected static final boolean localeAware = PropertiesManager.getPropertyAsBoolean("org.jasig.portal.i18n.LocaleManager.locale_aware");
// Caches
protected static final SmartCache channelRegistryCache = new SmartCache(registryCacheTimeout);
protected static final SmartCache channelTypesCache = new SmartCache(chanTypesCacheTimeout);
protected static final SmartCache cpdCache = new SmartCache(cpdCacheTimeout);
// Cache keys
private static final String CHANNEL_REGISTRY_CACHE_KEY = "channelRegistryCacheKey";
private static final String CHANNEL_TYPES_CACHE_KEY = "channelTypesCacheKey";
private static final String CPD_CACHE_KEY = "cpdCacheKey";
// Permission constants
private static final String FRAMEWORK_OWNER = "UP_FRAMEWORK";
private static final String SUBSCRIBER_ACTIVITY = "SUBSCRIBE";
private static final String GRANT_PERMISSION_TYPE = "GRANT";
/**
* Returns a copy of the channel registry as a Document.
* This document is not created according to a user's channel permissions.
* For a filtered list, see getChannelRegistry(IPerson person)
* @return a copy of the channel registry as a Document
*/
public static Document getChannelRegistry() throws PortalException {
Document channelRegistry = (Document)channelRegistryCache.get(CHANNEL_REGISTRY_CACHE_KEY);
if (channelRegistry == null) {
// Channel registry has expired, so get it and cache it
try {
channelRegistry = getChannelRegistryXML();
} catch (Exception e) {
throw new PortalException(e);
}
if (channelRegistry != null) {
channelRegistryCache.put(CHANNEL_REGISTRY_CACHE_KEY, channelRegistry);
log.info( "Caching channel registry.");
}
}
// Clone the original registry document so that it doesn't get modified
return (Document)channelRegistry.cloneNode(true);
}
/**
* Returns the channel registry as a Document. This document is filtered
* according to a user's channel permissions.
* @return the filtered channel registry as a Document
*/
public static Document getChannelRegistry(IPerson person, String activity) throws PortalException {
Document channelRegistry = getChannelRegistry();
// Filter the channel registry according to permissions
EntityIdentifier ei = person.getEntityIdentifier();
IAuthorizationPrincipal ap = AuthorizationService.instance().newPrincipal(ei.getKey(), ei.getType());
// Cycle through all the channels, looking for restricted channels
NodeList nl = channelRegistry.getElementsByTagName("channel");
for (int i = (nl.getLength()-1); i >=0; i--) {
Element channel = (Element)nl.item(i);
String channelPublishId = channel.getAttribute("chanID");
channelPublishId = channelPublishId.startsWith("chan") ? channelPublishId.substring(4) : channelPublishId;
// Take out channels which user doesn't have access to
if (activity == IPermission.CHANNEL_SUBSCRIBER_ACTIVITY) {
if (!ap.canSubscribe(Integer.parseInt(channelPublishId))) channel.getParentNode().removeChild(channel);
}
else if (activity == IPermission.CHANNEL_PUBLISHER_ACTIVITY) {
if (!ap.canPublish(Integer.parseInt(channelPublishId))) channel.getParentNode().removeChild(channel);
}
}
return channelRegistry;
}
/**
* Returns an XML document which describes the channel registry.
* See uPortal's channelRegistry.dtd
* @return doc the channel registry document
* @throws java.lang.Exception
*/
public static Document getChannelRegistryXML() throws Exception {
Document doc = DocumentFactory.getNewDocument();
Element registry = doc.createElement("registry");
doc.appendChild(registry);
IEntityGroup channelCategoriesGroup = GroupService.getDistinguishedGroup(GroupService.CHANNEL_CATEGORIES);
processGroupsRecursively(channelCategoriesGroup, doc, registry);
return doc;
}
private static void processGroupsRecursively(IEntityGroup group,
Document owner, Element parentGroup) throws Exception {
Date now = new Date();
Iterator iter = group.getMembers();
while (iter.hasNext()) {
IGroupMember member = (IGroupMember)iter.next();
if (member.isGroup()) {
IEntityGroup memberGroup = (IEntityGroup)member;
String key = memberGroup.getKey();
String name = memberGroup.getName();
String description = memberGroup.getDescription();
// Create category element and append it to its parent
Element categoryE = owner.createElement("category");
categoryE.setAttribute("ID", "cat" + key);
categoryE.setAttribute("name", name);
categoryE.setAttribute("description", description);
parentGroup.appendChild(categoryE);
processGroupsRecursively(memberGroup, owner, categoryE);
} else {
IEntity channelDefMember = (IEntity)member;
int channelPublishId = CommonUtils.parseInt(channelDefMember.getKey());
if ( channelPublishId > 0 ) {
ChannelDefinition channelDef = crs.getChannelDefinition(channelPublishId);
if (channelDef != null) {
// Make sure channel is approved
Date approvalDate = channelDef.getApprovalDate();
if (approvalDate != null && approvalDate.before(now)) {
Element channelDefE = channelDef.getDocument(owner, "chan" + channelPublishId);
channelDefE = (Element)owner.importNode(channelDefE, true);
parentGroup.appendChild(channelDefE);
}
}
}
}
}
}
/**
* Looks in channel registry for a channel element matching the
* given channel ID.
* @param channelPublishId the channel publish id
* @return the channel element matching specified channel publish id
* @throws PortalException
*/
public static Element getChannel (String channelPublishId) throws PortalException {
Document channelRegistry = getChannelRegistry();
Element channelE = null;
try {
// This is unfortunately dependent on Xalan 2. Is there a way to use a standard interface?
channelE = (Element)XPathAPI.selectSingleNode(channelRegistry, "(//channel[@ID = '" + channelPublishId + "'])[1]");
} catch (javax.xml.transform.TransformerException te) {
throw new GeneralRenderingException("Not able to find channel " + channelPublishId + " within channel registry: " + te.getMessageAndLocation());
}
return channelE;
}
/**
* Create XML representing this channel definition.
* I don't think this method really belongs in the
* ChannelRegistryManager since this XML fragment is
* related more to a channel instance, but we'll hold
* it here for now and find a better place for it later :)
* @param subscribeId the channel subscibe ID, formerly called instance ID
* @param channelDef a channel definition
* @return the XML representing this channel definition
*/
public static Element getChannelXML(String subscribeId, ChannelDefinition channelDef) {
Document doc = DocumentFactory.getNewDocument();
Element channelE = doc.createElement("channel");
channelE.setAttribute("ID", subscribeId);
channelE.setAttribute("chanID", String.valueOf(channelDef.getId()));
channelE.setAttribute("timeout", String.valueOf(channelDef.getTimeout()));
// I18n
if (localeAware) {
String locale=channelDef.getLocale();
channelE.setAttribute("name", channelDef.getName(locale));
channelE.setAttribute("title", channelDef.getTitle(locale));
channelE.setAttribute("locale", locale);
log.debug("ChannelRegistryManager::getChannelXML: locale=" + locale);
} else {
channelE.setAttribute("name", channelDef.getName());
channelE.setAttribute("title", channelDef.getTitle());
}
channelE.setAttribute("fname", channelDef.getFName());
channelE.setAttribute("class", channelDef.getJavaClass());
channelE.setAttribute("typeID", String.valueOf(channelDef.getTypeId()));
channelE.setAttribute("editable", channelDef.isEditable() ? "true" : "false");
channelE.setAttribute("hasHelp", channelDef.hasHelp() ? "true" : "false");
channelE.setAttribute("hasAbout", channelDef.hasAbout() ? "true" : "false");
channelE.setAttribute("secure", channelDef.isSecure() ? "true" : "false");
channelE.setAttribute("statistics", channelDef.trackStats() ? "true" : "false");
// Add any parameters
ChannelParameter[] parameters = channelDef.getParameters();
for (int i = 0; i < parameters.length; i++) {
ChannelParameter cp = parameters[i];
Element parameterE = doc.createElement("parameter");
parameterE.setAttribute("name", cp.getName());
parameterE.setAttribute("value", cp.getValue());
if (cp.getOverride()) {
parameterE.setAttribute("override", "yes");
}
channelE.appendChild(parameterE);
}
return channelE;
}
/**
* Update a channel definition with data from a channel XML
* element. I don't think this method really belongs in the
* ChannelRegistryManager since this XML fragment contains
* a channel subscribe ID, but we'll hold it here for now
* and find a better place for it later :)
* Note that this method does not set the ID, publisher ID,
* approver ID, pubish date, or approval date.
* @param channelE an XML element representing a channel definition
* @param channelDef the channel definition to update
*/
public static void setChannelXML(Element channelE, ChannelDefinition channelDef) {
channelDef.setFName(channelE.getAttribute("fname"));
channelDef.setName(channelE.getAttribute("name"));
channelDef.setDescription(channelE.getAttribute("description"));
channelDef.setTitle(channelE.getAttribute("title"));
channelDef.setJavaClass(channelE.getAttribute("class"));
String timeout = channelE.getAttribute("timeout");
if (timeout != null && timeout.trim().length() != 0) {
channelDef.setTimeout(Integer.parseInt(timeout));
}
String secure = channelE.getAttribute("secure");
channelDef.setIsSecure(secure != null && secure.equals("true") ? true : false);
String statistics = channelE.getAttribute("statistics");
channelDef.setTrackStats(statistics != null && statistics.equals("true") ? true : false);
channelDef.setTypeId(Integer.parseInt(channelE.getAttribute("typeID")));
String chanEditable = channelE.getAttribute("editable");
String chanHasHelp = channelE.getAttribute("hasHelp");
String chanHasAbout = channelE.getAttribute("hasAbout");
channelDef.setEditable(chanEditable != null && chanEditable.equals("true") ? true : false);
channelDef.setHasHelp(chanHasHelp != null && chanHasHelp.equals("true") ? true : false);
channelDef.setHasAbout(chanHasAbout != null && chanHasAbout.equals("true") ? true : false);
// I18n
if (localeAware) {
channelDef.setLocale(channelE.getAttribute("locale"));
}
// Now set the channel parameters
channelDef.clearParameters();
NodeList channelChildren = channelE.getChildNodes();
if (channelChildren != null) {
for (int i = 0; i < channelChildren.getLength(); i++) {
if (channelChildren.item(i).getNodeName().equals("parameter")) {
Element parameterE = (Element)channelChildren.item(i);
NamedNodeMap parameterAtts = parameterE.getAttributes();
String paramName = null;
String paramValue = null;
String paramOverride = "NULL";
for (int j = 0; j < parameterAtts.getLength(); j++) {
Node parameterAtt = parameterAtts.item(j);
String parameterAttName = parameterAtt.getNodeName();
String parameterAttValue = parameterAtt.getNodeValue();
if (parameterAttName.equals("name")) {
paramName = parameterAttValue;
} else if (parameterAttName.equals("value")) {
paramValue = parameterAttValue;
} else if (parameterAttName.equals("override") && parameterAttValue.equals("yes")) {
paramOverride = "Y";
}
}
if (paramName == null && paramValue == null) {
throw new RuntimeException("Invalid parameter node");
}
channelDef.addParameter(paramName, paramValue, paramOverride);
}
}
}
}
/**
* Create XML representing the channel types.
* It will look something like this:
*
*
*
null
if "custom" channel
* @throws org.jasig.portal.PortalException
*/
public static Document getCPD(String chanTypeID) throws PortalException {
// There are no CPD docs for custom channels (chanTypeID = -1)
if (chanTypeID == null || chanTypeID.equals("-1"))
return null;
Document cpd = (Document)cpdCache.get(CPD_CACHE_KEY + chanTypeID);
if (cpd == null) {
// CPD doc has expired, so get it and cache it
Element channelTypes = getChannelTypes().getDocumentElement();
// Look for channel type element matching the channel type ID
Element chanType = null;
for (Node n = channelTypes.getFirstChild(); n != null; n = n.getNextSibling()) {
if (n.getNodeType() == Node.ELEMENT_NODE && n.getNodeName().equals("channelType")) {
chanType = (Element)n;
if (chanTypeID.equals(chanType.getAttribute("ID")))
break;
}
}
// Find the cpd-uri within this element
String cpdUri = null;
for (Node n = chanType.getLastChild(); n != null; n = n.getPreviousSibling()) {
if (n.getNodeType() == Node.ELEMENT_NODE && n.getNodeName().equals("cpd-uri")) {
// Found the