Showing posts with label tomcat. Show all posts
Showing posts with label tomcat. Show all posts

Monday, November 26, 2007

Deploying Web Applications Outside the Tomcat Install Directory

Suppose you have Tomcat installed somewhere, e.g. /opt/tomcat6 or /usr/local/tomcat5. Now you want to give users the ability to deploy their applications and start/ stop the Tomcat server if needed (e.g. in case of an unrecoverable crash of the Tomcat server). You can start modifying the Tomcat install directory to support multiple users (e.g. in a given group) to write in the webapps directory and to be able to deploy context files under conf and maybe add dummy users to the tomcat-users.xml to test authentication. But that is breaking open the main Tomcat install directory and potentially allowing users to break things. Another drawback is that having a single instance will cause all applications running on this instance to be unavailable if the server is shut down.

I will describe an alternative way of supporting multiple users using the Tomcat install, by means of each user/ application having its own server. The drawback of this approach is that multiple port numbers are needed and this requires a bit of coordination between users. The advantages however are that not a single change is needed in the Tomcat installation directory whatsoever, users can start stop their own instances without impacting other users/ applications and .

First you'll need a strategy of where to put the user's or application's Tomcat stuff. You could use /app or /opt, e.g. /app/user1, /app/user2, /opt/application1, etc. /app might be a better choice as some software installs in /opt and this could potentially cause the user's/ application's Tomcat stuff to be mixed with other software that you may or want to have installed in /opt. In this example we will have Tomcat 6 installed in /usr/local/tomcat6 and use /app for the applications. We will have two applications, acmeweb (Acme corporation's E-Business website) and acmehr (Acme corporation's human resources application). Create users acmeweb and acmehr with groups acmeweb and acmehr respectively and set their passwords:

adduser --system --shell /bin/bash --group acmeweb
adduser --system --shell /bin/bash --group acmehr
passwd acmeweb
passwd acmehr


Underneath /app create the acmeweb directory. Underneath acmeweb, create the directories bin, conf, webapps, temp, work and logs:

mkdir -p /app/acmeweb
cd /app/acmeweb
mkdir bin conf webapps temp work logs


Copy the following Tomcat scripts to bin:

cd /usr/local/tomcat6/bin/
cp startup.sh shutdown.sh setclasspath.sh catalina.sh /app/acmeweb/bin


Copy the following Tomcat configuration files to conf:

cd ../conf
cp server.xml web.xml /app/acmeweb/conf


Recursively copy /app/acmeweb to /app/acmehr:

cd /app
cp -R acmeweb acmehr


Recursively change ownership of the directories to the proper user and group:

chown -R acmeweb.acmeweb acmeweb
chown -R acmehr.acmehr acmehr


Set the user to acmeweb and create a .profile:

su - acmeweb
vi .profile


Put the following contents in the .profile:

export JAVA_HOME=/usr/local/java6
export CATALINA_HOME=/usr/local/tomcat6
export CATALINA_BASE=/app/acmeweb


Logout and set user to acmeweb again. Edit /app/acmeweb/conf/server.xml:

ctrl-d
su - acmeweb
vi /app/acmeweb/conf/server.xml


Modify the server, AJP and HTTP connector ports and change them to 20105, 20109 and 20180 respectively (you may also consider turning off the AJP connector altogether):

<Server port="20105" shutdown="SHUTDOWN">
...
<Connector port="20180" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />
...
<Connector port="20109" protocol="AJP/1.3" redirectPort="8443" />


Create a ROOT context, add an index.html to the context and start Tomcat:

cd /app/acmeweb
mkdir webapps/ROOT
echo Acme Web > webapps/ROOT/index.html
bin/startup.sh


Logout as the acmeweb user, set the user to acmehr and create a .profile:

ctrl-d
su - acmehr
vi .profile


Put the following contents in the .profile:

export JAVA_HOME=/usr/local/java6
export CATALINA_HOME=/usr/local/tomcat6
export CATALINA_BASE=/app/acmehr


Logout and set user to acmehr again. Edit /app/acmehr/conf/server.xml:

ctrl-d
su - acmehr
vi /app/acmehr/conf/server.xml


Modify the server, AJP and HTTP connector ports and change them to 20205, 20209 and 20280 respectively (you may also consider turning off the AJP connector altogether):

<Server port="20205" shutdown="SHUTDOWN">
...
<Connector port="20280" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />
...
<Connector port="20209" protocol="AJP/1.3" redirectPort="8443" />


Create a ROOT context, add an index.html to the context and start Tomcat:

cd /app/acmehr
mkdir webapps/ROOT
echo Acme HR > webapps/ROOT/index.html
bin/startup.sh


Open a browser and connect to the IP address of your Tomcat server, ports 20180 and 20280:

http://<tomcat server ip address>:20180/
http://<tomcat server ip address>:20280/


The first should display Acme Web in your browser, the second Acme HR. Both instances are running under their own user, these users can restart their own instance of Tomcat and the configuration of the server can be done by those users:

acmeweb 5295 1 0 23:02 ? 00:00:04 /usr/local/java6/bin/java -Djava.endorsed.dirs=/usr/local/tomcat6/endorsed -classpath :/usr/local/tomcat6/bin/bootstrap.jar:/usr/local/tomcat6/bin/commons-logging-api.jar -Dcatalina.base=/app/acmeweb -Dcatalina.home=/usr/local/tomcat6 -Djava.io.tmpdir=/app/acmeweb/temp org.apache.catalina.startup.Bootstrap start

acmehr 5333 1 0 23:05 ? 00:00:03 /usr/local/java6/bin/java -Djava.endorsed.dirs=/usr/local/tomcat6/endorsed -classpath :/usr/local/tomcat6/bin/bootstrap.jar:/usr/local/tomcat6/bin/commons-logging-api.jar -Dcatalina.base=/app/acmehr -Dcatalina.home=/usr/local/tomcat6 -Djava.io.tmpdir=/app/acmehr/temp org.apache.catalina.startup.Bootstrap start


In this setup the main Tomcat install directory in /usr/local/tomcat6 remains untouched. The trick is setting the CATALINA_HOME to the tomcat install in /usr/local/tomcat6 and CATALINA_BASE to the user specific Tomcat directory in /app/acmeweb and /app/acmehr.

Tuesday, November 13, 2007

Solving java.io.FileNotFoundException in Spring and iBATIS

I'm happily hacking about with iBATIS Spring DAO classes. I exposed the DAO classes using RPC services with GWT and the GWT server library and I run these RPC services in Tomcat. I also wanted to have unit tests that test my DAO classes, and there is this wonderful thing called gienah-testing on Google code, that allowed me to use Spring to inject the DAO classes into my unit test classes.

Now at this stage I started to get a FileNotFoundException on either my Spring configuration files or my SqlMapConfig.xml. When I referred to the configuration files using the scheme "/com/acme/config.xml", this caused loading the SqlMapConfig configuration to fail when running in Tomcat. I could solve the problem in Tomcat by referring to "WEB-INF/classes/com/acme/config.xml", but this caused my gienah-testing unit test classes to fail with a FileNotFoundException on the Spring configuration.

To solve this problem, I had to make sure that everything was loaded from the classpath using the ClassPathXmlApplicationContext. This is very simple, just prefix the location of the configuration file with "classpath:" without the quotes, e.g. "classpath:/com/acme/config.xml". This way both the gienah-test unit tests will load the configuration from the classpath using the classloader (e.g. in /classes if that were to be your output folder in Eclipse) and the web container will also load the configuration from the classpath using the classloader (in this case WEB-INF/classes). Here is a snippet of the gienah-testing test class, where data.xml is the configuration file containing the iBATIS Spring DAO configuration:

@RunWith(value = SpringRunner.class)
@Configuration(locations = {"classpath:/com/acme/data/data.xml"
"classpath:/com/acme/test/test.xml"})
public class AcmeTest {


All other configuration files also refer to any underlying configuration files using the "classpath:" prefix, again in order to make sure that the ClassPathXmlApplicationContext is used. This is a snippet from the web.xml, injecting data.xml into the application context configuration file location:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:/com/acme/data/data.xml</param-value>
</context-param>


The data.xml containing the iBATIS Spring DAO configuration injects the SqlMapConfig.xml, also with the "classpath:" prefix, as shown in the snippet below:

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
    <property name="dataSource" ref="myDataSource" />
    <property name="configLocation" value="classpath:/com/acme/data/SqlMapConfig.xml" />
</bean>


This should solve any FileNotFoundException problems in both the gienah-testing unit tests and when running in Tomcat. It works like a charm for me :-)