How to convert properties to JSON the Spring MVC way? - java

I need my web service to serve me the messages.properties which contains localized texts in JSON format. I know that I can write my own parser to do that but where should I insert this logic in the Spring framework? Or is there a Spring infrastructure feature that already can do that?

You can use #PropertySource annotation on your class to load your property file into memory.
#Configuration
class MessagesConfiguration {
#Bean(name = "messageProperties")
public static PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("messages.properties"));
return bean;
}
#Resource(name="messageProperties")
private Properties messages = new Properties();
public Properties getMessages() {
return messages;
}
}
Properties.class is just a wrapper for Map<String, String> so you can convert it to JSON.

Related

Spring: Programmatically load properties file to an object

In a multi-tenant Spring Boot application, I'm trying to load configuration objects. Ideally, I'd like to load certain properties file into a configuration object programmatically. I'm looking for a simple way to load the configuration by passing a properties file and the final class to map it to. The following is just an example of what I'm trying to achieve.
Directory structure of the configurations:
config/
- common.properties
all_tenants_config/
- foo_tenant/
- database.properties
- api.properties
- bar_tenant/
- database.properties
- api.properties
Configuration POJOs:
class DatabaseProperties {
#Value("${database.url}")
private String url;
}
class APIProperties {
#Value("${api.endPoint}")
private String endPoint;
}
Configuration Provider:
#Singleton
class ConfigurationProvider {
private Map<String, DatabaseProperties> DB_PROPERTIES = new HashMap<>();
private Map<String, APIProperties> API_PROPERTIES = new HashMap<>();
public ConfigurationProvider(#Value(${"tenantsConfigPath"}) String tenantsConfigPath) {
for (File tenant : Path.of(tenantsConfigPath).toFile().listFiles()) {
String tenantName = tenant.getName();
for (File configFile : tenant.listFiles()) {
String configName = configFile.getName();
if ("database.properties".equals(configName)) {
// This is what I'm looking for. An easy way to load the configuration by passing a properties file and the final class to map it to.
DB_PROPERTIES.put(tenant, SPRING_CONFIG_LOADER.load(configFile, DatabaseProperties.class));
} else if ("api.properties".equals(configName)) {
API_PROPERTIES.put(tenant, SPRING_CONFIG_LOADER.load(configFile, API.class));
}
}
}
}
public currentTenantDBProperties() {
return DB_PROPERTIES.get(CURRENT_TENANT_ID);
}
public currentTenantAPIProperties() {
return API_PROPERTIES.get(CURRENT_TENANT_ID);
}
}
In short, is there a way in Spring that allows to map a properties file to an object without using the default Spring's configuration annotations.
Well, in this case you do not need any Spring's feature.
Spring is a bean container, but in this place you just new an object by yourself and put it on your map cache.
Step 1: decode property file to Java Properties Class Object
Step 2: turn your properties object to your target object, just use some utils like objectmapper
FileReader reader = new FileReader("db.properties"); // replace file name with your variable
Properties p = new Properties();
p.load(reader);
ObjectMapper mapper = new ObjectMapper();
DatabaseProperties databaseProperties = mapper.convertValue(p,
DatabaseProperties.class);

Spring Properties Decryption

