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.sitemap;
018:
019: import org.apache.avalon.framework.parameters.Parameters;
020: import org.apache.avalon.framework.service.ServiceException;
021: import org.apache.avalon.framework.service.ServiceManager;
022: import org.apache.avalon.framework.service.Serviceable;
023:
024: import org.apache.cocoon.ProcessingException;
025: import org.apache.cocoon.caching.CacheableProcessingComponent;
026: import org.apache.cocoon.components.source.SourceUtil;
027: import org.apache.cocoon.environment.SourceResolver;
028: import org.apache.cocoon.generation.Generator;
029: import org.apache.cocoon.xml.ContentHandlerWrapper;
030: import org.apache.cocoon.xml.XMLConsumer;
031: import org.apache.cocoon.xml.XMLUtils;
032:
033: import org.apache.commons.lang.BooleanUtils;
034: import org.apache.excalibur.source.Source;
035: import org.apache.excalibur.source.SourceException;
036: import org.apache.excalibur.source.SourceValidity;
037: import org.apache.excalibur.source.impl.validity.AggregatedValidity;
038: import org.xml.sax.Attributes;
039: import org.xml.sax.SAXException;
040:
041: import java.io.IOException;
042: import java.io.Serializable;
043: import java.util.ArrayList;
044: import java.util.Map;
045:
046: /**
047: * This generator implements the sitemap content aggregation.
048: * It combines several parts into one big XML document which is streamed
049: * into the pipeline.
050: *
051: * @author <a href="mailto:giacomo@apache.org">Giacomo Pati</a>
052: * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
053: * @version $Id: ContentAggregator.java 433543 2006-08-22 06:22:54Z crossley $
054: */
055: public class ContentAggregator extends ContentHandlerWrapper implements
056: Generator, CacheableProcessingComponent, Serviceable {
057:
058: /** The root element of the aggregated content */
059: protected Element rootElement;
060:
061: /** The aggregated parts */
062: protected ArrayList parts = new ArrayList();
063:
064: /** Indicates the position in the stack of the root element of the aggregated content */
065: private int rootElementIndex;
066:
067: /** The element used for the current part */
068: protected Element currentElement;
069:
070: /** The SourceResolver */
071: protected SourceResolver resolver;
072:
073: /** The service manager */
074: protected ServiceManager manager;
075:
076: /** This object holds the part parts :) */
077: protected static final class Part {
078: public String uri;
079: public Element element;
080: public Source source;
081: boolean stripRootElement;
082:
083: public Part(String uri, Element element, String stripRoot) {
084: this .uri = uri;
085: this .element = element;
086: this .stripRootElement = BooleanUtils.toBoolean(stripRoot);
087: }
088: }
089:
090: /** This object holds an element definition */
091: protected static final class Element {
092: public String namespace;
093: public String prefix;
094: public String name;
095:
096: public Element(String name, String namespace, String prefix) {
097: this .namespace = namespace;
098: this .prefix = prefix;
099: this .name = name;
100: }
101: }
102:
103: /**
104: * Generates the content
105: */
106: public void generate() throws IOException, SAXException,
107: ProcessingException {
108: if (getLogger().isDebugEnabled()) {
109: getLogger().debug("Generating aggregated content");
110: }
111: this .contentHandler.startDocument();
112: startElem(this .rootElement);
113:
114: for (int i = 0; i < this .parts.size(); i++) {
115: final Part part = (Part) this .parts.get(i);
116: this .rootElementIndex = part.stripRootElement ? -1 : 0;
117: if (part.element != null) {
118: this .currentElement = part.element;
119: startElem(part.element);
120: } else {
121: this .currentElement = this .rootElement;
122: }
123:
124: SourceUtil.parse(this .manager, part.source, this );
125:
126: if (part.element != null) {
127: endElem(part.element);
128: }
129: }
130:
131: endElem(this .rootElement);
132: this .contentHandler.endDocument();
133: getLogger().debug("Finished aggregating content");
134: }
135:
136: /**
137: * Generate the unique key.
138: * This key must be unique inside the space of this component.
139: *
140: * @return The generated key hashes the src
141: */
142: public Serializable getKey() {
143: try {
144: StringBuffer buffer = new StringBuffer(64);
145: buffer.append("CA(").append(this .rootElement.prefix)
146: .append(':').append(this .rootElement.name).append(
147: '<').append(this .rootElement.namespace)
148: .append(">)");
149:
150: for (int i = 0; i < this .parts.size(); i++) {
151: final Part part = (Part) this .parts.get(i);
152: final Source source = part.source;
153:
154: if (part.element == null) {
155: buffer.append("P=").append(part.stripRootElement)
156: .append(':').append(source.getURI())
157: .append(';');
158: } else {
159: buffer.append("P=").append(part.element.prefix)
160: .append(':').append(part.element.name)
161: .append('<').append(part.element.namespace)
162: .append(">:").append(part.stripRootElement)
163: .append(':').append(source.getURI())
164: .append(';');
165: }
166: }
167:
168: return buffer.toString();
169: } catch (Exception e) {
170: getLogger().error("Could not generateKey", e);
171: return null;
172: }
173: }
174:
175: /**
176: * Generate the validity object.
177: *
178: * @return The generated validity object or <code>null</code> if the
179: * component is currently not cacheable.
180: */
181: public SourceValidity getValidity() {
182: try {
183: AggregatedValidity v = new AggregatedValidity();
184: for (int i = 0; i < this .parts.size(); i++) {
185: final Source current = ((Part) this .parts.get(i)).source;
186: final SourceValidity sv = current.getValidity();
187:
188: if (sv == null) {
189: return null;
190: } else {
191: v.add(sv);
192: }
193: }
194:
195: return v;
196: } catch (Exception e) {
197: getLogger().error("Could not getValidity", e);
198: return null;
199: }
200: }
201:
202: /**
203: * Set the root element. Please make sure that the parameters are not null!
204: */
205: public void setRootElement(String element, String namespace,
206: String prefix) {
207: this .rootElement = new Element(element, namespace, prefix);
208: if (getLogger().isDebugEnabled()) {
209: getLogger().debug(
210: "Root element='" + element + "' ns='" + namespace
211: + "' prefix='" + prefix + "'");
212: }
213: }
214:
215: /**
216: * Add a part. Please make sure that the parameters are not null!
217: */
218: public void addPart(String uri, String element, String namespace,
219: String stripRootElement, String prefix) {
220: Element elem = null;
221: if (!element.equals("")) {
222: if (namespace.length() == 0) {
223: elem = new Element(element, this .rootElement.namespace,
224: this .rootElement.prefix);
225: } else {
226: elem = new Element(element, namespace, prefix);
227: }
228: }
229: this .parts.add(new Part(uri, elem, stripRootElement));
230: if (getLogger().isDebugEnabled()) {
231: getLogger().debug(
232: "Part uri='" + uri + "' element='" + element
233: + "' ns='" + namespace
234: + "' stripRootElement='" + stripRootElement
235: + "' prefix='" + prefix + "'");
236: }
237: }
238:
239: /**
240: * Set the <code>XMLConsumer</code> that will receive XML data.
241: *
242: * <br>
243: * This method will simply call <code>setContentHandler(consumer)</code>
244: * and <code>setLexicalHandler(consumer)</code>.
245: */
246: public void setConsumer(XMLConsumer consumer) {
247: setContentHandler(consumer);
248: setLexicalHandler(consumer);
249: }
250:
251: /**
252: * Recycle the producer by removing references
253: */
254: public void recycle() {
255: super .recycle();
256:
257: this .rootElement = null;
258: for (int i = 0; i < this .parts.size(); i++) {
259: final Part current = (Part) this .parts.get(i);
260: if (current.source != null) {
261: if (getLogger().isDebugEnabled()) {
262: getLogger().debug("Releasing " + current.source);
263: }
264: this .resolver.release(current.source);
265: }
266: }
267: this .parts.clear();
268: this .currentElement = null;
269: this .resolver = null;
270: }
271:
272: /**
273: * Set the <code>SourceResolver</code>, object model <code>Map</code>,
274: * the source and sitemap <code>Parameters</code> used to process the request.
275: */
276: public void setup(SourceResolver resolver, Map objectModel,
277: String src, Parameters par) throws ProcessingException,
278: SAXException, IOException {
279: this .resolver = resolver;
280: // get the Source for each part
281: try {
282: for (int i = 0; i < this .parts.size(); i++) {
283: final Part current = (Part) this .parts.get(i);
284: current.source = resolver.resolveURI(current.uri);
285: }
286: } catch (SourceException se) {
287: throw SourceUtil.handle("Unable to resolve source.", se);
288: }
289: }
290:
291: /**
292: * Private method generating startElement event for the aggregated parts
293: * and the root element
294: */
295: private void startElem(Element element) throws SAXException {
296: final String qname = (element.prefix.length() == 0) ? element.name
297: : element.prefix + ':' + element.name;
298: if (!element.namespace.equals("")) {
299: this .contentHandler.startPrefixMapping(element.prefix,
300: element.namespace);
301: }
302: this .contentHandler.startElement(element.namespace,
303: element.name, qname, XMLUtils.EMPTY_ATTRIBUTES);
304: }
305:
306: /**
307: * Private method generating endElement event for the aggregated parts
308: * and the root element
309: */
310: private void endElem(Element element) throws SAXException {
311: final String qname = (element.prefix.length() == 0) ? element.name
312: : element.prefix + ':' + element.name;
313: this .contentHandler.endElement(element.namespace, element.name,
314: qname);
315: if (!element.namespace.equals("")) {
316: this .contentHandler.endPrefixMapping(element.prefix);
317: }
318: }
319:
320: /**
321: * Ignore start and end document events
322: */
323: public void startDocument() throws SAXException {
324: }
325:
326: /**
327: * Ignore start and end document events
328: */
329: public void endDocument() throws SAXException {
330: }
331:
332: /**
333: * Override startElement() event to add namespace and prefix
334: */
335: public void startElement(String namespaceURI, String localName,
336: String raw, Attributes atts) throws SAXException {
337: this .rootElementIndex++;
338: if (this .rootElementIndex == 0) {
339: getLogger().debug("Skipping root element start event.");
340: return;
341: }
342: if (namespaceURI == null || namespaceURI.length() == 0) {
343: final String qname = this .currentElement.prefix.length() == 0 ? localName
344: : this .currentElement.prefix + ':' + localName;
345: this .contentHandler.startElement(
346: this .currentElement.namespace, localName, qname,
347: atts);
348: } else {
349: this .contentHandler.startElement(namespaceURI, localName,
350: raw, atts);
351: }
352: }
353:
354: /**
355: * Override startElement() event to add namespace and prefix
356: */
357: public void endElement(String namespaceURI, String localName,
358: String raw) throws SAXException {
359: this .rootElementIndex--;
360: if (this .rootElementIndex == -1) {
361: getLogger().debug("Skipping root element end event.");
362: return;
363: }
364: if (namespaceURI == null || namespaceURI.length() == 0) {
365: final String qname = this .currentElement.prefix.length() == 0 ? localName
366: : this .currentElement.prefix + ':' + localName;
367: this .contentHandler.endElement(
368: this .currentElement.namespace, localName, qname);
369: } else {
370: this .contentHandler
371: .endElement(namespaceURI, localName, raw);
372: }
373: }
374:
375: /* (non-Javadoc)
376: * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
377: */
378: public void service(ServiceManager manager) throws ServiceException {
379: this.manager = manager;
380: }
381: }
|