/** * 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.channels; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.regex.Pattern; import java.util.regex.Matcher; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.naming.Context; import javax.naming.NamingException; import javax.naming.NotContextException; import org.jasig.portal.ChannelCacheKey; import org.jasig.portal.ChannelRuntimeData; import org.jasig.portal.ChannelRuntimeProperties; import org.jasig.portal.ChannelStaticData; import org.jasig.portal.ICCRegistry; import org.jasig.portal.GeneralRenderingException; import org.jasig.portal.IMultithreadedCacheable; import org.jasig.portal.IMultithreadedChannel; import org.jasig.portal.PortalEvent; import org.jasig.portal.PortalException; import org.jasig.portal.ResourceMissingException; import org.jasig.portal.i18n.LocaleManager; import org.jasig.portal.security.LocalConnectionContext; import org.jasig.portal.services.PersonDirectory; import org.jasig.portal.channels.iccdemo.ViewerURL; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jasig.portal.utils.DTDResolver; import org.jasig.portal.utils.ResourceLoader; import org.jasig.portal.utils.XSLT; import org.w3c.dom.Document; import org.xml.sax.ContentHandler; import org.jasig.portal.utils.DocumentFactory; import org.w3c.dom.Element; import org.apache.xml.serialize.XMLSerializer; import java.io.PrintWriter; import java.io.FileOutputStream; /** *
A channel which transforms XML for rendering in the portal.
* ** Static channel parameters to be supplied: * 1) "xmlUri" - a URI representing the source XML document * 2) "upc_localConnContext" - The class name of the ILocalConnectionContext * implementation. Use when local data needs to be sent with the request for the URL. *
*
* This channel is intended to be used to display course-specific information. It can be
* edited by the course instructor, who publishes the channel, and includes 10 editable
* links to be viewed through interchannel-communication similiar to the ICCVIewer channel,
* except the URLs are contained in the course-specific XML file rather than the JNDI context.
* @author rtwigg@uccs.edu, adapted from CGenericXSLT.java and CURLSelector.java
* @version $Revision: 2.4.1 $
*/
public class CCourse extends BaseChannel implements IMultithreadedChannel, IMultithreadedCacheable
{
private static final Log log = LogFactory.getLog(CCourse.class);
Map stateTable;
static final String systemCacheId="org.jasig.portal.channels.CCourse"; // to prepend to the system-wide cache key
private static final String sslLocation = "CCourse/CCourse.ssl";
private static final String viewerFname="/portal/iccdemo/viewer";
// state class
private class CState
{
private String xmlUri;
private String instructorP;
private String iName;
private String iWebsite;
private Map params;
private long cacheTimeout;
private ChannelRuntimeData runtimeData;
private LocalConnectionContext localConnContext;
private String action;
private boolean refresh;
public CState()
{
xmlUri = instructorP = iName = action = null;
params = new HashMap();
runtimeData = null;
localConnContext = null;
refresh=false;
}
public String toString()
{
StringBuffer str = new StringBuffer();
str.append("xmlUri = "+xmlUri+"\n");
if (params != null) {
str.append("params = "+params.toString()+"\n");
}
return str.toString();
}
}
public CCourse()
{
stateTable = Collections.synchronizedMap(new HashMap());
}
public void setStaticData (ChannelStaticData sd, String uid) throws ResourceMissingException, PortalException
{
super.setStaticData(sd);
CState state = new CState();
state.xmlUri = sd.getParameter("xmlUri");
state.instructorP = sd.getParameter("instructor");
state.action="view";
state.refresh=false;
registerListeners();
String cacheTimeout = sd.getParameter("cacheTimeout");
if (cacheTimeout != null) {
state.cacheTimeout = Integer.parseInt(cacheTimeout);
}
String connContext = sd.getParameter ("upc_localConnContext");
if (connContext != null)
{
try
{
state.localConnContext = (LocalConnectionContext) Class.forName(connContext).newInstance();
state.localConnContext.init(sd);
}
catch (Exception e)
{
log.error( "CCourse: Cannot initialize ILocalConnectionContext: " + e);
}
}
// Get instructor display name from person attributes
Hashtable attribs = PersonDirectory.instance().getUserDirectoryInformation(state.instructorP);
String iName = (String)attribs.get("displayName");
if (iName != null) {
state.iName = iName;
}
String iWebsite = (String)attribs.get("labeledURI");
if (iWebsite != null) {
state.iWebsite = iWebsite;
}
stateTable.put(uid,state);
}
public void setRuntimeData (ChannelRuntimeData rd, String uid) throws PortalException
{
CState state = (CState)stateTable.get(uid);
if (state == null)
{
log.error("CCourse:setRuntimeData(): Attempting to access a non-established channel! setStaticData() has never been called on the =uid=\""+uid+"\"");
}
else
{
state.runtimeData=rd;
String form_action = rd.getParameter("uPCC_action");
if (form_action != null)
{
if (form_action.equals("view"))
{
String url = rd.getParameter("url");
if (url != null) {
setViewerURL(url);
}
}
else if (form_action.equals("save"))
{
state.action = "view";
state.refresh=true;
String xmlPath = null;
try
{
if (state.localConnContext != null) {
xmlPath = ResourceLoader.getResourceAsFileString(this.getClass(), state.localConnContext.getDescriptor(state.xmlUri, rd));
}
else {
xmlPath = ResourceLoader.getResourceAsFileString(this.getClass(), state.xmlUri);
}
}
catch (Exception e) {
throw new ResourceMissingException (state.xmlUri, "", e.getMessage());
}
try
{
Document emptyDoc = DocumentFactory.getNewDocument();
Element courseE = emptyDoc.createElement("course");
Element instructorE = emptyDoc.createElement("instructor");
String instructor = rd.getParameter("instructor");
if (instructor != null)
{
instructorE.appendChild(emptyDoc.createTextNode(instructor));
// If no intructor entered, use instructor param to get display name from persondir
}
else {
if (state.iName != null) {
instructorE.appendChild(emptyDoc.createTextNode(state.iName));
}
else {
instructorE.appendChild(emptyDoc.createTextNode(""));
}
}
courseE.appendChild(instructorE);
Element officeHoursE = emptyDoc.createElement("officeHours");
String officeHours = rd.getParameter("officeHours");
if (officeHours != null)
{
officeHoursE.appendChild(emptyDoc.createTextNode(officeHours));
}
courseE.appendChild(officeHoursE);
Element lectureHoursE = emptyDoc.createElement("lectureHours");
String lectureHours = rd.getParameter("lectureHours");
if (lectureHours != null)
{
lectureHoursE.appendChild(emptyDoc.createTextNode(lectureHours));
}
courseE.appendChild(lectureHoursE);
Element lectureLocE = emptyDoc.createElement("lectureLoc");
String lectureLoc = rd.getParameter("lectureLoc");
if (lectureLoc != null)
{
lectureLocE.appendChild(emptyDoc.createTextNode(lectureLoc));
}
courseE.appendChild(lectureLocE);
Element memoE = emptyDoc.createElement("memo");
String memo = rd.getParameter("memo");
if (memo != null)
{
memoE.appendChild(emptyDoc.createTextNode(memo));
}
courseE.appendChild(memoE);
Element urlListE = emptyDoc.createElement("urlList");
for (int i=1; i<11; i++) {
Element urlE = emptyDoc.createElement("url");
String enumDName = "dName" + i;
String enumFName = "fName" + i;
String dName = rd.getParameter(enumDName); // enumerated URL display name
String fName = rd.getParameter(enumFName); // enumerated URL functional name
if (fName != null) {
// Look for "\\" in url and ad default "http:\\" to url if absent
Pattern pattern = Pattern.compile("//");
Matcher matcher = pattern.matcher(fName);
if (!(matcher.find())) {
fName = "http://" + fName;
}
urlE.setAttribute("fName", fName + "");
}
if (dName != null) {
urlE.setAttribute("dName", dName + "");
}
urlListE.appendChild(urlE);
}
courseE.appendChild(urlListE);
emptyDoc.appendChild(courseE);
PrintWriter out = new PrintWriter(new FileOutputStream(xmlPath));
XMLSerializer serializer = new XMLSerializer(out,null);
serializer.serialize(emptyDoc);
out.flush();
out.close();
}
catch (Exception e)
{
log.error( "CCourse::setRuntimeData(): Unable to save xml document " + e.getMessage());
}
}
else if (form_action.equals("cancel"))
{
state.action = "view";
}
} // end if(form_action != null)
// grab the parameters and stuff them all into the state object
Enumeration enum = rd.getParameterNames();
while (enum.hasMoreElements()) {
String n = (String)enum.nextElement();
if (rd.getParameter(n) != null) {
state.params.put(n,rd.getParameter(n));
}
}
} // end else(state==null)
}
public void receiveEvent (PortalEvent ev, String uid)
{
CState state=(CState)stateTable.get(uid);
if (ev.getEventNumber() == PortalEvent.SESSION_DONE)
{
// clean up
stateTable.remove(uid);
}
if (ev.getEventNumber() == PortalEvent.EDIT_BUTTON_EVENT)
{
state.action="edit";
state.refresh=true;
}
}
public ChannelRuntimeProperties getRuntimeProperties (String uid)
{
ChannelRuntimeProperties rp=new ChannelRuntimeProperties();
CState state=(CState)stateTable.get(uid);
// determine if such channel is registered
if (stateTable.get(uid) == null)
{
rp.setWillRender(false);
log.error("CCourse::getRuntimeProperties() : attempting to access a non-established channel! setStaticData() has never been called on the uid=\""+uid+"\"");
}
return rp;
}
public void renderXML(ContentHandler out,String uid) throws PortalException
{
CState state=(CState)stateTable.get(uid);
state.refresh=false;
if (state == null)
log.error("CCourse::renderXML(): Attempting to access a non-established channel! setStaticData() has never been called on the uid=\""+uid+"\"");
else
{
log.debug("CCourse::renderXML(): State = " + state.toString() );
String xml;
Document xmlDoc, xmlClone;
InputStream inputStream = null;
try
{
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
DTDResolver dtdResolver = new DTDResolver();
docBuilder.setEntityResolver(dtdResolver);
URL url;
try {
if (state.localConnContext != null) {
url = ResourceLoader.getResourceAsURL(this.getClass(), state.localConnContext.getDescriptor(state.xmlUri, state.runtimeData));
}
else {
url = ResourceLoader.getResourceAsURL(this.getClass(), state.xmlUri);
}
URLConnection urlConnect = url.openConnection();
if (state.localConnContext != null)
{
try
{
state.localConnContext.sendLocalData(urlConnect, state.runtimeData);
}
catch (Exception e)
{
log.error( "CCourse:: renderXML(): Unable to send data through " + state.runtimeData.getParameter("upc_localConnContext") + ": " + e.getMessage());
}
}
inputStream = urlConnect.getInputStream();
xmlDoc = docBuilder.parse(inputStream);
// Clone the original document
xmlClone = (Document)xmlDoc.cloneNode(true);
}
catch (IOException ioe)
{
throw new ResourceMissingException (state.xmlUri, "", ioe.getMessage());
}
}
catch (Exception e)
{
throw new GeneralRenderingException("Problem parsing " + state.xmlUri + ": " + e);
}
finally
{
try
{
if (inputStream != null)
inputStream.close();
}
catch (IOException ioe)
{
throw new PortalException("CCourse::renderXML():: could not close InputStream");
}
}
state.runtimeData.put("baseActionURL", state.runtimeData.getBaseActionURL());
state.runtimeData.put("isRenderingAsRoot", String.valueOf(state.runtimeData.isRenderingAsRoot()));
// For edit event
if (state.action != null) {
state.runtimeData.put("action", state.action);
}
// Default instructor name
if (state.iName != null) {
state.runtimeData.put("iName", state.iName);
}
// Default instructor website
if (state.iWebsite != null) {
state.runtimeData.put("iWebsite", state.iWebsite);
}
// OK, pass everything we got cached in params...
if (state.params != null)
{
Iterator it = state.params.keySet().iterator();
while (it.hasNext()) {
String n = (String)it.next();
if (state.params.get((Object)n) != null) {
state.runtimeData.put(n,state.params.get((Object)n));
}
}
}
XSLT xslt = XSLT.getTransformer(this);
xslt.setXML(xmlClone);
xslt.setXSL(sslLocation, state.runtimeData.getBrowserInfo());
xslt.setTarget(out);
xslt.setStylesheetParameters(state.runtimeData);
xslt.transform();
}
}
/**
* A utility method to register viewer channel as
* listeners of the current channel
*/
private void registerListeners() {
ICCRegistry r=staticData.getICCRegistry();
String viewerId=getChannelId(viewerFname);
if(viewerId!=null) {
// add a listener channel
r.addListenerChannel(viewerId);
}
}
/**
* A utility method that communicates the new url to the viewer channel
*
* @param url a String
value
*/
private void setViewerURL(String url) {
// find viewer's id
String viewerId=getChannelId(viewerFname);
if(viewerId!=null) {
ViewerURL v=(ViewerURL) getBoundObject(viewerId);
if(v!=null) {
v.setNewURL(url);
}
}
}
/**
* A utility method for obtaining a channelSubscribeId given a channel fname
*
* @param fname a String
value
* @return channel's subscribe id, or null
if no channel with given fname was found
*/
private String getChannelId(String fname) {
String id=null;
Context globalIDContext = null;
try {
// Get the context that holds the global IDs for this user
globalIDContext = (Context)staticData.getJNDIContext().lookup("/channel-ids");
} catch (NotContextException nce) {
log.error( "CCourse::getChannelId(): Could not find subcontext /channel-ids in JNDI");
} catch (NamingException e) {
log.error("Lookup /channel-ids failed", e);
}
try {
id=(String)globalIDContext.lookup(fname);
} catch (NotContextException nce) {
log.error( "CCourse::getChannelId(): Could not find channel ID for fname="+fname);
} catch (NamingException e) {
log.error("Lookup " + fname + " failed", e);
}
return id;
}
/**
* A utility method to determine an object bound to the "chan-obj" branch
* for a particular channel id.
*
* @param channelSubscribeId a String
value of the channel who's object we're looking for
* @return an Object
value bound to that jndi location (or null
if channel didn't bind anything)
*/
private Object getBoundObject(String channelSubscribeId) {
Object o=null;
Context globalObjContext = null;
try {
globalObjContext = (Context)staticData.getJNDIContext().lookup("/channel-obj");
} catch (NotContextException nce) {
log.error( "CCourse::getChannelId(): Could not find subcontext /channel-obj in JNDI");
} catch (NamingException e) {
log.error("Lookup /channel-obj failed", e);
}
try {
o=globalObjContext.lookup(channelSubscribeId);
} catch (NotContextException nce) {
log.error( "CCourse::getChannelId(): Could not find channel bound object for channel id="+channelSubscribeId);
} catch (NamingException e) {
log.error("Lookup " + channelSubscribeId + " failed", e);
}
return o;
}
public ChannelCacheKey generateKey(String uid)
{
CState state = (CState)stateTable.get(uid);
if (state == null)
{
log.error("CDeptMsg:generateKey() : attempting to access a non-established channel! setStaticData() has never been called on the uid=\""+uid+"\"");
return null;
}
else
{
if (state.refresh)
{
return null;
}
else
{
ChannelCacheKey k = new ChannelCacheKey();
k.setKey(this.getKey(state)+","+uid);
k.setKeyScope(ChannelCacheKey.SYSTEM_KEY_SCOPE);
k.setKeyValidity(new Long(System.currentTimeMillis()));
return k;
}
}
}
public boolean isCacheValid(Object validity,String uid)
{
if (!(validity instanceof Long))
return false;
CState state = (CState)stateTable.get(uid);
if (state == null)
{
log.error("CCourse::isCacheValid() : attempting to access a non-established channel! setStaticData() has never been called on the uid=\""+uid+"\"");
return false;
}
else
return (System.currentTimeMillis() - ((Long)validity).longValue() < state.cacheTimeout*1000);
}
private String getKey(CState state)
{
// Maybe not the best way to generate a key, but it seems to work.
// If you know a better way, please change it!
StringBuffer sbKey = new StringBuffer(1024);
sbKey.append(systemCacheId).append(": ");
sbKey.append("xmluri:").append(state.xmlUri).append(", ");
String xslUriForKey = null;
try {
String sslUri = ResourceLoader.getResourceAsURLString(CCourse.class, sslLocation);
xslUriForKey = XSLT.getStylesheetURI(sslUri, state.runtimeData.getBrowserInfo());
} catch (Exception e) {
xslUriForKey = "Not attainable: " + e;
}
sbKey.append("locales:").append(LocaleManager.stringValueOf(state.runtimeData.getLocales()));
sbKey.append("xslUri:").append(xslUriForKey).append(", ");
sbKey.append("cacheTimeout:").append(state.cacheTimeout).append(", ");
sbKey.append("isRenderingAsRoot:").append(state.runtimeData.isRenderingAsRoot()).append(", ");
// If a local connection context is configured, include its descriptor in the key
if (state.localConnContext != null)
sbKey.append("descriptor:").append(state.localConnContext.getDescriptor(state.xmlUri, state.runtimeData)).append(", ");
sbKey.append("params:").append(state.params.toString());
return sbKey.toString();
}
}