FreeMarker Complex Collection - spring

I've got a HashMap as Map<Long, List<Map<String, Object>>> typeAndKno, in the FreeMarker page, I get the content of this map like this:
<#list typeAndKno?keys as typeId>
${typeAndKno.get(typeId).get(0).get('TYPE_NAME')}
<#list typeAndKno.get(typeId) as kno>
${kno.get('KNOWLEDGE_ID')}
</#list>
</#list>
This code works fine in Struts2, but after moved to Spring MVC, the code fails. I finally changed the code to this:
<#list typeAndKno?keys as typeId>
${typeAndKno[typeId]?first['TYPE_NAME']}
<#list typeAndKno[typeId?string] as kno>
${kno['KNOWLEDGE_ID']}
</#list>
</#list>
What's the difference between these two pieces of code? Is there a way to make the first piece of code work in Spring MVC?

Update:
As of 2.3.22 there's a much easier and non-disruptive solution for this: configure FreeMarker so that ?api works, and then you can use the Java API of Map where the keys aren't String-s. See this FAQ entry or this answer for more details.
And Strut's FreeMarker setup is something that's strongly discouraged now. Of course, back then, when they did that, that was maybe the most reasonable workaround, but it isn't anymore for a while, and especially not since 2.3.22.
Old answer (outdated):
The way you see Java objects from templates depends on the ObjectWrapper used, which is a FreeMarker configuration setting. Based on your example, Struts uses a BeansWrapper with its default settings, while Spring possibly uses the DefaultObjectWrapper. So that causes the differences. I wouldn't recommend using either, because:
With BeansWrapper with its default settings Map keys mix with the method names, with method names having priority. Surely you can safely use myMap.get(key) to get around that, but myMap.get('foo') is just horrible compared to myMap.foo, which will only work as far you have no method called foo. Also ?keys will return a mixture of real keys and method names with it... it's a mess.
With DefaultObjectWrapper you can safely write myMap.foo, but you won't be able to get entities with non-string keys, because myMap[key] only support strings, and you don't have myMag.get(key) anymore.
So what I have usually used with FreeMarker was a bw = new BeansWrapper(); bw.setSimpleMapWrapper(true). With this, the methods of Map-s are not visible, just like with DefaultObjectWrapper, so you can use myMap.foo and myMap[key] safely. But if rarely you need to get something with a non-string key, you can use myMap(nonStringKey) (yes, with () instead of []). This last doesn't work with DefaultObjectWrapper. (Hopefully FreeMarker 2.4 will solve this mess with non-string keys nonsense, but it's not like it will be out anytime soon...)
So the next question is how to set the object wrapper with Spring. I'm not at home there. As far as I see, you have a FreeMarkerConfigurer bean which has a freemarkerSettings property, which is a Properties object that's eventually is passed to FreeMarker's Properties-based configuration API. So there you should be able add an object_wrapper property that refers to the class name of the ObjectWrapper to use (or it could just be beans to use the default BeansWrapper instance like maybe Struts does). Problem is, the property-based API is rather limited, and so you can't both create and configure (call setSimpleMapWrapper) a BeansWrapper there. You could do that in the spring configuration file of course, but I don't see a way to inject that into the FreeMarkerConfigurer, unless you create the whole freemarker.template.Configuration object as a bean, and inject that into the FreeMarkerConfigurer with the configuration property of it. So maybe the easiest workaround is extending BeansWrapper to override the default of simpleMapWrapper, and then referring to the class of that extending class via object_wrapper. (Just in case somebody reads this later, it's probable that FreeMarker 2.3.21 will extended the properties configuration API so that you can just set object_wrapper to BeansWrapper() { simpleMapWrapper = true }.)

Related

Quarkus - #ConfigMapping: built-in way to show all properties like "toString()", instead of manual building

As #ConfigMapping uses interfaces, there are no ways to implement toString(); I cannot view all values and nested values without a lot of manual work(reflection and switch case to deal with each type).
Any plan to support easy view of all levels of properties? Like a super class to inherit which handles this manual toString() like building?
In SmallRye config doc page I read this:
ToString#
If the config mapping contains a toString method declaration, the config mapping instance will include a proper implementation of the toString method.
But I added #Override String toString(); method everywhere, Quarkus just complains about cannot find property "to_string".
OK I found this issue which is implemented in this commit, which exactly adds the sentence I read into the doc; but still not very clear to me.
Adding a String toString() method in your #ConfigMapping will generate the expected toString() implementation.
This is only available starting from SmallRye Config 2.11.0 and Quarkus 2.12.0.Final, which came out just a few weeks ago. Previous versions will just try to resolve the method as a configuration property. From your description, it seems that is the case, so you may be using an older Quarkus version that does not support this feature yet.

Are Thymeleaf model attributes not allowed to start with 'is'?

