1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.cocoon.generation;
18
19 import org.apache.commons.collections.ArrayStack;
20 import org.apache.avalon.framework.component.ComponentException;
21 import org.apache.avalon.framework.component.ComponentManager;
22 import org.apache.avalon.framework.configuration.Configurable;
23 import org.apache.avalon.framework.configuration.Configuration;
24 import org.apache.avalon.framework.configuration.ConfigurationException;
25 import org.apache.avalon.framework.parameters.Parameters;
26 import org.apache.cocoon.ProcessingException;
27 import org.apache.cocoon.ResourceNotFoundException;
28 import org.apache.cocoon.caching.CacheableProcessingComponent;
29 import org.apache.cocoon.components.language.generator.ProgramGenerator;
30 import org.apache.cocoon.components.source.SourceUtil;
31 import org.apache.cocoon.environment.SourceResolver;
32 import org.apache.cocoon.xml.AbstractXMLPipe;
33 import org.apache.excalibur.source.Source;
34 import org.apache.excalibur.source.SourceException;
35 import org.apache.excalibur.source.SourceValidity;
36 import org.xml.sax.Attributes;
37 import org.xml.sax.SAXException;
38
39 import java.io.IOException;
40 import java.io.Serializable;
41 import java.util.Map;
42
43 /**
44 * This class acts as a proxy to a dynamically loaded<code>Generator</code>
45 * delegating actual SAX event generation.
46 * <p>
47 * It has a single configuration item :
48 * <code><autocomplete-documents>true|false<autocomplete-documents></code>
49 * (default is <code>false</code>).
50 * <p>
51 * This tells the generator to automatically close all elements that weren't properly closed
52 * by the XSP, such as when a <code>return</code> statement is used to prematurely end
53 * processing. Activating this feature <em>sensibly increases CPU-usage</em> and should
54 * therefore be used only if really needed (it's better to have clean XSP pages that don't
55 * break abruptly generation flow).
56 *
57 * @author <a href="mailto:ricardo@apache.org">Ricardo Rocha</a>
58 * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
59 * @version $Id: ServerPagesGenerator.java 433543 2006-08-22 06:22:54Z crossley $
60 */
61 public class ServerPagesGenerator extends ServletGenerator
62 implements CacheableProcessingComponent, Configurable {
63 /**
64 * The sitemap-defined server pages program generator
65 */
66 protected ProgramGenerator programGenerator = null;
67
68 protected AbstractServerPage generator = null;
69
70 /** The source */
71 private Source inputSource;
72
73 private CompletionPipe completionPipe;
74
75 /**
76 * Set the global component manager. This method sets the sitemap-defined
77 * program generator
78 *
79 * @param manager The global component manager
80 */
81 public void compose(ComponentManager manager) throws ComponentException {
82 super.compose(manager);
83
84 if (programGenerator == null) {
85 this.programGenerator =
86 (ProgramGenerator) manager.lookup(ProgramGenerator.ROLE);
87 }
88 }
89
90 public void configure(Configuration config) throws ConfigurationException {
91 boolean autoComplete = config.getChild("autocomplete-documents").getValueAsBoolean(false);
92
93 if (autoComplete) {
94 this.completionPipe = new CompletionPipe();
95 this.completionPipe.enableLogging(getLogger());
96 }
97
98 this.markupLanguage = config.getChild("markup-language").getValue(DEFAULT_MARKUP_LANGUAGE);
99 this.programmingLanguage = config.getChild("programming-language").getValue(DEFAULT_PROGRAMMING_LANGUAGE);
100 }
101
102 /**
103 * Generate the unique key.
104 * This key must be unique inside the space of this component.
105 * This method must be invoked before the generateValidity() method.
106 *
107 * @return The generated key or <code>null</code> if the component
108 * is currently not cacheable.
109 */
110 public Serializable getKey() {
111 Object key = generator.getKey();
112 if (key == null) {
113 return this.inputSource.getURI();
114 }
115 return this.inputSource.getURI() + '-' + key;
116 }
117
118 /**
119 * Generate the validity object.
120 * Before this method can be invoked the generateKey() method
121 * must be invoked.
122 *
123 * @return The generated validity object or <code>null</code> if the
124 * component is currently not cacheable.
125 */
126 public SourceValidity getValidity() {
127 // VG: Input source's systemID is part of the key,
128 // and need not be included into the validity.
129 return generator.getValidity();
130 }
131
132 /**
133 * The loaded generator's <code>MarkupLanguage</code>
134 */
135 protected String markupLanguage;
136
137 /**
138 * The loaded generator's <code>ProgrammingLanguage</code>
139 */
140 protected String programmingLanguage;
141
142 /**
143 * The default <code>MarkupLanguage</code>
144 */
145 public final static String DEFAULT_MARKUP_LANGUAGE = "xsp";
146
147 /**
148 * The default <code>ProgrammingLanguage</code>
149 */
150 public final static String DEFAULT_PROGRAMMING_LANGUAGE = "java";
151
152 public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par)
153 throws ProcessingException, SAXException, IOException {
154
155 super.setup(resolver, objectModel, src, par);
156
157 String markupLanguage = this.parameters.getParameter(
158 "markup-language", this.markupLanguage);
159 String programmingLanguage = this.parameters.getParameter(
160 "programming-language", this.programmingLanguage);
161
162 try {
163 this.inputSource = this.resolver.resolveURI(src);
164 } catch (SourceException se) {
165 throw SourceUtil.handle("Error during resolving of '" + src + "'.", se);
166 //throw SourceUtil.handle(se);
167 }
168
169 try {
170 this.generator = (AbstractServerPage) programGenerator.load(this.manager,
171 this.inputSource, markupLanguage, programmingLanguage, this.resolver);
172 } catch (ProcessingException e) {
173 throw e;
174 } catch (Exception e) {
175 getLogger().warn("setup()", e);
176 throw new ProcessingException(e.getMessage(), e);
177 } catch (NoClassDefFoundError e) {
178 // VG: Usually indicates that page invoked with the wrong case.
179 // I.e., it was compiled as "my.xsp" and inoked as "My.xsp",
180 // results in different class name and an error.
181 getLogger().warn("Failed to load class: " + e);
182 throw new ResourceNotFoundException(e.getMessage());
183 }
184
185 // Give our own logger to the generator so that logs go in the correct category
186 generator.enableLogging(getLogger());
187
188 generator.setup(super.resolver, super.objectModel, super.source, super.parameters);
189 }
190
191 /**
192 * Generate XML data. This method loads a server pages generator associated
193 * with its (file) input source and delegates SAX event generator to it
194 * taking care of "closing" any event left open by the loaded generator as a
195 * result of its possible "premature" return (a common situation in server
196 * pages)
197 *
198 * @exception IOException IO Error
199 * @exception SAXException SAX event generation error
200 * @exception ProcessingException Error during load/execution
201 */
202 public void generate() throws IOException, SAXException, ProcessingException {
203
204 if (this.completionPipe != null) {
205 generator.setConsumer(this.completionPipe);
206 if (this.xmlConsumer != null) {
207 this.completionPipe.setConsumer(this.xmlConsumer);
208 } else {
209 this.completionPipe.setContentHandler(this.contentHandler);
210 this.completionPipe.setLexicalHandler(this.lexicalHandler);
211 }
212 } else {
213 if (this.xmlConsumer != null) {
214 generator.setConsumer(this.xmlConsumer);
215 } else {
216 generator.setContentHandler(this.contentHandler);
217 generator.setLexicalHandler(this.lexicalHandler);
218 }
219 }
220
221 // Fixes BUG#4062: Set document locator which is used by XIncludeTransformer
222 org.xml.sax.helpers.LocatorImpl locator = new org.xml.sax.helpers.LocatorImpl();
223 locator.setSystemId(this.inputSource.getURI());
224 this.contentHandler.setDocumentLocator(locator);
225
226 // Log exception and ensure that generator is released.
227 try {
228 generator.generate();
229 } catch (IOException e) {
230 getLogger().debug("IOException in generate()", e);
231 throw e;
232 } catch (SAXException e) {
233 getLogger().debug("SAXException in generate()", e);
234 throw e;
235 } catch (ProcessingException e) {
236 getLogger().debug("ProcessingException in generate()", e);
237 throw e;
238 } catch (Exception e) {
239 getLogger().debug("Exception in generate()", e);
240 throw new ProcessingException("Exception in ServerPagesGenerator.generate()", e);
241 } finally {
242 if (generator != null) {
243 programGenerator.release(generator);
244 }
245 generator = null;
246 }
247
248 if (this.completionPipe != null) {
249 this.completionPipe.flushEvents();
250 }
251 }
252
253 /**
254 * Recycle the generator by removing references
255 */
256 public void recycle() {
257 if (this.generator != null) {
258 this.programGenerator.release(this.generator);
259 this.generator = null;
260 }
261 if (this.inputSource != null) {
262 this.resolver.release(this.inputSource);
263 this.inputSource = null;
264 }
265 if (this.completionPipe != null) {
266 this.completionPipe.recycle();
267 this.completionPipe = null;
268 }
269 super.recycle();
270 }
271
272 /**
273 * dispose
274 */
275 public void dispose() {
276 this.manager.release(this.programGenerator);
277 this.programGenerator = null;
278 this.manager = null;
279 }
280
281 /* Completion pipe */
282
283 // int values for event types
284 private final static int DOCUMENT = 0;
285 private final static int ELEMENT = 1;
286 private final static int PREFIX_MAPPING = 2;
287 private final static int CDATA = 3;
288 private final static int DTD = 4;
289 private final static int ENTITY = 5;
290
291 // Integer equivalents to push on the stack
292 private final static Integer DOCUMENT_OBJ = new Integer(DOCUMENT);
293 private final static Integer ELEMENT_OBJ = new Integer(ELEMENT);
294 private final static Integer PREFIX_MAPPING_OBJ = new Integer(PREFIX_MAPPING);
295 private final static Integer CDATA_OBJ = new Integer(CDATA);
296 private final static Integer DTD_OBJ = new Integer(DTD);
297 private final static Integer ENTITY_OBJ = new Integer(ENTITY);
298
299 public class CompletionPipe extends AbstractXMLPipe {
300
301 /**
302 * The SAX event stack. Used for "completing" pendind SAX events left "open"
303 * by prematurely returning server pages generators
304 */
305 protected ArrayStack eventStack = new ArrayStack();
306
307 /**
308 * Receive notification of the beginning of a document.
309 */
310 public void startDocument() throws SAXException {
311 super.startDocument();
312 this.eventStack.push(DOCUMENT_OBJ);
313 }
314
315 /**
316 * Receive notification of the end of a document.
317 */
318 public void endDocument() throws SAXException {
319 this.eventStack.pop();
320 super.endDocument();
321 }
322
323 /**
324 * Receive notification of the beginning of an element.
325 */
326 public void startElement(String namespaceURI, String localName, String rawName, Attributes atts)
327 throws SAXException {
328 super.startElement(namespaceURI, localName, rawName, atts);
329 this.eventStack.push(rawName);
330 this.eventStack.push(localName);
331 this.eventStack.push(namespaceURI);
332 this.eventStack.push(ELEMENT_OBJ);
333 }
334
335 /**
336 * Receive notification of the end of an element.
337 */
338 public void endElement(String namespaceURI, String localName, String rawName)
339 throws SAXException {
340 this.eventStack.pop(); // ELEMENT_OBJ
341 this.eventStack.pop(); // namespaceURI
342 this.eventStack.pop(); // localName
343 this.eventStack.pop(); // rawName
344 super.endElement(namespaceURI, localName, rawName);
345 }
346
347 /**
348 * Begin the scope of a prefix-URI Namespace mapping.
349 */
350 public void startPrefixMapping(String prefix, String uri) throws SAXException {
351 super.startPrefixMapping(prefix, uri);
352 this.eventStack.push(prefix);
353 this.eventStack.push(PREFIX_MAPPING_OBJ);
354 }
355
356 /**
357 * End the scope of a prefix-URI mapping.
358 */
359 public void endPrefixMapping(String prefix) throws SAXException {
360 this.eventStack.pop(); // PREFIX_MAPPING_OBJ
361 this.eventStack.pop(); // prefix
362 super.endPrefixMapping(prefix);
363 }
364
365 public void startCDATA() throws SAXException {
366 super.startCDATA();
367 this.eventStack.push(CDATA_OBJ);
368 }
369
370 public void endCDATA() throws SAXException {
371 this.eventStack.pop();
372 super.endCDATA();
373 }
374
375 public void startDTD(String name, String publicId, String systemId)
376 throws SAXException {
377 super.startDTD(name, publicId, systemId);
378 this.eventStack.push(DTD_OBJ);
379 }
380
381 public void endDTD() throws SAXException {
382 this.eventStack.pop();
383 super.endDTD();
384 }
385
386 public void startEntity(String name) throws SAXException {
387 super.startEntity(name);
388 this.eventStack.push(name);
389 this.eventStack.push(ENTITY_OBJ);
390 }
391
392 public void endEntity(String name) throws SAXException {
393 this.eventStack.pop(); // ENTITY_OBJ
394 this.eventStack.pop(); // name
395 super.endEntity(name);
396 }
397
398 public void flushEvents() throws SAXException {
399
400 if (this.getLogger().isWarnEnabled()) {
401 if (this.eventStack.size() > 0) {
402 this.getLogger().warn("Premature end of document generated by " + inputSource.getURI());
403 }
404 }
405
406 // End any started events in case of premature return
407 while (this.eventStack.size() != 0) {
408 int event = ((Integer) eventStack.pop()).intValue();
409
410 switch (event) {
411 case DOCUMENT:
412 super.endDocument();
413 break;
414
415 case ELEMENT:
416 String namespaceURI = (String) eventStack.pop();
417 String localName = (String) eventStack.pop();
418 String rawName = (String) eventStack.pop();
419 super.endElement(namespaceURI, localName, rawName);
420 break;
421
422 case PREFIX_MAPPING:
423 super.endPrefixMapping((String) eventStack.pop());
424 break;
425
426 case CDATA:
427 super.endCDATA();
428 break;
429
430 case DTD:
431 super.endDTD();
432 break;
433
434 case ENTITY:
435 super.endEntity((String) eventStack.pop());
436 break;
437 }
438 }
439 }
440
441 public void recycle() {
442 this.eventStack.clear();
443 super.recycle();
444 }
445 }
446 }