001: /* Copyright 2005 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.channels;
007:
008: import java.util.HashMap;
009: import java.util.Map;
010:
011: import org.apache.commons.logging.Log;
012: import org.apache.commons.logging.LogFactory;
013: import org.jasig.portal.ChannelRuntimeData;
014: import org.jasig.portal.ChannelRuntimeProperties;
015: import org.jasig.portal.ChannelStaticData;
016: import org.jasig.portal.IChannel;
017: import org.jasig.portal.PortalException;
018: import org.jasig.portal.utils.XML;
019: import org.jasig.portal.utils.XSLT;
020: import org.w3c.dom.Document;
021: import org.xml.sax.ContentHandler;
022:
023: /**
024: * CAbstractXslt is an abstract IChannel which implements the
025: * boilerplate of applying a parameterized XSLT to an XML to render the channel
026: * output. Your IChannel can extend CAbstactXSLT and implement the template
027: * methods to provide the XML, XSLT, and parameters.
028: *
029: * @version $Revision$ $Date$
030: * @since uPortal 2.5
031: */
032: public abstract class CAbstractXslt implements IChannel {
033:
034: /**
035: * The most recently set ChannelRuntimeData.
036: * This data is available to our subclasses via an accessor method.
037: */
038: private ChannelRuntimeData runtimeData;
039:
040: /**
041: * The ChannelStaticData.
042: * This data is available to our subclasses via an accessor method.
043: */
044: private ChannelStaticData staticData;
045:
046: /**
047: * Commons Logging logger for the runtime class of this channel instance.
048: */
049: protected Log log = LogFactory.getLog(getClass());
050:
051: public final void setRuntimeData(ChannelRuntimeData rd) {
052: /*
053: * This implementation logs the received ChannelRuntimeData at trace
054: * level and then stores it as the instance variable runtimeData, which is
055: * available to extending classes via the accessor method getRuntimeData().
056: */
057: if (log.isTraceEnabled()) {
058: if (log.isTraceEnabled()) {
059: log
060: .trace("Received channel runtime data: [" + rd
061: + "]");
062: }
063: }
064:
065: this .runtimeData = rd;
066:
067: runtimeDataSet();
068: }
069:
070: /**
071: * This method is called on setRuntimeData() after CAbstractXslt has
072: * updated its state such that a call to getRuntimeData() will return
073: * the latest ChannelRuntimeData.
074: */
075: protected void runtimeDataSet() {
076: // do-nothing default implementation
077: // subclasses can override to be notified when setRuntimeData()
078: // has been received.
079: }
080:
081: protected final ChannelRuntimeData getRuntimeData() {
082: return this .runtimeData;
083: }
084:
085: public final void setStaticData(ChannelStaticData sd) {
086: /*
087: * This implementation logs the received ChannelStaticData at trace
088: * level and then stores it as the instance variable staticData, which is
089: * available to extending classes via the accessor method getStaticData().
090: */
091: if (log.isTraceEnabled()) {
092: if (log.isTraceEnabled()) {
093: log.trace("Received channel satic data: [" + sd + "]");
094: }
095: }
096:
097: this .staticData = sd;
098:
099: staticDataSet();
100: }
101:
102: /**
103: * This method is called on calls to setStaticData() after internal state
104: * has been updated such that getStaticData() will return the
105: * ChannelStaticData.
106: *
107: */
108: protected void staticDataSet() {
109: // do-nothing default implementation
110: // subclasses can override to be notified when static data was set.
111: }
112:
113: protected final ChannelStaticData getStaticData() {
114: return this .staticData;
115: }
116:
117: public ChannelRuntimeProperties getRuntimeProperties() {
118: /*
119: * This basic implementation returns a dummy ChannelRuntimeProperties,
120: * as it appears all known IChannel implementations currrently do. This
121: * method is not final so that if you find some useful ChannelRuntimeProperties
122: * to return you can return them.
123: */
124: return new ChannelRuntimeProperties();
125: }
126:
127: public final void renderXML(ContentHandler out)
128: throws PortalException {
129:
130: /*
131: * We implement renderXML by invoking our template methods to get
132: * the XML, XSLT URL, and XSLT stylesheet parameters, and then
133: * performing a boilerplate XSLT transformation.
134: */
135:
136: if (log.isTraceEnabled()) {
137: log.trace("entering renderXML()");
138: }
139:
140: try {
141: // Perform the transformation
142: XSLT xslt = XSLT.getTransformer(this , this .runtimeData
143: .getLocales());
144:
145: Document xml = getXml();
146:
147: if (log.isTraceEnabled()) {
148: log.trace("getXml() returned Document: [" + xml + "]");
149: String xmlAsString = XML.serializeNode(xml);
150: log.trace("XML DOM was: [" + xmlAsString + "]");
151: }
152:
153: if (xml == null) {
154: throw new IllegalStateException(
155: "The Document we would transform, as returned by getXml(), was illegally null.");
156: }
157:
158: xslt.setXML(xml);
159:
160: String xsltUri = getXsltUri();
161:
162: if (log.isTraceEnabled()) {
163: log.trace("getXsltUri() returned: [" + xsltUri + "]");
164: }
165:
166: if (xsltUri == null) {
167:
168: throw new IllegalStateException(
169: "The URI of our XSLT we would use to transform our Document, as returned by getXsltUri(), was illegally null.");
170:
171: /*
172: * It would probably be a neat feature to detect the case where
173: * getXsltUri() returns null and in that case dump the XML directly to
174: * the ContentHandler, but this is not yet implemented.
175: */
176: }
177:
178: xslt.setXSL(xsltUri);
179:
180: xslt.setTarget(out);
181:
182: Map paramsMap = getStylesheetParams();
183:
184: if (log.isTraceEnabled()) {
185: log.trace("getStylesheetParams() returned ["
186: + paramsMap + "]");
187: }
188:
189: if (paramsMap == null) {
190: xslt.setStylesheetParameters(new HashMap());
191: } else {
192: // XSLT requires HashMap or HashTable rather than
193: // accepting any Map.
194: // We accomodate this by dumping our paramsMap into a
195: // HashMap to ensure it is of an acceptable type.
196: // Skipping this putAll() where it is unnecessary probably wouldn't
197: // result in any worthwhile performance difference.
198: HashMap tempHashMap = new HashMap();
199: tempHashMap.putAll(paramsMap);
200: xslt.setStylesheetParameters(tempHashMap);
201: }
202:
203: if (log.isTraceEnabled()) {
204: log.trace("Configured XSLT as [" + xslt + "]");
205: }
206:
207: xslt.transform();
208: } catch (PortalException pe) {
209: // we just re-throw PortalExceptions, confident that the
210: // channel rendering framework will log and handle them, as
211: // defined by the IChannel API we are implementing.
212: throw pe;
213: } catch (RuntimeException re) {
214: // we just re-throw RuntimeExceptions, confident that the
215: // channel rendering framework will log and handle them.
216: // Wrapping them in a PortalException would come at the cost of
217: // their specificity and adds no value -- if they're going to be wrapped,
218: // our client can wrap them just as well as we can, and if we can
219: // skip wrapping them, so much the better.
220: throw re;
221: } catch (Exception e) {
222: // we log and wrap in PortalExceptions Exceptions other than
223: // PortalException and RuntimeException, so that we conform to
224: // the IChannel API.
225: log.error("Error rendering CAbstractXSLT instance.", e);
226: throw new PortalException(e);
227: }
228:
229: if (log.isTraceEnabled()) {
230: log.trace("returning from renderXML()");
231: }
232: }
233:
234: /**
235: * Get the Document we should feed to our XSLT.
236: *
237: * This method is declared to throw Exception for maximum convenience of
238: * the developer extending this class. Such developers should catch or declare
239: * exceptions as appropriate to your needs. Just because you can
240: * throw Exception here doesn't mean you shouldn't, for example, fallback to
241: * a default XSLT URL when your cannot programmatically determine the URL
242: * of your XSLT. On the other hand, there's no reason for you to wrap SqlExceptions
243: * if you're not going to do anything other than what this abstract class does with them
244: * (logs them and wraps them in PortalExceptions).
245: *
246: * The method invoking
247: * this template method, renderXML(), is declared to throw PortalException by the IChannel
248: * API. Any PortalException or RuntimeException thrown by getXsltUri() will
249: * be thrown all the way out of the abstract class's renderXML() method. This approach
250: * ensures that developers extending this class retain control over what exceptions
251: * their implementions throw. Note that you can map particular exceptions to particular
252: * XML representations and thus particular CError displays as of uPortal 2.5.
253: *
254: * Exceptions that are neither RuntimeExceptions nor PortalExceptions thrown by
255: * this method will be logged and wrapped in PortalExceptions so that this channel
256: * will conform to the IChannel API.
257: *
258: * Implementations of this method should not return null. When this method returns
259: * null, renderXML() throws an IllegalStateException.
260: *
261: * @return the Document we should feed to our XSLT.
262: * @throws Exception including PortalException or any RuntimeException on failure
263: */
264: protected abstract Document getXml() throws Exception;
265:
266: /**
267: * Get the URI whereat we can obtain the XSLT we should use to render.
268: *
269: * This method is declared to throw Exception for maximum convenience of
270: * the developer extending this class. Such developers should catch or declare
271: * exceptions as appropriate to your needs. Just because you can
272: * throw Exception here doesn't mean you shouldn't, for example, fallback to
273: * a default XSLT URL when your cannot programmatically determine the URL
274: * of your XSLT. On the other hand, there's no reason for you to wrap SqlExceptions
275: * if you're not going to do anything other than what this abstract class does with them
276: * (logs them and wraps them in PortalExceptions).
277: *
278: * The method invoking
279: * this template method, renderXML(), is declared to throw PortalException by the IChannel
280: * API. Any PortalException or RuntimeException thrown by getXsltUri() will
281: * be thrown all the way out of the abstract class's renderXML() method. This approach
282: * ensures that developers extending this class retain control over what exceptions
283: * their implementions throw. Note that you can map particular exceptions to particular
284: * XML representations and thus particular CError displays as of uPortal 2.5.
285: *
286: * Exceptions that are neither RuntimeExceptions nor PortalExceptions thrown by
287: * this method will be logged and wrapped in PortalExceptions so that this channel
288: * will conform to the IChannel API.
289: *
290: * Implementations of this method should not return null. The behavior of this class
291: * when this method returns null is currently undefined. The current implementation
292: * is to throw IllegalStateException. However, it might be an interesting improvement
293: * to make the meaning of returning null here be to perform no transformation and just
294: * dump the XML to the ContentHandler.
295: *
296: * @return URI of the XSLT to use to render the channel
297: * @throws Exception including PortalException or any RuntimeException on failure
298: */
299: protected abstract String getXsltUri() throws Exception;
300:
301: /**
302: * Get a Map from parameter names to parameter values for parameters to
303: * be passed to the XSLT.
304: *
305: * Returning null is equivalent to returning an empty map and will not be considered
306: * an error condition by the renderXML() implementation.
307: *
308: * This method is declared to throw Exception for maximum convenience of
309: * the developer extending this class. Such developers should catch or declare
310: * exceptions as appropriate to your needs. Just because you can
311: * throw Exception here doesn't mean you shouldn't, for example, fallback to
312: * default XSLT parameters when you cannot programmatically determine some or
313: * all of your XSLT parameters. Or, if you have a very channel-specific UI you want to
314: * render on failure, you might pass parameters to your XSLT characterizing the failure
315: * and let your XSLT render the response.
316: *
317: * There's likely no reason for you to wrap IOExceptions
318: * if you're not going to do anything other than what this abstract class does with them
319: * (logs them and wraps them in PortalExceptions).
320: *
321: * The method invoking
322: * this template method, renderXML(), is declared to throw PortalException by the IChannel
323: * API. Any PortalException or RuntimeException thrown by getStylesheetParams() will
324: * be thrown all the way out of the abstract class's renderXML() method. This approach
325: * ensures that developers extending this class retain control over what exceptions
326: * their implementions throw. Note that you can map particular exceptions to particular
327: * XML representations and thus particular CError displays as of uPortal 2.5.
328: *
329: * Exceptions that are neither RuntimeExceptions nor PortalExceptions thrown by
330: * this method will be logged and wrapped in PortalExceptions so that this channel
331: * will conform to the IChannel API.
332: *
333: * @return a Map from parameter names to parameter values, or null (equivalent to empty Map).
334: * @throws Exception including PortalException or any RuntimeException on failure.
335: */
336: protected abstract Map getStylesheetParams() throws Exception;
337:
338: }
|