So I'm writing a springboot application and came across a weird behaviour: If a property name start with is, for example: isIgnoreRequest thymeleaf won't find it but if i change it to ignoreRequest it works.
So my question is: Am I not allowed to have is at the beginning?
Here is some more context:
data class Response(val isIgnoreRequest: Boolean = false,
val name: String = StringUtils.EMPTY)
...
//This is how I add the attribute
//Info = Response object
redirectAttributes.addFlashAttribute(ATTRIBUTE_RESPONSE, info)
With the code above thymeleaf can't find the property:
Property or field 'isIgnoreRequest' cannot be found on object of type ... - maybe not public or not valid?
If I remove the is it works fine. Even though it sounds stupid I think the is is indeed my problem.
Yes, the model attributes can start with is. The issue isn't coming from thymeleaf, but from kotlin (nice job putting it in the tags). Let me explain:
When you reference a model attribute in thymeleaf, it looks for the getter/setter method of that attribute using the normal convention; in your example, for the attribute isIgnoreRequest, thymeleaf will look for the methods getIsIgnoreRequest and setIsIgnoreRequest.
What happens is kotlin generates the getters and setters for isXXX booleans in a different way than the standard, and thymeleaf fails when calling them with the standard syntax. You can see more on how kotlin generates the getters and setters for booleans in
https://github.com/sockeqwe/fragmentargs/issues/46 or
https://github.com/sockeqwe/sqlbrite-dao/issues/27
As to solve your issue, the best solution is probably naming your attributes in a different way so that kotlin doesn't mess with the standard for generating getter and setter methods (which IMO only complicates things unnecessarily; although some frameworks like JSF had a similar issue with isXXX booleans since forever).

Spring boot yaml property binding: collection types

