| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package flex2.compiler.util; |
| |
| import org.apache.flex.forks.velocity.Template; |
| import org.apache.flex.forks.velocity.VelocityContext; |
| import org.apache.flex.forks.velocity.app.Velocity; |
| import org.apache.flex.forks.velocity.app.VelocityEngine; |
| import org.apache.flex.forks.velocity.app.event.EventCartridge; |
| import org.apache.flex.forks.velocity.app.event.EventHandler; |
| import org.apache.flex.forks.velocity.app.event.MethodExceptionEventHandler; |
| import org.apache.flex.forks.velocity.app.event.ReferenceInsertionEventHandler; |
| import org.apache.flex.forks.velocity.runtime.RuntimeServices; |
| import org.apache.flex.forks.velocity.runtime.log.LogSystem; |
| import org.apache.flex.forks.velocity.util.introspection.Info; |
| import org.apache.flex.forks.velocity.util.introspection.UberspectImpl; |
| import org.apache.flex.forks.velocity.util.introspection.VelPropertyGet; |
| import org.apache.flex.forks.velocity.util.introspection.VelPropertySet; |
| import org.apache.flex.forks.util.SerializedTemplateFactory; |
| |
| import java.text.Format; |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| import java.util.Hashtable; |
| import java.util.Map; |
| |
| /** |
| * Encapsulates Velocity services. Velocity moving parts: |
| * <p/> |
| * 1. VelocityEngine: template loader, parser pool manager, configuration |
| * holder. Intended to be 1:1 with e.g. a servlet context. Note: any template |
| * "libraries" containing definitions outside the template itself are associated |
| * with instances of VelocityEngine. |
| * <p/> |
| * 2. VelocityContext: basically a HashMap of names to POJOs, along with some |
| * introspection services. When a template is "merged" with a context, $var |
| * references in the template become map lookups in the context. |
| * <p/> |
| * 3. Template: Represented internally as an AST. Standard usage is to parse |
| * them from source; so presumably not thread-safe. |
| * <p/> |
| * We want to support caller-specified libraries, but it's gratuitous to create |
| * lots of VelocityEngines, and inconvenient for callers to keep references to |
| * them. So we keep a map of VE instances by library config internally. (If the |
| * possibility arises of creating too many VEs, a size limiter could be added.) |
| * <p/> |
| * Standard VelocityManager usage pattern: |
| * <p/> |
| * <pre> |
| * Template t = VelocityManager.getTemplate(path[, libs]) |
| * VelocityContext c = VelocityManager.getCodeGenContext([custom util class]) |
| * c.add(name, value) // populate c with name/value pairs used by t |
| * t.merge(c, w) // w is a Writer |
| */ |
| public class VelocityManager |
| { |
| |
| private static final String LOGSYSTEM_CLASS = "flex2.compiler.util.VelocityManager$Logger"; |
| |
| private static final String STRICT_UBERSPECT_IMPL_CLASS = "flex2.compiler.util.VelocityManager$StrictUberspectImpl"; |
| |
| private static final String CLASSPATH_RESOURCE_LOADER_CLASS = "org.apache.flex.forks.velocity.runtime.resource.loader.ClasspathResourceLoader"; |
| |
| private static final String UTIL_KEY = "util"; |
| |
| // Use Hashtable which is synchronized so that the VelocityManager is thread-safe. |
| private static final Map<String, VelocityEngine> engines = new Hashtable<String, VelocityEngine>(); |
| private static final Map<String, Template> templates = new Hashtable<String, Template>(); |
| |
| /** |
| * Create a VelocityEngine instance configured with the given libs string |
| */ |
| private static final VelocityEngine createEngine(String lib) |
| { |
| VelocityEngine ve = new VelocityEngine(); |
| // use our logger, and customize log settings |
| ve.setProperty(Velocity.RUNTIME_LOG_LOGSYSTEM_CLASS, LOGSYSTEM_CLASS); |
| ve.setProperty(Velocity.RUNTIME_LOG_ERROR_STACKTRACE, "false"); |
| ve.setProperty(Velocity.RUNTIME_LOG_REFERENCE_LOG_INVALID, "true"); |
| // use our strict introspection |
| ve.setProperty(Velocity.UBERSPECT_CLASSNAME, STRICT_UBERSPECT_IMPL_CLASS); |
| // load from classpath and file system |
| ve.setProperty(Velocity.RESOURCE_LOADER, "file,class"); |
| // configure class resource loader |
| ve.setProperty("class." + Velocity.RESOURCE_LOADER + ".class", CLASSPATH_RESOURCE_LOADER_CLASS); |
| |
| // libs are precompiled in later in this function |
| ve.setProperty(Velocity.VM_LIBRARY, ""); |
| // initialize |
| |
| try |
| { |
| ve.init(); |
| } |
| catch (Exception e) |
| { |
| ThreadLocalToolkit.log(new InitializationError(e.getLocalizedMessage())); |
| } |
| |
| if (lib != null && ve != null) |
| { |
| getTemplate(lib, ve); |
| } |
| return ve; |
| } |
| |
| /** |
| * Look up the VelocityEngine instance configured with the given libs string |
| */ |
| private static final VelocityEngine getEngine(String lib) |
| { |
| String libKey = lib == null ? "" : lib; |
| VelocityEngine ve = engines.get(libKey); |
| if (ve == null) |
| ve = createEngine(lib); |
| if (ve != null) |
| engines.put(libKey, ve); |
| return ve; |
| } |
| |
| public static Template getTemplate(String path) |
| { |
| return getTemplate(path, (String) null); |
| } |
| |
| public static Template getTemplate(String path, String lib) |
| { |
| VelocityEngine ve = getEngine(lib); |
| String templateKey = path + (lib == null ? "" : lib); |
| |
| // from what I can tell templates and there parser tree are static (unchanging) |
| // data so that this should be thread safe, I think all the transient data comes |
| // from the context. |
| Template t = templates.get(templateKey); |
| if (t == null) |
| { |
| t = getTemplate(path, ve); |
| templates.put(templateKey, t); |
| } |
| return t; |
| } |
| |
| private static Template getTemplate(String path, VelocityEngine ve) |
| { |
| try |
| { |
| Template t = SerializedTemplateFactory.load(path + "s"); |
| t.setRuntimeServices(ve.getRuntimeServices()); |
| t.setName(path); |
| t.initDocument(); |
| return t; |
| } |
| catch (Exception e) |
| { |
| // any problems here are catastrophic failures |
| // FIXME: someone in the mxml fold should review |
| throw new RuntimeException(e); |
| } |
| } |
| |
| |
| /** |
| * Create new VelocityContext. Installs Util subclass if passed, otherwise |
| * instance of generic Util. Also, if passed util also implements one or |
| * more velo EventHandlers, we register it as such. Note: currently there's |
| * no reason to create a generic instance for every context, but we may want |
| * to add state - benchmarking stuff etc. |
| */ |
| public static VelocityContext getCodeGenContext(Util util) |
| { |
| VelocityContext cx = new VelocityContext(); |
| if (util == null) |
| { |
| util = new Util(); |
| } |
| |
| if (util instanceof EventHandler) |
| { |
| EventCartridge ec = new EventCartridge(); |
| ec.addEventHandler(util); |
| ec.attachToContext(cx); |
| } |
| cx.put(UTIL_KEY, util); |
| return cx; |
| } |
| |
| /** |
| * return a new VelocityContext with instance of default Util |
| */ |
| public static VelocityContext getCodeGenContext() |
| { |
| return getCodeGenContext(null); |
| } |
| |
| /** |
| * Handles MethodExceptionEvent, and exposes some generic utilities for use |
| * in a context. Users can subclass to add their own purpose-specific stuff |
| */ |
| public static class Util implements MethodExceptionEventHandler, ReferenceInsertionEventHandler |
| { |
| static Format dateTimeFormat = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z"); |
| |
| public static long Now() |
| { |
| return System.currentTimeMillis(); |
| } |
| |
| public static String getTimeStamp() |
| { |
| return dateTimeFormat.format(new Date(Now())); |
| } |
| |
| /** |
| * MethodExceptionEventHandler impl |
| */ |
| public final Object methodException(Class claz, String methodName, Exception e) throws Exception |
| { |
| ThreadLocalToolkit.log(new InvocationError(claz.getName(), methodName, e.getLocalizedMessage())); |
| return null; |
| } |
| |
| /** |
| * ReferenceInsertionEventHandler impl |
| */ |
| public Object referenceInsert(String s, Object o) |
| { |
| if (o == null) |
| { |
| ThreadLocalToolkit.log(new TemplateReferenceIsNull(s)); |
| } |
| |
| return o; |
| } |
| } |
| |
| /** |
| * Extension of Velocity's standard UberspectImpl. We log an error when an |
| * invocation target mathod is not resolved. Standard implementation's |
| * behavior is to silently return null. |
| */ |
| public static class StrictUberspectImpl extends UberspectImpl |
| { |
| public VelPropertyGet getPropertyGet(Object obj, String identifier, |
| Info i) throws Exception |
| { |
| VelPropertyGet getter = super.getPropertyGet(obj, identifier, i); |
| // there is no clean way to see if super succeeded |
| // @see http://issues.apache.org/bugzilla/show_bug.cgi?id=31742 |
| try |
| { |
| getter.getMethodName(); |
| } |
| catch (NullPointerException e) |
| { |
| ThreadLocalToolkit.log(new GetMethodNotFound(i.getTemplateName(), i.getLine(), i.getColumn(), identifier, obj.getClass().getName())); |
| } |
| |
| return getter; |
| } |
| |
| public VelPropertySet getPropertySet(Object obj, String identifier, |
| Object arg, Info i) throws Exception |
| { |
| VelPropertySet setter = super.getPropertySet(obj, identifier, arg, i); |
| if (setter == null) |
| { |
| ThreadLocalToolkit.log(new SetMethodNotFound(i.getTemplateName(), i.getLine(), i.getColumn(), identifier, obj.getClass().getName())); |
| } |
| |
| return setter; |
| } |
| } |
| |
| /** |
| * route velocity error messages to our logger; also send everything to an |
| * instance of Velo's default log system |
| */ |
| public static class Logger implements LogSystem |
| { |
| LogSystem als = null; |
| |
| public void init(RuntimeServices rs) throws Exception |
| { |
| try |
| { |
| // TODO enable velocity.log based on config setting. Only makes |
| // sense once there's an actual meta-compiler instance |
| // which holds the trans-compile settings specific to it, and |
| // which can hold onto a VelocityManager instance and |
| // pass it individual Compilers. Can/should be done as part of |
| // the "get default compiler config" facade. |
| // uncomment this for velocity.log |
| // (als = new AvalonLogSystem()).init(rs); |
| } |
| catch (NoClassDefFoundError e) |
| { |
| // ignore |
| } |
| } |
| |
| public void logVelocityMessage(int level, String message) |
| { |
| if ((level == ERROR_ID) && |
| (!(message.equals("VM #writeWatcher: error : too few arguments to macro. Wanted 1 got 0") || |
| message.equals("VM #writeEvaluationWatcherPart: error : too few arguments to macro. Wanted 2 got 0") || |
| message.equals("VM #writeWatcherBottom: error : too few arguments to macro. Wanted 1 got 0")))) |
| { |
| ThreadLocalToolkit.logWarning(message); |
| } |
| else |
| { |
| // ThreadLocalToolkit.logDebug(message); |
| } |
| |
| if (als != null) |
| { |
| als.logVelocityMessage(level, message); |
| } |
| } |
| } |
| |
| // error messages |
| |
| public static class InitializationError extends CompilerMessage.CompilerError |
| { |
| private static final long serialVersionUID = 6605160202727369019L; |
| |
| public InitializationError(String message) |
| { |
| super(); |
| this.message = message; |
| } |
| |
| public final String message; |
| } |
| |
| public static class InvocationError extends CompilerMessage.CompilerError |
| { |
| private static final long serialVersionUID = -8490870052703007082L; |
| |
| public InvocationError(String className, String methodName, String message) |
| { |
| super(); |
| this.className = className; |
| this.methodName = methodName; |
| this.message = message; |
| } |
| |
| public final String className, methodName, message; |
| } |
| |
| public static class TemplateReferenceIsNull extends CompilerMessage.CompilerError |
| { |
| private static final long serialVersionUID = -4029561325397237572L; |
| |
| public TemplateReferenceIsNull(String s) |
| { |
| super(); |
| this.s = s; |
| } |
| |
| public final String s; |
| } |
| |
| public static class GetMethodNotFound extends CompilerMessage.CompilerError |
| { |
| private static final long serialVersionUID = 2870457973013490956L; |
| public GetMethodNotFound(String template, int line, int column, String identifier, String className) |
| { |
| super(); |
| this.template = template; |
| this.line = line; |
| this.column = column; |
| this.identifier = identifier; |
| this.className = className; |
| } |
| |
| public final String template, identifier, className; |
| public final int line, column; |
| } |
| |
| public static class SetMethodNotFound extends CompilerMessage.CompilerError |
| { |
| private static final long serialVersionUID = -4644689922730063246L; |
| public SetMethodNotFound(String template, int line, int column, String identifier, String className) |
| { |
| super(); |
| this.template = template; |
| this.line = line; |
| this.column = column; |
| this.identifier = identifier; |
| this.className = className; |
| } |
| |
| public final String template, identifier, className; |
| public final int line, column; |
| } |
| } |