Externalizing Tomcat webapp config from .war file - linux

I am having trouble with configuring a webapp in Tomcat 7. In my WAR file, there is a properties file myApp/WEB-INF/classes/myProps.props, and it contains environment-specific properites. I am trying to override that configuration file on the server, so that the same WAR file will deploy to multiple environments.
I heard there was a way to do this using replacement config files in tomcat/conf/Catalina/myApp. This is the method I am having trouble figuring out.
Also, myApp.war is one of many running on the same Tomcat server, and it does not run as localhost. I want to be able to solve this problem for several of the webapps.
Server version: Apache Tomcat/7.0.23
Server built: Nov 20 2011 07:36:25
Server number: 7.0.23.0
OS Name: Linux

Your tomcat/conf/Catalina/<host> can contain context descriptors that let you configure lots of things including defining "environment entries", which are accessible from Java via JNDI. There are lots of ways to go about using it. Personally, I set an environment entry which is the file system path to my properties file. My app is built to check for this entry, and if it doesn't exist, look for the file on the classpath instead. That way, in dev, we have the dev properties right there on the classpath, but when we build and deploy, we point it to an external file.
There's good documentation for configuring a context on the Tomcat website. See the Defining a Context section on details of how to create the file and where to put it.
As an example, if your host is named myHost and your app is a war file named myApp.war in the webapps directory, then you could create tomcat/conf/Catalina/myHost/myApp.xml with this content:
<Context>
<Environment name="configurationPath" value="/home/tomcat/myApp.properties" type="java.lang.String"/>
</Context>
Then from your code, you'd do a JNDI lookup on java:comp/env/configurationPath (95% certainty here) to get that string value.

I like .properties files instead of
JNDI - why build complex object during program configuration instead of initialization time?
system properties - you can't separately configure several instances of same WAR in single Tomcat
context parameters - they accessible only in javax.servlet.Filter, javax.servlet.ServletContextListener which my be inconvenient
Tomcat 7 Context hold Loader element. According to docs deployment descriptor (what in <Context> tag) can be placed in:
$CATALINA_BASE/conf/server.xml - bad - require server restarts in order to reread config
$CATALINA_BASE/conf/context.xml - bad - shared across all applications
$CATALINA_BASE/work/$APP.war:/META-INF/context.xml - bad - require repackaging in order to change config
$CATALINA_BASE/work/[enginename]/[hostname]/$APP/META-INF/context.xml - nice, but see last option!!
$CATALINA_BASE/webapps/$APP/META-INF/context.xml - nice, but see last option!!
$CATALINA_BASE/conf/[enginename]/[hostname]/$APP.xml - best - completely out of application and automatically scanned for changes!!!
Context can hold custom Loader org.apache.catalina.loader.VirtualWebappLoader (available in modern Tomcat 7, you can add own separate classpath to your .properties), and Parameter (accessed via FilterConfig.getServletContext().getInitParameter(name)) and Environment (accessed via new InitialContext().lookup("java:comp/env").lookup("name")):
<Context docBase="${basedir}/src/main/webapp"
reloadable="true">
<!-- http://tomcat.apache.org/tomcat-7.0-doc/config/context.html -->
<Resources className="org.apache.naming.resources.VirtualDirContext"
extraResourcePaths="/WEB-INF/classes=${basedir}/target/classes,/WEB-INF/lib=${basedir}/target/${project.build.finalName}/WEB-INF/lib"/>
<Loader className="org.apache.catalina.loader.VirtualWebappLoader"
virtualClasspath="${basedir}/target/classes;${basedir}/target/${project.build.finalName}/WEB-INF/lib"/>
<JarScanner scanAllDirectories="true"/>
<Parameter name="min" value="dev"/>
<Environment name="app.devel.ldap" value="USER" type="java.lang.String" override="true"/>
<Environment name="app.devel.permitAll" value="true" type="java.lang.String" override="true"/>
</Context>
If you use Spring and it's XML config:
<context:property-placeholder location="classpath:app.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:#${db.host}:${db.port}:${db.user}"/>
<property name="username" value="${db.user}"/>
<property name="password" value="${db.pass}"/>
</bean>
With Spring injecting above properties into bean fields are easy:
#Value("${db.user}") String defaultSchema;
instead of JNDI:
#Inject ApplicationContext context;
Enviroment env = context.getEnvironment();
String defaultSchema = env.getProperty("db.user");
Note also that EL allow this (default values and deep recursive substitution):
#Value('${db.user:testdb}') private String dbUserName;
<property name='username' value='${db.user.${env}}'/>
See also:
Adding a directory to tomcat classpath
Can I create a custom classpath on a per application basis in Tomcat
How to read a properties file outside my webapp context in Tomcat
Configure Tomcat to use properties file to load DB connection information
Should you set up database connection properties in server.xml or context.xml
Externalize Tomcat configuration
NOTE With extending classpath to live directory you also allowed to externilize any other configs, like logging, auth, atc. I externilize logback.xmlin such way.
UPDATE Tomcat 8 change syntax for <Resources> and <Loader> elements, corresponding part now look like:
<Resources>
<PostResources className="org.apache.catalina.webresources.DirResourceSet"
webAppMount="/WEB-INF/classes" base="${basedir}/target/classes" />
<PostResources className="org.apache.catalina.webresources.DirResourceSet"
webAppMount="/WEB-INF/lib" base="${basedir}/target/${project.build.finalName}/WEB-INF/lib" />
</Resources>

