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.generation;
018:
019: import org.apache.commons.collections.ArrayStack;
020: import org.apache.avalon.framework.component.ComponentException;
021: import org.apache.avalon.framework.component.ComponentManager;
022: import org.apache.avalon.framework.configuration.Configurable;
023: import org.apache.avalon.framework.configuration.Configuration;
024: import org.apache.avalon.framework.configuration.ConfigurationException;
025: import org.apache.avalon.framework.parameters.Parameters;
026: import org.apache.cocoon.ProcessingException;
027: import org.apache.cocoon.ResourceNotFoundException;
028: import org.apache.cocoon.caching.CacheableProcessingComponent;
029: import org.apache.cocoon.components.language.generator.ProgramGenerator;
030: import org.apache.cocoon.components.source.SourceUtil;
031: import org.apache.cocoon.environment.SourceResolver;
032: import org.apache.cocoon.xml.AbstractXMLPipe;
033: import org.apache.excalibur.source.Source;
034: import org.apache.excalibur.source.SourceException;
035: import org.apache.excalibur.source.SourceValidity;
036: import org.xml.sax.Attributes;
037: import org.xml.sax.SAXException;
038:
039: import java.io.IOException;
040: import java.io.Serializable;
041: import java.util.Map;
042:
043: /**
044: * This class acts as a proxy to a dynamically loaded<code>Generator</code>
045: * delegating actual SAX event generation.
046: * <p>
047: * It has a single configuration item :
048: * <code><autocomplete-documents>true|false<autocomplete-documents></code>
049: * (default is <code>false</code>).
050: * <p>
051: * This tells the generator to automatically close all elements that weren't properly closed
052: * by the XSP, such as when a <code>return</code> statement is used to prematurely end
053: * processing. Activating this feature <em>sensibly increases CPU-usage</em> and should
054: * therefore be used only if really needed (it's better to have clean XSP pages that don't
055: * break abruptly generation flow).
056: *
057: * @author <a href="mailto:ricardo@apache.org">Ricardo Rocha</a>
058: * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
059: * @version $Id: ServerPagesGenerator.java 433543 2006-08-22 06:22:54Z crossley $
060: */
061: public class ServerPagesGenerator extends ServletGenerator implements
062: CacheableProcessingComponent, Configurable {
063: /**
064: * The sitemap-defined server pages program generator
065: */
066: protected ProgramGenerator programGenerator = null;
067:
068: protected AbstractServerPage generator = null;
069:
070: /** The source */
071: private Source inputSource;
072:
073: private CompletionPipe completionPipe;
074:
075: /**
076: * Set the global component manager. This method sets the sitemap-defined
077: * program generator
078: *
079: * @param manager The global component manager
080: */
081: public void compose(ComponentManager manager)
082: throws ComponentException {
083: super .compose(manager);
084:
085: if (programGenerator == null) {
086: this .programGenerator = (ProgramGenerator) manager
087: .lookup(ProgramGenerator.ROLE);
088: }
089: }
090:
091: public void configure(Configuration config)
092: throws ConfigurationException {
093: boolean autoComplete = config
094: .getChild("autocomplete-documents").getValueAsBoolean(
095: false);
096:
097: if (autoComplete) {
098: this .completionPipe = new CompletionPipe();
099: this .completionPipe.enableLogging(getLogger());
100: }
101:
102: this .markupLanguage = config.getChild("markup-language")
103: .getValue(DEFAULT_MARKUP_LANGUAGE);
104: this .programmingLanguage = config.getChild(
105: "programming-language").getValue(
106: DEFAULT_PROGRAMMING_LANGUAGE);
107: }
108:
109: /**
110: * Generate the unique key.
111: * This key must be unique inside the space of this component.
112: * This method must be invoked before the generateValidity() method.
113: *
114: * @return The generated key or <code>null</code> if the component
115: * is currently not cacheable.
116: */
117: public Serializable getKey() {
118: Object key = generator.getKey();
119: if (key == null) {
120: return this .inputSource.getURI();
121: }
122: return this .inputSource.getURI() + '-' + key;
123: }
124:
125: /**
126: * Generate the validity object.
127: * Before this method can be invoked the generateKey() method
128: * must be invoked.
129: *
130: * @return The generated validity object or <code>null</code> if the
131: * component is currently not cacheable.
132: */
133: public SourceValidity getValidity() {
134: // VG: Input source's systemID is part of the key,
135: // and need not be included into the validity.
136: return generator.getValidity();
137: }
138:
139: /**
140: * The loaded generator's <code>MarkupLanguage</code>
141: */
142: protected String markupLanguage;
143:
144: /**
145: * The loaded generator's <code>ProgrammingLanguage</code>
146: */
147: protected String programmingLanguage;
148:
149: /**
150: * The default <code>MarkupLanguage</code>
151: */
152: public final static String DEFAULT_MARKUP_LANGUAGE = "xsp";
153:
154: /**
155: * The default <code>ProgrammingLanguage</code>
156: */
157: public final static String DEFAULT_PROGRAMMING_LANGUAGE = "java";
158:
159: public void setup(SourceResolver resolver, Map objectModel,
160: String src, Parameters par) throws ProcessingException,
161: SAXException, IOException {
162:
163: super .setup(resolver, objectModel, src, par);
164:
165: String markupLanguage = this .parameters.getParameter(
166: "markup-language", this .markupLanguage);
167: String programmingLanguage = this .parameters.getParameter(
168: "programming-language", this .programmingLanguage);
169:
170: try {
171: this .inputSource = this .resolver.resolveURI(src);
172: } catch (SourceException se) {
173: throw SourceUtil.handle("Error during resolving of '" + src
174: + "'.", se);
175: //throw SourceUtil.handle(se);
176: }
177:
178: try {
179: this .generator = (AbstractServerPage) programGenerator
180: .load(this .manager, this .inputSource,
181: markupLanguage, programmingLanguage,
182: this .resolver);
183: } catch (ProcessingException e) {
184: throw e;
185: } catch (Exception e) {
186: getLogger().warn("setup()", e);
187: throw new ProcessingException(e.getMessage(), e);
188: } catch (NoClassDefFoundError e) {
189: // VG: Usually indicates that page invoked with the wrong case.
190: // I.e., it was compiled as "my.xsp" and inoked as "My.xsp",
191: // results in different class name and an error.
192: getLogger().warn("Failed to load class: " + e);
193: throw new ResourceNotFoundException(e.getMessage());
194: }
195:
196: // Give our own logger to the generator so that logs go in the correct category
197: generator.enableLogging(getLogger());
198:
199: generator.setup(super .resolver, super .objectModel,
200: super .source, super .parameters);
201: }
202:
203: /**
204: * Generate XML data. This method loads a server pages generator associated
205: * with its (file) input source and delegates SAX event generator to it
206: * taking care of "closing" any event left open by the loaded generator as a
207: * result of its possible "premature" return (a common situation in server
208: * pages)
209: *
210: * @exception IOException IO Error
211: * @exception SAXException SAX event generation error
212: * @exception ProcessingException Error during load/execution
213: */
214: public void generate() throws IOException, SAXException,
215: ProcessingException {
216:
217: if (this .completionPipe != null) {
218: generator.setConsumer(this .completionPipe);
219: if (this .xmlConsumer != null) {
220: this .completionPipe.setConsumer(this .xmlConsumer);
221: } else {
222: this .completionPipe
223: .setContentHandler(this .contentHandler);
224: this .completionPipe
225: .setLexicalHandler(this .lexicalHandler);
226: }
227: } else {
228: if (this .xmlConsumer != null) {
229: generator.setConsumer(this .xmlConsumer);
230: } else {
231: generator.setContentHandler(this .contentHandler);
232: generator.setLexicalHandler(this .lexicalHandler);
233: }
234: }
235:
236: // Fixes BUG#4062: Set document locator which is used by XIncludeTransformer
237: org.xml.sax.helpers.LocatorImpl locator = new org.xml.sax.helpers.LocatorImpl();
238: locator.setSystemId(this .inputSource.getURI());
239: this .contentHandler.setDocumentLocator(locator);
240:
241: // Log exception and ensure that generator is released.
242: try {
243: generator.generate();
244: } catch (IOException e) {
245: getLogger().debug("IOException in generate()", e);
246: throw e;
247: } catch (SAXException e) {
248: getLogger().debug("SAXException in generate()", e);
249: throw e;
250: } catch (ProcessingException e) {
251: getLogger().debug("ProcessingException in generate()", e);
252: throw e;
253: } catch (Exception e) {
254: getLogger().debug("Exception in generate()", e);
255: throw new ProcessingException(
256: "Exception in ServerPagesGenerator.generate()", e);
257: } finally {
258: if (generator != null) {
259: programGenerator.release(generator);
260: }
261: generator = null;
262: }
263:
264: if (this .completionPipe != null) {
265: this .completionPipe.flushEvents();
266: }
267: }
268:
269: /**
270: * Recycle the generator by removing references
271: */
272: public void recycle() {
273: if (this .generator != null) {
274: this .programGenerator.release(this .generator);
275: this .generator = null;
276: }
277: if (this .inputSource != null) {
278: this .resolver.release(this .inputSource);
279: this .inputSource = null;
280: }
281: if (this .completionPipe != null) {
282: this .completionPipe.recycle();
283: this .completionPipe = null;
284: }
285: super .recycle();
286: }
287:
288: /**
289: * dispose
290: */
291: public void dispose() {
292: this .manager.release(this .programGenerator);
293: this .programGenerator = null;
294: this .manager = null;
295: }
296:
297: /* Completion pipe */
298:
299: // int values for event types
300: private final static int DOCUMENT = 0;
301: private final static int ELEMENT = 1;
302: private final static int PREFIX_MAPPING = 2;
303: private final static int CDATA = 3;
304: private final static int DTD = 4;
305: private final static int ENTITY = 5;
306:
307: // Integer equivalents to push on the stack
308: private final static Integer DOCUMENT_OBJ = new Integer(DOCUMENT);
309: private final static Integer ELEMENT_OBJ = new Integer(ELEMENT);
310: private final static Integer PREFIX_MAPPING_OBJ = new Integer(
311: PREFIX_MAPPING);
312: private final static Integer CDATA_OBJ = new Integer(CDATA);
313: private final static Integer DTD_OBJ = new Integer(DTD);
314: private final static Integer ENTITY_OBJ = new Integer(ENTITY);
315:
316: public class CompletionPipe extends AbstractXMLPipe {
317:
318: /**
319: * The SAX event stack. Used for "completing" pendind SAX events left "open"
320: * by prematurely returning server pages generators
321: */
322: protected ArrayStack eventStack = new ArrayStack();
323:
324: /**
325: * Receive notification of the beginning of a document.
326: */
327: public void startDocument() throws SAXException {
328: super .startDocument();
329: this .eventStack.push(DOCUMENT_OBJ);
330: }
331:
332: /**
333: * Receive notification of the end of a document.
334: */
335: public void endDocument() throws SAXException {
336: this .eventStack.pop();
337: super .endDocument();
338: }
339:
340: /**
341: * Receive notification of the beginning of an element.
342: */
343: public void startElement(String namespaceURI, String localName,
344: String rawName, Attributes atts) throws SAXException {
345: super .startElement(namespaceURI, localName, rawName, atts);
346: this .eventStack.push(rawName);
347: this .eventStack.push(localName);
348: this .eventStack.push(namespaceURI);
349: this .eventStack.push(ELEMENT_OBJ);
350: }
351:
352: /**
353: * Receive notification of the end of an element.
354: */
355: public void endElement(String namespaceURI, String localName,
356: String rawName) throws SAXException {
357: this .eventStack.pop(); // ELEMENT_OBJ
358: this .eventStack.pop(); // namespaceURI
359: this .eventStack.pop(); // localName
360: this .eventStack.pop(); // rawName
361: super .endElement(namespaceURI, localName, rawName);
362: }
363:
364: /**
365: * Begin the scope of a prefix-URI Namespace mapping.
366: */
367: public void startPrefixMapping(String prefix, String uri)
368: throws SAXException {
369: super .startPrefixMapping(prefix, uri);
370: this .eventStack.push(prefix);
371: this .eventStack.push(PREFIX_MAPPING_OBJ);
372: }
373:
374: /**
375: * End the scope of a prefix-URI mapping.
376: */
377: public void endPrefixMapping(String prefix) throws SAXException {
378: this .eventStack.pop(); // PREFIX_MAPPING_OBJ
379: this .eventStack.pop(); // prefix
380: super .endPrefixMapping(prefix);
381: }
382:
383: public void startCDATA() throws SAXException {
384: super .startCDATA();
385: this .eventStack.push(CDATA_OBJ);
386: }
387:
388: public void endCDATA() throws SAXException {
389: this .eventStack.pop();
390: super .endCDATA();
391: }
392:
393: public void startDTD(String name, String publicId,
394: String systemId) throws SAXException {
395: super .startDTD(name, publicId, systemId);
396: this .eventStack.push(DTD_OBJ);
397: }
398:
399: public void endDTD() throws SAXException {
400: this .eventStack.pop();
401: super .endDTD();
402: }
403:
404: public void startEntity(String name) throws SAXException {
405: super .startEntity(name);
406: this .eventStack.push(name);
407: this .eventStack.push(ENTITY_OBJ);
408: }
409:
410: public void endEntity(String name) throws SAXException {
411: this .eventStack.pop(); // ENTITY_OBJ
412: this .eventStack.pop(); // name
413: super .endEntity(name);
414: }
415:
416: public void flushEvents() throws SAXException {
417:
418: if (this .getLogger().isWarnEnabled()) {
419: if (this .eventStack.size() > 0) {
420: this .getLogger().warn(
421: "Premature end of document generated by "
422: + inputSource.getURI());
423: }
424: }
425:
426: // End any started events in case of premature return
427: while (this .eventStack.size() != 0) {
428: int event = ((Integer) eventStack.pop()).intValue();
429:
430: switch (event) {
431: case DOCUMENT:
432: super .endDocument();
433: break;
434:
435: case ELEMENT:
436: String namespaceURI = (String) eventStack.pop();
437: String localName = (String) eventStack.pop();
438: String rawName = (String) eventStack.pop();
439: super .endElement(namespaceURI, localName, rawName);
440: break;
441:
442: case PREFIX_MAPPING:
443: super .endPrefixMapping((String) eventStack.pop());
444: break;
445:
446: case CDATA:
447: super .endCDATA();
448: break;
449:
450: case DTD:
451: super .endDTD();
452: break;
453:
454: case ENTITY:
455: super .endEntity((String) eventStack.pop());
456: break;
457: }
458: }
459: }
460:
461: public void recycle() {
462: this.eventStack.clear();
463: super.recycle();
464: }
465: }
466: }
|