0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.cocoon.generation;
0018:
0019: import java.beans.PropertyDescriptor;
0020: import java.io.BufferedReader;
0021: import java.io.IOException;
0022: import java.io.InputStream;
0023: import java.io.StringReader;
0024: import java.io.StringWriter;
0025: import java.util.ArrayList;
0026: import java.util.HashMap;
0027: import java.util.HashSet;
0028: import java.util.Iterator;
0029: import java.util.List;
0030: import java.util.Map;
0031: import java.util.Set;
0032:
0033: import org.apache.avalon.framework.activity.Initializable;
0034: import org.apache.avalon.framework.configuration.Configurable;
0035: import org.apache.avalon.framework.configuration.Configuration;
0036: import org.apache.avalon.framework.configuration.ConfigurationException;
0037: import org.apache.avalon.framework.context.ContextException;
0038: import org.apache.avalon.framework.context.DefaultContext;
0039: import org.apache.avalon.framework.parameters.Parameters;
0040: import org.apache.avalon.framework.service.ServiceException;
0041: import org.apache.cocoon.ProcessingException;
0042: import org.apache.cocoon.ResourceNotFoundException;
0043: import org.apache.cocoon.components.flow.FlowHelper;
0044: import org.apache.cocoon.components.flow.WebContinuation;
0045: import org.apache.cocoon.environment.ObjectModelHelper;
0046: import org.apache.cocoon.environment.Request;
0047: import org.apache.cocoon.environment.Response;
0048: import org.apache.cocoon.environment.Session;
0049: import org.apache.cocoon.environment.SourceResolver;
0050: import org.apache.commons.collections.ExtendedProperties;
0051: import org.apache.commons.jxpath.DynamicPropertyHandler;
0052: import org.apache.commons.jxpath.JXPathBeanInfo;
0053: import org.apache.commons.jxpath.JXPathIntrospector;
0054: import org.apache.commons.lang.StringUtils;
0055: import org.apache.excalibur.source.Source;
0056: import org.apache.excalibur.xml.sax.SAXParser;
0057: import org.apache.velocity.VelocityContext;
0058: import org.apache.velocity.app.VelocityEngine;
0059: import org.apache.velocity.context.Context;
0060: import org.apache.velocity.runtime.RuntimeConstants;
0061: import org.apache.velocity.runtime.RuntimeServices;
0062: import org.apache.velocity.runtime.log.LogSystem;
0063: import org.apache.velocity.runtime.resource.Resource;
0064: import org.apache.velocity.util.introspection.Info;
0065: import org.apache.velocity.util.introspection.UberspectImpl;
0066: import org.apache.velocity.util.introspection.VelMethod;
0067: import org.apache.velocity.util.introspection.VelPropertyGet;
0068: import org.apache.velocity.util.introspection.VelPropertySet;
0069: import org.mozilla.javascript.JavaScriptException;
0070: import org.mozilla.javascript.NativeArray;
0071: import org.mozilla.javascript.ScriptRuntime;
0072: import org.mozilla.javascript.Scriptable;
0073: import org.mozilla.javascript.ScriptableObject;
0074: import org.mozilla.javascript.Undefined;
0075: import org.mozilla.javascript.Wrapper;
0076: import org.xml.sax.InputSource;
0077: import org.xml.sax.SAXException;
0078: import org.xml.sax.SAXParseException;
0079:
0080: /**
0081: * <p>Cocoon {@link Generator} that produces dynamic XML SAX events
0082: * from a Velocity template file.</p>
0083: * If called from a Flowscript, the immediate properties of the context object from the Flowscript are available in the Velocity context.
0084: * In that case, the current Web Continuation from the Flowscript
0085: * is also available as a variable named <code>continuation</code>. You would
0086: * typically access its <code>id</code>:
0087: * <p><pre>
0088: * <form action="$continuation.id">
0089: * </pre></p>
0090: * <p>You can also reach previous continuations by using the <code>getContinuation()</code> function:</p>
0091: * <p><pre>
0092: * <form action="$continuation.getContinuation(1).id}" >
0093: * </pre></p>
0094: *
0095: * In addition the following implicit objects are always available in
0096: * the Velocity context:
0097: * <p>
0098: * <dl>
0099: * <dt><code>request</code> (<code>org.apache.cocoon.environment.Request</code>)</dt>
0100: * <dd>The Cocoon current request</dd>
0101: *
0102: * <dt><code>response</code> (<code>org.apache.cocoon.environment.Response</code>)</dt>
0103: * <dd>The Cocoon response associated with the current request</dd>
0104: *
0105: * <dt><code>session</code> (<code>org.apache.cocoon.environment.Session</code>)</dt>
0106: * <dd>The Cocoon session associated with the current request</dd>
0107: *
0108: * <dt><code>context</code> (<code>org.apache.cocoon.environment.Context</code>)</dt>
0109: * <dd>The Cocoon context associated with the current request</dd>
0110: *
0111: * <dt><code>parameters</code> (<code>org.apache.avalon.framework.parameters.Parameters</code>)</dt>
0112: * <dd>Any parameters passed to the generator in the pipeline</dd>
0113: * </dl>
0114: * </p>
0115: *
0116: *
0117: * <h2>Sitemap Configuration</h2>
0118: *
0119: * <p>
0120: * Attributes:
0121: * <dl>
0122: * <dt>usecache (optional; default: 'false')</dt>
0123: * <dd>set to 'true' to enable template caching on the 'cocoon'
0124: * resource loader</dd>
0125: *
0126: * <dt>checkInterval (optional; default: '0')</dt>
0127: * <dd>This is the number of seconds between modification checks when
0128: * caching is turned on. When this is an integer > 0, this represents
0129: * the number of seconds between checks to see if the template was
0130: * modified. If the template has been modified since last check, then
0131: * it is reloaded and reparsed. Otherwise nothing is done. When <= 0,
0132: * no modification checks will take place, and assuming that the
0133: * property cache (above) is true, once a template is loaded and
0134: * parsed the first time it is used, it will not be checked or
0135: * reloaded after that until the application or servlet engine is
0136: * restarted.</dd>
0137: * </dl>
0138: * </p>
0139: *
0140: * <p>
0141: * Child Elements:
0142: *
0143: * <dl>
0144: * <dt><property name="propertyName" value="propertyValue"/> (optional; 0..n)</dt>
0145: * <dd>An additional property to pass along to the Velocity template
0146: * engine during initialization</dd>
0147: *
0148: * <dt><resource-loader name="loaderName" class="javaClassName" > (optional; 0..n; children: property*)</dt>
0149: * <dd>The default configuration uses the 'cocoon' resource loader
0150: * which resolves resources via the Cocoon SourceResolver. Additional
0151: * resource loaders can be added with this configuration
0152: * element. Configuration properties for the resource loader can be
0153: * specified by adding a child property element of the resource-loader
0154: * element. The prefix '<name>.resource.loader.' is
0155: * automatically added to the property name.</dd>
0156: *
0157: * @version CVS $Id: VelocityGenerator.java 433543 2006-08-22 06:22:54Z crossley $
0158: */
0159: public class VelocityGenerator extends ServiceableGenerator implements
0160: Initializable, Configurable, LogSystem {
0161:
0162: /**
0163: * <p>Velocity context implementation specific to the Servlet environment.</p>
0164: *
0165: * <p>It provides the following special features:</p>
0166: * <ul>
0167: * <li>puts the request, response, session, and servlet context objects
0168: * into the Velocity context for direct access, and keeps them
0169: * read-only</li>
0170: * <li>supports a read-only toolbox of view tools</li>
0171: * <li>auto-searches servlet request attributes, session attributes and
0172: * servlet context attribues for objects</li>
0173: * </ul>
0174: *
0175: * <p>The {@link #internalGet(String key)} method implements the following search order
0176: * for objects:</p>
0177: * <ol>
0178: * <li>servlet request, servlet response, servlet session, servlet context</li>
0179: * <li>toolbox</li>
0180: * <li>local hashtable of objects (traditional use)</li>
0181: * <li>servlet request attribues, servlet session attribute, servlet context
0182: * attributes</li>
0183: * </ol>
0184: *
0185: * <p>The purpose of this class is to make it easy for web designer to work
0186: * with Java servlet based web applications. They do not need to be concerned
0187: * with the concepts of request, session or application attributes and the
0188: * live time of objects in these scopes.</p>
0189: *
0190: * <p>Note that the put() method always puts objects into the local hashtable.
0191: * </p>
0192: *
0193: * <p>Acknowledge: the source code is borrowed from the jakarta-velocity-tools
0194: * project with slight modifications.</p>
0195: *
0196: * @author <a href="mailto:albert@charcoalgeneration.com">Albert Kwong</a>
0197: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
0198: * @author <a href="mailto:sidler@teamup.com">Gabe Sidler</a>
0199: * @author <a href="mailto:albert@charcoalgeneration.com">Albert Kwong</a>
0200: */
0201: public static class ChainedContext extends VelocityContext {
0202:
0203: /**
0204: * A local reference to the current servlet request.
0205: */
0206: private Request request;
0207:
0208: /**
0209: * A local reference to the current servlet response.
0210: */
0211: private Response response;
0212:
0213: /**
0214: * A local reference to the servlet session.
0215: */
0216: private Session session;
0217:
0218: /**
0219: * A local reference to the servlet context.
0220: */
0221: private org.apache.cocoon.environment.Context application;
0222:
0223: /**
0224: * A local reference to pipeline parameters.
0225: */
0226: private Parameters parameters;
0227:
0228: /**
0229: * Key to the HTTP request object.
0230: */
0231: public static final String REQUEST = "request";
0232:
0233: /**
0234: * Key to the HTTP response object.
0235: */
0236: public static final String RESPONSE = "response";
0237:
0238: /**
0239: * Key to the HTTP session object.
0240: */
0241: public static final String SESSION = "session";
0242:
0243: /**
0244: * Key to the servlet context object.
0245: */
0246: public static final String APPLICATION = "context";
0247:
0248: /**
0249: * Key to the servlet context object.
0250: */
0251: public static final String PARAMETERS = "parameters";
0252:
0253: /**
0254: * Default constructor.
0255: */
0256: public ChainedContext(org.apache.velocity.context.Context ctx,
0257: Request request, Response response,
0258: org.apache.cocoon.environment.Context application,
0259: Parameters parameters) {
0260: super (null, ctx);
0261: this .request = request;
0262: this .response = response;
0263: this .session = request.getSession(false);
0264: this .application = application;
0265: this .parameters = parameters;
0266: }
0267:
0268: /**
0269: * <p>Looks up and returns the object with the specified key.</p>
0270: *
0271: * <p>See the class documentation for more details.</p>
0272: *
0273: * @param key the key of the object requested
0274: *
0275: * @return the requested object or null if not found
0276: */
0277: public Object internalGet(String key) {
0278: // make the four scopes of the Apocalypse Read only
0279: if (key.equals(REQUEST)) {
0280: return request;
0281: } else if (key.equals(RESPONSE)) {
0282: return response;
0283: } else if (key.equals(SESSION)) {
0284: return session;
0285: } else if (key.equals(APPLICATION)) {
0286: return application;
0287: } else if (key.equals(PARAMETERS)) {
0288: return parameters;
0289: }
0290:
0291: Object o = null;
0292:
0293: // try the local hashtable
0294: o = super .internalGet(key);
0295:
0296: // if not found, wander down the scopes...
0297: if (o == null) {
0298: o = request.getAttribute(key);
0299:
0300: if (o == null) {
0301: if (session != null) {
0302: o = session.getAttribute(key);
0303: }
0304:
0305: if (o == null) {
0306: o = application.getAttribute(key);
0307: }
0308: }
0309: }
0310:
0311: return o;
0312: }
0313:
0314: } // ChainedContext
0315:
0316: /**
0317: * Velocity Introspector that supports Rhino JavaScript objects
0318: * as well as Java Objects
0319: *
0320: */
0321: public static class JSIntrospector extends UberspectImpl {
0322:
0323: public static class JSMethod implements VelMethod {
0324:
0325: Scriptable scope;
0326: String name;
0327:
0328: public JSMethod(Scriptable scope, String name) {
0329: this .scope = scope;
0330: this .name = name;
0331: }
0332:
0333: public Object invoke(Object this Arg, Object[] args)
0334: throws Exception {
0335: org.mozilla.javascript.Context cx = org.mozilla.javascript.Context
0336: .enter();
0337: try {
0338: Object result;
0339: Scriptable this Obj;
0340: if (!(this Arg instanceof Scriptable)) {
0341: this Obj = org.mozilla.javascript.Context
0342: .toObject(this Arg, scope);
0343: } else {
0344: this Obj = (Scriptable) this Arg;
0345: }
0346: result = ScriptableObject
0347: .getProperty(this Obj, name);
0348: Object[] newArgs = null;
0349: if (args != null) {
0350: newArgs = new Object[args.length];
0351: for (int i = 0; i < args.length; i++) {
0352: newArgs[i] = args[i];
0353: if (args[i] != null
0354: && !(args[i] instanceof Number)
0355: && !(args[i] instanceof Boolean)
0356: && !(args[i] instanceof String)
0357: && !(args[i] instanceof Scriptable)) {
0358: newArgs[i] = org.mozilla.javascript.Context
0359: .toObject(args[i], scope);
0360: }
0361: }
0362: }
0363: result = ScriptRuntime.call(cx, result, this Obj,
0364: newArgs, scope);
0365: if (result == Undefined.instance
0366: || result == Scriptable.NOT_FOUND) {
0367: result = null;
0368: } else
0369: while (result instanceof Wrapper) {
0370: result = ((Wrapper) result).unwrap();
0371: }
0372: return result;
0373: } catch (JavaScriptException e) {
0374: throw new java.lang.reflect.InvocationTargetException(
0375: e);
0376: } finally {
0377: org.mozilla.javascript.Context.exit();
0378: }
0379: }
0380:
0381: public boolean isCacheable() {
0382: return false;
0383: }
0384:
0385: public String getMethodName() {
0386: return name;
0387: }
0388:
0389: public Class getReturnType() {
0390: return Object.class;
0391: }
0392:
0393: }
0394:
0395: public static class JSPropertyGet implements VelPropertyGet {
0396:
0397: Scriptable scope;
0398: String name;
0399:
0400: public JSPropertyGet(Scriptable scope, String name) {
0401: this .scope = scope;
0402: this .name = name;
0403: }
0404:
0405: public Object invoke(Object this Arg) throws Exception {
0406: org.mozilla.javascript.Context.enter();
0407: try {
0408: Scriptable this Obj;
0409: if (!(this Arg instanceof Scriptable)) {
0410: this Obj = org.mozilla.javascript.Context
0411: .toObject(this Arg, scope);
0412: } else {
0413: this Obj = (Scriptable) this Arg;
0414: }
0415: Object result = ScriptableObject.getProperty(
0416: this Obj, name);
0417: if (result == Undefined.instance
0418: || result == Scriptable.NOT_FOUND) {
0419: result = null;
0420: } else
0421: while (result instanceof Wrapper) {
0422: result = ((Wrapper) result).unwrap();
0423: }
0424: return result;
0425: } finally {
0426: org.mozilla.javascript.Context.exit();
0427: }
0428: }
0429:
0430: public boolean isCacheable() {
0431: return false;
0432: }
0433:
0434: public String getMethodName() {
0435: return name;
0436: }
0437:
0438: }
0439:
0440: public static class JSPropertySet implements VelPropertySet {
0441:
0442: Scriptable scope;
0443: String name;
0444:
0445: public JSPropertySet(Scriptable scope, String name) {
0446: this .scope = scope;
0447: this .name = name;
0448: }
0449:
0450: public Object invoke(Object this Arg, Object rhs)
0451: throws Exception {
0452: org.mozilla.javascript.Context.enter();
0453: try {
0454: Scriptable this Obj;
0455: Object arg = rhs;
0456: if (!(this Arg instanceof Scriptable)) {
0457: this Obj = org.mozilla.javascript.Context
0458: .toObject(this Arg, scope);
0459: } else {
0460: this Obj = (Scriptable) this Arg;
0461: }
0462: if (arg != null && !(arg instanceof Number)
0463: && !(arg instanceof Boolean)
0464: && !(arg instanceof String)
0465: && !(arg instanceof Scriptable)) {
0466: arg = org.mozilla.javascript.Context.toObject(
0467: arg, scope);
0468: }
0469: ScriptableObject.putProperty(this Obj, name, arg);
0470: return rhs;
0471: } finally {
0472: org.mozilla.javascript.Context.exit();
0473: }
0474: }
0475:
0476: public boolean isCacheable() {
0477: return false;
0478: }
0479:
0480: public String getMethodName() {
0481: return name;
0482: }
0483: }
0484:
0485: public static class NativeArrayIterator implements Iterator {
0486:
0487: NativeArray arr;
0488: int index;
0489:
0490: public NativeArrayIterator(NativeArray arr) {
0491: this .arr = arr;
0492: this .index = 0;
0493: }
0494:
0495: public boolean hasNext() {
0496: return index < (int) arr.jsGet_length();
0497: }
0498:
0499: public Object next() {
0500: org.mozilla.javascript.Context.enter();
0501: try {
0502: Object result = arr.get(index++, arr);
0503: if (result == Undefined.instance
0504: || result == Scriptable.NOT_FOUND) {
0505: result = null;
0506: } else
0507: while (result instanceof Wrapper) {
0508: result = ((Wrapper) result).unwrap();
0509: }
0510: return result;
0511: } finally {
0512: org.mozilla.javascript.Context.exit();
0513: }
0514: }
0515:
0516: public void remove() {
0517: arr.delete(index);
0518: }
0519: }
0520:
0521: public static class ScriptableIterator implements Iterator {
0522:
0523: Scriptable scope;
0524: Object[] ids;
0525: int index;
0526:
0527: public ScriptableIterator(Scriptable scope) {
0528: this .scope = scope;
0529: this .ids = scope.getIds();
0530: this .index = 0;
0531: }
0532:
0533: public boolean hasNext() {
0534: return index < ids.length;
0535: }
0536:
0537: public Object next() {
0538: org.mozilla.javascript.Context.enter();
0539: try {
0540: Object result = ScriptableObject.getProperty(scope,
0541: ids[index++].toString());
0542: if (result == Undefined.instance
0543: || result == Scriptable.NOT_FOUND) {
0544: result = null;
0545: } else
0546: while (result instanceof Wrapper) {
0547: result = ((Wrapper) result).unwrap();
0548: }
0549: return result;
0550: } finally {
0551: org.mozilla.javascript.Context.exit();
0552: }
0553: }
0554:
0555: public void remove() {
0556: org.mozilla.javascript.Context.enter();
0557: try {
0558: scope.delete(ids[index].toString());
0559: } finally {
0560: org.mozilla.javascript.Context.exit();
0561: }
0562: }
0563: }
0564:
0565: public Iterator getIterator(Object obj, Info i)
0566: throws Exception {
0567: if (!(obj instanceof Scriptable)) {
0568: return super .getIterator(obj, i);
0569: }
0570: if (obj instanceof NativeArray) {
0571: return new NativeArrayIterator((NativeArray) obj);
0572: }
0573: return new ScriptableIterator((Scriptable) obj);
0574: }
0575:
0576: public VelMethod getMethod(Object obj, String methodName,
0577: Object[] args, Info i) throws Exception {
0578: if (!(obj instanceof Scriptable)) {
0579: return super .getMethod(obj, methodName, args, i);
0580: }
0581: return new JSMethod((Scriptable) obj, methodName);
0582: }
0583:
0584: public VelPropertyGet getPropertyGet(Object obj,
0585: String identifier, Info i) throws Exception {
0586: if (!(obj instanceof Scriptable)) {
0587: return super .getPropertyGet(obj, identifier, i);
0588: }
0589: return new JSPropertyGet((Scriptable) obj, identifier);
0590: }
0591:
0592: public VelPropertySet getPropertySet(Object obj,
0593: String identifier, Object arg, Info i) throws Exception {
0594: if (!(obj instanceof Scriptable)) {
0595: return super .getPropertySet(obj, identifier, arg, i);
0596: }
0597: return new JSPropertySet((Scriptable) obj, identifier);
0598: }
0599: }
0600:
0601: /**
0602: * Velocity {@link org.apache.velocity.runtime.resource.loader.ResourceLoader}
0603: * implementation to load template resources using Cocoon's
0604: *{@link SourceResolver}. This class is created by the Velocity
0605: * framework via the ResourceLoaderFactory.
0606: *
0607: * @see org.apache.velocity.runtime.resource.loader.ResourceLoader
0608: */
0609: public static class TemplateLoader extends
0610: org.apache.velocity.runtime.resource.loader.ResourceLoader {
0611:
0612: private org.apache.avalon.framework.context.Context resolverContext;
0613:
0614: /**
0615: * Initialize this resource loader. The 'context' property is
0616: * required and must be of type {@link Context}. The context
0617: * is used to pass the Cocoon SourceResolver for the current
0618: * pipeline.
0619: *
0620: * @param config the properties to configure this resource.
0621: * @throws IllegalArgumentException thrown if the required
0622: * 'context' property is not set.
0623: * @throws ClassCastException if the 'context' property is not
0624: * of type {@link org.apache.avalon.framework.context.Context}.
0625: * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#init(ExtendedProperties)
0626: */
0627: public void init(ExtendedProperties config) {
0628: this .resolverContext = (org.apache.avalon.framework.context.Context) config
0629: .get("context");
0630: if (this .resolverContext == null) {
0631: throw new IllegalArgumentException(
0632: "Runtime Cocoon resolver context not specified in resource loader configuration.");
0633: }
0634: }
0635:
0636: /**
0637: * @param systemId the path to the resource
0638: * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getResourceStream(String)
0639: */
0640: public InputStream getResourceStream(String systemId)
0641: throws org.apache.velocity.exception.ResourceNotFoundException {
0642: try {
0643: return resolveSource(systemId).getInputStream();
0644: } catch (org.apache.velocity.exception.ResourceNotFoundException ex) {
0645: throw ex;
0646: } catch (Exception ex) {
0647: throw new org.apache.velocity.exception.ResourceNotFoundException(
0648: "Unable to resolve source: " + ex);
0649: }
0650: }
0651:
0652: /**
0653: * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#isSourceModified(Resource)
0654: */
0655: public boolean isSourceModified(Resource resource) {
0656: long lastModified = 0;
0657: try {
0658: lastModified = resolveSource(resource.getName())
0659: .getLastModified();
0660: } catch (Exception ex) {
0661: super .rsvc
0662: .warn("Unable to determine last modified for resource: "
0663: + resource.getName() + ": " + ex);
0664: }
0665:
0666: return lastModified > 0 ? lastModified != resource
0667: .getLastModified() : true;
0668: }
0669:
0670: /**
0671: * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(Resource)
0672: */
0673: public long getLastModified(Resource resource) {
0674: long lastModified = 0;
0675: try {
0676: lastModified = resolveSource(resource.getName())
0677: .getLastModified();
0678: } catch (Exception ex) {
0679: super .rsvc
0680: .warn("Unable to determine last modified for resource: "
0681: + resource.getName() + ": " + ex);
0682: }
0683:
0684: return lastModified;
0685: }
0686:
0687: /**
0688: * Store all the Source objects we lookup via the SourceResolver so that they can be properly
0689: * recycled later.
0690: *
0691: * @param systemId the path to the resource
0692: */
0693: private Source resolveSource(String systemId)
0694: throws org.apache.velocity.exception.ResourceNotFoundException {
0695: Map sourceCache;
0696: try {
0697: sourceCache = (Map) this .resolverContext
0698: .get(CONTEXT_SOURCE_CACHE_KEY);
0699: } catch (ContextException ignore) {
0700: throw new org.apache.velocity.exception.ResourceNotFoundException(
0701: "Runtime Cocoon source cache not specified in resource loader resolver context.");
0702: }
0703:
0704: Source source = (Source) sourceCache.get(systemId);
0705: if (source == null) {
0706: try {
0707: SourceResolver resolver = (SourceResolver) this .resolverContext
0708: .get(CONTEXT_RESOLVER_KEY);
0709: source = resolver.resolveURI(systemId);
0710: } catch (ContextException ex) {
0711: throw new org.apache.velocity.exception.ResourceNotFoundException(
0712: "No Cocoon source resolver associated with current request.");
0713: } catch (Exception ex) {
0714: throw new org.apache.velocity.exception.ResourceNotFoundException(
0715: "Unable to resolve source: " + ex);
0716: }
0717: }
0718:
0719: sourceCache.put(systemId, source);
0720: return source;
0721: }
0722: }
0723:
0724: /**
0725: * Key to lookup the {@link SourceResolver} from the context of
0726: * the resource loader
0727: */
0728: final private static String CONTEXT_RESOLVER_KEY = "resolver";
0729:
0730: /**
0731: * Key to lookup the source cache {@link Map} from the context of
0732: * the resource loader
0733: */
0734: final private static String CONTEXT_SOURCE_CACHE_KEY = "source-cache";
0735:
0736: private VelocityEngine tmplEngine;
0737: private boolean tmplEngineInitialized;
0738: private DefaultContext resolverContext;
0739: private Context velocityContext;
0740: private boolean activeFlag;
0741:
0742: /**
0743: * Read any additional objects to export to the Velocity context
0744: * from the configuration.
0745: *
0746: * @param configuration the class configurations.
0747: * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
0748: */
0749: public void configure(Configuration configuration)
0750: throws ConfigurationException {
0751: this .resolverContext = new DefaultContext();
0752: this .tmplEngine = new VelocityEngine();
0753:
0754: // Set up a JavaScript introspector for the Cocoon flow layer
0755: this .tmplEngine
0756: .setProperty(
0757: org.apache.velocity.runtime.RuntimeConstants.UBERSPECT_CLASSNAME,
0758: JSIntrospector.class.getName());
0759: this .tmplEngine.setProperty(
0760: RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, this );
0761:
0762: // First set up our default 'cocoon' resource loader
0763: this .tmplEngine.setProperty("cocoon.resource.loader.class",
0764: TemplateLoader.class.getName());
0765: this .tmplEngine.setProperty("cocoon.resource.loader.cache",
0766: configuration.getAttribute("usecache", "false"));
0767: this .tmplEngine.setProperty(
0768: "cocoon.resource.loader.modificationCheckInterval",
0769: configuration.getAttribute("checkInterval", "0"));
0770: this .tmplEngine.setProperty("cocoon.resource.loader.context",
0771: this .resolverContext);
0772:
0773: // Read in any additional properties to pass to the VelocityEngine during initialization
0774: Configuration[] properties = configuration
0775: .getChildren("property");
0776: for (int i = 0; i < properties.length; ++i) {
0777: Configuration c = properties[i];
0778: String name = c.getAttribute("name");
0779:
0780: // Disallow setting of certain properties
0781: if (name.startsWith("runtime.log")
0782: || name.indexOf(".resource.loader.") != -1) {
0783: if (getLogger().isInfoEnabled()) {
0784: getLogger().info(
0785: "ignoring disallowed property '" + name
0786: + "'.");
0787: }
0788: continue;
0789: }
0790: this .tmplEngine.setProperty(name, c.getAttribute("value"));
0791: }
0792:
0793: // Now read in any additional Velocity resource loaders
0794: List resourceLoaders = new ArrayList();
0795: Configuration[] loaders = configuration
0796: .getChildren("resource-loader");
0797: for (int i = 0; i < loaders.length; ++i) {
0798: Configuration loader = loaders[i];
0799: String name = loader.getAttribute("name");
0800: if (name.equals("cocoon")) {
0801: if (getLogger().isInfoEnabled()) {
0802: getLogger()
0803: .info(
0804: "'cocoon' resource loader already defined.");
0805: }
0806: continue;
0807: }
0808: resourceLoaders.add(name);
0809: String prefix = name + ".resource.loader.";
0810: String type = loader.getAttribute("class");
0811: this .tmplEngine.setProperty(prefix + "class", type);
0812: Configuration[] loaderProperties = loader
0813: .getChildren("property");
0814: for (int j = 0; j < loaderProperties.length; j++) {
0815: Configuration c = loaderProperties[j];
0816: String propName = c.getAttribute("name");
0817: this .tmplEngine.setProperty(prefix + propName, c
0818: .getAttribute("value"));
0819: }
0820: }
0821:
0822: // Velocity expects resource loaders as CSV list
0823: //
0824: StringBuffer buffer = new StringBuffer("cocoon");
0825: for (Iterator it = resourceLoaders.iterator(); it.hasNext();) {
0826: buffer.append(',');
0827: buffer.append((String) it.next());
0828: }
0829: tmplEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, buffer
0830: .toString());
0831: }
0832:
0833: /**
0834: * @see org.apache.avalon.framework.activity.Initializable#initialize()
0835: */
0836: public void initialize() throws Exception {
0837: //this.tmplEngine.init();
0838: }
0839:
0840: /**
0841: * @see org.apache.cocoon.sitemap.SitemapModelComponent#setup(SourceResolver, Map, String, Parameters)
0842: */
0843: public void setup(SourceResolver resolver, Map objectModel,
0844: String src, Parameters params) throws ProcessingException,
0845: SAXException, IOException {
0846: if (activeFlag) {
0847: throw new IllegalStateException(
0848: "setup called on recyclable sitemap component before properly recycling previous state");
0849: }
0850:
0851: super .setup(resolver, objectModel, src, params);
0852:
0853: // Pass along the SourceResolver to the Velocity resource loader
0854: this .resolverContext.put(CONTEXT_RESOLVER_KEY, resolver);
0855: this .resolverContext.put(CONTEXT_SOURCE_CACHE_KEY,
0856: new HashMap());
0857:
0858: // FIXME: Initialize the Velocity context. Use objectModel to pass these
0859: final Object bean = FlowHelper.getContextObject(objectModel);
0860: if (bean != null) {
0861:
0862: final WebContinuation kont = FlowHelper
0863: .getWebContinuation(objectModel);
0864:
0865: // Hack? I use JXPath to determine the properties of the bean object
0866: final JXPathBeanInfo bi = JXPathIntrospector
0867: .getBeanInfo(bean.getClass());
0868: DynamicPropertyHandler h = null;
0869: final PropertyDescriptor[] props;
0870: if (bi.isDynamic()) {
0871: Class cl = bi.getDynamicPropertyHandlerClass();
0872: try {
0873: h = (DynamicPropertyHandler) cl.newInstance();
0874: } catch (Exception exc) {
0875: exc.printStackTrace();
0876: h = null;
0877: }
0878: props = null;
0879: } else {
0880: h = null;
0881: props = bi.getPropertyDescriptors();
0882: }
0883: final DynamicPropertyHandler handler = h;
0884:
0885: this .velocityContext = new Context() {
0886: public Object put(String key, Object value) {
0887: if (key.equals("flowContext")
0888: || key.equals("continuation")) {
0889: return value;
0890: }
0891: if (handler != null) {
0892: handler.setProperty(bean, key, value);
0893: return value;
0894: } else {
0895: for (int i = 0; i < props.length; i++) {
0896: if (props[i].getName().equals(key)) {
0897: try {
0898: return props[i]
0899: .getWriteMethod()
0900: .invoke(
0901: bean,
0902: new Object[] { value });
0903: } catch (Exception ignored) {
0904: break;
0905: }
0906: }
0907: }
0908: return value;
0909: }
0910: }
0911:
0912: public boolean containsKey(Object key) {
0913: if (key.equals("flowContext")
0914: || key.equals("continuation")) {
0915: return true;
0916: }
0917: if (handler != null) {
0918: String[] result = handler
0919: .getPropertyNames(bean);
0920: for (int i = 0; i < result.length; i++) {
0921: if (key.equals(result[i])) {
0922: return true;
0923: }
0924: }
0925: } else {
0926: for (int i = 0; i < props.length; i++) {
0927: if (key.equals(props[i].getName())) {
0928: return true;
0929: }
0930: }
0931: }
0932: return false;
0933: }
0934:
0935: public Object[] getKeys() {
0936: Object[] result = null;
0937: if (handler != null) {
0938: result = handler.getPropertyNames(bean);
0939: } else {
0940: result = new Object[props.length];
0941: for (int i = 0; i < props.length; i++) {
0942: result[i] = props[i].getName();
0943: }
0944: }
0945: Set set = new HashSet();
0946: for (int i = 0; i < result.length; i++) {
0947: set.add(result[i]);
0948: }
0949: set.add("flowContext");
0950: set.add("continuation");
0951: result = new Object[set.size()];
0952: set.toArray(result);
0953: return result;
0954: }
0955:
0956: public Object get(String key) {
0957: if (key.equals("flowContext")) {
0958: return bean;
0959: }
0960: if (key.equals("continuation")) {
0961: return kont;
0962: }
0963: if (handler != null) {
0964: return handler.getProperty(bean, key);
0965: } else {
0966: for (int i = 0; i < props.length; i++) {
0967: if (props[i].getName().equals(key)) {
0968: try {
0969: return props[i].getReadMethod()
0970: .invoke(bean, null);
0971: } catch (Exception ignored) {
0972: break;
0973: }
0974: }
0975: }
0976: return null;
0977: }
0978: }
0979:
0980: public Object remove(Object key) {
0981: // not implemented
0982: return key;
0983: }
0984: };
0985: }
0986: this .velocityContext = new ChainedContext(this .velocityContext,
0987: ObjectModelHelper.getRequest(objectModel),
0988: ObjectModelHelper.getResponse(objectModel),
0989: ObjectModelHelper.getContext(objectModel), params);
0990: this .velocityContext.put("template", src);
0991: this .activeFlag = true;
0992: }
0993:
0994: /**
0995: * Free up the VelocityContext associated with the pipeline, and
0996: * release any Source objects resolved by the resource loader.
0997: *
0998: * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
0999: */
1000: public void recycle() {
1001: this .activeFlag = false;
1002:
1003: // Recycle all the Source objects resolved/used by our resource loader
1004: try {
1005: Map sourceCache = (Map) this .resolverContext
1006: .get(CONTEXT_SOURCE_CACHE_KEY);
1007: for (Iterator it = sourceCache.values().iterator(); it
1008: .hasNext();) {
1009: this .resolver.release((Source) it.next());
1010: }
1011: } catch (ContextException ignore) {
1012: }
1013:
1014: this .velocityContext = null;
1015: super .recycle();
1016: }
1017:
1018: /**
1019: * Generate XML data using Velocity template.
1020: *
1021: * @see org.apache.cocoon.generation.Generator#generate()
1022: */
1023: public void generate() throws IOException, SAXException,
1024: ProcessingException {
1025: // Guard against calling generate before setup.
1026: if (!activeFlag) {
1027: throw new IllegalStateException(
1028: "generate called on sitemap component before setup.");
1029: }
1030:
1031: SAXParser parser = null;
1032: StringWriter w = new StringWriter();
1033: try {
1034: parser = (SAXParser) this .manager.lookup(SAXParser.ROLE);
1035: if (getLogger().isDebugEnabled()) {
1036: getLogger().debug("Processing File: " + super .source);
1037: }
1038: if (!tmplEngineInitialized) {
1039: tmplEngine.init();
1040: tmplEngineInitialized = true;
1041: }
1042: /* lets render a template */
1043: this .tmplEngine.mergeTemplate(super .source,
1044: velocityContext, w);
1045:
1046: InputSource xmlInput = new InputSource(new StringReader(w
1047: .toString()));
1048: xmlInput.setSystemId(super .source);
1049: parser.parse(xmlInput, this .xmlConsumer);
1050: } catch (IOException e) {
1051: getLogger().warn("VelocityGenerator.generate()", e);
1052: throw new ResourceNotFoundException(
1053: "Could not get Resource for VelocityGenerator", e);
1054: } catch (SAXParseException e) {
1055: int line = e.getLineNumber();
1056: int column = e.getColumnNumber();
1057: if (line <= 0) {
1058: line = Integer.MAX_VALUE;
1059: }
1060: BufferedReader reader = new BufferedReader(
1061: new StringReader(w.toString()));
1062: StringBuffer message = new StringBuffer(e.getMessage());
1063: message.append(" In generated document:\n");
1064: for (int i = 0; i < line; i++) {
1065: String lineStr = reader.readLine();
1066: if (lineStr == null) {
1067: break;
1068: }
1069: message.append(lineStr);
1070: message.append("\n");
1071: }
1072: if (column > 0) {
1073: message.append(StringUtils.leftPad("^\n", column + 1));
1074: }
1075: SAXException pe = new SAXParseException(message.toString(),
1076: e.getPublicId(),
1077: "(Document generated from template "
1078: + e.getSystemId() + ")", e.getLineNumber(),
1079: e.getColumnNumber(), null);
1080: getLogger().error("VelocityGenerator.generate()", pe);
1081: throw pe;
1082: } catch (SAXException e) {
1083: getLogger().error("VelocityGenerator.generate()", e);
1084: throw e;
1085: } catch (ServiceException e) {
1086: getLogger().error("Could not get parser", e);
1087: throw new ProcessingException(
1088: "Exception in VelocityGenerator.generate()", e);
1089: } catch (ProcessingException e) {
1090: throw e;
1091: } catch (Exception e) {
1092: getLogger().error("Could not get parser", e);
1093: throw new ProcessingException(
1094: "Exception in VelocityGenerator.generate()", e);
1095: } finally {
1096: this .manager.release(parser);
1097: }
1098: }
1099:
1100: /**
1101: * This implementation does nothing.
1102: *
1103: * @see org.apache.velocity.runtime.log.LogSystem#init(RuntimeServices)
1104: */
1105: public void init(RuntimeServices rs) throws Exception {
1106: }
1107:
1108: /**
1109: * Pass along Velocity log messages to our configured logger.
1110: *
1111: * @see org.apache.velocity.runtime.log.LogSystem#logVelocityMessage(int, String)
1112: */
1113: public void logVelocityMessage(int level, String message) {
1114: switch (level) {
1115: case LogSystem.WARN_ID:
1116: getLogger().warn(message);
1117: break;
1118: case LogSystem.INFO_ID:
1119: getLogger().info(message);
1120: break;
1121: case LogSystem.DEBUG_ID:
1122: getLogger().debug(message);
1123: break;
1124: case LogSystem.ERROR_ID:
1125: getLogger().error(message);
1126: break;
1127: default:
1128: getLogger().info(message);
1129: break;
1130: }
1131: }
1132: }
|