Configuration file depending on environment variables in Java EE - java

I have created a REST API with Jersey and I'm trying to figure out how to handle configuration files depending on the environment.
The main problem is that my web application is deployed on a Linux server so I cannot have the same paths between my development env on Windows and my staging and prods envs on Linux.
My configuration file is a basic XML file. Is there a way to add environment variables to that XML file and tell Java to replace these env var at runtime to their corresponding values? If it's possible, it would be possible to add an env var corresponding to the root path of all the paths in the config file that would be changed depending on a single env var.
Is there a better way to handle config files depending on environments?

There are many ways to externalize your configuration. You can take an example from Spring Boot, which currently supports 14 sources of configuration (cf. Externalized Configuration).
In your case you probably want to support points 5 to 9, which can easily be implemented in a JAX-RS application. For example you can use something like this:
#Path("/hello")
public class Hello {
#Context
private ServletConfig servletConfig;
public static String getProperty(ServletConfig config, String key) {
// ServletConfig
String value = config.getInitParameter(key);
// ServletContext
if (value == null) {
value = config.getServletContext().getInitParameter(key);
}
// JNDI
if (value == null) {
try {
value = InitialContext.doLookup("java:comp/env/" + key);
} catch (ClassCastException | NamingException e) {
// No JNDI
}
}
// Java system property
if (value == null) {
value = System.getProperty(key);
}
// OS environment variable
if (value == null) {
value = System.getenv(value);
}
return value;
}
#GET
public String sayHello(#PathParam("id") String who) {
return getProperty(servletConfig, "who");
}
}
You can also stick to one source of properties like ServerContext init parameters and override them in a context.xml file using Java system properties or OS Environment properties: Tomcat replaces the placeholders ${variable_name} in all its XML config files.

Related

I can not test singleton without load() method