You can try to place your configuration (properties file) in Apache Tomcat\lib in JAR file and remove it from the web application. When the Tomcat's class loader won't find your config in webapp it will try to find in "lib" directory. So you can externalize your configuration just moving the config to global lib dir (it's shared among other webapps).

I just added a setenv.bat or setenv.sh script in the bin folder of tomcat. Set the classpath variable like
set CLASSPATH=my-propery-folder

Related

Hybris HAC Update Scenario

I wrote an interceptor in one of my projects to intercept all the requests. So usually in a spring project i will do normal build and start the server and my changes related to an interceptor will start reflecting. However this doesn't seem to be the case with a hybris project.
Do I need to do update in hybris hac as well? And if I do then out of the available below mentioned options which options do i need to choose and why.
1. Update running system
2. Clear the hMC configuration from the database
3. Create essential data
4. Localize types
Thanks,
Ashish
To answer the second part of your question, I have listed at least one reason for selecting each type of options. Hope this helps.
Update running system - Required when hybris type definition changes. For example, changing the content of file <extnesion-name>-items.xml
Clear the hMC configuration from the database - If you have chosen to persist hmc configuration in the Database and changing it. For example, changing content in the file hmc.xml
Create essential data - If there is a change in the content in the impex files which follows the naming pattern of essentialdata*.impex.
Localize types - If there is a change in the properties files for localizations. for example changing the content in file <extension-name>-locales_en.properties
Assuming you've not made any changes to any underlying data objects (Jalo items) then you won't need to run an update in the hybris hAC.
You should just be able to make your changes, run ant clean all from the platform and then start up the hybris ECP instance and your changes will be visible.
If this is a normal Spring MVC interceptor, then it should work fine.
Have you checked your spring configuration in the storefront extension you are working from?
For example, take a look at the accelerator Spring MVC configuration file:
hybris/bin/ext-template/yacceleratorstorefront/web/webroot/WEB-INF/config/spring-mvc-config.xml
This has some examples where this is used out-of-the-box:
<mvc:interceptors>
<ref bean="beforeControllerHandlerInterceptor" />
<ref bean="beforeViewHandlerInterceptor" />
<ref bean="csrfHandlerInterceptor" />
</mvc:interceptors>
As an example, the default before controller handler interceptor is defined as:
<!-- Interceptor that runs once per request and before the controller handler method is called -->
<alias name="defaultBeforeControllerHandlerInterceptor" alias="beforeControllerHandlerInterceptor" />
<bean id="defaultBeforeControllerHandlerInterceptor" class="de.hybris.platform.yacceleratorstorefront.interceptors.BeforeControllerHandlerInterceptor" >
<property name="beforeControllerHandlers">
<ref bean="beforeControllerHandlersList" />
</property>
</bean>
which references:
<alias name="defaultBeforeControllerHandlersList" alias="beforeControllerHandlersList" />
<util:list id="defaultBeforeControllerHandlersList" >
<!-- List of handlers to run -->
<bean class="de.hybris.platform.yacceleratorstorefront.interceptors.beforecontroller.SecurityUserCheckBeforeControllerHandler" />
<bean class="de.hybris.platform.yacceleratorstorefront.interceptors.beforecontroller.RequireHardLoginBeforeControllerHandler" >
<property name="userService" ref="userService"/>
<property name="redirectStrategy" ref="redirectStrategy"/>
...
</bean>
<bean class="de.hybris.platform.yacceleratorstorefront.interceptors.beforecontroller.DeviceDetectionBeforeControllerHandler" />
...
</util:list>
So you could either override this using the alias with your own implementation, or add additional controller handlers to the list.
As there is no change to the underlying data model - this is just wiring up Spring MVC related classes - no need for an update system or anything like that. Just an 'ant clean all' to recompile to pick up your new interceptor classes, and server restart to pick up the change in the Spring cornfiguraton.

Adding script maps during installation of web application IIS

has anyone successfully added IIS script maps to their Wix installation. I have been using this:
<CustomAction Id="AddHandlers_Cmd" Property="AddHandlers" Value=""[SystemFolder]inetsrv\appcmd" set config -section:system.webServer/handlers /+"[\[]name='GSOAP',path='*.dll', verb='*',modules='IsapiModule',scriptProcessor='[BIN]mod_gsoap.dll',resourceType='File', requireAccess='Execute'[\]]"" Execute="immediate"/>
<CustomAction Id="AddHandlers" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="deferred" Return="check" Impersonate="no"/>
However I get the 404 errors from my web application and when I add the script map manually after installing the application it works perfectly. I would appreciate if anyone could share how they done this.
It sounds like you need to add handler mapping to your web application. If that's the case, then you don't need a custom action to accomplish this.
Take a look at WebApplicationExtension element.
We are writing a separate Custom action in C# and Schedule it in after install Finalize to add handlers.
Try this, you can do anything with your own custom action.
I haven't worked with GSOAP handler, but used ASP.NET ISAPI extention. I suppose basically it has the same level of integration. I will post the code for ASP.NET and I hope you won't have problem with changing it to GSOAP. I have a huge risk of changing something incorrectly and make the examples unusable for you, so I let you do it better.
Here is my code to register extention itself if it isn't
<Component Id="Iis6ConfigExtentions" Guid="{GuidHash({ProductId}-1822E4F3-5850-47D5-9281-D1E0E20C77D4)}" KeyPath="yes" Permanent="yes">
<Condition><![CDATA[Installed OR (IISMAJORVERSION AND (IISMAJORVERSION = "#6"))]]></Condition>
<iis:WebServiceExtension Id="ExtensionASP4" Group="ASP.NET v4.0.30319" Allow="yes" File="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Description="ASP.NET v4.0.30319"/>
</Component>
and for adding those to the concrete site you need to use WebApplicationExtention, as #Yan already mentioned:
<iis:WebApplication Id="IIS6WebApp" Name="[SITE_APP_NAME]" WebAppPool="AppPool" >
<iis:WebApplicationExtension Verbs="GET,HEAD,POST" CheckPath="no" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" />
</iis:WebApplication>
The CheckPath="no" is required on IIS 6 because we use ASP.NET MVC which doesn't map urls to files on disk. You might need it too since service address probably doesn't map to physical file.
UPDATE:
Looks like it is really not possible to do it directly in WIX (at least in 3.5 version). You are going in right direction - custom action using appcmd. But the provided custom actions aren't complete or what you need: the first one just sets property value to the command you need to execute and the second one calls some Dll custom action which wasn't provided.
What I'd try to execute is:
<CustomAction Id="AddGsoapHandler" Directory="TARGETDIR" Impersonate="no" Execute="deferred" Return="check" ExeCommand="[SystemFolder]inetsrv\appcmd.exe set config -section:system.webServer/handlers /+"[\[]name='GSOAP',path='*.dll', verb='*',modules='IsapiModule',scriptProcessor='[BIN]mod_gsoap.dll',resourceType='File', requireAccess='Execute'[\]]" /commit:apphost" />
Please pay attention to the path attribute of your handler configuration. It will limit handler usage to only URL's which ends with ".dll" - I just copied it from your code but not sure if you've done it intentionally or just copied from example at Microsoft site. That might have led to 401 error even if handler is registered successfully.
Also I added the /commit:apphost argument. Not sure if you really need this, but I use it to explicitly specify that I need to configure my application's config.
UPDATE 2:
If you need to set configuration to only single application, you should specify application object as argument of appcmd instead of the 'config' which means global configuration. Please try this:
<CustomAction Id="AddGsoapHandler" Directory="TARGETDIR" Impersonate="no" Execute="deferred" Return="check" ExeCommand="[SystemFolder]inetsrv\appcmd.exe set app "MySite/app1" -section:system.webServer/handlers /+"[\[]name='GSOAP',path='*.dll', verb='*',modules='IsapiModule',scriptProcessor='[BIN]mod_gsoap.dll',resourceType='File', requireAccess='Execute'[\]]" /commit:apphost" />
For more details you can read this tutorial.

How to serve static web pages from a JSF application using Glassfish

I have a JSF 2.0 application running under Glassfish 3.1.1 and I need to serve static web pages that are physically located apart from my application root that is created when my WAR file is deployed. I've found various references (like this one and this one) to defining an alternate docroot using a tag such as
<property name="alternatedocroot_1" value="from=/myimages/* dir=/images"/>
added to sun-web.xml (which I would presume means glassfish-web.xml in Glassfish 3.1.1). I can't seem to get it to work, however. In my case, I think the problem is that glassfish-web.xml simply isn't defining enough context for the alternate docroot to have any meaning:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app error-url="">
<class-loader delegate="true"/>
<jsp-config>
<property name="keepgenerated" value="true">
<description>Keep a copy of the generated servlet class' java code.</description>
</property>
</jsp-config>
<property name="alternatedocroot_1" value="from=/myimages/* dir=/images"/>
</glassfish-web-app>
The meat of my application definition seems to be much more in web.xml than glassfish-web.xml. Do I perhaps need to put something there instead to reference the alternate docroot?
This exact configuration requires a /images/myimages folder relative to the absolute root of the same disk as where the JVM of the webserver is running in. A file foo.png in that folder is then available by http://localhost:8080/contextname/myimages/foo.png.
A common confusion around this setting is that the from attribute is been interpreted as alone the context path in URL and not as an actual subfolder in the dir location. But this is thus not true.
If you're running Glassfish on a Windows environment then you need to specify the disk letter in the dir as well like so dir=C:/images.

does JWS support to launch One-Jar

Can I supposed to launch One-Jar using JWS? One-JAR provides custom classloader that knows how to load classes and resources from a jars inside an archive whereas in JWS we need to specify each JAR that is being used in resources.
What I supposed to specify in JNLP if I am trying to launch One-Jar -
<?xml version="1.0" encoding="UTF-8"?>
<jnlp spec="1.0+" codebase="" href="">
<information>
<title>Application</title>
<vendor>ABC</vendor>
</information>
<resources>
<!-- Application Resources -->
<j2se version="1.5+"
href="http://java.sun.com/products/autodl/j2se"/>
<jar href="Application.jar" main="true" download="eager" />
</resources>
<application-desc main-class="com.simontuffs.onejar.Boot">
</application-desc>
<update check="background"/>
<security>
<all-permissions/>
</security>
</jnlp>
My Application JAR that is One-JAR contains -
com\simontuffs\onejar\<contains complied classes> like JarClassLoader$1.class etc
lib/<contains all jar>
OneJar.class
main/<my application's jar>
META-INF\MANIFEST.MF\ <contains >
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.1
Created-By: One-Jar 0.96 Ant taskdef
Main-Class: com.simontuffs.onejar.Boot
One-Jar-Main-Class: com.application.main.Entry
Name: com/simontuffs/onejar/Boot$3.class
SHA1-Digest: +LPrezs+UEFcE3J7QvumcAEO8Z0=
Name: OneJar.class
SHA1-Digest: 28pzzJWqEpLk1xFwJ/jsAav8LyI=
Name: lib/commons-io-1.4.jar
SHA1-Digest: qHYtB+ds/eI5Ulel2ke6fB29Pc4=
etc..etc..
How to specify these com/simontuffs/onejar/Boot$3.class paths in resource ?
Part of the problem of supporting tools like One-Jar is that loading Jars within Jars requires a custom class-loader. By default JWS will use the usual JRE class-loaders - which do not support it.
There are two possible ways that you might get around that (that I know of).
1. Get access to the custom class-loader.
Get an instance of the custom loader used by One-Jar and set it as the context class-loader. This would require a trusted app., but I get the impression that your app. is trusted.
I have no idea if the One-Jar API provides this loader for your own app.'s use.
2. Use some options when building.
From Options and VM Properties..
one-jar.jar.names
true: Recorded classes are kept in directories corresponding to their jar names.
false: Recorded classes are flattened into a single directory.
Duplicates are ignored (first wins)
The 2nd option sounds like a standard 'fat jar'. That should work with the conventional (default) class-loader used by JWS.

Tomcat: Restrict access to localhost for /just one/ webapp

I'm running Tomcat 6 to serve several web apps, most of which are public-facing. But I'd like to restrict access to just one webapp, allowing connections only from localhost.
I can restrict access for all webapps using a valve in context.xml, as described in:
Tomcat Restrict access by IP address
But I can't figure out how to restrict access on a per-app basis. Is there a way to do this with my app's web.xml? Or by adding additional rules to context.xml?
Thanks,
-B
Recapping Solution:
$ cp /var/lib/tomcat6/conf/context.xml \
/var/lib/tomcat6/conf/Catalina/localhost/my-app-name.xml
$ cat /var/lib/tomcat6/conf/Catalina/localhost/my-app-name.xml
<Context>
<Valve className="org.apache.catalina.valves.RemoteHostValve" allow="localhost"/>
... {as previously} ...
</Context>
You can create an individual context.xml for you app.
This is an excerpt from Tomcat doc on context configuraion:
Context elements may be explicitly defined:
In the $CATALINA_HOME/conf/context.xml file: the Context element information will be loaded by all webapps.
In the $CATALINA_HOME/conf/[enginename]/[hostname]/context.xml.default file: the Context element information will be loaded by all webapps of that host.
In individual files (with a .xml extension) in the $CATALINA_HOME/conf/[enginename]/[hostname]/ directory. The name of the file (less the .xml) extension will be used as the context path. Multi-level context paths may be defined using #, e.g. foo#bar.xml for a context path of /foo/bar. The default web application may be defined by using a file called ROOT.xml.
Only if a context file does not exist for the application in the $CATALINA_HOME/conf/[enginename]/[hostname]/; in an individual file at /META-INF/context.xml inside the application files. If the web application is packaged as a WAR then /META-INF/context.xml will be copied to $CATALINA_HOME/conf/[enginename]/[hostname]/ and renamed to match the application's context path. Once this file exists, it will not be replaced if a new WAR with a newer /META-INF/context.xml is placed in the host's appBase.
Allowing localhost didn't work for me. I use RemoteAddrValve instead. Keep in mind that some systems use IPv4 addresses (your filter has to match match 127.0.0.1) while others use IPv6 addresses (match the full address, not abbreviated notations like ::1).
<Context>
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="0:0:0:0:0:0:0:1,127\.0\.0\.1" />
</Context>
The attribute allow takes a regexp, so dots need to be escaped. As explained by Dmitry Negoda, this goes in /META-INF/context.xml.
Goto following path: C:\Program Files\Apache Software Foundation\Tomcat 6.0\conf\Catalina\localhost\
Under this path you find " manager.xml " file.
Edit " manager.xml " file,with following content:
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127.0.0.1,10.100.1.2"/>
<!-- Link to the user database we will get roles from
<ResourceLink name="users" global="UserDatabase"
type="org.apache.catalina.UserDatabase"/>
-->
****** save and run server....You got it.
NOTE : 127.0.0.1 MEANS YOUR SYSTEM IP
10.100.1.2 -THIS IS YOUR FRIEND

Resources