Deploying multiple Tomcat instances

You can always install more than one tomcat and change the ports and run many instances you want , However there is an easier way of doing it but it works only for tomcat 6.

simply start by downloading apache tomcat 6.
in the same directory of your tomcat create folder and name it as shared. and inside this folder create two folders, conf and logs. you can use conf folder to keep common configuration files for your multiple tomcat instances such as server.xml,tomcat-users.xml.
you can have a server.xml file as follows :

 <?xml version='1.0' encoding='utf-8'? >
 <Server port="${shutdown.port}" shutdown="SHUTDOWN" >

   <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" / >
   <Listener className="org.apache.catalina.core.JasperListener" / >
   <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" / >
   <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" / >

   <GlobalNamingResources >
     <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="${catalina.base}/../shared/conf/tomcat-users.xml" / >

     <Resource name="${tomcat.jndi.resources.name}" auth="${tomcat.jndi.resources.auth}"
	      type="${tomcat.jndi.resources.type}"
	      driverClassName="${tomcat.jndi.resources.driverclassname}"
	      url="${tomcat.jndi.resources.url}"
	      username="${tomcat.jndi.resources.username}"
	      password="${tomcat.jndi.resources.password}"
	      maxActive="${tomcat.jndi.resources.maxactive}"
	      maxIdle="${tomcat.jndi.resources.maxidle}"
	      maxWait="${tomcat.jndi.resources.maxwait}" / >		

   </GlobalNamingResources >

   <Service name="Catalina" >

     <Connector port="${http.port}" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" / >
     <Engine name="Catalina" defaultHost="localhost" >

       <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"/ >

       <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true"
            xmlValidation="false" xmlNamespaceAware="false" >
       </Host >
     </Engine >
   </Service >
 </Server >

The variables ${XXX} should be defined specifically for each tomcat instance.

in the same directory of your tomcat create a folder and name it as tomcat_instance1. it should have the same directory structure as normal tomcat installation.
so in the bin directory create a file called setenv.sh. this file is used to set environment variables for this specific tomcat instance. the content of the file can be as follows

CATALINA_OPTS="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError"

CATALINA_HOME=/usr/share/tomcat6/apache-tomcat-6.0.20
CATALINA_BASE=/usr/share/tomcat6/tomcat_instance_1
JAVA_HOME=/opt/java/jdk
CATALINA_PID=$CATALINA_BASE/logs/tomcat.pid

In the conf directory of tomcat_instance1 create a file called catalina.properties. this file is used to store the values for the variables refered in server.xml in shared directory.
the file content is as follows :

#changing the ports for your tomcat instance is just a matter of changing these lines below
shutdown.port=8005
http.port=8080
jmx.port=6969

package.access=sun.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.,sun.beans.
package.definition=sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.
common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
tomcat.util.buf.StringCache.byte.enabled=true

#this is jndi settings for the database, remember these variables are used in server.xml in shared directory
tomcat.jndi.resources.name=jndiname
tomcat.jndi.resources.auth=Container
tomcat.jndi.resources.type=javax.sql.DataSource
tomcat.jndi.resources.driverclassname=org.postgresql.Driver
tomcat.jndi.resources.url=jdbcurl
tomcat.jndi.resources.username=username
tomcat.jndi.resources.password=password
tomcat.jndi.resources.maxactive=100
tomcat.jndi.resources.maxidle=30
tomcat.jndi.resources.maxwait=10000

the only thing you need more is run.sh file which you should place it in the same directory of your tomcat6 intsallation. and the content of the file is as follows :

#!/bin/bash

syntax () {
  echo "Usage:"
  echo "./run.sh [start|stop|run] [instance number]"
}

PRG="$0"

while [ -h "$PRG" ]; do
  ls=`ls -ld "$PRG"`
  link=`expr "$ls" : '.*-> \(.*\)$'`
  if expr "$link" : '/.*' > /dev/null; then
    PRG="$link"
  else
    PRG=`dirname "$PRG"`/"$link"
  fi
done

# Get standard environment variables
PRGDIR=`dirname "$PRG"`

#Absolute path
PRGDIR=`cd "$PRGDIR" ; pwd`

