Spring-Boot Properties with nested Map - spring-boot

I have a application.properties file that needs some dynamic keys, which allow at least one level of nesting. In technical terms, the application starts and I am able to read those values, but the metadata doesn't seem to work correctly, because IntelliJ Ultimate is giving me some errors:
Cannot resolve property 'foo' in java.util.Map
# Static property
com.company.version=1.0
# Dynamic property, starting after products
com.company.products.first.foo=firstFoo
com.company.products.first.bar=firstBar
com.company.products.second.foo=SecondFoo
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
#Configuration
#ConfigurationProperties(prefix = "com.company")
public class Properties {
private String version;
private Map<String, Map<String, ArrayList<String>>> products = new HashMap<>();
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public Map<String, Map<String, ArrayList<String>>> getProducts() {
return products;
}
public void setProducts(Map<String, Map<String, ArrayList<String>>> products) {
this.products = products;
}
}
In additional I would like to know what the best practice would be to read those values. For "normal" static properties I used the Environment that I autowire in the constructor, but that is – as far as I know – unable to read the products map. Thats why I autowire my Properties class and use the getProducts function.
Thanks a lot for your help!

Might this work?
# Dynamic property, starting after products
com.company.products.first[foo]=firstFoo
com.company.products.first[bar]=firstBar
com.company.products.second[foo]=SecondFoo
If you want more than item in your array list do:
com.company.products.first[foo]=firstFoo,secondFoo
However, I've come massively unstuck in the past using deeply nested maps of maps. Maybe if you can declare a concrete class instead?
Also, I'd suggest using interfaces rather than concrete collections (e.g. List rather than ArrayList, or maybe even a Set to prevent duplicates?)

Related

Where does the filter for Ehcache 3 simple web page caching call the cache?

I am trying to cache a simple web page in Ehcache. Thanks to some help from another SO post I discovered that I need to implement my own filter based on Ehcache 2 code. When I look at the filter I don't understand it. Where does it ever call the cache to return a value? Here is my implementation (quite possibly wrong):
package com.sentiment360.pulse.cache;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.Element;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.Configuration;
import static org.ehcache.config.builders.CacheManagerBuilder.newCacheManager;
import org.ehcache.core.Ehcache;
import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;
import org.ehcache.xml.XmlConfiguration;
import javax.servlet.http.HttpServletRequest;
public class SimplePageCachingFilter implements CachingFilter {
public static final String DEFAULT_CACHE_NAME = "SimplePageCachingFilter";
private Logger LOG = Logger.getLogger(this.getClass().getName());
private String cacheName="basicCache";
protected String getCacheName() {
if (cacheName != null && cacheName.length() > 0) {
LOG.log(Level.INFO,"Using configured cacheName of {}.", cacheName);
return cacheName;
} else {
LOG.log(Level.INFO,"No cacheName configured. Using default of {}.", DEFAULT_CACHE_NAME);
return DEFAULT_CACHE_NAME;
}
}
protected CacheManager getCacheManager() {
return CacheManager.getInstance();
}
protected String calculateKey(HttpServletRequest httpRequest) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(httpRequest.getMethod()).append(httpRequest.getRequestURI()).append(httpRequest.getQueryString());
String key = stringBuffer.toString();
return key;
}
}
See in the super class.
But you do implements CachingFilter ?! Where is that interface? It does look like you were trying to "copy" the previous Ehcache's SimplePageCachingFilter, right? You would also need to port that abstract super class (and maybe read a little about javax.servlet.Filter, in case these aren't entirely clear...)
Now, you may also want to ping the dev team on the Ehcache Dev Google group about this. They should be able to provide pointers and then help with the implementation. Looks like a good idea for a future pull request! :)

Instantiate a field level HashMap in JCodeModel

