Velocity
  1. Velocity
  2. VELOCITY-414

Extend the MethodInvocation exception to be able to give the velocity macro writer a usefull error page

    Details

    • Type: Improvement Improvement
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 1.4, 1.5
    • Fix Version/s: 1.5
    • Component/s: Engine
    • Labels:
      None

      Description

      We use velocity macros that invoke methods in a java written web engine.
      When an invoked method fails because of an exception, it is not
      possible to use the
      MethodInvocation exception to give the velocity macro writer a usefull
      error page since the MethodInvocation Exception has not cause set.
      So to be short the reason why the method invocation failed can not be
      routed back to the veloticy macro writer on a running system.

      I extended the MethodInvocationException.java and the method execute in
      ASTMethod.java

      proposed changes in MethodInvocationException.java :
      ===============================================
      package org.apache.velocity.exception;

      import org.apache.velocity.exception.VelocityException;
      import org.apache.velocity.runtime.parser.Token;

      /*

      • Copyright 2001,2004 The Apache Software Foundation.
      • Licensed 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.
        */

      /**

      • Application-level exception thrown when a reference method is
      • invoked and an exception is thrown.
      • <br>
      • When this exception is thrown, a best effort will be made to have
      • useful information in the exception's message. For complete
      • information, consult the runtime log.
        *
      • @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
      • @version $Id: MethodInvocationException.java,v 1.2.14.1 2004/03/03 23:22:54 geirm Exp $
        */
        public class MethodInvocationException extends VelocityException
        {
        private String methodName = "";
        private String referenceName = "";
        private Throwable wrapped = null;
        private int line; // Added by CX
        private int column; // Added by CX

      /**

      • CTOR - wraps the passed in exception for
      • examination later
        *
      • @param message
      • @param e Throwable that we are wrapping
      • @param methodName name of method that threw the exception
        */
        public MethodInvocationException( String message, Throwable e, String methodName ) { super(message); this.wrapped = e; this.methodName = methodName; }

      /**

      • Returns the name of the method that threw the
      • exception
        *
      • @return String name of method
        */
        public String getMethodName() { return methodName; }

      /**

      • returns the wrapped Throwable that caused this
      • MethodInvocationException to be thrown
      • @return Throwable thrown by method invocation
        */
        public Throwable getWrappedThrowable() { return wrapped; }

      /**

      • Sets the reference name that threw this exception
        *
      • @param reference name of reference
        */
        public void setReferenceName( String ref ) { referenceName = ref; }

      /**

      • Retrieves the name of the reference that caused the
      • exception
        *
      • @return name of reference
        */
        public String getReferenceName() { return referenceName; }

      /**

      • Retrieves the line number where the error occured
      • @return line number
        */
        public int getLine() { return line; }

      /**

      • Sets the line number where the error occured
        *
      • @param line
        */
        public void setLine(int line) { this.line = line; }

      /**

      • Retrieves the line number where the error occured
      • @return column number
        */
        public int getColumn() { return column; }

      /**

      • Sets the column number where the error occured
      • @param column
        */
        public void setColumn(int column) { this.column = column; }

      }

      ===============================================
      Proposed changes in ASTMethod.java
      ===============================================
      package org.apache.velocity.runtime.parser.node;

      /*

      • Copyright 2000-2001,2004 The Apache Software Foundation.
      • Licensed 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.
        */

      import org.apache.velocity.context.InternalContextAdapter;
      import org.apache.velocity.runtime.parser.*;
      import org.apache.velocity.util.introspection.IntrospectionCacheData;
      import org.apache.velocity.util.introspection.VelMethod;
      import org.apache.velocity.util.introspection.Info;

      import org.apache.velocity.exception.MethodInvocationException;
      import java.lang.reflect.InvocationTargetException;

      import org.apache.velocity.app.event.EventCartridge;

      /**

      • ASTMethod.java
        *
      • Method support for references : $foo.method()
        *
      • NOTE :
        *
      • introspection is now done at render time.
        *
      • Please look at the Parser.jjt file which is
      • what controls the generation of this class.
        *
      • @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
      • @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
      • @version $Id: ASTMethod.java,v 1.24.4.1 2004/03/03 23:22:59 geirm Exp $
        */
        public class ASTMethod extends SimpleNode
        {
        private String methodName = "";
        private int paramCount = 0;

      public ASTMethod(int id)

      { super(id); }

      public ASTMethod(Parser p, int id)

      { super(p, id); }

      /** Accept the visitor. **/
      public Object jjtAccept(ParserVisitor visitor, Object data)

      { return visitor.visit(this, data); }

      /**

      • simple init - init our subtree and get what we can from
      • the AST
        */
        public Object init( InternalContextAdapter context, Object data)
        throws Exception { super.init( context, data ); /* * this is about all we can do */ methodName = getFirstToken().image; paramCount = jjtGetNumChildren() - 1; return data; }

      /**

      • invokes the method. Returns null if a problem, the
      • actual return if the method returns something, or
      • an empty string "" if the method returns void
        */
        public Object execute(Object o, InternalContextAdapter context)
        throws MethodInvocationException
        {
        /*
      • new strategy (strategery!) for introspection. Since we want
      • to be thread- as well as context-safe, we must do it now,
      • at execution time. There can be no in-node caching,
      • but if we are careful, we can do it in the context.
        */

      VelMethod method = null;

      Object [] params = new Object[paramCount];

      try
      {
      /*

      • check the cache
        */

      IntrospectionCacheData icd = context.icacheGet( this );
      Class c = o.getClass();

      /*

      • like ASTIdentifier, if we have cache information, and the
      • Class of Object o is the same as that in the cache, we are
      • safe.
        */

      if ( icd != null && icd.contextData == c )

      { /* * sadly, we do need recalc the values of the args, as this can * change from visit to visit */ for (int j = 0; j < paramCount; j++) params[j] = jjtGetChild(j + 1).value(context); /* * and get the method from the cache */ method = (VelMethod) icd.thingy; }

      else
      {
      /*

      • otherwise, do the introspection, and then
      • cache it
        */

      for (int j = 0; j < paramCount; j++)
      params[j] = jjtGetChild(j + 1).value(context);

      method = rsvc.getUberspect().getMethod(o, methodName, params, new Info("",1,1));

      if (method != null)

      { icd = new IntrospectionCacheData(); icd.contextData = c; icd.thingy = method; context.icachePut( this, icd ); }

      }

      /*

      • if we still haven't gotten the method, either we are calling
      • a method that doesn't exist (which is fine...) or I screwed
      • it up.
        */

      if (method == null)
      return null;
      }
      catch( MethodInvocationException mie )

      { /* * this can come from the doIntrospection(), as the arg values * are evaluated to find the right method signature. We just * want to propogate it here, not do anything fancy */ throw mie; }

      catch( Exception e )

      { /* * can come from the doIntropection() also, from Introspector */ rsvc.error("ASTMethod.execute() : exception from introspection : " + e); return null; }

      try
      {
      /*

      • get the returned object. It may be null, and that is
      • valid for something declared with a void return type.
      • Since the caller is expecting something to be returned,
      • as long as things are peachy, we can return an empty
      • String so ASTReference() correctly figures out that
      • all is well.
        */

      Object obj = method.invoke(o, params);

      if (obj == null)

      { if( method.getReturnType() == Void.TYPE) return new String(""); }

      return obj;
      }
      catch( InvocationTargetException ite )
      {
      /*

      • In the event that the invocation of the method
      • itself throws an exception, we want to catch that
      • wrap it, and throw. We don't log here as we want to figure
      • out which reference threw the exception, so do that
      • above
        */

      EventCartridge ec = context.getEventCartridge();

      /*

      • if we have an event cartridge, see if it wants to veto
      • also, let non-Exception Throwables go...
        */

      if ( ec != null && ite.getTargetException() instanceof java.lang.Exception)
      {
      try

      { return ec.methodException( o.getClass(), methodName, (Exception)ite.getTargetException() ); }

      catch( Exception e )

      { MethodInvocationException miex = new MethodInvocationException( "Invocation of method '" + methodName + "' in " + o.getClass() + " threw exception " + e.getClass() + " : " + e.getMessage(), e, methodName ); miex.initCause(ite.getTargetException()); miex.setLine(first.beginLine); miex.setColumn(first.beginColumn); throw miex; }

      }
      else

      { /* * no event cartridge to override. Just throw */ MethodInvocationException miex = new MethodInvocationException( "Invocation of method '" + methodName + "' in " + o.getClass() + " threw exception " + ite.getTargetException().getClass() + " : " + ite.getTargetException().getMessage(), ite.getTargetException(), methodName ); miex.initCause(ite.getTargetException()); miex.setLine(first.beginLine); miex.setColumn(first.beginColumn); throw miex; }

      }
      catch( Exception e )

      { rsvc.error("ASTMethod.execute() : exception invoking method '" + methodName + "' in " + o.getClass() + " : " + e ); return null; }

      }
      }

        Activity

        Mark Thomas made changes -
        Workflow Default workflow, editable Closed status [ 12551363 ] jira [ 12552071 ]
        Mark Thomas made changes -
        Workflow jira [ 12343184 ] Default workflow, editable Closed status [ 12551363 ]
        Henning Schmiedehausen made changes -
        Status Resolved [ 5 ] Closed [ 6 ]
        Henning Schmiedehausen made changes -
        Resolution Fixed [ 1 ]
        Status Open [ 1 ] Resolved [ 5 ]
        Henning Schmiedehausen made changes -
        Assignee Henning Schmiedehausen [ henning ]
        Will Glass-Husain made changes -
        Fix Version/s 1.5 [ 12310253 ]
        Matthijs Lambooy made changes -
        Field Original Value New Value
        Affects Version/s 1.5 [ 12310253 ]
        Matthijs Lambooy created issue -

          People

          • Assignee:
            Henning Schmiedehausen
            Reporter:
            Matthijs Lambooy
          • Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development