#Setup CATALINA_HOME to point to our binaries
[ -z "$CATALINA_HOME" ] && CATALINA_HOME=`cd "$PRGDIR/apache-tomcat-6.0.20" ; pwd`

#Make sure the instance directory exists.
if [ -z "$2" ]; then
  echo "Second argument must be an instance number."
  syntax
  exit 1
elif [ ! -r "$PRGDIR/tomcat_inst_$2" ]; then
  echo "Instance directory $PRGDIR/tomcat_inst_$2 does not exist."
  exit 1
fi

#Setup the instance flag
CATALINA_INSTANCE=tomcat_inst_$2
JAVA_OPTS="$JAVA_OPTS -Dcatalina.tomcat_inst=$2"

#Setup the Tomcat logger
JAVA_OPTS="$JAVA_OPTS -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager"

#Setup our CATALINA_BASE flag
CATALINA_BASE="$PRGDIR/tomcat_inst_$2"

#Setup our Process ID tracker
CATALINA_PID="$PRGDIR/shared/logs/tomcat_inst_$2.pid"

#Setup logging
LOGGING_CONFIG="-Djava.util.logging.config.file=$PRGDIR/shared/conf/logging.properties"

export CATALINA_HOME
export CATALINA_BASE
export CATALINA_PID
export LOGGING_CONFIG
export JAVA_OPTS

#SCRIPT_TO_RUN=$CATALINA_HOME/bin/echo.sh
SCRIPT_TO_RUN=$CATALINA_HOME/bin/catalina.sh

if [ -r $CATALINA_BASE/conf/server.xml ] ; then
  SERVER_CONFIG=$CATALINA_BASE/conf/server.xml
else
  SERVER_CONFIG=$PRGDIR/shared/conf/server.xml
fi

if [ "$1" = "start" ] ; then
  $SCRIPT_TO_RUN start -config $SERVER_CONFIG
elif [ "$1" = "run" ] ; then
  $SCRIPT_TO_RUN run -config $SERVER_CONFIG
elif [ "$1" = "stop" ] ; then
  $SCRIPT_TO_RUN stop -config $SERVER_CONFIG
else
  echo "Invalid first parameter"
  syntax
  exit 1
fi

And you can run your tomcat instance with this command : ./run.sh start 1
Hope it works for you too :)

Leave a Comment

Storing Large Data – Yay! Streaming !!!

The information below is written for Postgres 8.3, Spring 2.0 and Hibernate 3.0

Dealing with large data in database has always been an issue. There are various approaches to store and fetch large data in database. The simple and basic one is storing data as bytea and retrieving it as it is needed. The problem in this approach is that, because the size of the data is too large, loading it in a byte array as it is needed would cause an OutOfMemory error sooner or later.
As far as I know there are two ways of avoiding this problem but both ways have also their own limitations.

First one is storing large data as bytea in database and fetching it via stream. The downside of this method is a column type of bytea can hold up to 1 GB of binary data.

The first thing to do is to find out a way to read large data via input stream. By using jdbc it can be easily done but in Hibernate it is a bit tricky.

Let’s start with defining the entity class and its mapping file.

package streaming.entity;

import streaming.BlobStream;

public class Foo {
	private String id;
	private BlobStream data;
 //setter - getter
}
package streaming.type;
import java.io.InputStream;

public class BlobStream {
	private InputStream stream;
	//length of the data to be read
	private int length;
}

Here BlogStream is neither an entity nor a type class. It is just a simple class which is created to hold the inputstream and the length together.

the mapping file for Foo entity would be like this :

<hibernate-mapping package="streaming.entity">
  <class name="Foo">
     <id column="id" length="36" name="id">
      <generator class="uuid"/>
    </id>
    <property name="data"  type="CustomBlobType">
         <column name="data" sql-type="bytea"></column>
    </property>
  </class>
</hibernate-mapping>

As you may notice hibernate type of the field data is defined as “CustomBlobType” whereas sql_type is defined as bytea.
The reason for doing this is we want to create data column as bytea but we also want CustomBlobType class to be responsible for reading and writting to this column.

