Question

I am wondering, is it possible to achieve some sort of property proxying / scope inheritance in freemarker template model? Consider this:

public class A {
    String aProperty;
}

public class B {
    String bProperty;
    A parent;
}

So in my template I could write something like ${obj.aProperty} and it would either retrieve aProperty directly from obj if it has one (obj is an instance of A), or retrieve parent.aProperty if it has none (obj is an instance of B).

The only thing that I have come up to is to use custom BeansWrapper, but maybe there are other, less invasive solutions? Any help would be greatly appreciated.

Update

Solution with Proxy.

public interface TemplateModelObject {
    String getaProperty();
}

public class BProxy implements InvocationHandler {
    private B target;

    public BProxy(B target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getDeclaringClass().equals(TemplateModelObject.class)) {
            return method.invoke(target.getParent(), args);
        } else {
            return method.invoke(target, args);
        }
    }
}

Assume A implements TemplateModelObject.

public  class CustomWrapper extends BeansWrapper {
    public TemplateModel wrap(Object object) throws TemplateModelException {
        if (object != null && B.class.isAssignableFrom(object.getClass())) {
            try {
                return super.wrap(Proxy.newProxyInstance(TemplateModelObject.class.getClassLoader(), new Class[]{TemplateModelObject.class}, new BProxy((B) object)));
            } catch (Exception e) {
                throw new TemplateModelException(e);
            }
        } else {
            return super.wrap(object);
        }
    }
}

I will also need an interface declaring specific members of B so they can be accessed through proxy. But the main question is: won't CustomWrapper break something in Freemarker?

Was it helpful?

Solution

I don't think you can do this without a custom ObjectWrapper. After all, this is why the whole ObjectWrapper concept exists. The problem with this used to be that pretty much everyone wants the functionality of BeansWrapper and thus somehow extend that, and it often turns out that that's not so easy. Like here, my first guess is that you should override BeanModel.get(String) so that when it would return null, instead it takes the parent object, wraps it with BeanModel.wrapper, then delegates to the get(String) of that wrapped object. But it's not useful to override get in BeansWrapper directly, because it's only the superclass of the TemplateModel classes the BeansWrapper creates. Instead, you will have to override get in all subclasses of BeanModel, and then override getModelFactory in BeansWrapper (if memory server well...) so that BeansWrapper will create instances of your TemplateModel classes instead of the standard ones.

OTHER TIPS

You can access it as like

Using instance of A

${obj.aProperty}

with instance of B ,

${obj.bProperty}

${obj.parent.aProperty} // if parent is null then you will get error

// so first check parent object is not null, then access it property

<#if obj.parent?has_content >
${obj.parent.aProperty}
</#if>

This will work for your basic example. The template contains a function that you pass the object to and the name of the property. It checks if the property exists, if not it get the same property form the parent. You would need to extend it for further to check if a parent exists or if you need more than one level.

A.java

package com.cluboramic.web.controllers;

public class A {

    private String property;

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }

}

B.java

package com.cluboramic.web.controllers;

public class B {

    private String property;

    private A parent;

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }

    public A getParent() {
        return parent;
    }

    public void setParent(A parent) {
        this.parent = parent;
    }

}

Object setup

A a = new A();
a.setProperty("This is A");

B b1 = new B();
b1.setParent(a);

B b2 = new B();
b2.setProperty("This is B2");
b2.setParent(a);

Template

a = ${proxyProp(a "property")},

b1 = ${proxyProp(b1 "property")},

b2 = ${proxyProp(b2 "property")}

<#--
    obj is the object to access
    property is the name of the property
-->
<#function proxyProp obj property>
    <#if obj[property]??>
        <#return obj[property]>
    <#else>
        <#return obj.parent[property]>
    </#if>
</#function>

Output

a = This is A, b1 = This is A, b2 = This is B2 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top