Search code examples
javafreemarker

Freemarker call static java method from java.lang.Integer


i have a question. I would like to call a static java method Integer.toHexString() in a freemaker template. In my code i implemented following lines:

....
cfg.setSharedVariable("Integer",BeansWrapper.getDefaultInstance().
                                getStaticModels().
                                get("java.lang.Integer");
....
);

My template looks like this

<#list Items.iterator() as var>
  <#assign hex = Integer.toHexString(var) />
  ${hex}
</#list>

But if i execute the code i get following error:

freemarker.core._TemplateModelException: An error has occurred when reading existing sub-variable "Integer"; see cause exception! The type of the containing value was: extended_hash+string (org.json.JSONObject wrapped into f.e.b.StringModel)

----
FTL stack trace ("~" means nesting-related):
    - Failed at: #assign hex = Integer.toHexString(var...  [in template "decToHex.ftl"...]

What am i doing wrong? Thanks.


Solution

  • Based on the error message, your data-model (aka. template context) is a org.json.JSONObject. FreeMarker doesn't know that API, but it discovers that JSONObject has a get(String) method, and tries to use that. Unfortunately, that get method doesn't behave as a Map-style get(key). FreeMarker first calls JSONObject.get("Integer") to see if the variable is in the data-model. If it isn't, it expects a null to be returned, and then will try to get it from higher scopes (like from the shared variables). But JSONObject.get(String) throws JSONException instead of returning null, which is what you see in the error log (if you look at the whole stack trace, JSONException should be there as the cause exception).

    To solve this, you need teach FreeMarker how to deal with JSONObject-s:

    1. Create a class, let's call it JSONObjectAdapter, which implements TemplateHashModelEx2 (or, the much simpler TemplateHashModel can be enough for basic use-cases). Inside that, when implementing TemplateHashModel.get(String), you must call JSONObject.has(key) to check if the key exists, and if not, return null, otherwise continue with calling JSONObject.get(key).

    2. Create a class, let's call it DefaultObjectWrapperWithJSONSupport, that extends DefaultObjectWrapper. Your class should wrap JSONObject-s with JSONObjectAdapter.

    3. Where you already configure FreeMarker (NOT before each template processing), specify the objectWrapper to be a DefaultObjectWrapperWithJSONSupport.

    There's a few non-obvious things with doing above properly, so I strongly recommend starting out from this example: https://freemarker.apache.org/docs/pgui_datamodel_objectWrapper.html#pgui_datamodel_customObjectWrappingExample

    Above linked example does the above 3 steps, but to support the Tupple class, instead of JSONObject. That's a TemplateSequenceModel, instead of TemplateHashModel, but otherwise what has to be done is very similar.