I find Spring Boot's (or spring in general) handling of yaml collections to be a bit peculiar. Collections according to yaml specs should be written in .yaml files as:
myCollection: ['foo', 'bar']
or
myCollection:
- foo
- bar
But neither #Value("${myCollection}") annotation or Environment.getProperty("myCollection", String[].class) (also tried List.class) can read collection properties (returns null). The only method I know of that works is to use #ConfigurationProperties annotation described in spring boot docs.
The problem with #ConfigurationProperties annotation is that (a) it is too verbose if all I want is a single property and (b) it rely on bean injection to get an instance of the #ConfigurationProperties class. Under some circumstances, bean injection is not available and all we have is a reference to Environment (e.g: thru ApplicationContext).
In my particular case, I want to read some properties during ApplicationEnvironmentPreparedEvent event, since it happens before context is built, the listener has to be manually registered and therefore, no bean injection. Via the event argument, I can get a reference to Environment. So, I can read other properties but cannot read collections.
A couple of "solutions" I noted (quoted because I don't find them very satisfactory):
Specify collections in .yaml file as myCollection: foo, bar. But this is not ideal because, the format isn't really yaml anymore.
Read individual elements using an index, for example Environment.getProperty("myCollection[0]", String.class). Will require some not-so-elegant utility methods to read and put all elements into a List.
So, my questions is - What is a good way to read collection-type properties if I cannot use #ConfigurationProperties? Also curious why comma-separated format works but not yaml-style collections.
EDIT: corrected some typos
Quite Frankly Spring boot application.properties and application.yaml or application.yml is meant to load configuration properties.
The #ConfigurationProperties annotation is designed as an abstraction to hide the implementations of configuration properties and support both .properties and .yaml/.yml.
For yaml/yml however Spring uses org.yaml.snakeyaml.Yaml library underneath to parse the file and load it to a Properties object inside org.springframework.boot.env.YamlPropertySourceLoader and a Collection is mapped as a Set not an array or List. So you try doing the following;
Environment.getProperty("myCollection", Set.class)

Spring #Value and dynamically modifying values at runtime (PlaceholderConfigurerSupport)

This is a question about customisation of Spring's placeholder resolution in #Value annotations.
We initialise all properties in our app using #Value, normally from servlet context init params, eg:
web.xml
<context-param>
<param-name>app.some.param</param-name>
<param-value>SOME_VALUE</param-value>
</context-param>
Class file
#Value("${app.some.param:DEFAULT_VALUE}")
private String myParameter;
We actually don't use web.xml, we use Tomcat context files or even specify using vmargs.
What we'd like to support is dynamic changes to these properties at runtime. I want to somehow collect a list of property keys that are used in #Value and which also have a new annotation like #Dynamic. For properties marked as #Dynamic the bean may provide a corresponding setter, to do any re-initialisation when the property is modified.
I would then like to create a service that supports updating the property by key, eg:
void setProperty(String key, String value) {
// find all beans that have #Value and #Dynamic and set field or call setter
// NB - should support Spring type coercion, eg. string --> integer, boolean, list, etc.
}
I've been looking at the source for PlaceholderConfigurerSupport and BeanDefinitionVisitor. It seems I might be able to override PlaceholderConfigurerSupport.doProcessProperties and create a custom BeanDefinitionVisitor, but there is quite a lot of code to wade through. I wondered if anyone had looked at this before and found a solution.
I should note that there's more we ultimately want to do. We want to persist changed properties in a backing store, and use these instead of the configuration on startup if they've been modified. In this way we'd have a hierarchy of property sources: default in code, context/property files, peristed config that's been modified. We also want to provide a UI showing a set of all dynamic properties. You get the idea.
Thanks

How to add a custom ContentHander for JAXB2 support in Spring 3 (MVC)?

Scenario: I have a web application that uses Spring 3 MVC. Using the powerful new annotations in Spring 3 (#Controller, #ResponseBody etc), I have written some domain objects with #XML annotations for marhalling ajax calls to web clients. Everything works great. I declared my Controller class to have a return type #ResponseBody with root XML object - the payload gets marshalled correctly and sent to Client.
The problem is that some data in the content is breaking the XML compliance. I need to wrap this with CDATA when necessary. I saw a POST here How to generate CDATA block using JAXB? that recommends using a custom Content Handler. Ok, fantastic!
public class CDataContentHandler extends (SAXHandler|XMLSerializer|Other...) {
// see http://www.w3.org/TR/xml/#syntax
private static final Pattern XML_CHARS = Pattern.compile("[<>&]");
public void characters(char[] ch, int start, int length) throws SAXException {
boolean useCData = XML_CHARS.matcher(new String(c,start,length)).find();
if (useCData) super.startCDATA();
super.characters(ch, start, length);
if (useCData) super.endCDATA();
}
}
Using Spring MVC 3, how do I achieve this? Everything was "auto-magically" done for me with regards to the JAXB aspects of setup, Spring read the return type of the method, saw the annotations of the return type and picked up JAXB2 off the classpath to do the marshalling (Object to XML conversion). So where on earth is the "hook" that permits a user to register a custom Content Handler to the config?
Using EclipseLink JAXB implementation it is as easy as adding #XmlCDATA to the Object attribute concerned. Is there some smart way Spring can help out here / abstract this problem away into a minor configuration detail?
I know Spring isn't tied to any particular implementation but for the sake of this question, please can we assume I am using whatever the default implementation is. I tried the Docs here http://static.springsource.org/spring-ws/site/reference/html/oxm.html but it barely helped at all with this question from what I could understand.
Thanks all for any replies, be really appreciated.
Update:
Thanks for the suggested answer below Akshay. It was sufficient to put me on right tracks. Investigating further, I see there is a bit of history with this one between Spring version 3.05 and 3.2. In Spring 3.05 it used to be quite difficult to register a custom MessageConverter (this is really the goal here).
This conversation pretty much explains the thinking behind the development changes requested:
https://jira.springsource.org/browse/SPR-7504
Here is a link to the typically required class override to build a cusom solution:
http://static.springsource.org/spring/docs/3.1.0.M1/javadoc-api/org/springframework/http/converter/AbstractHttpMessageConverter.html
And the following Question on stack overflow is very similar to what I was asking for (except the #ResponseBody discussion relates to JSON and jackson) - the goal is basically the same.
Spring 3.2 and Jackson 2: add custom object mapper
So it looks like usage of , and overriding MarshallingHttpMessageConverter is needed, registering to AnnotationMethodHandlerAdapter. There is a recommended solution in link above to also get clever with this stuff and wrap the whole thing behind a custom defined Annotation.
I haven't yet developed a working solution but since I asked the questions, wanted to at least post something that may help others with the same sort of question, to get started. With all due respect, although this has all improved in Spring 3.2, it's still bit of a dogs dinner to get a little customization working... I really was expecting a one liner config change etc.
Rather than twist and bend Spring, perhaps the easiest answer for my particular issue is just to change JAXB2 implementation and use something like Eclipse Link JAXB that can do this out of the box.
Basically you need to create a custom HttpMessageConverter. Instead of relying on the Jaxb2RootElementHttpMessageConverter that spring uses by default.
Unfortunately, customizing one converter means you are telling spring that you will take care of loading all the converters you need! Which is fairly involved and can get complicated, based on whether you use annotations, component scanning, Spring 3.1 or earlier, etc.. The issue of how to add a custom converter is addressed here: Custom HttpMessageConverter with #ResponseBody to do Json things
In your custom message converter you are free to use any custom JAXB2 content handlers.
Another, simpler approach to solve your original problem would be to use a custom XmlJavaTypeAdapter. Create a custom implementation of javax.xml.bind.annotation.adapters.XmlAdapter to handle CDATA, in the marshal method wrap the return value with the cdata braces. Then in your mapped pojo, use the XmlAdapter annotation, pass it the class of your custom adapter and you should be done.
I have not myself implemented the adapter approach, so couldn't provide sample code. But it should work, and won't be a lot of work.
Hope this helps.

Resources