Is using VM arguments for servlet configuration 'clean'? - java

This relates to this answer:
System.getProperty("catalina.base") There can be scenario where client may use any other server
another server-independent system
property yourself, you can set as a VM
argument.
-Dconfig.location=/path/to/folder
In case of Tomcat, you can set it as
JAVA_OPTS environment variable, or
edit the catalina.bat startup file or
edit the Windows Service settings
(when it's installed as Windows
Service), etc. Other servers supports
similar constructs as well.
Is this considered 'clean'? We've been doing this for years, just want to know if this is acceptable, or there is a better way to configure runtime environment.

It feels maybe dirty, but there are apart from putting it in the classpath really no better ways if the requirement is to untouch the WAR whenever you want to change the location of external configuration files.
If untouching the WAR is not a strict requirement and rebuilding the WAR is allowed (e.g. you're using an inhouse developed application with continuous integration and serveradmins are in the same line, etc), then you could also use a <context-param> in web.xml instead.
<context-param>
<param-name>config.location<param-name>
<param-value>/path/to/file</param-value>
</context-param>
It's then in any Servlet (or better, ServletContextListener) available by ServletContext#getInitParameter():
String configLocation = servletContext.getInitParameter("config.location");
File configFile = new File(configLocation, "config.properties");
// ...

My understanding is that "more clean" would be using either <servlet-param> <init-param> in web.xml or some kind of IoC solution, like Spring.

I feel this is not the cleanest of ways to attain what you want. You can use the web.xml init params or servlet params tags.
Another way is using properties file or an XML configuration file.

I just solved a similar problem in a slightly different way. Our customer wants to configure database connection details, integration server locations and ports etc. without rebuilding the war. Using environment property to point an external file containing the information may or may not be okay, but it felt a bit dirty trick. Anyway, here's a slightly more enterprisey way.
For database connections we use JNDI lookup and below is the current solution for integration server parametrization. The parameters can come from at least three different sources now:
properties-file, which is overridable with Maven profiles and requires single line of xml in spring configuration to be accessible. This is obviously inside the war file.
web.xml context-param. This is also, of course, inside the war file.
Tomcat server can override the init parameters with context.xml which can be outside the war. This happens to be the same file where JNDI context is defined, which is nice.
Below is the implementation for configuration accessor bean. It can run in servlet context and also without one (for some unit tests it makes little sense to kickstart a full-blown web server, but we nevertheless need to satisfy spring bean injections).
I don't mean this to be a perfect solution, but it is one. Didn't find anything like this with google.
#Service
public class IntegrationConfigurationImpl implements
IntegrationConfiguration, InitializingBean,
ServletContextAware, ApplicationContextAware {
private static final String SERVER_HOST_PROPERTY = "integration.server.host";
private static final String SERVER_PORT_PROPERTY = "integration.server.port";
private static final String PROPERTY_BEAN_NAME = "integrationProperties";
private ServletContext servletContext;
private ApplicationContext applicationContext;
private static final Logger log = LoggerFactory.getLogger(IntegrationConfigurationImpl.class);
private String serverHost = "foo";
private int serverPort = 42;
#Override
public String getServerHost() {
return serverHost;
}
#Override
public int getServerPort() {
return serverPort;
}
#Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Override
public void afterPropertiesSet() throws Exception {
// konfiguraation validointi..
if (servletContext == null) {
log.info("servlet context not set, not running as a web application. Trying to get properties from application context");
if (applicationContext.containsBean(PROPERTY_BEAN_NAME)) {
Properties p = (Properties)applicationContext.getBean(PROPERTY_BEAN_NAME);
serverHost = p.getProperty(SERVER_HOST_PROPERTY);
serverPort = Integer.valueOf(p.getProperty(SERVER_PORT_PROPERTY)).intValue();
} else {
log.info("Property bean not found :" + PROPERTY_BEAN_NAME);
}
} else {
serverHost = servletContext.getInitParameter(SERVER_HOST_PROPERTY);
serverPort = Integer.valueOf(servletContext.getInitParameter(SERVER_PORT_PROPERTY)).intValue();
}
log.info("Using integration server " + getServerHost() + ", port " + getServerPort());
}
}

The disadvantage with having system property is you need to restart the container to modify the system parameter.
Having it as init-param in web.xml, can allow you to modify by just restarting the web app.
Having in init-param is a better way.

Related

How to set system variable from Spring Cloud Config Server

I'm searching for a way to set system variables (e.g. -Djavax.net.ssl.keyStore) from Spring Cloud Config Server, because I'm facing exactly this behaviour (https://stackoverflow.com/a/30199253/1406669) in a mutual ssl environment.
There are various ways to set it statically (https://stackoverflow.com/a/36895827/1406669 || https://gist.github.com/unamanic/a7eb0c17b78fb03617cc955b06285b1d). The thing I don't like about it, is setting it statically (once set on startup and never refreshed) and the need to define the keys in a static way. In this way I would have to redeploy the apps when there would be the need to introduce a new system variable. This is a the thing I try to avoid.
Does anybody got an idea?
The refresh event works via an ApplicationChangeEvent, which your app can listen for as well by implementing an ApplicationListener.
https://github.com/spring-cloud/spring-cloud-commons/blob/master/docs/src/main/asciidoc/spring-cloud-commons.adoc#environment-changes
#Component
public class DynamicSystemProperties implements ApplicationListener<EnvironmentChangeEvent>{
private final Environment env;
#Autowired
public DynamicSystemProperties(Environment env) {
this.env = env;
}
#Override
public void onApplicationEvent(EnvironmentChangeEvent environmentChangeEvent) {
if(env.containsProperty("system.javax.net.ssl.keyStore")) {
String keystore = env.getProperty("system.javax.net.ssl.keyStore");
System.out.println("system.javax.net.ssl.keyStore - " + keystore);
System.getProperties().setProperty("javax.net.ssl.keyStore", keystore);
}
}
}

How do you handle configuration on spring framework when developing custom libraries intended to be consumed by other applications?

I'm working on building a custom "event" library that encapsulates the technical details of an event buffer we are planning to share with multiple consumers. Ideally, we want this library to use the spring framework (note: not spring boot), and be environmentally aware. Something I am not groking from the current docs is how to make the library environmentally aware.
For example, we want to include with the library a static configuration for the queue end points the library will publish / consume from; however, we want to enable "overriding" these queues when in the development or integration environments. Ideally, I do not want to make multiple builds that swap out what the config file is, but include them all and know to read the "right" one.
Some of the things I am not understanding;
How to pass in a "profile" when debugging (it seems the Environment object won't honor the -Dspring.active.profiles property).
How to structure the #Configuration classes so that you do not hard code #Profile(prod).
Total Spring n00b, thanks in advance!
---UPDATE: Trying to provide a more concrete example.
So I have create a basic configuration class to hold the details that would be populated by configuration files:
#Configuration
public class EventConfiguration implements EnvironmentAware{
private static Environment env = null;
#Value("${events.queue1}")
private String queue1;
#Value("${events.queue2}")
private String queue2;
#Bean
public EventDispatcher eventDispatcher() {
return new EventDispatcher(this);
}
#Override
public void setEnvironment(Environment environment) {
env = environment;
}
... getters and setters
Essentially I want to either go the yaml approach and define the queues by environment "dev", "integration", "prod"; or have 3 different files following the application-{env}.properties convention.
Then, to help me understand how this works, I threw together a quick test so I can inspect the configuration / environment:
#Test
public void testContext() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext();
Environment env = ctx.getEnvironment();
ctx.scan("com...events");
ctx.refresh();
EventDispatcher dispatcher = ctx.getBean(EventDispatcher.class);
}
I started the debugger with a -Dspring.profiles.active=dev, after having created an application-dev.profile available on the class path.
Am I on the right track? Seems weird to have to have that type of boiler plate code to instantiate the objects, plus it didnt work. The Environment object only showed "default" as the active profile.

How to modify web environment entry in GlassFish 4

In my web.xml od my webapp application I have following element:
<env-entry>
<env-entry-name>aMessage</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>Hello World</env-entry-value>
</env-entry>
EJB in this web application can read it:
final InitialContext context = new InitialContext();
final Context env = (Context) context.lookup("java:comp/env");
System.out.println("MSG: " + env.lookup("aMessage")); // prints Hello World
Now I am trying to change that value with asadmin:
martin#bono:~/glassfish4/glassfish/bin$ ./asadmin set-web-env-entry --name=aMessage --value=test webapp
Previous env-entry setting of aMessage for application/module webapp was overridden.
Command set-web-env-entry executed successfully.
martin#bono:~/glassfish4/glassfish/bin$ ./asadmin list-web-env-entry webapp
Reported 1 env-entry setting
aMessage (java.lang.String) = test ignoreDescriptorItem=true //
Command list-web-env-entry executed successfully.
Unfortunately my EJB still prints the old value "Hello World", even after re-enabling this webapp or restarting webserver.
I've also tried to set-web-env-entry for names not defined in web.xml and also played with --ignoredescriptoritem parameter, but nothing helped. Enumerating whole environment also shows no additional or changed web environment entries, but shows he old one plus many other objects not related to this problem:
final NamingEnumeration<Binding> enu = env.listBindings("");
while (enu.hasMore()) {
final Binding binding = enu.next();
System.out.println(binding);
}
What I am doing wrong?
It seems to be a bug - but I have another solution for your needs. You can use custom resources which are available in glassfish. You have to declare custom resource in domain.xml
<resources>
<custom-resource factory-class="org.glassfish.resources.custom.factory.PropertiesFactory" res-type="java.util.Properties" jndi-name="test/properties">
<property name="aMessage" value="Hello World"></property>
</custom-resource>
</resources>
then you can use it in the code
public class Environment
{
public String getProperty() {
InitialContext ctx = new InitialContext();
properties = (Properties) ctx.lookup("test/properties");
if(properties == null) {
return "default value - hello";
}
return properties.getProperty("aMessage");
}
}
The one disadvantage of this approach is that custom resources are global for whole domain. But this solution has advantage you can change resources by using asadmin and admin web console also.

Java: How custom DI configuration works for WARs and EARs

I have written a Bootstrapper class that reads an XML file from the classpath and can be used as a lightweight dependency injector by other classes at runtime:
<!-- myAppConfig.xml -->
<appConfig>
<environment runningOn="LOCAL" host="localhost.example.com" system="Windows 7"/>
</appConfig>
public class Bootstrapper
{
private String runningOn = "Unknown";
private String host = "Unknown";
private String system = "Unknown";
public Bootstrapper(final String appConfigFileName)
{
setRunningOn(extractRunningOn(appConfigFileName));
setHost(extractHost(appConfigFileName));
setSystem(extractSystem(appConfigFileName));
}
public String getHost()
{
return host;
}
// Rest of class ommitted for brevity...
}
// Then in an executable JAR's main method:
public static void main(String[] args)
{
Bootstrapper bootstrapper = new Bootstrapper("myAppConfig.xml");
// Prints: "This app is running on the localhost.example.com server node."
System.out.println("This app is running on the " +
bootstrapper.getHost() + " server node.");
}
In this sense appConfig and Bootstrapper kind of serve as ultra lightweight "DI" mechanisms.
What I'm wondering is: how do I translate this design to a WAR's web.xml and an EAR's server.xml?
Whereas in an executable JAR, the main method explicitly instantiates a Bootstrapper which can then be interrogated for its fields/properties, in a WAR/EAR everything is defined in an XML file (web.xml/server.xml) without a single "entry point". So in a WAR or EAR, if I had multiple classes that each needed to know what the local host name was, I would have to keep instantiating the same Bootstrapper over and over again, passing it the same myAppConfig.xml each time.
I'm wondering if there's a way to configure web.xml and server.xml to instantiate a Bootstrapper on startup/deployment, and to automatically inject/populate my dependent classes right then and there (or, at the very least, to give each dependent class access to the global/singleton Bootstrapper defined in the XML file).
Thanks in advance!
For a war (and an ear since it will contain a war) project you could use a ServletContextListener to instantiate your Bootstrapper.
A good example of how to use a ServletContextListener is here.
However if you are using Java EE 6 then a better way to do this would be to use an EJB 3.1 Singleton and some CDI.
import javax.ejb.Singleton
import javax.ejb.Startup
import javax.enterprise.context.ApplicationScoped
#Singleton // EJB 3.1 Singleton
#Startup // Telling the container to eagerly load on startup
#ApplicationScoped // CDI Scope
public class Bootstrapper {
private String host = "Unknown";
#PostConstruct
public void readConfiguration() {
// ... load your xml file
}
public String getHost() {
return host;
}
}
Using the above you can now inject this Bootstrapper bean within most of the EE 6 ecosystem with a simple #Inject or #EJB annotation.

Is there a way to access web.xml properties from a Java Bean?

Is there any way in the Servlet API to access properties specified in web.xml (such as initialization parameters) from within a Bean or Factory class that is not associated at all with the web container?
For example, I'm writing a Factory class, and I'd like to include some logic within the Factory to check a hierarchy of files and configuration locations to see which if any are available to determine which implementation class to instantiate - for example,
a properties file in the classpath,
a web.xml parameter,
a system property, or
some default logic if nothing else is available.
I'd like to be able to do this without injecting any reference to ServletConfig or anything similiar to my Factory - the code should be able to run ok outside of a Servlet Container.
This might sound a little bit uncommon, but I'd like for this component I'm working on to be able to be packaged with one of our webapps, and also be versatile enough to be packaged with some of our command-line tools without requiring a new properties file just for my component - so I was hoping to piggyback on top of other configuration files such as web.xml.
If I recall correctly, .NET has something like Request.GetCurrentRequest() to get a reference to the currently executing Request - but since this is a Java app I'm looking for something simliar that could be used to gain access to ServletConfig.
One way you could do this is:
public class FactoryInitialisingServletContextListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent event) {
}
public void contextInitialized(ServletContextEvent event) {
Properties properties = new Properties();
ServletContext servletContext = event.getServletContext();
Enumeration<?> keys = servletContext.getInitParameterNames();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
String value = servletContext.getInitParameter(key);
properties.setProperty(key, value);
}
Factory.setServletContextProperties(properties);
}
}
public class Factory {
static Properties _servletContextProperties = new Properties();
public static void setServletContextProperties(Properties servletContextProperties) {
_servletContextProperties = servletContextProperties;
}
}
And then have the following in your web.xml
<listener>
<listener-class>com.acme.FactoryInitialisingServletContextListener<listener-class>
</listener>
If your application is running in a web container, then the listener will be invoked by the container once the context has been created. In which case, the _servletContextProperties will be replaced with any context-params specified in the web.xml.
If your application is running outside a web container, then _servletContextProperties will be empty.
Have you considered using the Spring framework for this? That way, your beans don't get any extra cruft, and spring handles the configuration setup for you.
I think that you will have to add an associated bootstrap class which takes a reference to a ServletConfig (or ServletContext) and transcribes those values to the Factory class. At least this way you can package it separately.
#toolkit : Excellent, most humbled - This is something that I have been trying to do for a while

Categories

Resources