blob: 51332cef0960f3dd34a04d6906b4637cf4308dc3 [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.generation;
import org.apache.commons.collections.ArrayStack;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.ResourceNotFoundException;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.components.language.generator.ProgramGenerator;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.xml.AbstractXMLPipe;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceException;
import org.apache.excalibur.source.SourceValidity;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.io.Serializable;
import java.util.Map;
/**
* This class acts as a proxy to a dynamically loaded<code>Generator</code>
* delegating actual SAX event generation.
* <p>
* It has a single configuration item :
* <code>&lt;autocomplete-documents&gttrue|false&lt;autocomplete-documents&gt;</code>
* (default is <code>false</code>).
* <p>
* This tells the generator to automatically close all elements that weren't properly closed
* by the XSP, such as when a <code>return</code> statement is used to prematurely end
* processing. Activating this feature <em>sensibly increases CPU-usage</em> and should
* therefore be used only if really needed (it's better to have clean XSP pages that don't
* break abruptly generation flow).
*
* @author <a href="mailto:ricardo@apache.org">Ricardo Rocha</a>
* @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
* @version $Id$
*/
public class ServerPagesGenerator extends ServletGenerator
implements CacheableProcessingComponent, Configurable {
/**
* The sitemap-defined server pages program generator
*/
protected ProgramGenerator programGenerator = null;
protected AbstractServerPage generator = null;
/** The source */
private Source inputSource;
private CompletionPipe completionPipe;
/**
* Set the global component manager. This method sets the sitemap-defined
* program generator
*
* @param manager The global component manager
*/
public void compose(ComponentManager manager) throws ComponentException {
super.compose(manager);
if (programGenerator == null) {
this.programGenerator =
(ProgramGenerator) manager.lookup(ProgramGenerator.ROLE);
}
}
public void configure(Configuration config) throws ConfigurationException {
boolean autoComplete = config.getChild("autocomplete-documents").getValueAsBoolean(false);
if (autoComplete) {
this.completionPipe = new CompletionPipe();
this.completionPipe.enableLogging(getLogger());
}
this.markupLanguage = config.getChild("markup-language").getValue(DEFAULT_MARKUP_LANGUAGE);
this.programmingLanguage = config.getChild("programming-language").getValue(DEFAULT_PROGRAMMING_LANGUAGE);
}
/**
* Generate the unique key.
* This key must be unique inside the space of this component.
* This method must be invoked before the generateValidity() method.
*
* @return The generated key or <code>null</code> if the component
* is currently not cacheable.
*/
public Serializable getKey() {
Object key = generator.getKey();
if (key == null) {
return this.inputSource.getURI();
}
return this.inputSource.getURI() + '-' + key;
}
/**
* Generate the validity object.
* Before this method can be invoked the generateKey() method
* must be invoked.
*
* @return The generated validity object or <code>null</code> if the
* component is currently not cacheable.
*/
public SourceValidity getValidity() {
// VG: Input source's systemID is part of the key,
// and need not be included into the validity.
return generator.getValidity();
}
/**
* The loaded generator's <code>MarkupLanguage</code>
*/
protected String markupLanguage;
/**
* The loaded generator's <code>ProgrammingLanguage</code>
*/
protected String programmingLanguage;
/**
* The default <code>MarkupLanguage</code>
*/
public final static String DEFAULT_MARKUP_LANGUAGE = "xsp";
/**
* The default <code>ProgrammingLanguage</code>
*/
public final static String DEFAULT_PROGRAMMING_LANGUAGE = "java";
public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par)
throws ProcessingException, SAXException, IOException {
super.setup(resolver, objectModel, src, par);
String markupLanguage = this.parameters.getParameter(
"markup-language", this.markupLanguage);
String programmingLanguage = this.parameters.getParameter(
"programming-language", this.programmingLanguage);
try {
this.inputSource = this.resolver.resolveURI(src);
} catch (SourceException se) {
throw SourceUtil.handle("Error during resolving of '" + src + "'.", se);
//throw SourceUtil.handle(se);
}
try {
this.generator = (AbstractServerPage) programGenerator.load(this.manager,
this.inputSource, markupLanguage, programmingLanguage, this.resolver);
} catch (ProcessingException e) {
throw e;
} catch (Exception e) {
getLogger().warn("setup()", e);
throw new ProcessingException(e.getMessage(), e);
} catch (NoClassDefFoundError e) {
// VG: Usually indicates that page invoked with the wrong case.
// I.e., it was compiled as "my.xsp" and inoked as "My.xsp",
// results in different class name and an error.
getLogger().warn("Failed to load class: " + e);
throw new ResourceNotFoundException(e.getMessage());
}
// Give our own logger to the generator so that logs go in the correct category
generator.enableLogging(getLogger());
generator.setup(super.resolver, super.objectModel, super.source, super.parameters);
}
/**
* Generate XML data. This method loads a server pages generator associated
* with its (file) input source and delegates SAX event generator to it
* taking care of "closing" any event left open by the loaded generator as a
* result of its possible "premature" return (a common situation in server
* pages)
*
* @exception IOException IO Error
* @exception SAXException SAX event generation error
* @exception ProcessingException Error during load/execution
*/
public void generate() throws IOException, SAXException, ProcessingException {
if (this.completionPipe != null) {
generator.setConsumer(this.completionPipe);
if (this.xmlConsumer != null) {
this.completionPipe.setConsumer(this.xmlConsumer);
} else {
this.completionPipe.setContentHandler(this.contentHandler);
this.completionPipe.setLexicalHandler(this.lexicalHandler);
}
} else {
if (this.xmlConsumer != null) {
generator.setConsumer(this.xmlConsumer);
} else {
generator.setContentHandler(this.contentHandler);
generator.setLexicalHandler(this.lexicalHandler);
}
}
// Fixes BUG#4062: Set document locator which is used by XIncludeTransformer
org.xml.sax.helpers.LocatorImpl locator = new org.xml.sax.helpers.LocatorImpl();
locator.setSystemId(this.inputSource.getURI());
this.contentHandler.setDocumentLocator(locator);
// Log exception and ensure that generator is released.
try {
generator.generate();
} catch (IOException e) {
getLogger().debug("IOException in generate()", e);
throw e;
} catch (SAXException e) {
getLogger().debug("SAXException in generate()", e);
throw e;
} catch (ProcessingException e) {
getLogger().debug("ProcessingException in generate()", e);
throw e;
} catch (Exception e) {
getLogger().debug("Exception in generate()", e);
throw new ProcessingException("Exception in ServerPagesGenerator.generate()", e);
} finally {
if (generator != null) {
programGenerator.release(generator);
}
generator = null;
}
if (this.completionPipe != null) {
this.completionPipe.flushEvents();
}
}
/**
* Recycle the generator by removing references
*/
public void recycle() {
if (this.generator != null) {
this.programGenerator.release(this.generator);
this.generator = null;
}
if (this.inputSource != null) {
this.resolver.release(this.inputSource);
this.inputSource = null;
}
if (this.completionPipe != null) {
this.completionPipe.recycle();
this.completionPipe = null;
}
super.recycle();
}
/**
* dispose
*/
public void dispose() {
this.manager.release(this.programGenerator);
this.programGenerator = null;
this.manager = null;
}
/* Completion pipe */
// int values for event types
private final static int DOCUMENT = 0;
private final static int ELEMENT = 1;
private final static int PREFIX_MAPPING = 2;
private final static int CDATA = 3;
private final static int DTD = 4;
private final static int ENTITY = 5;
// Integer equivalents to push on the stack
private final static Integer DOCUMENT_OBJ = new Integer(DOCUMENT);
private final static Integer ELEMENT_OBJ = new Integer(ELEMENT);
private final static Integer PREFIX_MAPPING_OBJ = new Integer(PREFIX_MAPPING);
private final static Integer CDATA_OBJ = new Integer(CDATA);
private final static Integer DTD_OBJ = new Integer(DTD);
private final static Integer ENTITY_OBJ = new Integer(ENTITY);
public class CompletionPipe extends AbstractXMLPipe {
/**
* The SAX event stack. Used for "completing" pendind SAX events left "open"
* by prematurely returning server pages generators
*/
protected ArrayStack eventStack = new ArrayStack();
/**
* Receive notification of the beginning of a document.
*/
public void startDocument() throws SAXException {
super.startDocument();
this.eventStack.push(DOCUMENT_OBJ);
}
/**
* Receive notification of the end of a document.
*/
public void endDocument() throws SAXException {
this.eventStack.pop();
super.endDocument();
}
/**
* Receive notification of the beginning of an element.
*/
public void startElement(String namespaceURI, String localName, String rawName, Attributes atts)
throws SAXException {
super.startElement(namespaceURI, localName, rawName, atts);
this.eventStack.push(rawName);
this.eventStack.push(localName);
this.eventStack.push(namespaceURI);
this.eventStack.push(ELEMENT_OBJ);
}
/**
* Receive notification of the end of an element.
*/
public void endElement(String namespaceURI, String localName, String rawName)
throws SAXException {
this.eventStack.pop(); // ELEMENT_OBJ
this.eventStack.pop(); // namespaceURI
this.eventStack.pop(); // localName
this.eventStack.pop(); // rawName
super.endElement(namespaceURI, localName, rawName);
}
/**
* Begin the scope of a prefix-URI Namespace mapping.
*/
public void startPrefixMapping(String prefix, String uri) throws SAXException {
super.startPrefixMapping(prefix, uri);
this.eventStack.push(prefix);
this.eventStack.push(PREFIX_MAPPING_OBJ);
}
/**
* End the scope of a prefix-URI mapping.
*/
public void endPrefixMapping(String prefix) throws SAXException {
this.eventStack.pop(); // PREFIX_MAPPING_OBJ
this.eventStack.pop(); // prefix
super.endPrefixMapping(prefix);
}
public void startCDATA() throws SAXException {
super.startCDATA();
this.eventStack.push(CDATA_OBJ);
}
public void endCDATA() throws SAXException {
this.eventStack.pop();
super.endCDATA();
}
public void startDTD(String name, String publicId, String systemId)
throws SAXException {
super.startDTD(name, publicId, systemId);
this.eventStack.push(DTD_OBJ);
}
public void endDTD() throws SAXException {
this.eventStack.pop();
super.endDTD();
}
public void startEntity(String name) throws SAXException {
super.startEntity(name);
this.eventStack.push(name);
this.eventStack.push(ENTITY_OBJ);
}
public void endEntity(String name) throws SAXException {
this.eventStack.pop(); // ENTITY_OBJ
this.eventStack.pop(); // name
super.endEntity(name);
}
public void flushEvents() throws SAXException {
if (this.getLogger().isWarnEnabled()) {
if (this.eventStack.size() > 0) {
this.getLogger().warn("Premature end of document generated by " + inputSource.getURI());
}
}
// End any started events in case of premature return
while (this.eventStack.size() != 0) {
int event = ((Integer) eventStack.pop()).intValue();
switch (event) {
case DOCUMENT:
super.endDocument();
break;
case ELEMENT:
String namespaceURI = (String) eventStack.pop();
String localName = (String) eventStack.pop();
String rawName = (String) eventStack.pop();
super.endElement(namespaceURI, localName, rawName);
break;
case PREFIX_MAPPING:
super.endPrefixMapping((String) eventStack.pop());
break;
case CDATA:
super.endCDATA();
break;
case DTD:
super.endDTD();
break;
case ENTITY:
super.endEntity((String) eventStack.pop());
break;
}
}
}
public void recycle() {
this.eventStack.clear();
super.recycle();
}
}
}