sylvain 01/12/03 07:05:09 Modified: src/org/apache/cocoon/generation Tag: cocoon_20_branch ServerPagesGenerator.java Log: Refactored document completion : - it is now optional (disabled by default), - if enabled, made faster by the use of a single ArrayStack instead of a LinkedList of new objects. Revision Changes Path No revision No revision 1.7.2.11 +344 -394 xml-cocoon2/src/org/apache/cocoon/generation/ServerPagesGenerator.java Index: ServerPagesGenerator.java =================================================================== RCS file: /home/cvs/xml-cocoon2/src/org/apache/cocoon/generation/ServerPagesGenerator.java,v retrieving revision 1.7.2.10 retrieving revision 1.7.2.11 diff -u -r1.7.2.10 -r1.7.2.11 --- ServerPagesGenerator.java 2001/10/10 09:25:14 1.7.2.10 +++ ServerPagesGenerator.java 2001/12/03 15:05:08 1.7.2.11 @@ -7,457 +7,239 @@ *****************************************************************************/ package org.apache.cocoon.generation; +import org.apache.avalon.excalibur.collections.ArrayStack; import org.apache.avalon.excalibur.pool.Recyclable; import org.apache.avalon.framework.activity.Disposable; - import org.apache.avalon.framework.component.Component; - import org.apache.avalon.framework.component.ComponentException; - import org.apache.avalon.framework.component.ComponentManager; - import org.apache.avalon.framework.parameters.Parameters; import org.apache.cocoon.ProcessingException; - import org.apache.cocoon.ResourceNotFoundException; - import org.apache.cocoon.caching.CacheValidity; - import org.apache.cocoon.caching.Cacheable; - import org.apache.cocoon.caching.CompositeCacheValidity; - import org.apache.cocoon.caching.ParametersCacheValidity; - import org.apache.cocoon.components.language.generator.ProgramGenerator; - import org.apache.cocoon.components.language.markup.xsp.XSPGenerator; - import org.apache.cocoon.environment.Source; - import org.apache.cocoon.environment.SourceResolver; - import org.apache.cocoon.util.HashUtil; - -import org.apache.cocoon.xml.XMLConsumer; +import org.apache.cocoon.xml.AbstractXMLPipe; import org.xml.sax.Attributes; - import org.xml.sax.Locator; - import org.xml.sax.SAXException; - - import java.io.IOException; import java.util.HashMap; - import java.util.LinkedList; - import java.util.Map; +import org.apache.avalon.framework.configuration.Configurable; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; /** * 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><autocomplete-documents>true|false<autocomplete-documents></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:[EMAIL PROTECTED]">Ricardo Rocha</a> - * @version CVS $Revision: 1.7.2.10 $ $Date: 2001/10/10 09:25:14 $ + * @author <a href="mailto:[EMAIL PROTECTED]">Sylvain Wallez</a> + * @version CVS $Revision: 1.7.2.11 $ $Date: 2001/12/03 15:05:08 $ */ -public class ServerPagesGenerator - extends ServletGenerator - implements Recyclable, Disposable, Cacheable, XMLConsumer +public class ServerPagesGenerator extends ServletGenerator + implements Recyclable, Disposable, Cacheable, Configurable { - /** - * The sitemap-defined server pages program generator - */ - protected static ProgramGenerator programGenerator = null; - - protected XSPGenerator generator = null; - - /** The source */ - private Source inputSource; - - /** - * 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) { - getLogger().debug("Looking up " + ProgramGenerator.ROLE); - try { - this.programGenerator = (ProgramGenerator) - manager.lookup(ProgramGenerator.ROLE); - } catch (Exception e) { - getLogger().error("Could not find ProgramGenerator", e); - } - } - } - - /** - * Generates the unique key. - * This key must be unique inside the space of this component. - * Users may override this method to take - * advantage of SAX event cacheing - * - * @return A long representing the cache key (defaults to not cachable) - */ - public long generateKey() { - return HashUtil.hash(this.inputSource.getSystemId() + generator.generateKey()); - } - - /** - * Generate the validity object. - * - * @return The generated validity object or <code>null</code> if the - * component is currently not cachable. - */ - public CacheValidity generateValidity() { - CacheValidity genValidity = generator.generateValidity(); - if (genValidity != null) { - HashMap map = new HashMap (1); - map.put("source", this.inputSource.getSystemId()); - ParametersCacheValidity pcv = new ParametersCacheValidity(map); - return new CompositeCacheValidity(genValidity, pcv); - } - return null; - } - - /** - * 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); - - this.inputSource = this.resolver.resolve(super.source); - - if (this.markupLanguage == null) { - this.markupLanguage = this.parameters.getParameter( - "markup-language", DEFAULT_MARKUP_LANGUAGE - ); - this.programmingLanguage = this.parameters.getParameter( - "programming-language", DEFAULT_PROGRAMMING_LANGUAGE - ); - } - - try { - generator = (XSPGenerator) - programGenerator.load(this.manager, super.source, this.markupLanguage, this.programmingLanguage, this.resolver); - } catch (ProcessingException e) { - throw e; - } catch (Exception e) { - getLogger().warn("ServerPagesGenerator.generate()", e); - throw new ResourceNotFoundException(e.getMessage(), e); - } catch (NoClassDefFoundError e) { - getLogger().warn("Failed to load class: " + e); - throw new ResourceNotFoundException(e.getMessage()); - } - generator.setup(this.resolver, this.objectModel, super.source, this.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 { - - generator.setConsumer(this); - - // log exception and ensure that generator is released. - try { - generator.generate(); - } catch (IOException e){ - getLogger().error("IOException in ServerPagesGenerator.generate()", e); - throw e; - } catch (SAXException e){ - getLogger().error("SAXException in ServerPagesGenerator.generate()", e); - throw e; - } catch (ProcessingException e){ - getLogger().error("ProcessingException in ServerPagesGenerator.generate()", e); - throw e; - } catch (Exception e){ - getLogger().error("Exception in ServerPagesGenerator.generate()", e); - throw new ProcessingException("Exception in ServerPagesGenerator.generate()", e); - } finally { - if(generator != null) - programGenerator.release(generator); - generator = null; - } - - // End any started events in case of premature return - while (this.eventStack.size()!=0) { - EventData eventData = (EventData) this.eventStack.removeFirst(); - - switch (eventData.eventType) { - case DOCUMENT: - this.contentHandler.endDocument(); - break; - case ELEMENT: - this.contentHandler.endElement( - eventData.getNamespaceURI(), - eventData.getLocalName(), - eventData.getRawName() - ); - break; - case PREFIX_MAPPING: - this.contentHandler.endPrefixMapping(eventData.getPrefix()); - break; - case CDATA: - this.lexicalHandler.endCDATA(); - break; - case DTD: - this.lexicalHandler.endDTD(); - break; - case ENTITY: - this.lexicalHandler.endEntity(eventData.getName()); - break; - } - } - } - - /* Handlers */ - - /** - * The SAX event stack. Used for "completing" pendind SAX events left "open" - * by prematurely returning server pages generators - */ - protected LinkedList eventStack = new LinkedList(); - /** - * Receive notification of character data. + * The sitemap-defined server pages program generator */ - public void characters(char[] ch, int start, int length) throws SAXException { - this.contentHandler.characters(ch, start, length); - } - - /** - * Receive notification of the end of a document. - */ - public void endDocument() throws SAXException { - this.eventStack.removeFirst(); - this.contentHandler.endDocument(); - } - - /** - * Receive notification of the end of an element. - */ - public void endElement(String namespaceURI, String localName, String rawName) - throws SAXException - { - this.eventStack.removeFirst(); - this.contentHandler.endElement(namespaceURI, localName, rawName); - } - - /** - * End the scope of a prefix-URI mapping. - */ - public void endPrefixMapping(String prefix) throws SAXException { - this.eventStack.removeFirst(); - this.contentHandler.endPrefixMapping(prefix); - } + protected static ProgramGenerator programGenerator = null; + + protected XSPGenerator 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) { + getLogger().debug("Looking up " + ProgramGenerator.ROLE); + try { + this.programGenerator = (ProgramGenerator) + manager.lookup(ProgramGenerator.ROLE); + } catch (Exception e) { + getLogger().error("Could not find ProgramGenerator", e); + } + } + } - /** - * Receive notification of ignorable whitespace in element content. - */ - public void ignorableWhitespace(char[] ch, int start, int length) - throws SAXException - { - this.contentHandler.ignorableWhitespace(ch, start, length); - } + public void configure(Configuration config) throws ConfigurationException { + boolean autoComplete = config.getChild("autocomplete-documents").getValueAsBoolean(false); + if (autoComplete) { + this.completionPipe = new CompletionPipe(); + this.completionPipe.setLogger(getLogger()); + } + } + /** - * Receive notification of a processing instruction. - */ - public void processingInstruction(String target, String data) - throws SAXException - { - this.contentHandler.processingInstruction(target, data); - } + * Generates the unique key. + * This key must be unique inside the space of this component. + * Users may override this method to take + * advantage of SAX event cacheing + * + * @return A long representing the cache key (defaults to not cachable) + */ + public long generateKey() { + return HashUtil.hash(this.inputSource.getSystemId() + generator.generateKey()); + } /** - * Receive an object for locating the origin of SAX document events. - */ - public void setDocumentLocator(Locator locator) { - this.contentHandler.setDocumentLocator(locator); - } + * Generate the validity object. + * + * @return The generated validity object or <code>null</code> if the + * component is currently not cachable. + */ + public CacheValidity generateValidity() { + CacheValidity genValidity = generator.generateValidity(); + if (genValidity != null) { + HashMap map = new HashMap (1); + map.put("source", this.inputSource.getSystemId()); + ParametersCacheValidity pcv = new ParametersCacheValidity(map); + return new CompositeCacheValidity(genValidity, pcv); + } + return null; + } /** - * Receive notification of a skipped entity. + * The loaded generator's <code>MarkupLanguage</code> */ - public void skippedEntity(String name) - throws SAXException - { - this.contentHandler.skippedEntity(name); - } - + protected String markupLanguage; + /** - * Receive notification of the beginning of a document. + * The loaded generator's <code>ProgrammingLanguage</code> */ - public void startDocument() throws SAXException { - this.contentHandler.startDocument(); - this.eventStack.addFirst(new EventData(DOCUMENT)); - } - + protected String programmingLanguage; + /** - * Receive notification of the beginning of an element. + * The default <code>MarkupLanguage</code> */ - public void startElement( - String namespaceURI, String localName, String rawName, Attributes atts - ) - throws SAXException - { - this.contentHandler.startElement(namespaceURI, localName, rawName, atts); - this.eventStack.addFirst(new EventData(ELEMENT, namespaceURI, localName, rawName)); - } - + public final static String DEFAULT_MARKUP_LANGUAGE = "xsp"; + /** - * Begin the scope of a prefix-URI Namespace mapping. + * The default <code>ProgrammingLanguage</code> */ - public void startPrefixMapping(String prefix, String uri) throws SAXException - { - this.contentHandler.startPrefixMapping(prefix, uri); - this.eventStack.addFirst(new EventData(PREFIX_MAPPING, prefix, uri)); - } - - public void comment(char[] ch, int start, int length) throws SAXException { - this.lexicalHandler.comment(ch, start, length); - } - - public void endCDATA() throws SAXException { - this.lexicalHandler.endCDATA(); - this.eventStack.removeFirst(); - } - - public void endDTD() throws SAXException { - this.lexicalHandler.endDTD(); - this.eventStack.removeFirst(); - } - - public void endEntity(String name) throws SAXException { - this.lexicalHandler.endEntity(name); - this.eventStack.removeFirst(); - } - - public void startCDATA() throws SAXException { - this.lexicalHandler.startCDATA(); - this.eventStack.addFirst(new EventData(CDATA)); - } - - public void startDTD(String name, String publicId, String systemId) - throws SAXException - { - this.lexicalHandler.startDTD(name, publicId, systemId); - this.eventStack.addFirst(new EventData(DTD, name, publicId, systemId)); - } - - public void startEntity(String name) throws SAXException { - this.lexicalHandler.startEntity(name); - this.eventStack.addFirst(new EventData(ENTITY, name)); - } - - protected final static int DOCUMENT = 0; - protected final static int ELEMENT = 1; - protected final static int PREFIX_MAPPING = 2; - protected final static int CDATA = 3; - protected final static int DTD = 4; - protected final static int ENTITY = 5; - - protected class EventData { - protected int eventType; - protected String namespaceURI; - protected String localName; - protected String rawName; - protected String prefix; - protected String publicId; - protected String systemId; - protected String name; - - protected EventData(int eventType) { - this.eventType = eventType; // DOCUMENT | CDATA - } - - protected EventData( - int eventType, String data1, String data2, String data3 - ) - { - this.eventType = eventType; - switch (this.eventType) { - case ELEMENT: - this.namespaceURI = data1; - this.localName = data2; - this.rawName = data3; - break; - case DTD: - this.name = data1; - this.publicId = data2; - this.systemId = data3; - break; - } - } + public final static String DEFAULT_PROGRAMMING_LANGUAGE = "java"; + + public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) + throws ProcessingException, SAXException, IOException { - protected EventData( - int eventType, String data1, String data2 - ) - { - this.eventType = eventType; - switch (this.eventType) { - case PREFIX_MAPPING: - this.prefix = data1; - this.namespaceURI = data2; - break; - } + super.setup(resolver, objectModel, src, par); + + this.inputSource = this.resolver.resolve(super.source); + + if (this.markupLanguage == null) { + this.markupLanguage = this.parameters.getParameter( + "markup-language", DEFAULT_MARKUP_LANGUAGE + ); + this.programmingLanguage = this.parameters.getParameter( + "programming-language", DEFAULT_PROGRAMMING_LANGUAGE + ); + } + + try { + generator = (XSPGenerator) + programGenerator.load(this.manager, super.source, this.markupLanguage, this.programmingLanguage, this.resolver); + } catch (ProcessingException e) { + throw e; + } catch (Exception e) { + getLogger().warn("ServerPagesGenerator.generate()", e); + throw new ResourceNotFoundException(e.getMessage(), e); + } catch (NoClassDefFoundError e) { + getLogger().warn("Failed to load class: " + e); + throw new ResourceNotFoundException(e.getMessage()); + } + generator.setup(this.resolver, this.objectModel, super.source, this.parameters); } - protected EventData(int eventType, String data) { - this.eventType = eventType; - switch (this.eventType) { - case ENTITY: - this.name = data; - break; - } + /** + * 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); + } + } + + // log exception and ensure that generator is released. + try { + generator.generate(); + } catch (IOException e){ + getLogger().error("IOException in ServerPagesGenerator.generate()", e); + throw e; + } catch (SAXException e){ + getLogger().error("SAXException in ServerPagesGenerator.generate()", e); + throw e; + } catch (ProcessingException e){ + getLogger().error("ProcessingException in ServerPagesGenerator.generate()", e); + throw e; + } catch (Exception e){ + getLogger().error("Exception in ServerPagesGenerator.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(); + } } - protected String getNamespaceURI() { return this.namespaceURI; } - protected String getLocalName() { return this.localName; } - protected String getRawName() { return this.rawName; } - protected String getPrefix() { return this.prefix; } - protected String getPublicId() { return this.publicId; } - protected String getSystemId() { return this.systemId; } - protected String getName() { return this.name; } - } - /** * Recycle the generator by removing references */ @@ -470,7 +252,9 @@ this.inputSource.recycle(); this.inputSource = null; } - this.eventStack.clear(); + if (this.completionPipe != null) { + this.completionPipe.recycle(); + } } /** @@ -478,5 +262,171 @@ */ public void dispose() { if(this.programGenerator != null) manager.release((Component)this.programGenerator); + } + + /* 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 (getLogger().isWarnEnabled()) { + if (this.eventStack.size() > 0) { + getLogger().warn("Premature end of document generated by " + inputSource.getSystemId()); + } + } + + // 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(); + } } }
---------------------------------------------------------------------- In case of troubles, e-mail: [EMAIL PROTECTED] To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]