I want to declare and instantiate a HashMap in one go in JCodeModel.
I do:
jc.field(JMod.PRIVATE, HashMap.class, "initAttributes");
which declares it but doesn't instantiate it. How do I instantiate it?
Thanks
In the simplest case, you can just append the initialization directly to your creation of the field:
jc.field(JMod.PRIVATE, HashMap.class, "initAttributes")
.init(JExpr._new(codeModel.ref(HashMap.class)));
Some further hints:
Considering that you should usually program to an interface, it is a good practice to declare the variable using a type that is "as basic as possible". You should hardly ever declare a variable as
private HashMap map;
but basically always only as
private Map map;
because Map is the interface that is relevant here.
You can also add generics in JCodeModel. These usually involve some calls to narrow on certain types. It is a bit more effort, but it will generate code that can be compiled without causing warnings due to the raw types.
An example is shown here. (It uses String as the key type and Integer as the value type of the map. You may adjust this accordingly)
import java.util.HashMap;
import java.util.Map;
import com.sun.codemodel.CodeWriter;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JMod;
import com.sun.codemodel.writer.SingleStreamCodeWriter;
public class InitializeFieldInCodeModel
{
public static void main(String[] args) throws Exception
{
JCodeModel codeModel = new JCodeModel();
JDefinedClass definedClass = codeModel._class("com.example.Example");
JClass keyType = codeModel.ref(String.class);
JClass valueType = codeModel.ref(Integer.class);
JClass mapClass =
codeModel.ref(Map.class).narrow(keyType, valueType);
JClass hashMapClass =
codeModel.ref(HashMap.class).narrow(keyType, valueType);
definedClass.field(JMod.PRIVATE, mapClass, "initAttributes")
.init(JExpr._new(hashMapClass));
CodeWriter codeWriter = new SingleStreamCodeWriter(System.out);
codeModel.build(codeWriter);
}
}
The generated class looks as follows:
package com.example;
import java.util.HashMap;
import java.util.Map;
public class Example {
private Map<String, Integer> initAttributes = new HashMap<String, Integer>();
}

Deserialise JSON fields based on user role

I have some fields in a model that I only want to be returned when the logged in user has the role ROLE_ADMIN. I can use #JsonIgnore but that hides it for everyone. How can I make it hide dynamically?
You should use Jackson Json Views technology to acheive it - it allows to choose a different set of fields to be serialized programatically. It is also supported by Spring
Consider you have a class Model with two properties: commonField which should be available for everyone and secretField which should be available only for certain users. You should create an hierarchy of views (any classes would work) and specify which field is available in which view using #JsonView annotation
package com.stackoverflow.jsonview;
import com.fasterxml.jackson.annotation.JsonView;
public class Model {
public static class Public {}
public static class Secret extends Public {}
#JsonView(Public.class)
private String commonField;
#JsonView(Secret.class)
private String secretField;
public Model() {
}
public Model(String commonField, String secretField) {
this.commonField = commonField;
this.secretField = secretField;
}
public String getCommonField() {
return commonField;
}
public void setCommonField(String commonField) {
this.commonField = commonField;
}
public String getSecretField() {
return secretField;
}
public void setSecretField(String secretField) {
this.secretField = secretField;
}
}
Now you can specify the view you want to use in concrete ObjectMapper
package com.stackoverflow.jsonview;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*/
public class ModelTest {
#Test
public void testSecretField() throws JsonProcessingException {
Model model = new Model("commonField","secretField");
assertEquals("{\"commonField\":\"commonField\",\"secretField\":\"secretField\"}", new ObjectMapper().writerWithView(Model.Secret.class).writeValueAsString(model));
assertEquals("{\"commonField\":\"commonField\"}", new ObjectMapper().writerWithView(Model.Public.class).writeValueAsString(model));
}
}
I am not sure if you can use declaratie approach to make spring choose the right view based on user role out of the box, so probably you will have to write some code like this:
#RequestMapping("/data")
public String getData(HttpServletRequest request) {
Model model = service.getModel();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper = request.isUserInRole("ROLE_ADMIN") ? objectMapper.writerWithView(Model.Secret.class) : objectMapper.writerWithView(Model.Public.class);
return objectMapper.writeValueAsString(model);
}
I solved this after literally a full month of trying various things. I'm working with Spring 4.3.1 and boot, with data being returned in Hal using a pagedrepository.
extend RepositoryRestMvcConfiguration as MyRepositoryRestMvcConfiguration and add #Configuration to the class, make sure your starter class has #EnableWebMvc
add this to MyRepositoryRestMvcConfiguration- extend TypeConstrainedMappingJackson2HttpMessageConverter as MyResourceSupportHttpMessageConverter
add this to MyRepositoryRestMvcConfiguration
#Override
#Bean
public TypeConstrainedMappingJackson2HttpMessageConverter halJacksonHttpMessageConverter() {
ArrayList<MediaType> mediaTypes = new ArrayList<MediaType>();
mediaTypes.add(MediaTypes.HAL_JSON);
if (config().useHalAsDefaultJsonMediaType()) {
mediaTypes.add(MediaType.APPLICATION_JSON);
}
int order = config().useHalAsDefaultJsonMediaType() ? Ordered.LOWEST_PRECEDENCE - 10
: Ordered.LOWEST_PRECEDENCE - 1;
TypeConstrainedMappingJackson2HttpMessageConverter converter = new MyResourceSupportHttpMessageConverter(
order);
converter.setObjectMapper(halObjectMapper());
converter.setSupportedMediaTypes(mediaTypes);
converter.getObjectMapper().addMixIn(Object.class, MyFilteringMixin.class);
final FilterProvider myRestrictionFilterProvider = new SimpleFilterProvider()
.addFilter("MyFilteringMixin", new MyPropertyFilter()).setFailOnUnknownId(false);
converter.getObjectMapper().setFilterProvider(myRestrictionFilterProvider);
return converter;
}
Create an empty Mixin
package filters;
import com.fasterxml.jackson.annotation.JsonFilter;
#JsonFilter("MyFilteringMixin")
public class MyFilteringMixin {}
Create an empty Mixin
create class MyPropertyFilter extending SimpleBeanPropertyFilter and override adapt this method
serializeAsField(Object, JsonGenerator, SerializerProvider, PropertyWriter)you need to call either super.serializeAsField(pPojo, pJgen, pProvider, pWriter) or pWriter.serializeAsOmittedField(pPojo, pJgen, pProvider) depending on whether you wish to include or discard this particular field.
I added an annotation to the particular fields I wanted to alter and interrogated that annotation when deciding which of these two to call. I injected the security role and stored permitted roles in the annotation.
This alters what Hal shares out to the caller, not what Hal is holding in its repository. Thus you can morph it depending on who the caller is.

