001: /* Copyright 2001, 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;
007:
008: import java.io.IOException;
009: import java.io.UnsupportedEncodingException;
010: import java.util.Vector;
011:
012: import org.jasig.portal.serialize.CachingSerializer;
013: import org.jasig.portal.utils.SAX2FilterImpl;
014: import org.xml.sax.Attributes;
015: import org.xml.sax.ContentHandler;
016: import org.xml.sax.SAXException;
017: import org.xml.sax.helpers.AttributesImpl;
018:
019: /**
020: * A filter that incorporates channel content into the main SAX stream.
021: * Unlike a regular {@link ChannelIncorporationFilter}, this class can
022: * feed cache character buffers to the {@link CachingSerializer}.
023: *
024: * <p>Replaces
025: * <channel ID="channelSubcribeId"/>
026: * elements with channel output from the IChannelRenderer for that
027: * channelSubscribeId.</p>
028: *
029: * <p><Replaces
030: * <channel-title
031: * channelSubcribeId="channelSubcribeId"
032: * defaultValue="defaultTitle" />
033: *
034: * elements with dynamic channel title from the IChannelRenderer for that
035: * channelSubcribeId, or the provided defaultValue in the case where there is
036: * no dynamic channel title.</p>
037: *
038: * @author Peter Kharchenko {@link <a href="mailto:pkharchenko@interactivebusiness.com"">pkharchenko@interactivebusiness.com"</a>}
039: * @version $Revision: 36690 $ $Date: 2006-08-25 14:03:25 -0700 (Fri, 25 Aug 2006) $
040: */
041: public class CharacterCachingChannelIncorporationFilter extends
042: SAX2FilterImpl {
043:
044: /**
045: * Track what, if any, incorporation element we are currently processing.
046: * "channel" if we are "in" the <channel> element.
047: * "channel-title" if we are "in" the <channel-title> element.
048: * Null if we are not in one of these elements. Other elements are not
049: * processed by this filter (are not "incorporation elements").
050: */
051: private String insideElement = null;
052:
053: ChannelManager cm;
054:
055: /**
056: * ChannelSubscribeId of the currently-being-incorporated channel, if any.
057: * Null if not currently incorporating a channel (not in an incorporation
058: * element.)
059: */
060: private String channelSubscribeId = null;
061:
062: private String channelTitle = null;
063:
064: private boolean ccaching;
065: private CachingSerializer ser;
066:
067: Vector systemCCacheBlocks;
068: Vector channelIdBlocks;
069:
070: // constructors
071:
072: /**
073: * Downward chaining constructor.
074: */
075: public CharacterCachingChannelIncorporationFilter(
076: ContentHandler handler, ChannelManager chanm,
077: boolean ccaching) {
078: super (handler);
079:
080: if (handler instanceof CachingSerializer) {
081: ser = (CachingSerializer) handler;
082: this .ccaching = true;
083: } else {
084: this .ccaching = false;
085: }
086:
087: this .cm = chanm;
088: this .ccaching = (this .ccaching && ccaching);
089: if (this .ccaching) {
090: log
091: .debug("CharacterCachingChannelIncorporationFilter() : ccaching=true");
092: systemCCacheBlocks = new Vector();
093: channelIdBlocks = new Vector();
094: } else {
095: log
096: .debug("CharacterCachingChannelIncorporationFilter() : ccaching=false");
097: }
098: }
099:
100: /**
101: * Obtain system character cache blocks.
102: *
103: * @return a <code>Vector</code> of system character blocks in between which channel renderings should be inserted.
104: */
105: public Vector getSystemCCacheBlocks() {
106: if (ccaching) {
107: return systemCCacheBlocks;
108: } else {
109: return null;
110: }
111: }
112:
113: /**
114: * Obtain a vector of channels to be inserted into a current character cache.
115: *
116: * @return a <code>Vector</code> of cache entry blocks corresponding to channel
117: * subscribe Id(s) in an order in which they appear in the overall document.
118: */
119: public Vector getChannelIdBlocks() {
120: if (ccaching) {
121: return channelIdBlocks;
122: } else {
123: return null;
124: }
125: }
126:
127: public void startDocument() throws SAXException {
128: if (ccaching) {
129: // start caching
130: try {
131: if (!ser.startCaching()) {
132: log
133: .error("CharacterCachingChannelIncorporationFilter::startDocument() "
134: + ": unable to start caching!");
135: }
136: } catch (IOException ioe) {
137: log.error(
138: "CharacterCachingChannelIncorporationFilter::startDocument() "
139: + ": unable to start caching!", ioe);
140: }
141: }
142: super .startDocument();
143: }
144:
145: public void endDocument() throws SAXException {
146: super .endDocument();
147: if (ccaching) {
148: // stop caching
149: try {
150: if (ser.stopCaching()) {
151: try {
152: systemCCacheBlocks.add(ser.getCache());
153: } catch (UnsupportedEncodingException e) {
154: log
155: .error(
156: "CharacterCachingChannelIncorporationFilter::endDocument() "
157: + ": unable to obtain character cache, invalid encoding specified ! ",
158: e);
159: } catch (IOException ioe) {
160: log
161: .error(
162: "CharacterCachingChannelIncorporationFilter::endDocument() "
163: + ": IO exception occurred while retreiving character cache ! ",
164: ioe);
165: }
166: } else {
167: log
168: .error("CharacterCachingChannelIncorporationFilter::endDocument() "
169: + ": unable to stop caching!");
170: }
171: } catch (IOException ioe) {
172: log.error(
173: "CharacterCachingChannelIncorporationFilter::endDocument() "
174: + ": unable to stop caching!", ioe);
175: }
176:
177: }
178: }
179:
180: public void startElement(String uri, String localName,
181: String qName, Attributes atts) throws SAXException {
182:
183: if (log.isTraceEnabled()) {
184: log
185: .trace("CharacterCachingChannelIncorporationFilter is filtering element with "
186: + "uri="
187: + uri
188: + " localName="
189: + localName
190: + " qName="
191: + qName
192: + "atts="
193: + atts
194: + " . Current channelSubscribeId="
195: + this .channelSubscribeId
196: + " and in element " + this .insideElement);
197: }
198:
199: if (!isInIncorporationElement()) {
200: // recognizing "channel"
201: if (qName.equals("channel")) {
202: this .insideElement = "channel";
203: this .channelSubscribeId = atts.getValue("ID");
204: if (this .channelSubscribeId == null) // fname access used
205: {
206: String fname = atts.getValue("fname");
207: if (fname.equals("")) // can't obtain subscribe id
208: log
209: .error("Incurred a channel with no subscribe id "
210: + "in attribute 'ID' and no functional name "
211: + "in attribute 'fname'.");
212: else {
213: // get the channel from layout if there or instantiate
214: // in transient layout manager if not
215: try {
216: this .channelSubscribeId = cm
217: .getSubscribeId(fname);
218: } catch (PortalException e) {
219: log
220: .error(
221: "Unable to obtain subscribe id for "
222: + "channel with functional name '"
223: + fname + "'.", e);
224: }
225: }
226: }
227: if (ccaching) {
228: // save the old cache state
229: try {
230: if (ser.stopCaching()) {
231: if (log.isDebugEnabled()) {
232: log
233: .debug("CharacterCachingChannelIncorporationFilter::endElement() "
234: + ": obtained the following system character entry: \n"
235: + ser.getCache());
236: }
237: systemCCacheBlocks.add(ser.getCache());
238: } else {
239: log
240: .error("CharacterCachingChannelIncorporationFilter::startElement() "
241: + ": unable to reset cache state ! Serializer was not caching when it should've been !");
242: }
243: } catch (UnsupportedEncodingException e) {
244: log
245: .error(
246: "CharacterCachingChannelIncorporationFilter::startElement() "
247: + ": unable to obtain character cache, invalid encoding specified ! ",
248: e);
249: } catch (IOException ioe) {
250: log
251: .error(
252: "CharacterCachingChannelIncorporationFilter::startElement() "
253: + ": IO exception occurred while retreiving character cache ! ",
254: ioe);
255: }
256: }
257: } else if (qName.equals("channel-title")) {
258: this .insideElement = "channel-title";
259: this .channelSubscribeId = atts
260: .getValue("channelSubscribeId");
261: this .channelTitle = atts.getValue("defaultValue");
262: } else {
263: // not in an incorporation element and not starting one this class
264: // handles specially, so pass the element through this filter.
265: super .startElement(uri, localName, qName, atts);
266:
267: }
268: } else {
269: // inside an incorporation element, do nothing.
270: if (log.isTraceEnabled()) {
271: log.trace("Ignoring element " + qName);
272: }
273: }
274: }
275:
276: public void endElement(String uri, String localName, String qName)
277: throws SAXException {
278:
279: if (log.isTraceEnabled()) {
280: log
281: .trace("CharacterCachingChannelIncorporationFilter is filtering end element with "
282: + "url="
283: + uri
284: + " localName="
285: + localName
286: + " qName="
287: + qName
288: + " . Current channelSubscribeId="
289: + this .channelSubscribeId
290: + " and in element " + this .insideElement);
291: }
292:
293: // if inside an element this filter handles (incorporates content in place of)
294: // then this endElement call may be the time to end one of these special elements.
295: if (isInIncorporationElement()) {
296: if (qName.equals("channel")
297: && this .insideElement.equals("channel")) {
298:
299: try {
300: ContentHandler contentHandler = getContentHandler();
301: if (contentHandler != null) {
302: if (ccaching) {
303: channelIdBlocks.add(channelSubscribeId);
304: }
305: cm.outputChannel(channelSubscribeId,
306: contentHandler);
307: if (ccaching) {
308: // start caching again
309: try {
310: if (!ser.startCaching()) {
311: log
312: .error("CharacterCachingChannelIncorporationFilter::endElement() : unable to restart cache after a channel end!");
313: }
314: } catch (IOException ioe) {
315: log
316: .error(
317: "CharacterCachingChannelIncorporationFilter::endElement() : unable to start caching!",
318: ioe);
319: }
320: }
321: } else {
322: // contentHandler was null. This is a serious problem,
323: // since
324: // filtering is pointless if it's not writing back to a
325: // contentHandler
326: log
327: .error("null ContentHandler prevents outputting channel with subscribe id = "
328: + channelSubscribeId);
329: }
330: } finally {
331: endIncorporationElement();
332: }
333: } else if (qName.equals("channel-title")
334: && this .insideElement.equals("channel-title")) {
335:
336: if (log.isDebugEnabled()) {
337: log
338: .debug("Incorporating title for channel with subscribeId=["
339: + this .channelSubscribeId + "]");
340: }
341:
342: try {
343:
344: ContentHandler contentHandler = getContentHandler();
345: if (contentHandler != null) {
346: String dynamicChannelTitle = cm
347: .getChannelTitle(channelSubscribeId);
348:
349: // if there is a dynamic channel title, use that.
350: // otherwise, stick with the title that was read from
351: // the channel-title element we're replacing.
352: if (dynamicChannelTitle != null) {
353: this .channelTitle = dynamicChannelTitle;
354: }
355:
356: AttributesImpl noAttributes = new AttributesImpl();
357:
358: //contentHandler.startElement("", "", "span", noAttributes);
359:
360: char[] channelTitleArray = this .channelTitle
361: .toCharArray();
362:
363: contentHandler.characters(channelTitleArray, 0,
364: channelTitleArray.length);
365: //contentHandler.endElement("", "", "span");
366:
367: } else {
368: // contentHandler was null. This is a serious problem,
369: // since filtering is pointless if it's not writing back
370: // to a contentHandler.
371: log
372: .error("null ContentHandler prevents outputting channel title with subcribe id = "
373: + this .channelSubscribeId);
374: }
375:
376: } finally {
377: endIncorporationElement();
378: }
379:
380: }
381:
382: } else {
383: // pass non-incorporation elements through untouched.
384: super .endElement(uri, localName, qName);
385: }
386: }
387:
388: public String toString() {
389: StringBuffer sb = new StringBuffer();
390: sb.append(getClass());
391: sb.append(" caching: ").append(this .ccaching);
392: sb.append(" currently processing: subscribeId=").append(
393: this .channelSubscribeId);
394: sb.append(" in incorporation element: ").append(
395: this .insideElement);
396:
397: return sb.toString();
398: }
399:
400: /**
401: * Returns true if this filter is currently processing an incorporation element.
402: * The purpose of this method is to allow ignoring of elements starting within elements
403: * this filter is already incorporating, and allow ignoring of end elements except when
404: * those end elements end incorporation elements.
405: *
406: * @return true if in an incorporation element, false otherwise
407: */
408: private boolean isInIncorporationElement() {
409: // in an incorporation element when the stored name of that element is not null.
410: return this .insideElement != null;
411: }
412:
413: /**
414: * Reset filter state to not being "inside" an incorporation element (and
415: * therefore instead being available to process the next incorporation
416: * element encountered).
417: *
418: * @throws IllegalStateException if not currently inside an element.
419: */
420: private void endIncorporationElement() {
421: if (this .channelSubscribeId == null
422: || this .insideElement == null) {
423: throw new IllegalStateException(
424: "Cannot end element when not in element:" + this);
425: }
426: this.channelSubscribeId = null;
427: this.insideElement = null;
428: }
429:
430: }
|