Part of my webapp involves uploading image files. On the production server, the files will need to be written to /somepath_on_production_server/images. For local development, I want to write the files to /some_different_path/images.
What's the best way to handle these configuration differences?
One important requirement is this: I don't want to have to mess with the production server at all, I just want to be able to deploy a war file and have it work. So I don't want to use any technique which will require me to mess with the environment variables/classpath/etc. on the production machine. I'm fine with setting those on my local machine though.
I'm imaginine two possible general approaches:
loading a special "dev" configuration file at runtime if certain conditions are met (environment variable/classpath/etc)
flipping a switch during the build process (maven profiles maybe?)
Simple things like a String can be declared as environment entries in the web.xml and obtained via JNDI. Below, an example with an env-entry named "imagePath".
<env-entry>
<env-entry-name>imagePath</env-entry-name>
<env-entry-value>/somepath_on_production_server/images</env-entry-value>
<env-entry-type>java.lang.String</env-entry-type>
</env-entry>
To access the properties from your Java code, do a JNDI lookup:
// Get a handle to the JNDI environment naming context
Context env = (Context)new InitialContext().lookup("java:comp/env");
// Get a single value
String imagePath = (String)env.lookup("imagePath");
This is typically done in an old fashioned ServiceLocator where you would cache the value for a given key.
Another option would be to use a properties files.
And the maven way to deal with multiple environments typically involves profiles and filtering (either of a properties file or even the web.xml).
Resources
Introduction to Build Profiles
9.3. Resource Filtering
Have defaults in your WAR file corresponding to the production setting, but allow them to be overriden externally e.g. through system properties or JNDI.
String uploadLocation = System.getProperty("upload.location", "c:/dev");
(untested)
Using a properties file isn't too difficult and is a little more readable the web.xml
InputStream ldapConfig = getClass().getResourceAsStream(
"/ldap-jndi.properties");
Properties env = new Properties();
try {
env.load(ldapConfig);
} finally {
if (ldapConfig != null) {
ldapConfig.close();
}
}
Related
I'm writing my first "real" applicaion in my first job. I could deploy my app using Spring Boot and it works just fine. One thing that i doubt is datasource config part. Now i write all datasource config in application.properties file:
spring.datasource.url = jdbc:postgresql://10.60.6.34:5432/postgres
spring.datasource.username = *username*
spring.datasource.password = *password*
spring.jpa.hibernate.ddl-auto = update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQL94Dialect
And that's it! What else should i do to make my app production ready? What about connection pooling and all that stuff? (i'm not quite familiar with all that datasource config stuff) Thanks in advance!
So Joe W is not wrong - profiles is an OK way to handle this issue. However, what I'd recommend instead is to handle the issue using environment variables. This will make your application compatible across not only all operating systems (which profiles will too), but will also allow you to run it within Docker (containers) more easily. You will need to do some amount of this anyway, since profiles still requires you to specify which profile you're running, which you'll need to do with an environment variable.
Luckily for you, Spring Boot auto-wires environment variables with no extra work on your side. You can read more about this here: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
When dealing with environment variables, you use underscores instead of periods, so your configs would look like this:
SPRING_DATASOURCE_URL = jdbc:postgresql://10.60.6.34:5432/postgres
SPRING_DATASOURCE_USERNAME = *username*
SPRING_DATASOURCE_PASSWORD = *password*
SPRING_JPA_HIBERNATE_DDL_AUTO = update
SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT = org.hibernate.dialect.PostgreSQL94Dialect
Then you can set your environment variables to whatever you want, and you don't need to worry about pulling in new profiles for each server. In addition, since environment variables are higher in the hierarchy than file-based configurations, you can leave your current file-based configurations alone (if you'd like) and your environment variables will override them when you deploy.
Around your connection pooling, this is going to heavily depend on your backing servlet container (I.e., tomcat vs other) and your backing database (looks like postgres). I'd recommend you look at tomcat-jdbc usage with Spring boot, which will then allow you to configure the things like max connection pools and such within Spring's environment variables as well.
You should take advantage of SpringBoot profiles that will allow you to define separate configuration for dev, stage, prod, and any other environment you want based on a property.
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html
A profile gives you a way to control which configuration is loaded based on a property that defines where the application is deployed.
Your question on data connection pooling depends on your backing data storage selection and how that particular storage is setup. In general, when you go to production you should be using connection pooling of some kind but how much and what kind depend on your implementation.
Additionally, you can use spring config service . Config-service is central location(which can be more secure) for all properties and with very minimal configuration/change your spring-boot application can read properties from config-service.
You should provide your database parameters as environment variables then set them in your application.properties as placeholders. For example:
spring.datasource.url=${DATASOURCE_URL}
Where DATASOURCE_URL is one of the env. variable.
In your IDE you set them in the project setting (for example)
So at your work you set your local parameters in IDE, and on the production machine you set prod parameters as environment variable.
Currently, we store our application's environment properties in a .properties file in the WEB-INF. We want to move them to a database table. But we still want to specify the jndi name, and when running in our test environment locally, we want to be able to override certain properties just for our workspace for test and development.
Apache commons' DatabaseConfigurator seemed nice, but wouldn't play nice with the jndi name being defined as a property in the file. Nothing I did to ask it to look at the property file first worked.
I decided to subclass apache commons' AbstractConfiguration to try to create a single configurator that would check the file and database as I wished, but again, it didn't really work. Spring wants that jndi name absolutely first, probably because the data source has to be passed into the configurator as a parameter.
How can I get what I am after here? Mostly properties in the database, but those that are in the file override them. And jndi name for the datasource should not have to be hardcoded in the spring config.
Why don't you write a ApplicationContext listener that will read the configuration from your DB and inject them in the JNDI? Then you can override the configuration in the JNDI with a context.xml file that will be placed in the src/local/webapp/META-INF/.
This is how we get this working in our webapp.
I have a config.properties file which contains configurable properties e.g. database connection details in a webapp deployed on tomcat. e.g.
local.driver: com.mysql.jdbc.Driver
local.username:myuser
local.password:mypass
dev.driver: com.mysql.jdbc.Driver
dev.username:mydevuser
dev.password:mydevpass
I can retrieve the values from config.properties using spring Environment object or #Value.
My question is how do you make Spring's environment object pick local properties when running on local and dev properties when running on dev? Also it doesn't sound right to save sensitive data e.g. production database connection
details in properties file which will float around in code base. So how do you add production detail when in production environment? Ideally I would want to change them as and when I like and not have to redeploy the app. Am I going the right direction?
Note - All the answers I have seen on SO discuss changing these properties within java code. I don't want to do that I want to be able to configure these values independent of the application code.
Thanks
You can have a look at spring profiles to load a specific file for a specific environment.
Alternatively, you can also parameterize the file from where the properties are loaded in the application context using a JNDI property or an environment property set in the container.
Example:
<context:property-placeholder ignore-unresolvable="true" location="${env.config.file:classpath*:META-INF/spring/profiles/dev/dev.properties}" />
The env.config.file can be set at the container level (say Tomcat) using -Denv.config.file= when starting it. By doing this, Spring automagically finds the property in the system props and replaces it. If you don't set it explicitly (for example, in dev where you might use some other container, such as jetty), it would use the given default value (in this example, dev.properties).
By putting the properties files outside the war / ear, they can be changed at will, and only the context needs to be restarted. Alternatively, you could look into re-loadable property placeholders. This also helps if you don't want passwords stored in the war in clear.
For encrypting information in the property files, if you're using Spring 3, you can also check: http://www.jasypt.org/spring3.html.
for picking env specific values you have couple of options
If you can create multiple properties file based on env then you can use Spring profile feature (i.e spring.profiles.active), this will allow to control properties file to loaded via JVM parameter.
If you still want to keep all the stuff in single fle then you can override PropertyPlaceholderConfigurer to take env details from JVM parameter or default to some value
On security question , one the approach is to store encrypted password in prop file.
In projects I work(ed) on, deployment parameters - such as storage path or DB login - are usually given through a parameter file, which is stored in the war file.
I find that unsuitable because those values needs to be changed each time the webapp is packaged for a different deployment (dev vs prod, change of executing computer). The source code being versioned, this makes it even more bothering.
Is there some better option to pass parameters such as listed above?
By better, I mean:
practical: simple to setup, change and explain to others
separated from the war
as independent as possible to the web container (if dependent, I'm using tomcat in prod)
Edit
I chose the answer of #aksappy to reward the work done in the answer and because it provided several methods using standard tools. However, depending on the context I could go for any other solutions:
method of #Necreaux has best simplicity
method of #Luiggi Mendoza has a good design and is still simple
method of #OldCurmudgeon would be a really good one if the code covered other cases.
You can use a multitude of things based on your environment. Here are somethings which may be considered
Use datasources
The datasources defined in the server context removes the hard wired dependency of managing db configurations and connection pool from the web application. In Tomcat, this can be done as below in the context.xml
<Context>
...
<Resource name="jdbc/EmployeeDB" auth="Container"
type="javax.sql.DataSource"
description="Employees Database for HR Applications"/>
</Context>
Use Contexts
You can configure named values that will be made visible to the web application as environment entry resources, by nesting entries inside this element. For example, you can create an environment entry like this: (Source here). This can be set as context parameters or environment entries. They are equivalent to the entries made in a web.xml or a properties file except that they are available from the server's context.
Use database configurations and load those configuration at ServletContextListener
Another approach which I tend to follow is to create a relational schema of properties in a database. Instead of loading the properties file during server startup, load the properties from the database during start up.
public class ContextInitialize implements ServletContextListener {
private static Properties props;
public void contextInitialized(ServletContextEvent servletContextEvent) {
// connect to DB
// Load all the key values pairs as required
//put this into a Properties object, or create a hashtable, hashmap ..
}
//Getter
public String getProperty(String key){
// get value of key
}
//Setter
public void setProperty(String key, String value){
// set value to a key
}
}
Note: above is just an example.
Use environment variables or classpath variables
Use classpath / path variables in Environment variables and use System.getenv() in your java code to get these values as necessary.
We normally put our web app properties files in the Tomcat home folder. POJOS look on the launch folder. There will be other standard locations for other web servers.
final String tomcatHome = System.getProperty("catalina.home");
if (tomcatHome == null) {
// POJOs look in "."
searchPaths.add(".");
} else {
searchPaths.add(tomcatHome);
webApp = true;
}
An strategy is to pack all the properties and configuration files in an external jar and make this jar a dependency for your application(s): war, ear, etc. Then, you can deploy this jar in a common folder where the application server will load it and make it available for all the applications deployed there. This means that you will deploy the jar with the values for each environment once (or every time you need to change it, but its changes must be slow compared to the changes made to your main artifacts) and you can deploy and redeploy your war or any other project in your application server without problems.
In case of Tomcat, you may deploy this jar inside %CATALINA_HOME%/lib as explained in Tomcat Tutorial. Class Loader Definitions
To consume (read) these files in my application, I just load them like any other resource in my application.
Two strategies I've used:
JVM Parameters -- Custom JVM parameters can be set by the container at startup. This can get a bit verbose though if you have a lot of settings.
Configuration Files -- These can be read by the application. Either the location is hardcoded, put inside the container path, or to get the best of both worlds, specify the location via a JVM parameter.
I am developing a Struts 2 web application, all the constant values and hardcoded values are moved to a properties file along with those, the constants which are specific to environment like Data source name, some of the server connection urls and few user ids are also placed in the properties file.
Initially I placed the properties file in the class path and accessed using resource bundle as below
ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle("config\appProps");
Problem with that is Because of those environment specific constants for ever environment(DEV / UAT / PROD) a separate war needs to be generated by changing the properties file environment specific constants value, to avoid that I am using below process
I kept the properties file absolute path in the context parameter of the web.xml and get the path from the context and reading the properties file as below
Properties prop = new Properties();
prop.load(new FileInputStream("<<file system absolute path from context param>>"));
This eliminated the process of generating different war for each environment as path on the server can be maintained same, but I came to know that security wise we should not use absolute paths like that which might expose the server file system details
Please let me know What is the correct way of loading the properties file in a web application by considering security as well as eliminating generation of different war files for each environment.
Thanks.
You could maybe hoist the whole properties object into JNDI, that would fix it, but implementations may be server specific. And now you've got a new problem; make sure that the properties are loaded before you read them.