001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.portal.serialization;
018:
019: import java.io.IOException;
020: import java.io.OutputStream;
021: import java.util.HashMap;
022: import java.util.LinkedList;
023: import java.util.Map;
024:
025: import org.apache.avalon.framework.configuration.Configuration;
026: import org.apache.avalon.framework.configuration.ConfigurationException;
027: import org.apache.cocoon.serialization.HTMLSerializer;
028: import org.xml.sax.Attributes;
029: import org.xml.sax.SAXException;
030:
031: /**
032: * This is a special serializer that allows to include content that is not XML at the
033: * last possible point.
034: * This is very useful for the portlets as they don't deliver valid XML but HTML.
035: *
036: * The trick is to insert a special token in the characters of the SAX stream: '~~'.
037: * This token is filtered later on and replaced with the complete content of the portlet.
038: *
039: * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
040: *
041: * @version CVS $Id: IncludingHTMLSerializer.java 496376 2007-01-15 15:59:54Z cziegeler $
042: */
043: public class IncludingHTMLSerializer extends HTMLSerializer {
044:
045: public static final ThreadLocal portlets = new ThreadLocal();
046:
047: public static final String NAMESPACE = "http://apache.org/cocoon/portal/include";
048:
049: protected LinkedList orderedPortletList = new LinkedList();
050:
051: protected static final char token = '~';
052:
053: protected static final char[] tokens = new char[] { token, token };
054:
055: /** The encoding. */
056: protected String encoding = "utf-8";
057:
058: /**
059: * @see org.apache.cocoon.serialization.HTMLSerializer#configure(org.apache.avalon.framework.configuration.Configuration)
060: */
061: public void configure(Configuration conf)
062: throws ConfigurationException {
063: super .configure(conf);
064: this .encoding = conf.getChild("encoding").getValue(
065: this .encoding);
066: }
067:
068: /**
069: * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
070: */
071: public void recycle() {
072: super .recycle();
073: Map map = (Map) portlets.get();
074: if (map != null) {
075: map.clear();
076: }
077: this .orderedPortletList.clear();
078: }
079:
080: /**
081: * Add a portlet
082: */
083: public static void addPortlet(String name, String content) {
084: Map map = (Map) portlets.get();
085: if (map == null) {
086: map = new HashMap();
087: portlets.set(map);
088: }
089: map.put(name, content);
090: }
091:
092: /**
093: * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
094: */
095: public void endElement(String uri, String loc, String raw)
096: throws SAXException {
097: if (!NAMESPACE.equals(uri)) {
098: super .endElement(uri, loc, raw);
099: }
100: }
101:
102: /**
103: * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
104: */
105: public void startElement(String uri, String loc, String raw,
106: Attributes a) throws SAXException {
107: if (!NAMESPACE.equals(uri)) {
108: super .startElement(uri, loc, raw, a);
109: } else {
110: final String portletId = a.getValue("portlet");
111:
112: String value = null;
113: Map map = (Map) portlets.get();
114: if (map != null) {
115: value = (String) map.get(portletId);
116: }
117: if (value != null) {
118: this .orderedPortletList.addFirst(value);
119: this .characters(tokens, 0, tokens.length);
120: }
121: }
122: }
123:
124: /**
125: * @see org.apache.cocoon.sitemap.SitemapOutputComponent#setOutputStream(java.io.OutputStream)
126: */
127: public void setOutputStream(OutputStream stream) throws IOException {
128: super .setOutputStream(new ReplacingOutputStream(stream,
129: this .orderedPortletList, this .encoding));
130: }
131:
132: }
133:
134: final class ReplacingOutputStream extends OutputStream {
135:
136: /** Stream. */
137: protected final OutputStream stream;
138:
139: protected boolean inKey;
140:
141: protected final LinkedList orderedValues;
142:
143: /** Encoding. */
144: protected final String encoding;
145:
146: /**
147: * Constructor.
148: */
149: public ReplacingOutputStream(OutputStream stream,
150: LinkedList values, String enc) {
151: this .stream = stream;
152: this .orderedValues = values;
153: this .inKey = false;
154: this .encoding = enc;
155: }
156:
157: /**
158: * @see java.io.OutputStream#close()
159: */
160: public void close() throws IOException {
161: this .stream.close();
162: }
163:
164: /**
165: * @see java.io.OutputStream#flush()
166: */
167: public void flush() throws IOException {
168: this .stream.flush();
169: }
170:
171: /**
172: * @see java.io.OutputStream#write(byte[], int, int)
173: */
174: public void write(byte[] b, int off, int len) throws IOException {
175: if (len == 0) {
176: return;
177: }
178: if (this .inKey) {
179: if (b[off] == IncludingHTMLSerializer.token) {
180: this .writeNextValue();
181: off++;
182: len--;
183: } else {
184: this .write(IncludingHTMLSerializer.token);
185: }
186: this .inKey = false;
187: }
188: // search for key
189: boolean end = false;
190: do {
191: int s = off;
192: int e = off + len;
193: while (s < e && b[s] != IncludingHTMLSerializer.token) {
194: s++;
195: }
196: if (s == e) {
197: this .stream.write(b, off, len);
198: end = true;
199: } else if (s == e - 1) {
200: this .stream.write(b, off, len - 1);
201: this .inKey = true;
202: end = true;
203: } else {
204: if (b[s + 1] == IncludingHTMLSerializer.token) {
205: final int l = s - off;
206: this .stream.write(b, off, l);
207: off += (l + 2);
208: len -= (l + 2);
209: this .writeNextValue();
210:
211: } else {
212: final int l = s - off + 2;
213: this .stream.write(b, off, l);
214: off += l;
215: len -= l;
216: }
217: end = (len == 0);
218: }
219: } while (!end);
220: }
221:
222: /**
223: * @see java.io.OutputStream#write(byte[])
224: */
225: public void write(byte[] b) throws IOException {
226: this .write(b, 0, b.length);
227: }
228:
229: /**
230: * @see java.io.OutputStream#write(int)
231: */
232: public void write(int b) throws IOException {
233: if (b == IncludingHTMLSerializer.token) {
234: if (this .inKey) {
235: this .writeNextValue();
236: }
237: this .inKey = !this .inKey;
238: } else {
239: if (this .inKey) {
240: this .inKey = false;
241: this .stream.write(IncludingHTMLSerializer.token);
242: }
243: this .stream.write(b);
244: }
245: }
246:
247: /**
248: * Write next value
249: */
250: protected void writeNextValue() throws IOException {
251: final String value = (String) this .orderedValues.removeLast();
252: if (value != null) {
253: final byte[] o = value.getBytes(this .encoding);
254: this .stream.write(o, 0, o.length);
255: }
256: }
257: }
|