blob: 5ba2c7e3d6e9a7ff960efdaf7483d7310dce2a74 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.sitemap;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.generation.Generator;
import org.apache.cocoon.xml.ContentHandlerWrapper;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.cocoon.xml.XMLUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceException;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.source.impl.validity.AggregatedValidity;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Map;
/**
* This generator implements the sitemap content aggregation.
* It combines several parts into one big XML document which is streamed
* into the pipeline.
*
* @author <a href="mailto:giacomo@apache.org">Giacomo Pati</a>
* @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
* @version $Id$
*/
public class ContentAggregator extends ContentHandlerWrapper
implements Generator, CacheableProcessingComponent,
Serviceable {
/** The root element of the aggregated content */
protected Element rootElement;
/** The aggregated parts */
protected ArrayList parts = new ArrayList();
/** Indicates the position in the stack of the root element of the aggregated content */
private int rootElementIndex;
/** The element used for the current part */
protected Element currentElement;
/** The SourceResolver */
protected SourceResolver resolver;
/** The service manager */
protected ServiceManager manager;
/** This object holds the part parts :) */
protected static final class Part {
public String uri;
public Element element;
public Source source;
boolean stripRootElement;
public Part(String uri, Element element, String stripRoot) {
this.uri = uri;
this.element = element;
this.stripRootElement = BooleanUtils.toBoolean(stripRoot);
}
}
/** This object holds an element definition */
protected static final class Element {
public String namespace;
public String prefix;
public String name;
public Element(String name, String namespace, String prefix) {
this.namespace = namespace;
this.prefix = prefix;
this.name = name;
}
}
/**
* Generates the content
*/
public void generate()
throws IOException, SAXException, ProcessingException {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Generating aggregated content");
}
this.contentHandler.startDocument();
startElem(this.rootElement);
for (int i = 0; i < this.parts.size(); i++) {
final Part part = (Part) this.parts.get(i);
this.rootElementIndex = part.stripRootElement ? -1 : 0;
if (part.element != null) {
this.currentElement = part.element;
startElem(part.element);
} else {
this.currentElement = this.rootElement;
}
SourceUtil.parse(this.manager, part.source, this);
if (part.element != null ) {
endElem(part.element);
}
}
endElem(this.rootElement);
this.contentHandler.endDocument();
getLogger().debug("Finished aggregating content");
}
/**
* Generate the unique key.
* This key must be unique inside the space of this component.
*
* @return The generated key hashes the src
*/
public Serializable getKey() {
try {
StringBuffer buffer = new StringBuffer(64);
buffer.append("CA(")
.append(this.rootElement.prefix).append(':')
.append(this.rootElement.name).append('<')
.append(this.rootElement.namespace).append(">)");
for (int i = 0; i < this.parts.size(); i++) {
final Part part = (Part) this.parts.get(i);
final Source source = part.source;
if (part.element == null) {
buffer.append("P=")
.append(part.stripRootElement).append(':')
.append(source.getURI()).append(';');
} else {
buffer.append("P=")
.append(part.element.prefix).append(':')
.append(part.element.name)
.append('<').append(part.element.namespace).append(">:")
.append(part.stripRootElement).append(':')
.append(source.getURI()).append(';');
}
}
return buffer.toString();
} catch (Exception e) {
getLogger().error("Could not generateKey", e);
return null;
}
}
/**
* Generate the validity object.
*
* @return The generated validity object or <code>null</code> if the
* component is currently not cacheable.
*/
public SourceValidity getValidity() {
try {
AggregatedValidity v = new AggregatedValidity();
for (int i = 0; i < this.parts.size(); i++) {
final Source current = ((Part) this.parts.get(i)).source;
final SourceValidity sv = current.getValidity();
if (sv == null) {
return null;
} else {
v.add(sv);
}
}
return v;
} catch (Exception e) {
getLogger().error("Could not getValidity", e);
return null;
}
}
/**
* Set the root element. Please make sure that the parameters are not null!
*/
public void setRootElement(String element, String namespace, String prefix) {
this.rootElement = new Element(element,
namespace,
prefix);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Root element='" + element +
"' ns='" + namespace + "' prefix='" + prefix + "'");
}
}
/**
* Add a part. Please make sure that the parameters are not null!
*/
public void addPart(String uri,
String element,
String namespace,
String stripRootElement,
String prefix) {
Element elem = null;
if (!element.equals("")) {
if (namespace.length() == 0) {
elem = new Element(element,
this.rootElement.namespace,
this.rootElement.prefix);
} else {
elem = new Element(element,
namespace,
prefix);
}
}
this.parts.add(new Part(uri,
elem,
stripRootElement));
if (getLogger().isDebugEnabled()) {
getLogger().debug("Part uri='" + uri +
"' element='" + element + "' ns='" + namespace +
"' stripRootElement='" + stripRootElement + "' prefix='" + prefix + "'");
}
}
/**
* Set the <code>XMLConsumer</code> that will receive XML data.
*
* <br>
* This method will simply call <code>setContentHandler(consumer)</code>
* and <code>setLexicalHandler(consumer)</code>.
*/
public void setConsumer(XMLConsumer consumer) {
setContentHandler(consumer);
setLexicalHandler(consumer);
}
/**
* Recycle the producer by removing references
*/
public void recycle() {
super.recycle();
this.rootElement = null;
for (int i = 0; i < this.parts.size(); i++) {
final Part current = (Part) this.parts.get(i);
if (current.source != null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Releasing " + current.source);
}
this.resolver.release(current.source);
}
}
this.parts.clear();
this.currentElement = null;
this.resolver = null;
}
/**
* Set the <code>SourceResolver</code>, object model <code>Map</code>,
* the source and sitemap <code>Parameters</code> used to process the request.
*/
public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par)
throws ProcessingException, SAXException, IOException {
this.resolver = resolver;
// get the Source for each part
try {
for (int i = 0; i < this.parts.size(); i++) {
final Part current = (Part) this.parts.get(i);
current.source = resolver.resolveURI(current.uri);
}
} catch (SourceException se) {
throw SourceUtil.handle("Unable to resolve source.", se);
}
}
/**
* Private method generating startElement event for the aggregated parts
* and the root element
*/
private void startElem(Element element)
throws SAXException {
final String qname = (element.prefix.length() == 0) ? element.name : element.prefix + ':' + element.name;
if (!element.namespace.equals("")) {
this.contentHandler.startPrefixMapping(element.prefix, element.namespace);
}
this.contentHandler.startElement(element.namespace, element.name, qname, XMLUtils.EMPTY_ATTRIBUTES);
}
/**
* Private method generating endElement event for the aggregated parts
* and the root element
*/
private void endElem(Element element) throws SAXException {
final String qname = (element.prefix.length() == 0) ? element.name : element.prefix + ':' + element.name;
this.contentHandler.endElement(element.namespace, element.name, qname);
if (!element.namespace.equals("")) {
this.contentHandler.endPrefixMapping(element.prefix);
}
}
/**
* Ignore start and end document events
*/
public void startDocument() throws SAXException {
}
/**
* Ignore start and end document events
*/
public void endDocument() throws SAXException {
}
/**
* Override startElement() event to add namespace and prefix
*/
public void startElement(String namespaceURI, String localName, String raw, Attributes atts)
throws SAXException {
this.rootElementIndex++;
if (this.rootElementIndex == 0) {
getLogger().debug("Skipping root element start event.");
return;
}
if (namespaceURI == null || namespaceURI.length() == 0) {
final String qname = this.currentElement.prefix.length() == 0 ? localName : this.currentElement.prefix + ':' + localName;
this.contentHandler.startElement(this.currentElement.namespace, localName, qname, atts);
} else {
this.contentHandler.startElement(namespaceURI, localName, raw, atts);
}
}
/**
* Override startElement() event to add namespace and prefix
*/
public void endElement(String namespaceURI, String localName, String raw) throws SAXException {
this.rootElementIndex--;
if (this.rootElementIndex == -1) {
getLogger().debug("Skipping root element end event.");
return;
}
if (namespaceURI == null || namespaceURI.length() == 0) {
final String qname = this.currentElement.prefix.length() == 0 ? localName : this.currentElement.prefix + ':' + localName;
this.contentHandler.endElement(this.currentElement.namespace, localName, qname);
} else {
this.contentHandler.endElement(namespaceURI, localName, raw);
}
}
/* (non-Javadoc)
* @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
*/
public void service(ServiceManager manager) throws ServiceException {
this.manager = manager;
}
}