Generally a problem most projects face is how to handle configuration information that varies between environments. Many times you can handle this by using one of WAS built in resources such as a SMTP server or a JDBC connection pool. However many times the configuration data won’t fit one of these existing WAS resource types, such as an e-mail address, a server name or a URL. To solve this a lot of approaches are available some use build tools such as Maven to build different EAR files, others use properties files located outside of the EAR. Unfortunately both of these solutions have their issues and can be difficult to manage.
To solve this problem WAS provides and WPS leverages the resource environment providers. These allow you to specify your settings as a part of the WAS console at the Cell, Node or Server level. This provides an easy way to maintain values across many servers while also doing away with the need to have special EARs built for each environment.
This IBM article discusses this.
Unfortunately it requires deployment to the App Server lib/ext directory, something that is not always so easily done particularly in a shared enterprise environment.
However WPS uses Resource Environment Providers but does so differently than the above article specifies, it uses the “Custom Properties” of the resource environment provider and requires no deployment to the App Server, an ideal solution.
While it is possible to bind your code directly to the fetches to the Resource Environment Provider I find a more flexible and much more modern way of addressing the problem is to use Spring and its facility for PropertyPlaceholderConfigurer.
There is one special caveat about using this, the Spring initialization must run under an ID which has admin rights on the WAS instance. This can be accomplished in two easy ways, first ensure your application context is initialized during application startup, Spring provides a listener for just this purpose. Second it appears that the servlet init() function also runs as an admin so you could initialize the application context at that time. For the record, you should probably use the listener, it is the most “normal” and efficient way to do it.
This allows for a Spring config something like this:
<bean id=”MyBean” class=”com.perficient.sample.MyBean”> <property name=”${administratorsEmailAddress}” /> </bean>And the property administratorsEmailAddress will be replaced with the value from the Resource Environment Provider before the context is initialized.The whole thing would look something like this<?xml version="1.0" encoding="UTF-8"?><beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xmlns:util="http://www.springframework.org/schema/util"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"><bean><property name="resourceProviderName" value="NameOfMyResourceEnvironmentProvider" /></bean> <bean id=”MyBean” class=”com.perficient.sample.MyBean”> <property name=”${administratorsEmailAddress}” /> </bean></beans>
This will lookup the custom property “administratorsEmailAddress” under the Resource Environment Provider named “NameOfMyResourceEnvironmentProvider” and do the substitution before initializing the context. Of course it also follows the standard WAS rules allowing for the Node to trump the Cell setting and the Server to trump the Node and Cell settings allowing a lot of flexibility in how the property gets propagated to your configuration.
The code for the WebSphereResourceEnvironmentProviderPlaceHolderConfigurer follows:
package com.perficient.spring;import java.util.List;import java.util.Properties;import javax.management.AttributeList;import javax.management.AttributeNotFoundException;import javax.management.ObjectName;import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;import com.ibm.websphere.management.AdminService;import com.ibm.websphere.management.AdminServiceFactory;import com.ibm.websphere.management.Session;import com.ibm.websphere.management.configservice.ConfigService;import com.ibm.websphere.management.configservice.ConfigServiceFactory;import com.ibm.websphere.management.configservice.ConfigServiceHelper;import com.ibm.websphere.management.exception.ConfigServiceException;import com.ibm.websphere.management.exception.ConnectorException;/**
- The Class WebSphereResourceEnvironmentProviderPlaceHolderConfigurer implements a placeholder that uses WebSphere Application Server
- Resource Environment Provider custom properties as a source. These will work in the normal way with anythign declared at the server layer overriding anything at the node layer which in turns overrides anything at the cell layer.
- For development you may find it useful to either get rid of this definition or use a standard properties file for the placeholders. As this is inherited off the standard placeholder you can still use the normal properties file definition as a fallback.
- To use it simply create a resource environment provider in WAS at whatever level pleases you, and then add the data you want as custom properties to that. Then in spring specify the name of the resource environment provider as the “resourceProviderName” in the spring configuration for this:
WebSphereResourceEnvironmentProviderPlaceHolderConfigurer.
**/public class WebSphereResourceEnvironmentProviderPlaceHolderConfigurer extends PropertyPlaceholderConfigurer {/** The resource provider name. */String resourceProviderName;/** The server properties. */Properties serverProperties = null;/** The node properties. */Properties nodeProperties = null;/** The cell properties. */Properties cellProperties = null;/*** Gets the resource provider name.** @return the resource provider name*/public String getResourceProviderName() {return resourceProviderName;}/*** Sets the resource provider name.** @param resourceProviderName the new resource provider name*/public void setResourceProviderName(String resourceProviderName) {this.resourceProviderName = resourceProviderName;}/*** Gets the properties.** @param level the level* @param providerName the provider name* @param service the service* @param session the session* @return the properties* @throws ConfigServiceException the config service exception* @throws ConnectorException the connector exception* @throws AttributeNotFoundException the attribute not found exception*/private Properties getProperties(ObjectName level, ObjectName providerName, ConfigService service, Session session) throws ConfigServiceException, ConnectorException, AttributeNotFoundException {Properties rv = new Properties();ObjectName sampleProviderName = null;ObjectName[] matches;try {matches = service.queryConfigObjects(session, level, providerName, null);} catch (Exception ex) {ex.printStackTrace();return rv;}if (matches.length > 0) {sampleProviderName = matches[0]; // use the first provider found}if (sampleProviderName != null) {// search for the repository provider attributesAttributeList value = service.getAttributes(session, sampleProviderName, new String[] { "propertySet" }, false);ObjectName propertySet = (ObjectName) ConfigServiceHelper.getAttributeValue(value, "propertySet");value = service.getAttributes(session, propertySet, new String[] { "resourceProperties" }, false);List resourceProperties = (List) ConfigServiceHelper.getAttributeValue(value, "resourceProperties");for (int i = 0; i < resourceProperties.size(); i++) {// load all custom propertiesObjectName on = (ObjectName) resourceProperties.get(i);String customName = (String) service.getAttribute(session, on, "name");String customValue = (String)service.getAttribute(session, on, "value");// put it in the property setrv.put(customName, customValue);}}return rv;}/*** Load environment provider properties.** @throws ConfigServiceException the config service exception* @throws ConnectorException the connector exception* @throws AttributeNotFoundException the attribute not found exception*/private void loadEnvironmentProviderProperties() throws ConfigServiceException, ConnectorException, AttributeNotFoundException {ConfigService service = ConfigServiceFactory.getConfigService();Session session = new Session();AdminService adminService = AdminServiceFactory.getAdminService();// for some reason the ObjectName returned by the adminService throws a null exception if you try to query with it// so this is just as well, server names are unique in a cell so this will always return just one server.ObjectName server = ConfigServiceHelper.createObjectName(null, "Server", adminService.getLocalServer().getKeyProperty("name") );ObjectName[] matches = service.queryConfigObjects(session, null, server, null);server = matches[0]; // will be exact matchObjectName node = ConfigServiceHelper.createObjectName(null, "Node", adminService.getNodeName());matches = service.queryConfigObjects(session, null, node, null);node = matches[0]; // will be exact matchObjectName cell = ConfigServiceHelper.createObjectName(null, "Cell", adminService.getCellName());matches = service.queryConfigObjects(session, null, cell, null);cell = matches[0]; // will be exact match// direct searchObjectName providerName = ConfigServiceHelper.createObjectName(null, "ResourceEnvironmentProvider", "SeedlistTransformer");serverProperties = getProperties(server, providerName, service, session);nodeProperties = getProperties(node, providerName, service, session);cellProperties = getProperties(cell, providerName, service, session);}/*** Resolve provider value.** @param name the name* @return the string*/private String resolveProviderValue(String name) {if (serverProperties.containsKey(name) ) {return serverProperties.getProperty(name);} else if (nodeProperties.containsKey(name)) {return nodeProperties.getProperty(name);} else if (cellProperties.containsKey(name) ) {return cellProperties.getProperty(name);} else {return null;}}/* (non-Javadoc)* @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer#resolvePlaceholder(java.lang.String, java.util.Properties)*/@Overrideprotected String resolvePlaceholder(String placeholder, Properties props) {if (serverProperties == null) {try {loadEnvironmentProviderProperties();} catch (ConfigServiceException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (AttributeNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (ConnectorException e) {// TODO Auto-generated catch blocke.printStackTrace();}}String value = resolveProviderValue(placeholder);if (value == null) {return super.resolvePlaceholder(placeholder, props);} else {return value;}}}