jknack / handlebars.java

Logic-less and semantic Mustache templates with Java
http://jknack.github.io/handlebars.java
Other
1.46k stars 381 forks source link

Specifying a resolver causes the context data to not convert objects to JS #640

Open tedconn opened 6 years ago

tedconn commented 6 years ago

I am trying to extend JavaBeanValueResolver in order to prevent fields from the base class being converted to JS.

public class A extends B {

    public String getName() {
        return "Hello World";
    }
}

public abstract class B {

    public String getUnsupported() {
        throw new UnsupportedOperationException();
    }
}

This is a contrived example. In reality, apart from the exception, my base class has many, many properties which I don't want converted to JS, for performance reasons.

So I extended JavaBeanValueResolver like this:

public class CustomPropertyResolver extends JavaBeanValueResolver {

    public static final ValueResolver INSTANCE = new SiteComPropertyResolver();

    @Override
    protected void members(final Class<?> clazz, final Set<Method> members) {
        if (clazz != Object.class && clazz != A.class) { // < -- the only change to this method is the addtl check
            // Keep backing up the inheritance hierarchy.
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
              if (matches(method, memberName(method))) {
                members.add(method);
              }
            }
            if (clazz.getSuperclass() != null) {
              members(clazz.getSuperclass(), members);
            }
            for (Class<?> superIfc : clazz.getInterfaces()) {
              members(superIfc, members);
            }
        }
    }
}

Regardless of my implementation above, anytime I specify a resolver for my context, the context data is not being converted at all. Even when I specify the default resolver! So when my template is executed, the helpers which require that data to be set is not there, and a JS exception is rightfully thrown:

Map<String, Object> model = new HashMap<String, Object>();
model.put("required", required);

Context context = Context.newBuilder(model).resolver(CustomPropertyResolver.INSTANCE).build();

Handlebars handlebars = new Handlebars();
TalonResource helpersResource = 'path/to/helper.js';
handlebars.registerHelpers(FilenameUtils.getName(helpersResource), helpersResource.getInputStream());

Resource htmlResource = new Resource('path/to/template');
String htmlString = IOUtils.toString(htmlResource.getInputStream(), StandardCharsets.UTF_8);
Template htmlTemplate = handlebars.compile(new StringTemplateSource(FilenameUtils.getName('path/to/template'), 
String html = htmlTemplate.apply(context);

Even if I do the above but use the default Resolver:

Context context = Context.newBuilder(model).resolver(JavaBeanValueResolver.INSTANCE).build();

The same error is thrown.

It's only when I don't specify a resolver at all:

Context context = Context.newBuilder(model).build();

In that case the attributes being set on the context model are attempted to convert to JS (can see from breakpoints) and the UnsupportedOperationException is thrown.

It seems to me that at the minimum, specifying the default resolver should have the same behavior as not specifying a resolver at all.

janacm commented 5 years ago

Can you use a suffix/prefix to limit which files you want to pick up?