We have mix of some legacy spring apps which are not yet migrated to spring-boot or spring cloud and also spring boot apps. I am working on creating a Spring component that will automatically decrypt spring properties when the environment is loaded if the property value is encrypted and has a prefix. The properties can be in .properties files(for legacy apps) or in .yaml files(newer spring boot apps).
The component should be able to decrypt any spring property regardless of the source, and should work with any spring version and not tied to spring boot.The component should also transparently decrypt properties. It should read passphrase from a property file, so the passphrase file needs to be loaded in the beginning.
We have our own ecrypt/decrypt and don't want to use jaspyt.
Things tried so far:
I liked this approach of creating an ApplicationListener, but this is tied up with spring boot(ApplicationEnvironmentPreparedEvent). With Spring events like ContextRefreshed or ContextStart , i don't see how can i get ConfigurableApplicationContext/ConfigurableEnvironment. Anyone created a Listener for encrypt/decrypt withouth spring boot/cloud?
I also created a custom ApplicationContextInitializer, and added it to web.xml's context-param, but this doesn't seems to be working. When i debug into it, i don't think it is loading/reading properties from my app.properties file.
#Component
public class DecryptingPropertyContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize( ConfigurableApplicationContext applicationContext ) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for ( PropertySource<?> propertySource : environment.getPropertySources() ) {
Map<String, Object> propertyOverrides = new LinkedHashMap<>();
decodePasswords( propertySource, propertyOverrides );
if ( !propertyOverrides.isEmpty() ) {
PropertySource<?> decodedProperties = new MapPropertySource( "decoded " + propertySource.getName(),
propertyOverrides );
environment.getPropertySources().addBefore( propertySource.getName(), decodedProperties );
}
}
}
private void decodePasswords(PropertySource<?> source, Map<String, Object> propertyOverrides) {
if ( source instanceof EnumerablePropertySource ) {
EnumerablePropertySource<?> enumerablePropertySource = (EnumerablePropertySource<?>) source;
for ( String key : enumerablePropertySource.getPropertyNames() ) {
Object rawValue = source.getProperty( key );
if ( rawValue instanceof String ) {
//decrypt logic here
propertyOverrides.put( key, decryptedValue );
}
}
}
}
}
Does anyone had to do something similar or has any better ideas ? Is there a way i can listen to application events and then process?
Appreciate your help
You can write your own PropertiesFactoryBean and override createProperties to decrypt encrypted values:
public class DecryptingPropertiesFactoryBean extends PropertiesFactoryBean {
#Override
protected Properties createProperties() throws IOException {
final Properties encryptedProperties = super.createProperties();
final Properties decryptedProperties = decrypt(encryptedProperties);
return decryptedProperties;
}
}
and a PropertySourcesPlaceholderConfigurer bean using these properties:
#Configuration
public class PropertiesConfiguration {
#Bean
public static DecryptingPropertiesFactoryBean propertyFactory() {
final DecryptingPropertiesFactoryBean factory = new DecryptingPropertiesFactoryBean();
final Resource[] propertyLocations = new Resource[] {
new FileSystemResource(new File("path/to/file.properties"))
};
factory.setLocations(propertyLocations);
return factory;
}
#Bean
public static Properties properties() throws Exception {
return propertyFactory().getObject();
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
final PropertySourcesPlaceholderConfigurer bean = new PropertySourcesPlaceholderConfigurer();
bean.setIgnoreResourceNotFound(true);
bean.setIgnoreUnresolvablePlaceholders(false);
bean.setProperties(properties());
return bean;
}
}

Spring application.yml to resource bundle

I have SpringBoot app properties in application.yml.
I am using a third party api, which accepts properties as ResourceBundle object only.
How can I convert my application.yml to a ResourceBundle object (preferably with auto-wiring)
Here I wanted to use default application.yml which is loaded by spring, so that I don't want to provide the file name.
Right now, I am thinking of below approach, but hope there's better one (off course, I will inject below as beans,
#Bean(name = "appPoperties")
public Properties yamlProperties() throws IOException {
YamlPropertiesFactoryBean bean = new YamlPropertiesFactoryBean();
bean.setResources(new ClassPathResource("application.yml"));
return bean.getObject();
}
ResourceBundleMessageSource rbs = new ResourceBundleMessageSource();
rbs.setCommonMessages(yamlProperties());
ResourceBundle rb = new MessageSourceResourceBundle(rbs, Locale.getDefault());

How to decrypt a property value when using PropertySourcesPlaceholderConfigurer

