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 java.beans.PropertyDescriptor;
20 import java.io.BufferedReader;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.StringReader;
24 import java.io.StringWriter;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32
33 import org.apache.avalon.framework.activity.Initializable;
34 import org.apache.avalon.framework.configuration.Configurable;
35 import org.apache.avalon.framework.configuration.Configuration;
36 import org.apache.avalon.framework.configuration.ConfigurationException;
37 import org.apache.avalon.framework.context.ContextException;
38 import org.apache.avalon.framework.context.DefaultContext;
39 import org.apache.avalon.framework.parameters.Parameters;
40 import org.apache.avalon.framework.service.ServiceException;
41 import org.apache.cocoon.ProcessingException;
42 import org.apache.cocoon.ResourceNotFoundException;
43 import org.apache.cocoon.components.flow.FlowHelper;
44 import org.apache.cocoon.components.flow.WebContinuation;
45 import org.apache.cocoon.environment.ObjectModelHelper;
46 import org.apache.cocoon.environment.Request;
47 import org.apache.cocoon.environment.Response;
48 import org.apache.cocoon.environment.Session;
49 import org.apache.cocoon.environment.SourceResolver;
50 import org.apache.commons.collections.ExtendedProperties;
51 import org.apache.commons.jxpath.DynamicPropertyHandler;
52 import org.apache.commons.jxpath.JXPathBeanInfo;
53 import org.apache.commons.jxpath.JXPathIntrospector;
54 import org.apache.commons.lang.StringUtils;
55 import org.apache.excalibur.source.Source;
56 import org.apache.excalibur.xml.sax.SAXParser;
57 import org.apache.velocity.VelocityContext;
58 import org.apache.velocity.app.VelocityEngine;
59 import org.apache.velocity.context.Context;
60 import org.apache.velocity.runtime.RuntimeConstants;
61 import org.apache.velocity.runtime.RuntimeServices;
62 import org.apache.velocity.runtime.log.LogSystem;
63 import org.apache.velocity.runtime.resource.Resource;
64 import org.apache.velocity.util.introspection.Info;
65 import org.apache.velocity.util.introspection.UberspectImpl;
66 import org.apache.velocity.util.introspection.VelMethod;
67 import org.apache.velocity.util.introspection.VelPropertyGet;
68 import org.apache.velocity.util.introspection.VelPropertySet;
69 import org.mozilla.javascript.JavaScriptException;
70 import org.mozilla.javascript.NativeArray;
71 import org.mozilla.javascript.ScriptRuntime;
72 import org.mozilla.javascript.Scriptable;
73 import org.mozilla.javascript.ScriptableObject;
74 import org.mozilla.javascript.Undefined;
75 import org.mozilla.javascript.Wrapper;
76 import org.xml.sax.InputSource;
77 import org.xml.sax.SAXException;
78 import org.xml.sax.SAXParseException;
79
80 /**
81 * <p>Cocoon {@link Generator} that produces dynamic XML SAX events
82 * from a Velocity template file.</p>
83 * If called from a Flowscript, the immediate properties of the context object from the Flowscript are available in the Velocity context.
84 * In that case, the current Web Continuation from the Flowscript
85 * is also available as a variable named <code>continuation</code>. You would
86 * typically access its <code>id</code>:
87 * <p><pre>
88 * <form action="$continuation.id">
89 * </pre></p>
90 * <p>You can also reach previous continuations by using the <code>getContinuation()</code> function:</p>
91 * <p><pre>
92 * <form action="$continuation.getContinuation(1).id}" >
93 * </pre></p>
94 *
95 * In addition the following implicit objects are always available in
96 * the Velocity context:
97 * <p>
98 * <dl>
99 * <dt><code>request</code> (<code>org.apache.cocoon.environment.Request</code>)</dt>
100 * <dd>The Cocoon current request</dd>
101 *
102 * <dt><code>response</code> (<code>org.apache.cocoon.environment.Response</code>)</dt>
103 * <dd>The Cocoon response associated with the current request</dd>
104 *
105 * <dt><code>session</code> (<code>org.apache.cocoon.environment.Session</code>)</dt>
106 * <dd>The Cocoon session associated with the current request</dd>
107 *
108 * <dt><code>context</code> (<code>org.apache.cocoon.environment.Context</code>)</dt>
109 * <dd>The Cocoon context associated with the current request</dd>
110 *
111 * <dt><code>parameters</code> (<code>org.apache.avalon.framework.parameters.Parameters</code>)</dt>
112 * <dd>Any parameters passed to the generator in the pipeline</dd>
113 * </dl>
114 * </p>
115 *
116 *
117 * <h2>Sitemap Configuration</h2>
118 *
119 * <p>
120 * Attributes:
121 * <dl>
122 * <dt>usecache (optional; default: 'false')</dt>
123 * <dd>set to 'true' to enable template caching on the 'cocoon'
124 * resource loader</dd>
125 *
126 * <dt>checkInterval (optional; default: '0')</dt>
127 * <dd>This is the number of seconds between modification checks when
128 * caching is turned on. When this is an integer > 0, this represents
129 * the number of seconds between checks to see if the template was
130 * modified. If the template has been modified since last check, then
131 * it is reloaded and reparsed. Otherwise nothing is done. When <= 0,
132 * no modification checks will take place, and assuming that the
133 * property cache (above) is true, once a template is loaded and
134 * parsed the first time it is used, it will not be checked or
135 * reloaded after that until the application or servlet engine is
136 * restarted.</dd>
137 * </dl>
138 * </p>
139 *
140 * <p>
141 * Child Elements:
142 *
143 * <dl>
144 * <dt><property name="propertyName" value="propertyValue"/> (optional; 0..n)</dt>
145 * <dd>An additional property to pass along to the Velocity template
146 * engine during initialization</dd>
147 *
148 * <dt><resource-loader name="loaderName" class="javaClassName" > (optional; 0..n; children: property*)</dt>
149 * <dd>The default configuration uses the 'cocoon' resource loader
150 * which resolves resources via the Cocoon SourceResolver. Additional
151 * resource loaders can be added with this configuration
152 * element. Configuration properties for the resource loader can be
153 * specified by adding a child property element of the resource-loader
154 * element. The prefix '<name>.resource.loader.' is
155 * automatically added to the property name.</dd>
156 *
157 * @version CVS $Id: VelocityGenerator.java 433543 2006-08-22 06:22:54Z crossley $
158 */
159 public class VelocityGenerator extends ServiceableGenerator
160 implements Initializable, Configurable, LogSystem {
161
162 /**
163 * <p>Velocity context implementation specific to the Servlet environment.</p>
164 *
165 * <p>It provides the following special features:</p>
166 * <ul>
167 * <li>puts the request, response, session, and servlet context objects
168 * into the Velocity context for direct access, and keeps them
169 * read-only</li>
170 * <li>supports a read-only toolbox of view tools</li>
171 * <li>auto-searches servlet request attributes, session attributes and
172 * servlet context attribues for objects</li>
173 * </ul>
174 *
175 * <p>The {@link #internalGet(String key)} method implements the following search order
176 * for objects:</p>
177 * <ol>
178 * <li>servlet request, servlet response, servlet session, servlet context</li>
179 * <li>toolbox</li>
180 * <li>local hashtable of objects (traditional use)</li>
181 * <li>servlet request attribues, servlet session attribute, servlet context
182 * attributes</li>
183 * </ol>
184 *
185 * <p>The purpose of this class is to make it easy for web designer to work
186 * with Java servlet based web applications. They do not need to be concerned
187 * with the concepts of request, session or application attributes and the
188 * live time of objects in these scopes.</p>
189 *
190 * <p>Note that the put() method always puts objects into the local hashtable.
191 * </p>
192 *
193 * <p>Acknowledge: the source code is borrowed from the jakarta-velocity-tools
194 * project with slight modifications.</p>
195 *
196 * @author <a href="mailto:albert@charcoalgeneration.com">Albert Kwong</a>
197 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
198 * @author <a href="mailto:sidler@teamup.com">Gabe Sidler</a>
199 * @author <a href="mailto:albert@charcoalgeneration.com">Albert Kwong</a>
200 */
201 public static class ChainedContext extends VelocityContext
202 {
203
204 /**
205 * A local reference to the current servlet request.
206 */
207 private Request request;
208
209 /**
210 * A local reference to the current servlet response.
211 */
212 private Response response;
213
214 /**
215 * A local reference to the servlet session.
216 */
217 private Session session;
218
219 /**
220 * A local reference to the servlet context.
221 */
222 private org.apache.cocoon.environment.Context application;
223
224 /**
225 * A local reference to pipeline parameters.
226 */
227 private Parameters parameters;
228
229 /**
230 * Key to the HTTP request object.
231 */
232 public static final String REQUEST = "request";
233
234 /**
235 * Key to the HTTP response object.
236 */
237 public static final String RESPONSE = "response";
238
239 /**
240 * Key to the HTTP session object.
241 */
242 public static final String SESSION = "session";
243
244 /**
245 * Key to the servlet context object.
246 */
247 public static final String APPLICATION = "context";
248
249 /**
250 * Key to the servlet context object.
251 */
252 public static final String PARAMETERS = "parameters";
253
254
255 /**
256 * Default constructor.
257 */
258 public ChainedContext(org.apache.velocity.context.Context ctx,
259 Request request,
260 Response response,
261 org.apache.cocoon.environment.Context application,
262 Parameters parameters)
263 {
264 super(null, ctx);
265 this.request = request;
266 this.response = response;
267 this.session = request.getSession(false);
268 this.application = application;
269 this.parameters = parameters;
270 }
271
272
273 /**
274 * <p>Looks up and returns the object with the specified key.</p>
275 *
276 * <p>See the class documentation for more details.</p>
277 *
278 * @param key the key of the object requested
279 *
280 * @return the requested object or null if not found
281 */
282 public Object internalGet( String key )
283 {
284 // make the four scopes of the Apocalypse Read only
285 if ( key.equals( REQUEST ))
286 {
287 return request;
288 }
289 else if( key.equals(RESPONSE) )
290 {
291 return response;
292 }
293 else if ( key.equals(SESSION) )
294 {
295 return session;
296 }
297 else if ( key.equals(APPLICATION))
298 {
299 return application;
300 }
301 else if ( key.equals(PARAMETERS))
302 {
303 return parameters;
304 }
305
306 Object o = null;
307
308 // try the local hashtable
309 o = super.internalGet( key );
310
311 // if not found, wander down the scopes...
312 if (o == null)
313 {
314 o = request.getAttribute( key );
315
316 if ( o == null )
317 {
318 if ( session != null )
319 {
320 o = session.getAttribute( key );
321 }
322
323 if ( o == null )
324 {
325 o = application.getAttribute( key );
326 }
327 }
328 }
329
330 return o;
331 }
332
333
334 } // ChainedContext
335
336 /**
337 * Velocity Introspector that supports Rhino JavaScript objects
338 * as well as Java Objects
339 *
340 */
341 public static class JSIntrospector extends UberspectImpl {
342
343 public static class JSMethod implements VelMethod {
344
345 Scriptable scope;
346 String name;
347
348 public JSMethod(Scriptable scope, String name) {
349 this.scope = scope;
350 this.name = name;
351 }
352
353 public Object invoke(Object thisArg, Object[] args)
354 throws Exception {
355 org.mozilla.javascript.Context cx = org.mozilla.javascript.Context.enter();
356 try {
357 Object result;
358 Scriptable thisObj;
359 if (!(thisArg instanceof Scriptable)) {
360 thisObj = org.mozilla.javascript.Context.toObject(thisArg, scope);
361 } else {
362 thisObj = (Scriptable)thisArg;
363 }
364 result = ScriptableObject.getProperty(thisObj, name);
365 Object[] newArgs = null;
366 if (args != null) {
367 newArgs = new Object[args.length];
368 for (int i = 0; i < args.length; i++) {
369 newArgs[i] = args[i];
370 if (args[i] != null &&
371 !(args[i] instanceof Number) &&
372 !(args[i] instanceof Boolean) &&
373 !(args[i] instanceof String) &&
374 !(args[i] instanceof Scriptable)) {
375 newArgs[i] = org.mozilla.javascript.Context.toObject(args[i], scope);
376 }
377 }
378 }
379 result = ScriptRuntime.call(cx, result, thisObj,
380 newArgs, scope);
381 if (result == Undefined.instance ||
382 result == Scriptable.NOT_FOUND) {
383 result = null;
384 } else while (result instanceof Wrapper) {
385 result = ((Wrapper)result).unwrap();
386 }
387 return result;
388 } catch (JavaScriptException e) {
389 throw new java.lang.reflect.InvocationTargetException(e);
390 } finally {
391 org.mozilla.javascript.Context.exit();
392 }
393 }
394
395 public boolean isCacheable() {
396 return false;
397 }
398
399 public String getMethodName() {
400 return name;
401 }
402
403 public Class getReturnType() {
404 return Object.class;
405 }
406
407 }
408
409 public static class JSPropertyGet implements VelPropertyGet {
410
411 Scriptable scope;
412 String name;
413
414 public JSPropertyGet(Scriptable scope, String name) {
415 this.scope = scope;
416 this.name = name;
417 }
418
419 public Object invoke(Object thisArg) throws Exception {
420 org.mozilla.javascript.Context.enter();
421 try {
422 Scriptable thisObj;
423 if (!(thisArg instanceof Scriptable)) {
424 thisObj = org.mozilla.javascript.Context.toObject(thisArg, scope);
425 } else {
426 thisObj = (Scriptable)thisArg;
427 }
428 Object result = ScriptableObject.getProperty(thisObj, name);
429 if (result == Undefined.instance ||
430 result == Scriptable.NOT_FOUND) {
431 result = null;
432 } else while (result instanceof Wrapper) {
433 result = ((Wrapper)result).unwrap();
434 }
435 return result;
436 } finally {
437 org.mozilla.javascript.Context.exit();
438 }
439 }
440
441 public boolean isCacheable() {
442 return false;
443 }
444
445 public String getMethodName() {
446 return name;
447 }
448
449 }
450
451 public static class JSPropertySet implements VelPropertySet {
452
453 Scriptable scope;
454 String name;
455
456 public JSPropertySet(Scriptable scope, String name) {
457 this.scope = scope;
458 this.name = name;
459 }
460
461 public Object invoke(Object thisArg, Object rhs) throws Exception {
462 org.mozilla.javascript.Context.enter();
463 try {
464 Scriptable thisObj;
465 Object arg = rhs;
466 if (!(thisArg instanceof Scriptable)) {
467 thisObj = org.mozilla.javascript.Context.toObject(thisArg, scope);
468 } else {
469 thisObj = (Scriptable)thisArg;
470 }
471 if (arg != null &&
472 !(arg instanceof Number) &&
473 !(arg instanceof Boolean) &&
474 !(arg instanceof String) &&
475 !(arg instanceof Scriptable)) {
476 arg = org.mozilla.javascript.Context.toObject(arg, scope);
477 }
478 ScriptableObject.putProperty(thisObj, name, arg);
479 return rhs;
480 } finally {
481 org.mozilla.javascript.Context.exit();
482 }
483 }
484
485 public boolean isCacheable() {
486 return false;
487 }
488
489 public String getMethodName() {
490 return name;
491 }
492 }
493
494 public static class NativeArrayIterator implements Iterator {
495
496 NativeArray arr;
497 int index;
498
499 public NativeArrayIterator(NativeArray arr) {
500 this.arr = arr;
501 this.index = 0;
502 }
503
504 public boolean hasNext() {
505 return index < (int)arr.jsGet_length();
506 }
507
508 public Object next() {
509 org.mozilla.javascript.Context.enter();
510 try {
511 Object result = arr.get(index++, arr);
512 if (result == Undefined.instance ||
513 result == Scriptable.NOT_FOUND) {
514 result = null;
515 } else while (result instanceof Wrapper) {
516 result = ((Wrapper)result).unwrap();
517 }
518 return result;
519 } finally {
520 org.mozilla.javascript.Context.exit();
521 }
522 }
523
524 public void remove() {
525 arr.delete(index);
526 }
527 }
528
529 public static class ScriptableIterator implements Iterator {
530
531 Scriptable scope;
532 Object[] ids;
533 int index;
534
535 public ScriptableIterator(Scriptable scope) {
536 this.scope = scope;
537 this.ids = scope.getIds();
538 this.index = 0;
539 }
540
541 public boolean hasNext() {
542 return index < ids.length;
543 }
544
545 public Object next() {
546 org.mozilla.javascript.Context.enter();
547 try {
548 Object result =
549 ScriptableObject.getProperty(scope,
550 ids[index++].toString());
551 if (result == Undefined.instance ||
552 result == Scriptable.NOT_FOUND) {
553 result = null;
554 } else while (result instanceof Wrapper) {
555 result = ((Wrapper)result).unwrap();
556 }
557 return result;
558 } finally {
559 org.mozilla.javascript.Context.exit();
560 }
561 }
562
563 public void remove() {
564 org.mozilla.javascript.Context.enter();
565 try {
566 scope.delete(ids[index].toString());
567 } finally {
568 org.mozilla.javascript.Context.exit();
569 }
570 }
571 }
572
573 public Iterator getIterator(Object obj, Info i)
574 throws Exception {
575 if (!(obj instanceof Scriptable)) {
576 return super.getIterator(obj, i);
577 }
578 if (obj instanceof NativeArray) {
579 return new NativeArrayIterator((NativeArray)obj);
580 }
581 return new ScriptableIterator((Scriptable)obj);
582 }
583
584 public VelMethod getMethod(Object obj, String methodName,
585 Object[] args, Info i)
586 throws Exception {
587 if (!(obj instanceof Scriptable)) {
588 return super.getMethod(obj, methodName, args, i);
589 }
590 return new JSMethod((Scriptable)obj, methodName);
591 }
592
593 public VelPropertyGet getPropertyGet(Object obj, String identifier,
594 Info i)
595 throws Exception {
596 if (!(obj instanceof Scriptable)) {
597 return super.getPropertyGet(obj, identifier, i);
598 }
599 return new JSPropertyGet((Scriptable)obj, identifier);
600 }
601
602 public VelPropertySet getPropertySet(Object obj, String identifier,
603 Object arg, Info i)
604 throws Exception {
605 if (!(obj instanceof Scriptable)) {
606 return super.getPropertySet(obj, identifier, arg, i);
607 }
608 return new JSPropertySet((Scriptable)obj, identifier);
609 }
610 }
611
612 /**
613 * Velocity {@link org.apache.velocity.runtime.resource.loader.ResourceLoader}
614 * implementation to load template resources using Cocoon's
615 *{@link SourceResolver}. This class is created by the Velocity
616 * framework via the ResourceLoaderFactory.
617 *
618 * @see org.apache.velocity.runtime.resource.loader.ResourceLoader
619 */
620 public static class TemplateLoader
621 extends org.apache.velocity.runtime.resource.loader.ResourceLoader {
622
623 private org.apache.avalon.framework.context.Context resolverContext;
624
625 /**
626 * Initialize this resource loader. The 'context' property is
627 * required and must be of type {@link Context}. The context
628 * is used to pass the Cocoon SourceResolver for the current
629 * pipeline.
630 *
631 * @param config the properties to configure this resource.
632 * @throws IllegalArgumentException thrown if the required
633 * 'context' property is not set.
634 * @throws ClassCastException if the 'context' property is not
635 * of type {@link org.apache.avalon.framework.context.Context}.
636 * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#init(ExtendedProperties)
637 */
638 public void init(ExtendedProperties config) {
639 this.resolverContext = (org.apache.avalon.framework.context.Context) config.get("context");
640 if (this.resolverContext == null) {
641 throw new IllegalArgumentException("Runtime Cocoon resolver context not specified in resource loader configuration.");
642 }
643 }
644
645 /**
646 * @param systemId the path to the resource
647 * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getResourceStream(String)
648 */
649 public InputStream getResourceStream(String systemId)
650 throws org.apache.velocity.exception.ResourceNotFoundException {
651 try {
652 return resolveSource(systemId).getInputStream();
653 } catch (org.apache.velocity.exception.ResourceNotFoundException ex) {
654 throw ex;
655 } catch (Exception ex) {
656 throw new org.apache.velocity.exception.ResourceNotFoundException("Unable to resolve source: " + ex);
657 }
658 }
659
660 /**
661 * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#isSourceModified(Resource)
662 */
663 public boolean isSourceModified(Resource resource) {
664 long lastModified = 0;
665 try {
666 lastModified = resolveSource(resource.getName()).getLastModified();
667 } catch (Exception ex) {
668 super.rsvc.warn("Unable to determine last modified for resource: "
669 + resource.getName() + ": " + ex);
670 }
671
672 return lastModified > 0 ? lastModified != resource.getLastModified() : true;
673 }
674
675 /**
676 * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(Resource)
677 */
678 public long getLastModified(Resource resource) {
679 long lastModified = 0;
680 try {
681 lastModified = resolveSource(resource.getName()).getLastModified();
682 } catch (Exception ex) {
683 super.rsvc.warn("Unable to determine last modified for resource: "
684 + resource.getName() + ": " + ex);
685 }
686
687 return lastModified;
688 }
689
690 /**
691 * Store all the Source objects we lookup via the SourceResolver so that they can be properly
692 * recycled later.
693 *
694 * @param systemId the path to the resource
695 */
696 private Source resolveSource(String systemId) throws org.apache.velocity.exception.ResourceNotFoundException {
697 Map sourceCache;
698 try {
699 sourceCache = (Map) this.resolverContext.get(CONTEXT_SOURCE_CACHE_KEY);
700 } catch (ContextException ignore) {
701 throw new org.apache.velocity.exception.ResourceNotFoundException("Runtime Cocoon source cache not specified in resource loader resolver context.");
702 }
703
704 Source source = (Source) sourceCache.get(systemId);
705 if (source == null) {
706 try {
707 SourceResolver resolver = (SourceResolver) this.resolverContext.get(CONTEXT_RESOLVER_KEY);
708 source = resolver.resolveURI(systemId);
709 } catch (ContextException ex) {
710 throw new org.apache.velocity.exception.ResourceNotFoundException("No Cocoon source resolver associated with current request.");
711 } catch (Exception ex) {
712 throw new org.apache.velocity.exception.ResourceNotFoundException("Unable to resolve source: " + ex);
713 }
714 }
715
716 sourceCache.put(systemId, source);
717 return source;
718 }
719 }
720
721 /**
722 * Key to lookup the {@link SourceResolver} from the context of
723 * the resource loader
724 */
725 final private static String CONTEXT_RESOLVER_KEY = "resolver";
726
727 /**
728 * Key to lookup the source cache {@link Map} from the context of
729 * the resource loader
730 */
731 final private static String CONTEXT_SOURCE_CACHE_KEY = "source-cache";
732
733 private VelocityEngine tmplEngine;
734 private boolean tmplEngineInitialized;
735 private DefaultContext resolverContext;
736 private Context velocityContext;
737 private boolean activeFlag;
738
739 /**
740 * Read any additional objects to export to the Velocity context
741 * from the configuration.
742 *
743 * @param configuration the class configurations.
744 * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
745 */
746 public void configure(Configuration configuration) throws ConfigurationException {
747 this.resolverContext = new DefaultContext();
748 this.tmplEngine = new VelocityEngine();
749
750 // Set up a JavaScript introspector for the Cocoon flow layer
751 this.tmplEngine.setProperty(org.apache.velocity.runtime.RuntimeConstants.UBERSPECT_CLASSNAME,
752 JSIntrospector.class.getName());
753 this.tmplEngine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, this);
754
755 // First set up our default 'cocoon' resource loader
756 this.tmplEngine.setProperty("cocoon.resource.loader.class",
757 TemplateLoader.class.getName());
758 this.tmplEngine.setProperty("cocoon.resource.loader.cache",
759 configuration.getAttribute("usecache", "false"));
760 this.tmplEngine.setProperty("cocoon.resource.loader.modificationCheckInterval",
761 configuration.getAttribute("checkInterval", "0"));
762 this.tmplEngine.setProperty("cocoon.resource.loader.context",
763 this.resolverContext);
764
765 // Read in any additional properties to pass to the VelocityEngine during initialization
766 Configuration[] properties = configuration.getChildren("property");
767 for (int i = 0; i < properties.length; ++i) {
768 Configuration c = properties[i];
769 String name = c.getAttribute("name");
770
771 // Disallow setting of certain properties
772 if (name.startsWith("runtime.log")
773 || name.indexOf(".resource.loader.") != -1) {
774 if (getLogger().isInfoEnabled()) {
775 getLogger().info("ignoring disallowed property '" + name + "'.");
776 }
777 continue;
778 }
779 this.tmplEngine.setProperty(name, c.getAttribute("value"));
780 }
781
782 // Now read in any additional Velocity resource loaders
783 List resourceLoaders = new ArrayList();
784 Configuration[] loaders = configuration.getChildren("resource-loader");
785 for (int i = 0; i < loaders.length; ++i) {
786 Configuration loader = loaders[i];
787 String name = loader.getAttribute("name");
788 if (name.equals("cocoon")) {
789 if (getLogger().isInfoEnabled()) {
790 getLogger().info("'cocoon' resource loader already defined.");
791 }
792 continue;
793 }
794 resourceLoaders.add(name);
795 String prefix = name + ".resource.loader.";
796 String type = loader.getAttribute("class");
797 this.tmplEngine.setProperty(prefix + "class", type);
798 Configuration[] loaderProperties = loader.getChildren("property");
799 for (int j = 0; j < loaderProperties.length; j++) {
800 Configuration c = loaderProperties[j];
801 String propName = c.getAttribute("name");
802 this.tmplEngine.setProperty(prefix + propName, c.getAttribute("value"));
803 }
804 }
805
806 // Velocity expects resource loaders as CSV list
807 //
808 StringBuffer buffer = new StringBuffer("cocoon");
809 for (Iterator it = resourceLoaders.iterator(); it.hasNext();) {
810 buffer.append(',');
811 buffer.append((String) it.next());
812 }
813 tmplEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, buffer.toString());
814 }
815
816 /**
817 * @see org.apache.avalon.framework.activity.Initializable#initialize()
818 */
819 public void initialize() throws Exception {
820 //this.tmplEngine.init();
821 }
822
823 /**
824 * @see org.apache.cocoon.sitemap.SitemapModelComponent#setup(SourceResolver, Map, String, Parameters)
825 */
826 public void setup(SourceResolver resolver, Map objectModel, String src, Parameters params)
827 throws ProcessingException, SAXException, IOException {
828 if (activeFlag) {
829 throw new IllegalStateException("setup called on recyclable sitemap component before properly recycling previous state");
830 }
831
832 super.setup(resolver, objectModel, src, params);
833
834 // Pass along the SourceResolver to the Velocity resource loader
835 this.resolverContext.put(CONTEXT_RESOLVER_KEY, resolver);
836 this.resolverContext.put(CONTEXT_SOURCE_CACHE_KEY, new HashMap());
837
838 // FIXME: Initialize the Velocity context. Use objectModel to pass these
839 final Object bean = FlowHelper.getContextObject(objectModel);
840 if (bean != null) {
841
842 final WebContinuation kont = FlowHelper.getWebContinuation(objectModel);
843
844 // Hack? I use JXPath to determine the properties of the bean object
845 final JXPathBeanInfo bi = JXPathIntrospector.getBeanInfo(bean.getClass());
846 DynamicPropertyHandler h = null;
847 final PropertyDescriptor[] props;
848 if (bi.isDynamic()) {
849 Class cl = bi.getDynamicPropertyHandlerClass();
850 try {
851 h = (DynamicPropertyHandler) cl.newInstance();
852 } catch (Exception exc) {
853 exc.printStackTrace();
854 h = null;
855 }
856 props = null;
857 } else {
858 h = null;
859 props = bi.getPropertyDescriptors();
860 }
861 final DynamicPropertyHandler handler = h;
862
863 this.velocityContext = new Context() {
864 public Object put(String key, Object value) {
865 if (key.equals("flowContext")
866 || key.equals("continuation")) {
867 return value;
868 }
869 if (handler != null) {
870 handler.setProperty(bean, key, value);
871 return value;
872 } else {
873 for (int i = 0; i < props.length; i++) {
874 if (props[i].getName().equals(key)) {
875 try {
876 return props[i].getWriteMethod().invoke(bean, new Object[]{value});
877 } catch (Exception ignored) {
878 break;
879 }
880 }
881 }
882 return value;
883 }
884 }
885
886 public boolean containsKey(Object key) {
887 if (key.equals("flowContext")
888 || key.equals("continuation")) {
889 return true;
890 }
891 if (handler != null) {
892 String[] result = handler.getPropertyNames(bean);
893 for (int i = 0; i < result.length; i++) {
894 if (key.equals(result[i])) {
895 return true;
896 }
897 }
898 } else {
899 for (int i = 0; i < props.length; i++) {
900 if (key.equals(props[i].getName())) {
901 return true;
902 }
903 }
904 }
905 return false;
906 }
907
908 public Object[] getKeys() {
909 Object[] result = null;
910 if (handler != null) {
911 result = handler.getPropertyNames(bean);
912 } else {
913 result = new Object[props.length];
914 for (int i = 0; i < props.length; i++) {
915 result[i] = props[i].getName();
916 }
917 }
918 Set set = new HashSet();
919 for (int i = 0; i < result.length; i++) {
920 set.add(result[i]);
921 }
922 set.add("flowContext");
923 set.add("continuation");
924 result = new Object[set.size()];
925 set.toArray(result);
926 return result;
927 }
928
929 public Object get(String key) {
930 if (key.equals("flowContext")) {
931 return bean;
932 }
933 if (key.equals("continuation")) {
934 return kont;
935 }
936 if (handler != null) {
937 return handler.getProperty(bean, key);
938 } else {
939 for (int i = 0; i < props.length; i++) {
940 if (props[i].getName().equals(key)) {
941 try {
942 return props[i].getReadMethod().invoke(bean, null);
943 } catch (Exception ignored) {
944 break;
945 }
946 }
947 }
948 return null;
949 }
950 }
951
952 public Object remove(Object key) {
953 // not implemented
954 return key;
955 }
956 };
957 }
958 this.velocityContext =
959 new ChainedContext (this.velocityContext,
960 ObjectModelHelper.getRequest(objectModel),
961 ObjectModelHelper.getResponse(objectModel),
962 ObjectModelHelper.getContext(objectModel),
963 params);
964 this.velocityContext.put("template", src);
965 this.activeFlag = true;
966 }
967 /**
968 * Free up the VelocityContext associated with the pipeline, and
969 * release any Source objects resolved by the resource loader.
970 *
971 * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
972 */
973 public void recycle() {
974 this.activeFlag = false;
975
976 // Recycle all the Source objects resolved/used by our resource loader
977 try {
978 Map sourceCache = (Map) this.resolverContext.get(CONTEXT_SOURCE_CACHE_KEY);
979 for (Iterator it = sourceCache.values().iterator(); it.hasNext();) {
980 this.resolver.release((Source) it.next());
981 }
982 } catch (ContextException ignore) {
983 }
984
985 this.velocityContext = null;
986 super.recycle();
987 }
988
989 /**
990 * Generate XML data using Velocity template.
991 *
992 * @see org.apache.cocoon.generation.Generator#generate()
993 */
994 public void generate()
995 throws IOException, SAXException, ProcessingException {
996 // Guard against calling generate before setup.
997 if (!activeFlag) {
998 throw new IllegalStateException("generate called on sitemap component before setup.");
999 }
1000
1001 SAXParser parser = null;
1002 StringWriter w = new StringWriter();
1003 try {
1004 parser = (SAXParser) this.manager.lookup(SAXParser.ROLE);
1005 if (getLogger().isDebugEnabled()) {
1006 getLogger().debug("Processing File: " + super.source);
1007 }
1008 if (!tmplEngineInitialized) {
1009 tmplEngine.init();
1010 tmplEngineInitialized = true;
1011 }
1012 /* lets render a template */
1013 this.tmplEngine.mergeTemplate(super.source, velocityContext, w);
1014
1015 InputSource xmlInput =
1016 new InputSource(new StringReader(w.toString()));
1017 xmlInput.setSystemId(super.source);
1018 parser.parse(xmlInput, this.xmlConsumer);
1019 } catch (IOException e) {
1020 getLogger().warn("VelocityGenerator.generate()", e);
1021 throw new ResourceNotFoundException("Could not get Resource for VelocityGenerator", e);
1022 } catch (SAXParseException e) {
1023 int line = e.getLineNumber();
1024 int column = e.getColumnNumber();
1025 if (line <= 0) {
1026 line = Integer.MAX_VALUE;
1027 }
1028 BufferedReader reader =
1029 new BufferedReader(new StringReader(w.toString()));
1030 StringBuffer message = new StringBuffer(e.getMessage());
1031 message.append(" In generated document:\n");
1032 for (int i = 0; i < line; i++) {
1033 String lineStr = reader.readLine();
1034 if (lineStr == null) {
1035 break;
1036 }
1037 message.append(lineStr);
1038 message.append("\n");
1039 }
1040 if (column > 0) {
1041 message.append(StringUtils.leftPad("^\n", column + 1));
1042 }
1043 SAXException pe = new SAXParseException(message.toString(),
1044 e.getPublicId(),
1045 "(Document generated from template "+e.getSystemId() + ")",
1046 e.getLineNumber(),
1047 e.getColumnNumber(),
1048 null);
1049 getLogger().error("VelocityGenerator.generate()", pe);
1050 throw pe;
1051 } catch (SAXException e) {
1052 getLogger().error("VelocityGenerator.generate()", e);
1053 throw e;
1054 } catch (ServiceException e) {
1055 getLogger().error("Could not get parser", e);
1056 throw new ProcessingException("Exception in VelocityGenerator.generate()", e);
1057 } catch (ProcessingException e) {
1058 throw e;
1059 } catch (Exception e) {
1060 getLogger().error("Could not get parser", e);
1061 throw new ProcessingException("Exception in VelocityGenerator.generate()", e);
1062 } finally {
1063 this.manager.release(parser);
1064 }
1065 }
1066
1067
1068 /**
1069 * This implementation does nothing.
1070 *
1071 * @see org.apache.velocity.runtime.log.LogSystem#init(RuntimeServices)
1072 */
1073 public void init(RuntimeServices rs) throws Exception {
1074 }
1075
1076 /**
1077 * Pass along Velocity log messages to our configured logger.
1078 *
1079 * @see org.apache.velocity.runtime.log.LogSystem#logVelocityMessage(int, String)
1080 */
1081 public void logVelocityMessage(int level, String message) {
1082 switch (level) {
1083 case LogSystem.WARN_ID:
1084 getLogger().warn(message);
1085 break;
1086 case LogSystem.INFO_ID:
1087 getLogger().info(message);
1088 break;
1089 case LogSystem.DEBUG_ID:
1090 getLogger().debug(message);
1091 break;
1092 case LogSystem.ERROR_ID:
1093 getLogger().error(message);
1094 break;
1095 default :
1096 getLogger().info(message);
1097 break;
1098 }
1099 }
1100 }