How to use #ConfigurationProperties for list-valued or array-valued mapping?

I've got a project where I'd like to configure String-keyed "groups"
of String-keyed "commands" of arrays of Strings. That is, I'd like to
be able to express something like the following in the config.yml, and
consume via #ConfigurationProperties(prefix="config.base"):
---
config:
base:
"bin group":
- "Directory Listing": ["/bin/ls", "-la"]
- "Server Date/Time": ["/bin/date", "-u"]
"usr/bin group":
- "Find .txt Files": ["/usr/bin/find", ".", "-name", "*.txt"]
"usr/local/bin group":
- "Tree Listing": ["/usr/local/bin/tree"]
Ideally, I'd want the #ConfigurationProperties object to be a LinkedHashMap<String, LinkedHashMap<String, String[]>>
but I can't figure out how to do that. Or anything reasonably close to that.
The closest I've gotten is like the following:
package us.w7tek.bug;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import javax.annotation.PostConstruct;
import java.util.LinkedHashMap;
#EnableConfigurationProperties
#SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
#ConfigurationProperties("someConfig")
#Bean
public ExternalizedConfig externalizedConfig() { return new ExternalizedConfig(); }
public static class ExternalizedConfig extends LinkedHashMap<String, String[]> {
// oops, #ConfigurationProperties ends up putting LinkedHashMap<String, String> in the values of the top-level mapping,
// and that second-level LinkedHashMap has keys that could have come from Integer#toString
}
#Controller
public static class ControllerThatConsumesConfig {
private static final String A_KEY_THAT_COULDNT_BE_A_PROPERTY_NAME = "this == config cannot be expressed as a bean with properties, because the keys cannot be made into Java language identifiers for bean property setters and getters";
#Autowired
ExternalizedConfig config;
#PostConstruct
void init() {
String[] strings = config.get(A_KEY_THAT_COULDNT_BE_A_PROPERTY_NAME); // ClassCastException occurs here
// doesn't have to occur in #PostConstruct, that was just a convenient place for my demo.
}
}
}
with the following example application.yml in the project:
---
someConfig:
"this is a key": ["this", "value", "is", "not", "an", "String[]"]
"this is another key": ["it", "is", "deserialized", "as", "LinkedHashMap", "having", "keys", "like", "\"0\"", "and", "\"1\"", "etc."]
"this == config cannot be expressed as a bean with properties, because the keys cannot be made into Java language identifiers for bean property setters and getters": ["thereby", "subverting", "Java's", "static", "typing", "and", "resulting", "in", "ClassCastException", "at", "runtime"]
As indicated in the comment, that code explodes when the Spring Boot #ConfigurationProperties binder creates an object of type LinkedHashMap<String, LinkedHashMap<String, LinkedHashMap<String, String>>> and places it in the config field. Of course, as soon as any method accesses config according to its statically-declared type, a ClassCastException occurs. I'm not sure whether to believe this is a bug with the property binder code used by #ConfigurationProperties, or just my gross misunderstanding. I think that the above code is the simplest possible thing that exhibits the problem. Also found at https://github.com/w7tek/demo-configproperties-bug.git, in case anyone wants to compile and run to see the stack trace.
Does anyone have any examples of #ConfigurationProperties with collections? I can see the way forward from where I'm at, by simply matching the declared type to the actual type Spring has deserialized, but that ends up being significantly less convenient to use. I'd really like to get the innermost values of this config as List<> or array type, if possible, but I can't figure out how.
Here is what you needed:
don't use tab, use 2 space for each inner element.
config:
base:
"bin group":
"Directory Listing": ["/bin/ls", "-la"]
"Server Date/Time": ["/bin/date", "-u"]
"usr/bin group":
"Find txt Files": ["/usr/bin/find", ".", "-name", "*.txt"]
"usr/local/bin group":
"Tree Listing": ["/usr/local/bin/tree"]
and here is the Configuration class:
#Configuration
#ConfigurationProperties(prefix = "config")
public class Conf_Test {
private LinkedHashMap<String, LinkedHashMap<String, List<String>>> base;
public LinkedHashMap<String, LinkedHashMap<String, List<String>>> getBase() {
return base;
}
public void setBase(LinkedHashMap<String, LinkedHashMap<String, List<String>>> base) {
this.base = base;
}
}
Apperantly, you cannot use "." inside the map key, it just cut the key so I removed the one in "Find .txt Files" key. Also, spring-boot doesn't support auto-groving arrays inside map, so String[] is not possible for now but list is working.