FYI : If this sql_type was not defined Hibernate would create this column as an OID which is basically a long value references to the actual Large Object. But Spring’s DefaultLobHandler does not support handling large objects in postgres 8.3 as it calls ps.setBinaryStream(paramIndex, binaryStream, contentLength); and bytea is tried to be inserted into a long type column. So this is a trick which forces Hibernate to create the inputStream type field as bytea in database.
Actually according to the Postgres documentation the releases prior to 7.2 ps.setBinaryStream and ps.getBinaryStream methods operated on the oid data type associated with large objects but with 7.2 release it has changed to bytea. Probably this is the reason behind the failure of DefaultLobHandler class on handling large objects for postgres 8.3.

After this has done, CustomBlobType class has to be created and it should look like this :

package streaming.type;

import java.io.InputStream;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import streaming.type.BlobStream;

import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.orm.hibernate3.support.AbstractLobType;

public class CustomBlobType extends AbstractLobType {

	//called when the column is to be retrieved from db
	protected Object nullSafeGetInternal(ResultSet rs, String[] names, Object owner, LobHandler lobHandler)
					throws SQLException {

		InputStream is = lobHandler.getBlobAsBinaryStream(rs, names[0]);

		return new BlobStream(is);
	}

	//called when the data is to be stroed in db
	protected void nullSafeSetInternal(PreparedStatement ps, int index, Object value, LobCreator lobCreator)
					throws SQLException {
		if (value != null) {
			BlobStream blob = (BlobStream) value;
			lobCreator.setBlobAsBinaryStream(ps, index, blob.getStream(), blob.getLength());

		} else {
			lobCreator.setBlobAsBytes(ps, index, null);
		}
	}

	/**
	 * defines what type of object that this class returns or gets as a a parameter
	 * when the above methods are called.
	 *
	 * @return Class
	 */
	public Class returnedClass() {
		return BlobStream.class;
	}

	/**
	 * Return the SQL type codes for the columns mapped by this type. The codes
	 * are defined on <tt>java.sql.Types</tt>.
	 *
	 * @see java.sql.Types
	 * @return int[] the typecodes
	 */
	public int[] sqlTypes() {
		return new int[] { Types.BLOB };
	}
}

So each time our Foo object is persisted “nullSafeSetInternal” method is called for the field “data” by the hibernate process. Within this method “lobCreator” saves the inputstream as a binary stream on to the statement object.
If “DefaultLobHandler” is used then basically what lobCreator does here is like that :

	ps.setBinaryStream(paramIndex, binaryStream, contentLength);

DefaultLobHandler is a handler which provides support for handling large objects with different database types and it has to be added to sessionFactory as lob handler which can be done simply like this :

 <bean id="defaultLobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler" />
 <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean" >
		<property name="lobHandler" ref="deafultLobHandler"/>
		<property name="dataSource" ref="dataSource" />
		<property name="mappingLocations" ref="mappingLocations"/>
		<property name="hibernateProperties">
		...
		</property>
</bean>

One last thing is defining “CustomBlobType” as a Hibernate type which can be done simply by using typedef element.

<typedef name="CustomBlobType" class="streaming.type.CustomBlobType"></typedef>

Second way of stroring large data is using postgres Large Object. The good thing is there is not a file limit because the data that you store in your table is just a reference to the original data. However when the data needs to deleted, deleting the row is not enough, a special deleting process has to be done. Despite all this to implement Large Object handling for postgres in Spring we need a new handler but defining a new handler would break the database independence of the code as you end up definining certain LobHandlers for certain database types.

Considering all these issues if you are determined to use Large Objects, you can define Postgres Lob Handler like this :

package streaming.handler;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.apache.commons.dbcp.PoolableConnection;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.postgresql.PGConnection;
import org.postgresql.jdbc3.Jdbc3Connection;
import org.postgresql.largeobject.LargeObject;
import org.postgresql.largeobject.LargeObjectManager;
import org.springframework.jdbc.support.lob.AbstractLobHandler;
import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor;

public class PostgresLobHandler extends AbstractLobHandler {

	protected final Log logger = LogFactory.getLog(getClass());

	private NativeJdbcExtractor nativeJdbcExtractor;

	/**
	 * @param nativeJdbcExtractor
	 *            the nativeJdbcExtractor to set
	 */
	public void setNativeJdbcExtractor(NativeJdbcExtractor nativeJdbcExtractor) {
		this.nativeJdbcExtractor = nativeJdbcExtractor;
	}

