Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

The $1, $2 references are Javassist constructs which allow you to specify the first and second argument passed in to the calling method.

 ExpressionBinding

As stated previously, this class represents a single OGNL expression in Tapestry when used directly in html templates - such as:

No Format

<div jwcid="@Input" value="ognl:user.firstName" />

What you will want to examine in this class is how it deals with incrementally attempting expression evaluations using the local members _writeFailed, _accessor. Looking through the source of this implementation will probably be the best documentation available - but keep in mind that in many instances this object also has to deal with the possibility that a write statement may never happen.

 BeanProviderPropertyAccessor / Custom PropertyAccessor implementations

Besides the OgnlExpressionCompiler logic this will probably be the second most impactual area people will have to deal with in terms of having to write new code. In this specific instance there are three new PropertyAccessor methods you must implement in order to compile your expressions:

No Format

public Class getPropertyClass(OgnlContext context, Object target, Object name)
{
  IBeanProvider provider = (IBeanProvider)target;
  String beanName = ((String)name).replaceAll("\"", "");

  if (provider.canProvideBean(beanName))
    return provider.getBean(beanName).getClass();

  return super.getPropertyClass(context, target, name);
}

public String getSourceAccessor(OgnlContext context, Object target, Object name)
{
   IBeanProvider provider = (IBeanProvider)target;
   String beanName = ((String)name).replaceAll("\"", "");

   if (provider.canProvideBean(beanName)) {

       Class type = OgnlRuntime.getCompiler().getInterfaceClass(provider.getBean(beanName).getClass());

       ExpressionCompiler.addCastString(context, "((" + type.getName() + ")");

       context.setCurrentAccessor(IBeanProvider.class);
       context.setCurrentType(type);

       return ".getBean(" + name + "))";
   }

   return super.getSourceAccessor(context, target, name);
}

public String getSourceSetter(OgnlContext context, Object target, Object name)
{
  throw new UnsupportedCompilationException("Can't set beans on IBeanProvider.");
}

Although this example may not provide with all of the possible use cases you may need to learn to properly implement these methods in your own PropertyAccessor implementations - the built in OGNL versions like ObjectPropertyAccessor, MapPropertyAccessor, ListPropertyAccessor, etc should provide more than enough data to work from. http://svn.opensymphony.com/svn/ognl/trunk/

The most important part of the above logic you will want to look at is in how the new OgnlContext methods for setting object/accessor types are being set:

No Format

context.setCurrentAccessor(IBeanProvider.class);
context.setCurrentType(type);

This meta information is used by the OgnlExpressionCompiler to correctly cast your specific expression object types during compilation. This process of casting/converting in to and out of native types is the most complicated part of this new logic and also the source of the greatest number of bugs reported in the OGNL jira. http://jira.opensymphony.com/browse/OGNL

In this property accessor example the goal is to turn general statements like beans.emailValidator in to their pure source form - which would look something like this when all is said and done:

No Format

((ValidatingBean)beanProvider.getBean("emailValidator"))

There is also the ever important cast handling which you must do:

No Format

Class type = OgnlRuntime.getCompiler().getInterfaceClass(provider.getBean(beanName).getClass());

ExpressionCompiler.addCastString(context, "((" + type.getName() + ")");

In this example the PropertyAccessor is trying to determine the class type and manually adding the cast string for the specific type to the overall statement by invoking the utility method addCastString(OgnlContext, String) on ExpressionCompiler. In many instances of expression compilation you might also be dealing with unknown method calls, where the more preferred way to do this kind of logic would be something like this: (taken from the OGNL ObjectPropertyAccessor implementation)

No Format

Method m = ...(various reflection gynamistics used to find a java.reflect.Method instance)

context.setCurrentType(m.getReturnType());
context.setCurrentAccessor(OgnlRuntime.getCompiler().getSuperOrInterfaceClass(m, m.getDeclaringClass()));

When dealing with method calls it is very important that you do this specific kind of type setting on the OgnlContext class so that the casting done on your statements (which happens outside of the ObjectPropertyAccessor in this instance) can be done on the highest level interface defining that method. This becomes important when you are dealing with expressions that you would like to re-use against different object instances. For example, suppose we had an ognl expression like this (for Tapestry):

No Format

user.firstName

and the object it was compiled against was an instance of something looking like this:

No Format

public abstract LoginPage extends BasePage implements UserPermissions {

  public abstract User getUser();

}

..
/**
 * Interface for any page/component that holds references to the current system
 * User.
 */
public interface UserPermissions {
   User getUser();
}

BasePage is a Tapestry specific class which is unimportant in this example. What is important to know is that if we had done something like this in the previous context setting example:

No Format

context.setCurrentType(m.getReturnType());
context.setCurrentAccessor(m.getDeclaringClass());

It would have resulted in a compiled expression of:

No Format

public void get(OgnlContext context, Object root)
{
  return ((LoginPage)root).getUser();
}

This is undesirable in situations where you would like to re-use OGNL expressions across many different class instances (which is what Tapestry does via the ExpressionCacheImpl listed above). The better/more re-usable compiled version should really look like:

No Format

public void get(OgnlContext context, Object root)
{
  return ((UserPermissions)root).getUser();
}

These are the more delicate parts of the compiler API that the majority of people will need to worry about during any integration efforts.

 Known Issues / Limitations

  • Compiler Errors - Despite the substantially large number of unit tests set up and thorough usage of many different types of expressions Tapestry users are still currently running in to fatal/non caught runtime errors when some of their OGNL expressions are compiled. In some instances these errors are blockers and they must either wait for someone to fix the bug (after being posted to http://jira.opensymphony.com/browse/OGNL correctly) or re-work their expression to get around the error. I (jesse) generally try to fix these reported errors within a day or two (or sooner) when I can and immediately deploy the fixes to the OGNL snapshot maven2 repository. This doesn't mean that the vast majority of expressions won't compile fine, but it is something to keep in mind when you decide how to integrate the compiler logic in to your own framework.
  • Compile vs. normal expression evaluation - The current Tapestry implementation compiles OGNL expressions in both development AND production modes. This has the undesirable side effect of causing needless multiple method invocations on objects when compiling as well as the general overhead of performing compilations at all when people are just developing applications and not serving them in production environments. It is hoped that when OGNL becomes final this special development mode can go back to using normal OGNL expression evaluation during development and save compilation for production environments, but until then we've been worried about giving people false positives when testing their applications. Meaning - something may evaluate just fine when using Ognl.getValue(OgnlContext, Object root, String expression but fail completely when they deploy their app to production and the compiler kicks in. If you framework doesn't handle separate modes or have this kind of state set up it is something to keep in mind. The number of JIRA issues reported has gone way down since this all started but they do still trickle in which is enough to know that things aren't yet 100% reliable. I'm sure the plethora of Struts/WebWork/etc users available should be enough to iron out any remaining issues found but it's something to keep in mind.
  • Snapshot Repository - The current maven2 location of the OGNL development/snapshot release are all made to http://opencomponentry.com/repository/m2-snapshot-repo/, while releases go out to ibiblio as per normal. If someone has a better place for these release to be made please feel free to contact jesse ( jkuhnert at gmail.com) with accessor information / instructions.