I have a properties file containing values like jdbc.password={enc}laksksjdjdj
Using JDK 1.7 and Spring 4.1.5 my configuration class looks like this
#PropertySources({
#PropertySource("classpath:application.properties"),
#PropertySource("classpath:env.properties")
})
#ComponentScan("com.acme")
#Configuration
public class SpringConfig
{
#Autowired
private ConfigurableEnvironment env;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer()
{
return new EncryptedPropertySourcesPlaceholderConfigurer();
}
}
What I am trying to achieve is to translate any values in my properties file from an encrypted value to the actual value. Some values will be encrypted and some won't. This is what I have attempted so far and when I place a breakpoint on convertProperties() the props argument is always empty. I can't make any sense of this because I can see that at this point this.environment is loaded with all the properties from the files.
public class EncryptedPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer
{
#Override
protected void convertProperties(Properties props)
{
Enumeration<?> propertyNames = props.propertyNames();
while (propertyNames.hasMoreElements()) {
String propertyName = (String) propertyNames.nextElement();
String propertyValue = props.getProperty(propertyName);
// String convertedValue = <translate the value>;
props.setProperty(propertyName, convertedValue);
}
}
#Override
protected Properties mergeProperties() throws IOException {
final Properties mergedProperties = super.mergeProperties();
convertProperties(mergedProperties);
return mergedProperties;
}
}
Has anyone been able to achieve this using PropertySourcesPlaceholderConfigurer? I have similar logic working in older applications using PropertyPlaceholderConfigurer but wanted to use the newer Spring configuration.
I noticed that Jasypt has a similar EncryptablePropertySourcesPlaceholderConfigurer but this behaves in the same fashion, so I'm confused.
For some reason using the #PropertySources annotation(s) does not work as expected.
When EncryptedPropertySourcesPlaceholderConfigurer.convertProperties() is called the autowired ConfigurableEnvironment does not contain the entries from my properties files. There are no references to my listed properties files at all.
To get around this limitation I removed the annotations and explicitly loaded these resources in in the config class. So it now reads like this
#ComponentScan("com.acme")
#Configuration
public class SpringConfig
{
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer()
{
EncryptedPropertySourcesPlaceholderConfigurer p = new EncryptedPropertySourcesPlaceholderConfigurer(new KeyfileDecryptor());
p.setLocations(
new ClassPathResource("application.properties"),
new ClassPathResource("env.properties")
);
return p;
}
#Bean
public DataSource dataSource(
#Value("${jdbc.driverclassname}") String driverclassname,
#Value("${jdbc.url}") String url,
#Value("${jdbc.username}") String username,
#Value("${jdbc.password}") String password)
{
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverclassname);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
This change introduced the issue that the autowired ConfigurableEnvironment will be null so I had to change to using #Value injection in order to configure my dataSource from the properties.
Everything now works as expected and EncryptedPropertySourcesPlaceholderConfigurer.convertProperties() receives all of the properties values from both files.
Written one sample application to do add support for using Encrypted values here
https://github.com/pushpendra-jain/spring-examples/tree/master/PropertySourcesPlaceholderEncrypter
Basically instead of trying to invoke its convertProperty method overrided its processProperties method and getting the job done and code surely does not change any existing behavior.
see https://github.com/pushpendra-jain/spring-examples/blob/master/README.md for steps.

How to use Dozer with Spring Boot?

I am working on a Spring Boot project. I just have annotation configuration. I want to include dozer to transform Entities to DTO and DTO to Entities. I see in the dozer website, they explain i have to add the following configuration in spring xml configuration file. Since i have not xml file but annotation configuration Java class, i don't know how to translate this into Java Configuration class.
<bean id="org.dozer.Mapper" class="org.dozer.DozerBeanMapper">
<property name="mappingFiles">
<list>
<value>dozer-global-configuration.xml</value>
<value>dozer-bean-mappings.xml</value>
<value>more-dozer-bean-mappings.xml</value>
</list>
</property>
</bean>
If someone could you give me an example it'll be very useful. Thanks
I think something like this should work:
#Configuration
public class YourConfiguration {
#Bean(name = "org.dozer.Mapper")
public DozerBeanMapper dozerBean() {
List<String> mappingFiles = Arrays.asList(
"dozer-global-configuration.xml",
"dozer-bean-mappings.xml",
"more-dozer-bean-mappings.xml"
);
DozerBeanMapper dozerBean = new DozerBeanMapper();
dozerBean.setMappingFiles(mappingFiles);
return dozerBean;
}
...
}
If you are using DozerBeanMapperFactoryBean instead of DozerBeanMapper you may use something like this.
#Configuration
public class MappingConfiguration {
#Bean
public DozerBeanMapperFactoryBean dozerBeanMapperFactoryBean(#Value("classpath*:mappings/*mappings.xml") Resource[] resources) throws Exception {
final DozerBeanMapperFactoryBean dozerBeanMapperFactoryBean = new DozerBeanMapperFactoryBean();
// Other configurations
dozerBeanMapperFactoryBean.setMappingFiles(resources);
return dozerBeanMapperFactoryBean;
}
}
This way you can import your mappings automatically. Than simple inject your Mapper and use.
#Autowired
private Mapper mapper;
Update with Dozer 5.5.1
In dozer 5.5.1, DozerBeanMapperFactoryBean is removed. So if you want to go with an updated version you need do something like below,
#Bean
public Mapper mapper(#Value(value = "classpath*:mappings/*mappings.xml") Resource[] resourceArray) throws IOException {
List<String> mappingFileUrlList = new ArrayList<>();
for (Resource resource : resourceArray) {
mappingFileUrlList.add(String.valueOf(resource.getURL()));
}
DozerBeanMapper dozerBeanMapper = new DozerBeanMapper();
dozerBeanMapper.setMappingFiles(mappingFileUrlList);
return dozerBeanMapper;
}
Now inject mapper as told above
#Autowired
private Mapper mapper;
And use like below example,
mapper.map(source_object, destination.class);
eg.
mapper.map(admin, UserDTO.class);
Just in case someone wants to avoid xml dozer file. You can use a builder directly in java. For me it's the way to go in a annotation Spring context.
See more information at mapping api dozer
#Bean
public DozerBeanMapper mapper() throws Exception {
DozerBeanMapper mapper = new DozerBeanMapper();
mapper.addMapping(objectMappingBuilder);
return mapper;
}
BeanMappingBuilder objectMappingBuilder = new BeanMappingBuilder() {
#Override
protected void configure() {
mapping(Bean1.class, Bean2.class)
.fields("id", "id").fields("name", "name");
}
};
In my case it was more efficient (At least the first time). Didn't do any benchmark or anything.

Categories