	public InputStream getBlobAsBinaryStream(ResultSet rs, int columnIndex) throws SQLException {

		InputStream is = null;
		try {
			PGConnection pgConn = getPostgresConnection(rs.getStatement());
			if (pgConn != null) {
				// Get the Large Object Manager to perform operations with
				LargeObjectManager lobj = pgConn.getLargeObjectAPI();

				long oid = rs.getLong(columnIndex);
				LargeObject obj = lobj.open(oid, LargeObjectManager.READ);
				is = obj.getInputStream();
			}

		} catch (ClassNotFoundException e) {
			this.logger.error("Error", e);
		}
		return is;
	}
	//other methods

	/**
	 * Retrieve the underlying PGConnection, using a NativeJdbcExtractor if set.
	 */
	protected PGConnection getPostgresConnection(Statement st) throws SQLException, ClassNotFoundException {

		PGConnection pgConn = null;
		Connection conn = (this.nativeJdbcExtractor != null) ? this.nativeJdbcExtractor
						.getNativeConnectionFromStatement(st) : st.getConnection();

		if (conn instanceof PoolableConnection) {
			PoolableConnection poolConn = (PoolableConnection) conn;
			Connection innermostDelegate = poolConn.getInnermostDelegate();
			if (innermostDelegate instanceof PGConnection) {
				pgConn = (PGConnection) innermostDelegate;

			}
		}
		return pgConn;
	}

	protected class PostgresLobCreator implements LobCreator {

		public void setBlobAsBytes(PreparedStatement ps, int paramIndex, byte[] content) throws SQLException {

			ps.setBytes(paramIndex, content);
			if (PostgresLobHandler.this.logger.isDebugEnabled()) {
				PostgresLobHandler.this.logger.debug(content != null ? "Set bytes for BLOB with length "
								+ content.length : "Set BLOB to null");
			}
		}

		public void setBlobAsBinaryStream(PreparedStatement ps, int paramIndex, InputStream binaryStream,
						int contentLength) throws SQLException {

			try {
				// All LargeObject API calls must be within a transaction block
				PGConnection pgConn = getPostgresConnection(ps);
				if (pgConn != null) {

					// Get the Large Object Manager to perform operations with
					LargeObjectManager lobj = pgConn.getLargeObjectAPI();

					// Create a new large object
					long oid = lobj.createLO(LargeObjectManager.READ | LargeObjectManager.WRITE);

					// Open the large object for writing
					LargeObject obj = lobj.open(oid, LargeObjectManager.WRITE);

					// Copy the data from the file to the large object
					byte buf[] = new byte[2048];
					int s, tl = 0;
					while ((s = binaryStream.read(buf, 0, 2048)) > 0) {
						obj.write(buf, 0, s);
						tl += s;
					}

					// Close the large object
					obj.close();
					ps.setLong(paramIndex, oid);
				}
			} catch (ClassNotFoundException e) {
				PostgresLobHandler.this.logger.error("Error", e);
			} catch (IOException ex) {
				PostgresLobHandler.this.logger.error("Error", ex);
			}

		}

		// other methods

		public void close() {
			PostgresLobHandler.this.logger.debug("Closing DefaultLobCreator");
			// nothing to do here
		}
	}
}

to enable PostgresLobHandler to handle Large Objects

 <bean id="postgresLobHandler" class="streaming.handler.PostgresLobHandler" />
 <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean" >
		<property name="lobHandler" ref="postgresLobHandler"/>
		<property name="dataSource" ref="dataSource" />
		<property name="mappingLocations" ref="mappingLocations"/>
		<property name="hibernateProperties">
		...
		</property>
</bean>

Now we don’t have to force Hibernate to create the column as bytea so the mapping xml will be like this :

<hibernate-mapping package="streaming.entity">
  <class name="Foo">
     <id column="id" length="36" name="id">
      <generator class="uuid"/>
    </id>
    <property name="data"  type="CustomBlobType"/>
  </class>
</hibernate-mapping>

References :
http://turgaykivrak.wordpress.com/tag/spring/
http://www.postgresql.org/docs/7.2/static/jdbc-binary-data.html

Leave a Comment

Older Posts »