Gson.toJson() and inheriting from a generic class

I have the following class:
public static class TestSomething {
Integer test;
public TestSomething(Integer test) {
this.test = test;
}
// getter and setter for test
}
Ok, now create a collection of this class and serialize it with Gson:
Collection<TestSomething> tests = Arrays.asList(
new TestSomething(1),
new TestSomething(2),
new TestSomething(3)
);
String json = new Gson().toJson(tests, new TypeToken<Collection<TestSomething>>() {}.getType());
After this, the String json is set to
[{"test":1},{"test":2},{"test":3}]
Which is great.
But now, all of my model classes inherit from a generic type Identifiable<T> which provides just two methods T getId() and void setId(T). So I change the TestSomething-class from above to
public static class TestSomething extends Identifiable<Long> {
// same as above
}
When I try to put this through Gson.toJson(), Gson ends up with the following Exception:
java.lang.UnsupportedOperationException: Expecting parameterized type, got class path.to.TestSomething.
Are you missing the use of TypeToken idiom?
See http://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Gener
at com.google.gson.TypeInfoFactory.getActualType(TypeInfoFactory.java:97)
...
So, what do I have to do to get this work?
I don't know the answer, but I know that generic type resolution is a tricky thing to get right: specifically full type resolution from interface with type parameter T up through to generic parameter declaration (T=Long). In these cases it is not enough to check for Method object's parameters but also resolve generic type parameters. This is most likely what causes issues; it may be a bug in Gson.
Since you are serializing things, perhaps you could just omit any type declarations? Although your TypeToken is correct for the use case, maybe it confuses Gson.
But just in case you could not make Gson work with this, I know that of other JSON libraries Jackson can handle such cases correctly.
Perhaps this issue was resolved in one of the Gson releases newer than what the original questioner was using, because the example in the original question now serializes as expected.
// output:
// [{"test":1},{"test":2},{"test":3}]
import java.util.Arrays;
import java.util.Collection;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
public class Foo
{
public static void main(String[] args)
{
Collection<TestSomething> tests = Arrays.asList(
new TestSomething(1),
new TestSomething(2),
new TestSomething(3));
String json = new Gson().toJson(tests, new TypeToken<Collection<TestSomething>>() {}.getType());
System.out.println(json);
}
}
class TestSomething extends Identifiable<Long>
{
Integer test;
public TestSomething(Integer test)
{
this.test = test;
}
#Override
Long getId()
{
return new Long(test);
}
#Override
void setId(Long t)
{
this.test = (int)(t.longValue());
}
}
abstract class Identifiable<T>
{
abstract T getId();
abstract void setId(T t);
}

Resources