I want Thymeleaf to throw an exception if a variable used in a template is not found in the Context. It seems by default Thymeleaf will inject empty text in an HTML tag if the bound variable is not found in the Context. This seems dangerous as it essentially silently hides errors.
<div data-th-text="${amount}">Blah</div>
Code:
Context context = new Context();
// never set "amount" variable
Output:
<div></div>
I assumed there would be a "strict" mode where it would throw an exception with the variable name and additional context if the variable is not found in the Context. I've been searching stackoverflow and the API docs but cannot find anything like this. I thought of overriding the Context getVariable so that it does a containsVariable check, but I'm not sure if that has performance implications. Also, I cannot capture any metadata about where in the template it failed. Am I missing something obvious?
I'm using Thymeleaf as a standalone engine -- not as part of a web/spring app.
You can do this by taking advantage of th:assert. If this assertion is not satisfied then thymeleaf will throw an exception during document processing and print a relevant error to know why it failed.
So your code will be
<div th:assert="${amount} != null" data-th-text="${amount}">Blah</div>
If context variable amount is never set it would be null so then the assertion will fail which will produce the exception.
Related
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.
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:
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).
Create a class, let's call it DefaultObjectWrapperWithJSONSupport, that extends DefaultObjectWrapper. Your class should wrap JSONObject-s with JSONObjectAdapter.
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.
First let me say that using Struts2 + Freemarker is a real blast.
Yet there's something is driving me crazy, because I cannot understand why it happens. I ask here as maybe someone else has an idea to share about it.
I've got an action, with a property.
Say
private String myText;
Then I've got a setter and a getter:
public void setMyText(String myText)
{
this.myText = myText;
}
public String getMyText()
{
if (myText == null)
myText = "(empty)";
return this.myText;
}
The result (in struts.xml) is a freemarker result.
So in my Freemarker template there's a line like the following:
<p>The text is: ${myText}</p>
Now consider I'm calling the action without any text parameter: say the url is
http:localhost:8080/myapp/myaction
As the getter provides a default value, when the action is processed and the result passed to my template, the property is set to the default; so I get (html on the browser side)
<p>The text is: (empty)</p>
If I call my action with the parameter set, instead (I mean with something like:
http:localhost:8080/myapp/myaction?myText=hallo
) things go wrong. Freemarker fires the following exception:
Exception occurred during processing request: For "${...}" content:
Expected a string or something automatically convertible to string
(number, date or boolean), but this has evaluated to a
sequence+extended_hash (String[] wrapped into f.e.b.ArrayModel)
It seems that "myText" is found twice...
What am I doing wrong? Or, at least, is there anyone that can explain to me why it happens?
P.S.: it's really found twice; the following is a way to workaround the problem:
<#if myText?is_sequence>${myText[0]}<#else>${myText}</#if>
Yet it seems to me not viable to wrap every variable in that way.
P.P.S.: a further hint: in the freemarker template there's a call to another action some lines before. Something like:
<#s.action var="innerAction" name="getTable" namespace="/foo" />
If I comment the line above, everything works fine.
The myText could be a variable from the freemarker context, but if you want to use action property
<p>The text is: ${action.myText}</p>
Note, that action prefix is not required to access action properties. A property resolution method is applied when resolving freemarker variables:
Property Resoloution:
Your action properties are automatically resolved - just like in a
velocity view.
for example ${name} will result in stack.findValue("name"), which
generally results in action.getName() being executed.
A search process is used to resolve the variable, searching the
following scopes in order, until a value is found :
freemarker variables
value stack
request attributes
session attributes
servlet context attributes
And later you can read what objects are accessible from the context.
Objects in the Context:
The following variables exist in the FreeMarker views
req - the current HttpServletRequest
res - the current HttpServletResponse
stack - the current OgnlValueStack
ognl - the OgnlTool instance
This class contains useful methods to execute OGNL expressions against arbitary objects, and a method to generate a select list using
the <s:select> pattern. (i.e. taking the name of the list property, a
listKey and listValue)
struts - an instance of StrutsBeanWrapper
action - the current Struts action
exception - optional the Exception instance, if the view is a JSP exception or Servlet exception view
The error might be caused by searches from the value stack and returning something that you didn't expect depending on the structure of the stack at the moment of execution.
Adding a prefix to the variable to point out the exact location of the property should fix the redundancy in the code when searching in the value stack.
I have a spring bound form (modelAttribute) which displays the user information.
The user's telephone number is displayed in a formatted manner but a requirement is that the number is saved to the database without any signs.
So in the getter method of my user object I format the telephone number according to the rules and in the setter I put the code to remove the special signs.
The formatting part works fine, but setter part where I remove the signs does not seem to occur.
In my constructor I also did:
setTelephoneNumber(TelephoneNumber);
So the constructor also invokes the setter.
I'm using Spring 3.0.4 and Spring-mvc.
Any input on this issue and how to resolve it would be appreciated.
edit:
controller section:
model.addAttribute("user", user);
JSP (shortened it a bit but this is the gist. submitUrl is due to a portal environment:
<form:form action="${submitUrl}" modelAttribute="user">
<form:input path="telephoneNumber"/>
</form>
Model telephoneNumber setter:
if(!StringUtils.isBlank(telephoneNumber)){
this.telephoneNumber = telephoneNumber.replaceAll("[^0-9]", "");
} else{
this.telephoneNumber= "";
}
And I think so because the value lands in the database with the formatting I used. (spacing)
Even if it is not the correct answer to your question:
I strongly recommend to do the formating in an other way then by setter getter
Spring 3.0 provideds something they called "type conversion"
spring blog with example
spring reference "Validation, Data Binding, and Type Conversion"
Using this would be much more cleaner.
Back to your question:
Spring path binding: is it bound directly to the variable or does it invoke the constructor/setters?
As fare I understand the Java Doc and some code snippets, Spring uses BeanWrapper (BeanWrapperImpl) to set the values of Beans (#see Reference: 5.4 Bean manipulation and the BeanWrapper). And BeanWrapperImpl behaves like the reference said:
uses setter and getter to access "simple" values.
It is exactly like the reference said in section "5.4.1 Setting and getting basic and nested properties": For an expression "name":
Indicates the property name
corresponding to the methods getName()
or isName() and setName(..)
So at least this answer your question, so I assume that the cause for your problem is some thing else.
I'm getting the following error in ours logs:
Error looking up property "foo" in
object type "foo.bar". Cause: null
java.lang.reflect.InvocationTargetException
at sun.reflect.GeneratedMethodAccessor363.invoke(Unknown
Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(PropertyUtilsBean.java:1773)
I cannot for the life of me recreate it, I was wondering if anyone has any experience with this kind of problem with JSP/Java Bean. What I wanted to know was, will this prevent the user from getting the web page to show up?
I know this isn't a whole lot of information, but any advice could help.
Something on some page is trying to "navigate" into a bean instance (a Java object, that is), and it's trying to get to a property that isn't there on the bean in question.
<span id='name'>${fn:escapeXml(someBean.user.fullName)}</span>
If the bean "someBean" has no "user" property, of if the user object has no "fullName" property, you get an exception like that.
From what you're giving here, the only suggestion I have is to make sure that you have indeed a property called "foo" and to not have a period in "foo.bar". You cannot name your variables/objects using a period in the name. JSP will automatically go and look for a property called "bar" in "foo". Call it instead "fooBar".
Java is calling the getter method on the bean providing the property which is in turn throwing an exception. If you can see the target exception - that is the target of InvocationTargetException you will know what is causing this to fail.
Can Velocity be configure to fail (i.e. throw an Exception) when a $var is undefined.
Such a "fail-fast" strategy would help in our testing cycles.
In Velocity 1.6 you can add the following property to your velocity.properties
runtime.references.strict = true
Edit: Full list of configuration is available here: http://velocity.apache.org/engine/devel/configuration.html
You can register an event handler which tells Velocity to throw an exception on an undefined reference
You could switch to FreeMarker. It throws exceptions on missing fields, and, invalid types.
Not only that the exceptions thron are precise and readable. ' Missing field FOO at line 234 in BAR.ftl ' etc. etc.
I would absolutly recommend Freemarker over any other templating system.