When I start the application, I need to load properties from different sources: war, file system, database, and JVM. I need to load properties once and use them within running my application. I do not need to refresh it. I don't have DI - it is a simple java application with singletons. I decide to create AppProperties singleton and load properties when starting the application. It is the best solution by the current time for me(I hope somebody makes the best solution). It is my Singleton:
import java.io.InputStream;
import java.util.Properties;
public class AppProperties {
private static AppProperties instance;
private Properties propertiesFromWar;
private Properties propertiesFromFile;
private Properties propertiesFromDB;
private AppProperties() {
propertiesFromWar = new Properties();
try {
propertiesFromWar.load(getPropertiesAsInputStreamFromWar());
propertiesFromFile.load(getPropertiesAsInputStreamFromFile());
propertiesFromDB.load(getPropertiesAsInputStreamFromDB());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private InputStream getPropertiesAsInputStreamFromDB() {
//some implementation
return null;
}
private InputStream getPropertiesAsInputStreamFromFile() {
//some implementation
return null;
}
private InputStream getPropertiesAsInputStreamFromWar() {
return getClass().getResourceAsStream("META-INF/application.properties");
}
public static AppProperties getInstance() {
if (instance == null) {
instance = new AppProperties();
}
return instance;
}
public String getProperty(String key) {
String value;
value = System.getProperty(key);
if (value == null) {
value = propertiesFromDB.getProperty(key);
if (value == null) {
value = propertiesFromFile.getProperty(key);
if (value == null) {
value = propertiesFromWar.getProperty(key);
}
}
}
return value;
}
}
But I do not understand, How can I use it in tests. Because I hardcode paths for aplication.properties files. And when I will create this instance in the tests, I will create AppProperties with real properties.
I tried to add a public method like load(filePath). But with this method, it will be not a singleton. If somebody will call this method in another place of application - my singleton will be reloaded with new data. Now I have 2 problems.
If I add load() method - it will be dangerous for reloading data. But I can use it in tests.
If I do not add this method - I can not test it.
P.S I read this article The Little Singleton
But I do not understand some moments. If I have singleton with private constructor, I can not extend it like in article.
In your test resources directory, create META-INF directory. Here create a file application.properties and add some properties for testing purposes in it.
Make sure the above directory is in the classpath when you will run the tests. This way, when getPropertiesAsInputStreamFromWar() is called, it will look for META-INF/application.properties in the classpath.
Being tests allow specifying JVM launch arguments, this can be "solved" pretty easily.
This also adds some flexibility.
java -DpropertiesPath="..." -jar yourJar.jar
And, adapting your code
private InputStream getPropertiesAsInputStreamFromWar() {
final String propertiesPath = Objects.requireNonNull(System.getProperty("propertiesPath"));
return getClass().getResourceAsStream(propertiesPath);
}
Instead of requireNonNull, you could use a default value, e.g.
META-INF/application.properties.

How to access Karate config parameters in JUnit tests?

Is there a way to access the config parameters from karate-config.js within JUnit tests?
Example:
karate-config.js
function fn() {
var env = karate.env; // get java system property 'karate.env'
karate.log('karate.env system property was:', env);
if (!env) {
env = 'dev'; // a custom 'intelligent' default
}
var config = { // base config JSON
appId: 'my.app.id',
appSecret: 'my.secret',
someUrlBase: 'https://some-host.com/v1/auth/',
anotherUrlBase: 'https://another-host.com/v1/'
};
if (env == 'stage') {
// over-ride only those that need to be
config.someUrlBase = 'https://stage-host/v1/auth';
} else if (env == 'e2e') {
config.someUrlBase = 'https://e2e-host/v1/auth';
}
// don't waste time waiting for a connection or if servers don't respond within 5 seconds
karate.configure('connectTimeout', 5000);
karate.configure('readTimeout', 5000);
return config;
}
MyTest.java
public class MyTest {
#Test
public void test() {
// How to access e.g. config.appId?
}
}
But why !?
There are multiple ways, but first - maybe you are over-engineering things and note that it is possible to read a *.properties file in Karate: properties.feature
You can also create a feature file with a single, empty Scenario - and call it from the Java API: https://github.com/intuit/karate#java-api
Map<String, Object> result = Runner.runFeature('classpath:foo.feature', null, true);
Which will give you the values of config in the returned Map.
if you need to call external javascript functions from java code I suggest you take a look at this

How to replace a property in the deployment.properties of tomcat temporarily from java method?

I have a method that consumes a rest service. The URL for this rest service is taken from deployment.properties of tomcat using org.springframework.core.env.Environment
#PropertySource("file:${catalina.home}/conf/deployment.properties")
public class OriginalService{
#Autowired
private Environment env;
public void originalMethod(){
String endPoint = env.getProperty("rest.url");
.
.
}
}
Though I own that original service, I don't want to change anything in that project. I wish to call this method as it is from another java project but, just replace the URL in the above line, so that the method consumes my dummy service instead of original one.
Both the projects are deployed on the same tomcat server.
Is there any way I can replace the rest.url property in the deployment.properties of tomcat temporarily from a java method?
Rather than updating the original file, it can be altered while loading the properties. It can be done by overriding the PropertyPlaceHolderConfigurer class as follows.
public class MyPropertyConfigurer extends PropertyPlaceHolderConfigurer{
protected void convertProperties(Properties props){
Enumeration<?> propertyNames = props.propertyNames();
while(propertyNames.hasMoreElements()){
String propName = (String)propertyNames.nextElement();
String propValue = (String)props.getProperty(propName);
if(propName.indexOf("rest.url") != -1){
setPropertyValue(props,propName,propValue);
}
}
}
private void setPropertyValue(Properties props,String propName,String propValue){
String newRestUrl = "<your local url>";
props.setProperty(propName,newRestUrl);
}
}
MyPropertyConfigurer can be registered as any other bean. I think, this is much more cleaner way.

"Correct" way to lookup Cloud Service Configurations from Java in AEM 6

AEM's Cloud Services API isn't terribly well documented, and provides a number of somewhat awkward to use methods that can be used to find a given Configuration from a resource. Below are a few examples I've found "in the wild" that do this.
Method 1: Getting all Configurations and iterating them until a named config is found:
public static Configuration findConfiguration(ConfigurationManager configurationManager, Resource resource, String serviceName) {
Iterator configurations = configurationManager.getConfigurations(resource);
while (configurations.hasNext()) {
Configuration configuration = (Configuration) configurations.next();
if (serviceName.equals(configuration.get("cq:cloudservicename", ""))) {
return configuration;
}
}
return null;
}
Method 2: Lookup all the cq:cloudserviceconfigs that apply to the resource and ask for the one you want:
public static Configuration findConfiguration(ConfigurationManager configurationManager, Resource resource, String serviceName) {
HierarchyNodeInheritanceValueMap pageProperties =
new HierarchyNodeInheritanceValueMap(resource);
String[] allServices =
pageProperties.getInherited("cq:cloudserviceconfigs", new String[0]);
return configurationManager.getConfiguration(serviceName, allServices);
}
Method 3: Similar to method 2, but looks like it'll only work if the requested config is also defined on the first parent that has a cq:cloudserviceconfigs property?
public static Configuration findConfiguration(ConfigurationManager configurationManager, Resource resource, String serviceName) {
Resource configurationResource =
configurationManager.getConfigurationResource(resource);
if (configurationResource != null) {
String[] cloudServiceConfigs = configurationResource.adaptTo(Page.class)
.getProperties().get("cq:cloudserviceconfigs", String[].class);
if (cloudServiceConfigs != null && cloudServiceConfigs.length > 0) {
return configurationManager.getConfiguration(serviceName, cloudServiceConfigs);
}
}
return null;
}
I find it odd that whoever designed this interface didn't simply add a method like Configuration ConfigurationManager#getService(Resource resource, String serviceName) - the hoops you have to jump through to achieve what seems like a common use case are frustrating, but that's how it is apparently.
So my question is this:
Is there a better API or library to use than ConfigurationManager for looking up a specific cloud service config for a page or resource?
If not, what is good, robust, performant way of using ConfigurationManager to do this?
Thanks.

Should i store file names in Java Enum or properties file

I want to store file names, which keep on changing as the new files get added. I am looking for a minimum change in server code later when there is a need to support a new 'file' The thought I have is to store them either in properties file or as Java enum, but still thinking which is a better approach.
I am using REST and having 'file type' in the URL.
Example rest url:
hostname/file-content/TYPE
where value of TYPE could be any of these: standardFileNames1,standardFileNames2,randomFileName1,randomFileName2
I have used TYPE to group the files, so as to minimize the change in url when a new file is added. Dont want to have file names in the URL due to security issues.
my thought goes like this:
having as ENUM:
public enum FileType
{
standardFileNames1("Afile_en", "Afile_jp"),
standardFileNames2("Bfile_en","Bfile_jp"),
randomFileName1("xyz"),
randomFileName2("abc"),
...
...
}
having as properties file:
standardFileNames1=Afile_en,Afile_jp
standardFileNames2=Bfile_en,Bfile_jp
randomFileName1=xyz
randomFileName2=abc
I know having this in properties will save build efforts on every change, but still want to know your views to figure out best solution with all considerations.
Thanks!
Akhilesh
I often use property file + enum combination. Here is an example:
public enum Constants {
PROP1,
PROP2;
private static final String PATH = "/constants.properties";
private static final Logger logger = LoggerFactory.getLogger(Constants.class);
private static Properties properties;
private String value;
private void init() {
if (properties == null) {
properties = new Properties();
try {
properties.load(Constants.class.getResourceAsStream(PATH));
}
catch (Exception e) {
logger.error("Unable to load " + PATH + " file from classpath.", e);
System.exit(1);
}
}
value = (String) properties.get(this.toString());
}
public String getValue() {
if (value == null) {
init();
}
return value;
}
}
Now you also need a property file (I ofter place it in src, so it is packaged into JAR), with properties just as you used in enum. For example:
constants.properties:
#This is property file...
PROP1=some text
PROP2=some other text
Now I very often use static import in classes where I want to use my constants:
import static com.some.package.Constants.*;
And an example usage
System.out.println(PROP1);
Source:http://stackoverflow.com/questions/4908973/java-property-file-as-enum
My suggestion is to keep in properties or config file and write a generic code to get the file list and parse in java. So that whenever a new file comes, there will be no change on server side rather you will add an entry to the properties or config file.

Categories