How to pass parameters in EL methods

This article describes how to pass any number of parameters in EL functions in 3 simple steps.

Step 1: Extend the class ELMethod

Extend the class ELMethod.java, and implement the following method:

public abstract Object result(Object[] args);

For example, say we want to create a formatDate method which takes 2 arguments

  1. the format String
  2. the Date to be formatted

All you need to do is extend the ELMethod class and implement the result(Object[] args) method.

Example

import java.text.SimpleDateFormat;
import java.util.Date;

public class FormatDate extends ELMethod {
 private static final long serialVersionUID = 1L;

 public FormatDate() {
   // number of arguments to the result method
   super(2);
 }

 public Object result(Object[] args) {
   String pattern = (String) args[0];
   Date date = (Date) args[1];

   return new SimpleDateFormat(pattern).format(date);
 }
}

Step 2: Add an instance of you class as an application scoped bean

There are many ways to do this, depending on this technology you are using. Here are some examples:

Set request scope in JSP

request.setAttribute("formatDate", new FormatDate());

Set session scope in JSP

session.setAttribute("formatDate", new FormatDate());

Set application scope from Servlet

this.getServletContext().setAttribute("formatDate", new FormatDate())

Set application scope from JSP

<jsp:useBean id="formatDate" class="FormatDate" scope="application" />

Set application scope in Seam

Add these annotations to your class

@Name("formatDate")
@Scope(ScopeType.APPLICATION)

Step 3: Call the function via map syntax

Simply pass each argument using the map syntax [] as follows

EL code

${formatDate["MMM, dd"][account.creationDate]}

All arguments passed via EL are sent to the result(Object[] args) method of the FormatDate class.

How does it work

Each argument is collected via the get(Object key) method and is added to an array. When all the arguments have been specified the result method is invoked and the resulting value is returned.

Reference

  • Source code of ELMethod.java on my Google Code project. This class is thread safe and can be application scoped.
  • FormatDate.java example on my Google Code project
  • Add.java example, which adds 2 numbers in EL (e.g. ${add[1][2]})

How to automatically recover Tomcat from crashes

Tomcat occasionally crashes if you do frequent hot-deploys or if you are running it on a machine with low memory. Every time tomcat crashes someone has to manually restart it, so I wrote a script which automatically detects that tomcat has crashed and restarts it.

Here’s the pseudo logic:

every few minutes {
  check tomcat status;

  if (status is "not running") {
    start tomcat;
  }
}

Here’s a shell script to implement the above logic. It assumes that you are running on a unix/linux system and have /etc/init.d/tomcat* script setup to manage tomcat.

Adjust the path to “/etc/init.d/tomcat” in the script below to reflect the correct path on your computer. Sometimes it is called /etc/init.d/tomcat5 or /etc/init.d/tomcat6 depending on your tomcat version. Also make sure that the message “Tomcat Servlet Container is not running.” matches with the message that you get when you run the script when tomcat is stopped.

#! /bin/sh
SERVICE=/etc/init.d/tomcat
STOPPED_MESSAGE="Tomcat Servlet Container is not running."

if [ "`$SERVICE status`" == "$STOPPED_MESSAGE"];
then
{
  $SERVICE start
}
fi

To run the script every 10 minutes:

1. Save the above script to “/root/bin/recover-tomcat.sh”.

2. Add execute permission:

chmod +x /root/bin/recover-tomcat.sh

3. Add this to root’s crontab, type the following as root:

crontab -e

4. Add the following lines to the crontab:

# monitor tomcat every 10 minutes
*/10 * * * * /root/bin/recover-tomcat.sh

What if I don’t have /etc/init.d/tomcat* script on my computer?

Tomcat creates a pid file, typically in the TOMCAT_HOME/bin directory. This file contains the process id of the tomcat process running on the machine. The pseudo logic in that case would be:

if (the PID file does not exist) {
  // conclude that tomcat is not running
  start tomcat
}
else {
  read the process id from the PID file
  if (no process that id is running) {
    // conclude that tomcat has crashed
    start tomcat
  }
}

You can implement the above logic as follows. The following is experimental and is merely a suggested way, test it on your computer before using it.

# adjust this to reflect tomcat home on your computer
TOMCAT_HOME=/opt/tomcat5

if [ -f $TOMCAT_HOME/bin/tomcat.pid ]
then
  echo "PID file exists"
  pid="`cat $TOMCAT_HOME/bin/tomcat.pid`"
  if [ "X`ps -p $pid | awk '{print $1}' | tail -1`" = "X"]
  then
    echo "Tomcat is running"
  else
    echo "Tomcat had crashed"
    $TOMCAT_HOME/bin/startup.sh
  fi
else
  echo "PID file does not exist. Restarting..."
  $TOMCAT_HOME/bin/startup.sh
fi

Why would tomcat crash?

The most common reason is low memory. For example, if you have allocated 1024MB of max memory to tomcat and enough memory is not available on that machine. Other reasons may involve repeated hot-deploys causing memory leaks, rare JVM bugs causing the JVM to crash.

Getting started with Nexus Maven Repo Manager

Overview

This tutorial outlines steps required to install Nexus (Maven Repository Manager) under Tomcat, or another webapp container. It shows you practical configuration and includes code snippets that go in your pom.xml and settings.xml in order to read and publish artifacts to your Nexus server.

Step 1: Download

Download Nexus from here (at the time of writing, latest is 1.6.0)

Step 2: Install

Copy the war to TOMCAT_HOME/webapps/nexus.war

Though not required, it is a generally good idea to restart tomcat after installing a new war.

/etc/init.d/tomcat restart

Step 3: Configure security

a) Change default admin password: The default admin username/password is admin/admin123. Login as admin and change the password to a secure password.

Login -> [admin, admin123] -> Left Menu -> Security -> Change Password -> click “Change Password”

b) Anonymous Access: By default Nexus is open to the public. If you want to secure access to nexus, disable ‘Nexus anonymous user’

Admin -> Left Menu -> Users -> ‘Nexus anonymous user’ -> Status=Disabled

c) Deployment user: Change password for deployment user

Admin -> Left menu -> Users -> Deployment user -> Change email address

Admin -> Left menu -> Users -> Right click on ‘Deployment user’ in the user list -> Set Password -> click ‘Set password’ to finish

Step 4: Set SMTP server

It is a good idea to configure SMTP server, so that you can receive emails from Nexus.

Admin login -> Left menu -> Administration -> Server ->SMTP Settings -> (host localhost, port 25, no login, no password mostly works on a linux machine)

Step 5: Change Base Url

If you are running Nexus behind Apache using mod_jk or mod_proxy, change your base url here.

Admin login -> Left menu -> Administration -> Server -> Application Server Settings -> Base url

Step 6: Add a task to periodically remove old snapshots

If you or your CI server publishes snapshots to Nexus several times a day, then you should consider adding a task to delete duplicate/old snapshots for the same GAV (group, artifact, version). If you don’t do this, you will notice that the Nexus disk usage will increase with time.

Admin login -> Left menu -> Administration -> Scheduled tasks -> Add… -> name=”Remove old snapshots”, Repository/Group=Snapshots (Repo), Minimum Snapshot Count=1, Snapshot Retention(days)=3, Recurrence=Daily, Recurring time=2:00 -> click ‘Save’

Step 7: Using Nexus: reading and publishing artifacts

If you want to deploy your artifacts to your Nexus, you need to configure 2 files: pom.xml and settings.xml

a) pom.xml – for each project which wishes to publish to Nexus, add your repo to the pom.xml

<distributionManagement>
 <!-- Publish the versioned releases here -->
 <repository>
  <id>vineetmanohar-nexus</id>
  <name>vineetmanohar nexus</name>
  <url>dav:http://nexus.vineetmanohar.com/nexus/content/repositories/releases</url>
 </repository>

 <!-- Publish the versioned releases here -->
 <snapshotRepository>
  <id>vineetmanohar-nexus</id>
  <name>vineetmanohar nexus</name>
  <url>dav:http://nexus.vineetmanohar.com/nexus/content/repositories/snapshots</url>
 </snapshotRepository>
</distributionManagement>

<!-- download artifacts from this repo -->
<repositories>
 <repository>
  <id>vineetmanohar-nexus</id>
  <name>vineetmanohar</name>
  <url>http://nexus.vineetmanohar.com/nexus/content/groups/public</url>
  <releases>
   <enabled>true</enabled>
  </releases>

  <snapshots>
   <enabled>true</enabled>
  </snapshots>
 </repository>
</repositories>

<!-- download plugins from this repo -->
<pluginRepositories>
 <pluginRepository>
  <id>vineetmanohar-nexus</id>
  <name>vineetmanohar</name>
  <url>http://nexus.vineetmanohar.com/nexus/content/groups/public</url>
  <releases>
   <enabled>true</enabled>
  </releases>
  <snapshots>
   <enabled>true</enabled>
  </snapshots>
 </pluginRepository>
</pluginRepositories>

b) settings.xml – If you have disabled anonymous access to Nexus, add the deployment password to your ~/.m2/repository/settings.xml file

<settings>
 <servers>
  <server>
   <!-- this id should match the id of the repo server in pom.xml -->
   <id>vineetmanohar-nexus</id>
   <username>deployment</username>
   <password>password_goes_here</password>
  </server>
 </servers>
</settings>

What is Google TV … in 2 minutes

Google TV is a Smart TV (or set-top box) which aims to seamlessly combine your Internet and TV experience. It runs Android OS, Chrome browser and Flash 10. Third party developers can create interesting applications and make them available to end users via an App marketplace.

Google TV aims to take TV viewing experience to the next level, similar to what smart phones (Android, iPhone) did for phones. The key here, in my opinion, is creating a framework and open up the TV-internet application space to third-party developers.

Summary

  • Google TV was announced at 2010 Google I/O, in stores Fall 2010
  • Google has partnered with Sony (TVs), Intel (atom processor), Best Buy (sales to end users), Dish Network (Set-top box), Logitech (peripherals) and Adobe (Flash player)
  • Google TV units will be available (in form of TVs and/or Set-top boxes) this fall, well in time for the Christmas season
  • Runs Android OS, Chrome Browser, Flash 10
  • Third party developers can write apps and users can download apps via a Marketplace
  • End users can surf channels on TV as they normally do, and also access relevant internet related apps like Youtube, Picasa and discover apps written by third party developers
  • Sign up here to receive updates about Google TV

This video was embedded using the YouTuber plugin by Roy Tanck. Adobe Flash Player is required to view the video.

2 ways to convert Java Map to String

This article shows 2 ways to convert Java Map to String.

  • Approach 1: simple, lightweight – produces query string like output, but restrictive.
  • Approach 2: uses Java XML bean serialization, more robust but produces overly verbose output.

Approach 1: Map to query string format

Approach 1 converts a map to a query-string like output. Here’s what an output looks like:

name1=value1&name2=value2

Full code:

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

public class MapUtil {
 public static String mapToString(Map<String, String> map) {
   StringBuilder stringBuilder = new StringBuilder();

   for (String key : map.keySet()) {
    if (stringBuilder.length() > 0) {
     stringBuilder.append("&");
    }
    String value = map.get(key);
    try {
     stringBuilder.append((key != null ? URLEncoder.encode(key, "UTF-8") : ""));
     stringBuilder.append("=");
     stringBuilder.append(value != null ? URLEncoder.encode(value, "UTF-8") : "");
    } catch (UnsupportedEncodingException e) {
     throw new RuntimeException("This method requires UTF-8 encoding support", e);
    }
   }

   return stringBuilder.toString();
  }

  public static Map<String, String> stringToMap(String input) {
   Map<String, String> map = new HashMap<String, String>();

   String[] nameValuePairs = input.split("&");
   for (String nameValuePair : nameValuePairs) {
    String[] nameValue = nameValuePair.split("=");
    try {
     map.put(URLDecoder.decode(nameValue[0], "UTF-8"), nameValue.length > 1 ? URLDecoder.decode(
     nameValue[1], "UTF-8") : "");
    } catch (UnsupportedEncodingException e) {
     throw new RuntimeException("This method requires UTF-8 encoding support", e);
    }
   }

   return map;
  }
}

Example usage code

 Map<String, String> map = new HashMap<String, String>();
 map.put("color", "red");
 map.put("symbols", "{,=&*?}");
 map.put("empty", "");
 String output = MapUtil.mapToString(map);
 Map<String, String> parsedMap = MapUtil.stringToMap(output);
 for (String key : map.keySet()) {
  Assert.assertEquals(parsedMap.get(key), map.get(key));
 }

Output with Approach 1

symbols=%7B%2C%3D%26*%3F%7D&color=red&empty=

Caveat

  • Only supports String keys and values.
  • Due to the nature of serialization, null keys and values are not supported. Null will be converted to an empty String. This is because there is no way to distinguish between a null and an empty String in the serialized form. If you need support for null keys and values, use java.beans.XMLEncoder as shown below.

Approach 2: Java Bean XMLEncoder: Map to String

Java provides XMLEncoder and XMLDecoder classes as part of the java.beans package as a standard way to serialize and deserialize objects. This

 Map<String, String> map = new HashMap<String, String>();
 map.put("color", "red");
 map.put("symbols", "{,=&*?}");
 map.put("empty", "");
 ByteArrayOutputStream bos = new ByteArrayOutputStream();
 XMLEncoder xmlEncoder = new XMLEncoder(bos);
 xmlEncoder.writeObject(map);
 xmlEncoder.flush();

 String serializedMap = bos.toString()
 System.output.println(serializedMap);

Output with Approach 2

The serialized value is shown below. As you can see this is more verbose, but can accommodate different data types and null keys and values.

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.5.0_17">
 <object>
  <void method="put">
   <string>symbols</string>
   <string>{,=&amp;*?}</string>
  </void>
  <void method="put">
   <string>color</string>
   <string>red</string>
  </void>
  <void method="put">
   <string>empty</string>
   <string></string>
  </void>
 </object>

Java Bean XMLDecoder: String to Map

 XMLDecoder xmlDecoder = new XMLDecoder(new ByteArrayInputStream(serializedMap.getBytes()));
 Map<String, String> parsedMap = (Map<String, String>) xmlDecoder.readObject();

 for (String key : map.keySet()) {
  Assert.assertEquals(parsedMap.get(key), map.get(key));
 }

Summary

While Java provides a standard (and overly verbose) way to serialize and deserialize objects, this articles discusses an alternative lightweight way to convert a Java Map to String and back. If you are serializing a map with non-null String keys and values, then you should be able to use this alternative way, otherwise use the Java bean serialization.

How to configure multiple page.xml files in Seam 2.2

Overview

Seam lets you specify navigation rules in the pages.xml file which can become very large for apps with many pages and transitions. Seam allows you to break this file into multiple small files. This allows you to easily manage large applications by creating multiple pages.xml files, typically one per page. This article describes how to create multiple pages.xml files in Seam 2.2.

Step 1: Configure /WEB-INF/components.xml

Make sure you declare the navigation namespace at the top of components.xml and each file under <navigation:pages> as follows

<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
 xmlns:core="http://jboss.com/products/seam/core" xmlns:persistence="http://jboss.com/products/seam/persistence"
 xmlns:drools="http://jboss.com/products/seam/drools" xmlns:bpm="http://jboss.com/products/seam/bpm"
 xmlns:security="http://jboss.com/products/seam/security" xmlns:mail="http://jboss.com/products/seam/mail"
 xmlns:web="http://jboss.com/products/seam/web" xmlns:navigation="http://jboss.com/products/seam/navigation"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.2.xsd
 http://jboss.com/products/seam/persistence http://jboss.com/products/seam/persistence-2.2.xsd
 http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-2.2.xsd
 http://jboss.com/products/seam/bpm http://jboss.com/products/seam/bpm-2.2.xsd
 http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.2.xsd
 http://jboss.com/products/seam/mail http://jboss.com/products/seam/mail-2.2.xsd
 http://jboss.com/products/seam/navigation http://jboss.com/products/seam/navigation-2.2.xsd
 http://jboss.com/products/seam/web http://jboss.com/products/seam/web-2.2.xsd
 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd">

 <mail:mail-session host="localhost" tls="false" ssl="false" port="25" />

 <component>
  <property name="createTempFiles">true</property>
  <property name="maxRequestSize">1000000000</property>
 </component>

 <component name="org.jboss.seam.core.init">
  <!-- enabling this slows down Seam significantly -->
  <property name="debug">false</property>
 </component>

 <core:init transaction-management-enabled="false" />

 <security:identity authenticate-method="#{loginController.authenticate}"
  remember-me="true" />

 <event type="org.jboss.seam.security.notLoggedIn">
  <action execute="#{redirect.captureCurrentView}" />
 </event>

 <event type="org.jboss.seam.security.postAuthenticate">
  <action execute="#{redirect.returnToCapturedView}" />
 </event>

 <navigation:pages>
  <navigation:resources>
   <!-- global -->
   <value>/WEB-INF/pages.xml</value>

   <!-- one per page -->
   <value>/WEB-INF/createAccount.page.xml</value>
   <value>/WEB-INF/login.page.xml</value>
  </navigation:resources>
 </navigation:pages>
</components>

Step 2. Save all .page.xml files in WEB-INF directory

WEB-INF/createAccount.page.xml
WEB-INF/login.page.xml

Step 3. Create the .page.xml files.

See the sample file: createAccount.page.xml below. Note that the root tag is <pages>. Make sure that the same page is not repeated in another file.

<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.2.xsd">

 <page view-id="/createAccount.xhtml" login-required="false">
 <!-- Create account -->
 <navigation
  from-action="#{createAccountController.createAccountFormCreateAccount}">

   <!-- Account created -->
   <rule if-outcome="accountCreated">
    <redirect view-id="/login.xhtml">
     <message>Your account has been created. Please log in.</message>
    </redirect>
   </rule>
  </navigation>
 </page>
</pages>

FAQ

Can I use Seam 2.0 style <view id>.page.xml files?

You should be able to use viewid.page.xml xml files according to the examples that ship with Seam 2.2.0. However, some users have complained that it doesn’t work in Seam 2.1. I could not get it to work with Seam 2.2.0 as well.

Spring classpath scan breaks when migrating from JBoss4 to JBoss5

Spring 2.5 classpath scan does not work on JBoss5 due to internal architecture changes in JBoss5. You may face this issue when migrating from JBoss4 to JBoss5. This article walks you through a simple fix, with code snippets.

Classpath scan issue

In JBoss 4, your application was probably using Spring 2.5.6 with classpath scan to load spring beans (as shown below). When migrating your code to JBoss5, you will notice that component-scan does not work with JBoss5. Symptoms may include null Spring components or if you were using Spring Seam integration and the Spring beans were loaded through classpath scan then you may be the following error:

Seam Code

public class LoginController extends Controller {
    @In("#{userService}")
    protected demo.entity.service.UserService userService;

Stacktrace

Caused by: org.jboss.seam.RequiredException: @In attribute requires non-null value: loginController.#{userService}
 at org.jboss.seam.Component.getValueToInject(Component.java:2335)
 at org.jboss.seam.Component.injectAttributes(Component.java:1736)
 at org.jboss.seam.Component.inject(Component.java:1554)
 at org.jboss.seam.core.BijectionInterceptor.aroundInvoke(BijectionInterceptor.java:61)
 at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)

Spring 2.5.6 dependency in pom.xml

 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring</artifactId>
 <version>2.5.6</version>
 </dependency

Old spring config XML

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-2.5.xsd

http://www.springframework.org/schema/context

 http://www.springframework.org/schema/context/spring-context-2.5.xsd"
 default-init-method="init" default-destroy-method="shutdown">
 <context:annotation-config />
 <context:component-scan base-package="demo.dao.jpa" />
</beans>

If you run the above code in JBoss5, component-scan does not work. See this JIRA issue on Spring’s website.

Solution

You need to migrate your spring version from 2.5.6 to 3.0.0.RELEASE. Remove your old spring dependency for 2.5.6 and add the following. Add snippets for the components that your application needs. Here’s a list of common spring components and their maven dependencies.

New dependency

 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-orm</artifactId>
 <version>3.0.0.RELEASE</version>
 </dependency>

 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-web</artifactId>
 <version>3.0.0.RELEASE</version>
 </dependency>

 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-tx</artifactId>
 <version>3.0.0.RELEASE</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-test</artifactId>
 <version>3.0.0.RELEASE</version>
 <scope>test</scope>
 </dependency>

Complete list is available here. After making this change, build your project with the new dependencies. Your old code should work with Spring 3 on JBoss 5.1.0.GA.

public abstract class GeneratedLoginController extends Controller {
@In(“#{userDao}”)
protected demo.dao.UserDao userDao;

@In(“#{userService}”)
protected demo.entity.service.UserService userService;

EJB3 JPA error when migrating from JBoss version 4 to 5

When I tried migrating my JBoss 4.2 [war] application which uses Spring to manage transactions, to JBoss 5.1, I got the following error message.

The error message

11:18:55,683 ERROR [AbstractKernelController] Error installing to Start: name=persistence.unit:unitName=#demo state=Create
java.lang.RuntimeException: Specification violation [EJB3 JPA 6.2.1.2] - You have not defined a jta-data-source for a JTA enabled persistence context named: demo
	at org.jboss.jpa.deployment.PersistenceUnitInfoImpl.<init>(PersistenceUnitInfoImpl.java:115)
	at org.jboss.jpa.deployment.PersistenceUnitDeployment.start(PersistenceUnitDeployment.java:275)

Here’s the persistence.xml file

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
 version="1.0">
 <persistence-unit name="demo">
 <provider>org.hibernate.ejb.HibernatePersistence</provider>
 <class>demo.jpa.User</class>
 <class>demo.jpa.Issue</class>
 <class>demo.jpa.UploadedFile</class>
 <properties>
   <property name="hibernate.hbm2ddl.auto" value="create" />
   <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
   <property name="hibernate.show_sql" value="true" />
   <property name="hibernate.format_sql" value="true" />
   <property name="hibernate.use_sql_comments" value="true" />
 </properties>
 </persistence-unit>
</persistence>

Solution/workaround

JBoss loads file named “persistence.xml” through this file:

jboss-5.1.0.GA/server/default/deployers/ejb3.deployer/META-INF/jpa-deployers-jboss-beans.xml

… via this code snippet (line 21-95)

   <bean name="PersistenceParsingDeployer" class="org.jboss.jpa.deployers.PersistenceParsingDeployer"/>
   <bean name="PersistenceDeployer" class="org.jboss.jpa.deployers.PersistenceDeployer"/>

   <bean name="PersistenceUnitDeployer" class="org.jboss.jpa.deployers.PersistenceUnitDeployer">
    ...
   </bean>

Solution 1:

Comment out the above lines. This solution is a server configuration modification and therefore must be done on every server (dev/qa/production) that you want to run your app on. You may not be able to use this approach if you don’t have control over your [production] server configuration. See Solution 2 for an application oriented solution.

Solution 2:

  1. Rename the persistence.xml file to something else, for example, spring-persistence.xml
  2. Change the config for the spring entityManagerFactory bean to explicitly refer to this new file. See below

Spring configuration – before:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
 <property name="dataSource" ref="dataSource" />
 <property name="jpaVendorAdapter">
   <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
 </property>
</bean>

Spring configuration – after:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
 <property name="dataSource" ref="dataSource" />
 <property name="jpaVendorAdapter">
   <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
 </property>
 <!-- ADD THE FOLLOWING PROPERTY -->
 <!-- provide the location of new persistence.xml -->
 <property name="persistenceXmlLocation" value="classpath:META-INF/spring-persistence.xml" />
</bean>

Reference

See more details of the corresponding JIRA issue at jboss.org

<bean name=”PersistenceParsingDeployer” class=”org.jboss.jpa.deployers.PersistenceParsingDeployer”/>

<bean name=”PersistenceDeployer” class=”org.jboss.jpa.deployers.PersistenceDeployer”/>
<bean name=”PersistenceUnitDeployer” class=”org.jboss.jpa.deployers.PersistenceUnitDeployer”>

How to rotate Tomcat catalina.out

If catalina.out becomes 2GB in size, tomcat crashes and fails to start without any error message. To avoid this scenario you should rotate catalina.out frequently. This article describes how to setup auto rotation of catalina.out on a linux/unix machine.

How to automatically rotate catalina.out daily or when it becomes bigger than 5M

1. Create this file

/etc/logrotate.d/tomcat

2. Copy the following contents into the above file

/var/log/tomcat/catalina.out {
 copytruncate
 daily
 rotate 7
 compress
 missingok
 size 5M
}

About the above configuration:

  • Make sure that the path /var/log/tomcat/catalina.out above is adjusted to point to your tomcat’s catalina.out
  • daily - rotates the catalina.out daily
  • rotate – keeps at most 7 log files
  • compress – compresses the rotated files
  • size – rotates if the size of catalina.out is bigger than 5M
  • copytruncate – truncates the original log file in place after creating a copy, instead of moving the old log file and optionally creating a new one, It can be used when some program can not be told to close its logfile and thus might continue writing (appending) to the previous log file forever. Note that there is a very small time slice between copying the file and truncating it, so some logging data might be lost. When this option is used, the create option will have no effect, as the old log file stays in place.

You don’t need to do anything else.

How it works

  1. Every night the cron daemon runs jobs listed in the /etc/cron.daily/ directory
  2. This triggers the /etc/cron.daily/logrotate file which is generally shipped with linux installations. It runs the command “/usr/sbin/logrotate /etc/logrotate.conf
  3. The /etc/logrotate.conf includes all scripts in the /etc/logrotate.d/ directory.
  4. This triggers the /etc/logrotate.d/tomcat file that you wrote in the previous step.

Run logrotate manually

Run the following command to run the cron job manually

/usr/sbin/logrotate /etc/logrotate.conf

Is it completely safe?

See the description of copytruncate method above. There is a slight chance of a small amount of logging data loss between copy and truncate steps, usually it is acceptable but sometimes its not.

More logrotate options

To see all logrotate options on your system, see the manual:

man logrotate

Cache Java webapps with Squid Reverse Proxy

This article shows you step by step how to cache your entire tomcat web application with Squid reverse Proxy without writing any java code.

What is Squid

Squid is a free proxy server for HTTP, HTTPS and FTP which saves bandwidth and increases response time by caching frequently requested web pages. While squid can be used as a proxy server when users try to download pages from the internet, it can be also used as a reverse-proxy by putting squid between the user and your webapp. All user requests first hit Squid. If the requested page already exists in Squid’s cache it is served directly from the cache without hitting your Webapp. If the page does not exist in Squid’s cache, it is fetched from your web application and stored in the cache for future requests.

Squid reduces hits to your server by caching response pages. You don’t have to worry about building page level caching in every application that your write, Squid takes care of that part.

When should I use Squid

Ideally you should use Squid for pages which have a high ratio of reads to writes. In other words, a page that changes less frequently but is accessed very often. Here are some scenarios:

  • A dynamical web page which displays news and is updated once an hour, and receives hundreds of hits during the hour
  • A static web page accessed freqently. Squid can give performance boost by caching frequently accessed static web pages in memory

When should I not use Squid

In most cases, if the request URL is the only factor which determines the response then you can safely use Squid. See more specific examples below:

  • If the entire apps is very dynamic in nature, and the validity of pages changes immediately.
  • Squid is not suitable for apps which require login. This unfortunately is a large number of applications. Such applications need to resort to back end caching, for example use other caching frameworks like Ehcache to cache re-usable page fragments and/or cache database queries and/or other performance bottlenecks.
  • Apps which heavily use browser cookies. Squid relies on URLs to cache pages. If the page served is computed from URLs + cookies, then you should not cache those pages in Squid.

How does the overall setup work

Apache Squid Tomcat architecture

Apache Squid Tomcat architecture


Apache receives requests on port 80. Apache calls Squid with the request. Squid checks its cache to see if it has the response cached from before. If yes and if the response is not expired, it returns the cached response.In this case:

Squid will write the following header to the response:

X-Cache: HIT from www.vineetmanohar.com

If the response is not found in Squid’s cache, squid will make a call to Tomcat on port 8082. Tomcat’s proxy connector is listening on this port. It processes the request and sends the response back to Squid. Squid saves the response in its cache, unless caching is disabled for that URL. Squid returns the final response to Apache which sends the response back to the user.

What if I don’t want to use Apache

Using Apache is not required to use Squid. You can run Squid on port 80, and point your users directly to Squid. If that is the case, skip section one and directly jump to section 2 below.

Step 1/3: Apache Httpd Config

If you are using Apache as a front end, you need to instruct Apache to forward requests to Squid at port 3128. See the following code snippet. Change the server name and paths to reflect your real values.

Apache config file:

/etc/httpd/conf/httpd.conf
<VirtualHost x.x.x.x>
ServerName www.vineetmanohar.com
DocumentRoot /home/webadmin/www.vineetmanohar.com/html
# forward requests to squid running on port 3128
ProxyPass / http://localhost:3128/
ProxyPassReverse / http://localhost:3128/
</VirtualHost>

In addition to the above, you also need mod_proxy installed. If you see the following in your httpd.conf, you probably already have mod_proxy installed. If you first need to install mod_proxy.

LoadModule  proxy_module         modules/mod_proxy.so
LoadModule  proxy_http_module    modules/mod_proxy_http.so

Step 2/3: Squid Config

First make sure that Squid is installed on your server. You can download Squid from here.

The squid config file on Linux/Unix is located at this location.

/etc/squid/squid.conf

The config file is pretty long. Follow these instructions and set the values appropriately.

# leave the port to 3128
http_port 3128

# how much memory cache do you want? depends on how much memory you have on the machine
cache_mem 200 MB

# what's the biggest page that you want stored in memory. If you home page is 100 KB and
# you want it stored in memory, you may set it to a number bigger than that.
maximum_object_size_in_memory 100 KB

# how much disk cache do you want. It is 6400 MB in the following example, change it as per
# your needs. Make sure you have that much disk space free.
cache_dir ufs /var/spool/squid 6400 16 256

# this is probably the most important config section. Here you can configure the cache life for
# each URL pattern. 

# Time is in minutes
# 1 day = 1440, 2 days = 2880, 7 days = 10080, 28 days = 40320

# do not cache url1
refresh_pattern ^http://127.0.0.1:8082/url1/           0     20%    0

# cache url2 for 1 day
refresh_pattern ^http://127.0.0.1:8082/url2/        1440     20%    1440 override-expire override-lastmod reload-into-ims ignore-reload

# cache css for 7 days
refresh_pattern ^http://127.0.0.1:8082/css         10080     20%   10080  override-expire override-lastmod reload-into-ims ignore-reload

# by default cache the whole website for 1 minute
refresh_pattern ^http://127.0.0.1:8082/                0     20%       0 override-expire override-lastmod reload-into-ims ignore-reload

# how long should the errors should be cached for. For example 404s, HTTP 500 errors
negative_ttl 0 seconds

# On which host does tomcat run. Set 127.0.0.1 for localhost
httpd_accel_host 127.0.0.1

# this is the proxy port as defined in Tomcat server.xml. By default it is "8082"
httpd_accel_port 8082

# set this to "on". Read more documentation if you want to change this.
httpd_accel_single_host on

# To access Squid stats via the manager interface, you need to enter a password here
cachemgr_passwd your_clear_text_password all

# Say "off" if you want the query string to appear in the squid logs.
strip_query_terms off

Step 3/3: Tomcat Config

Make sure that the HTTP Proxy Connector is defined in TOMCAT_HOME/conf/server.xml.

<Service name="Catalina">
<Connector port="8082"
 maxThreads="50" minSpareThreads="5" maxSpareThreads="10"
 enableLookups="false" acceptCount="100" connectionTimeout="20000"
 proxyName="www.vineetmanohar.com"
 compressableMimeType="text/html,text/xml,text/css,text/javascript,text/plain" compression="on"
 proxyPort="80" disableUploadTimeout="true" />

If needed, see additional documentation on Tomcat proxy connector.

Squid Manager Interface

You can access the Squid config and stats via the Squid Manger HTTP interface. Make sure that the “cachemgr.cgi” file which ships with squid installation is in your cgi-bin directory. More documentation on setting that up here.

Once you’ve set it up, you can access the cache manager via this URL:

http://<hostname>/cgi-bin/cachemgr.cgi

To continue enter the following values:

Cache host: localhost
Cache port: 3128
Manager name: manager
Password: <the value you entered for "cachemgr_passwd" in squid.conf>
  • Store Directory Stats shows you how much disk space is used by the disk cache.
  • Cache Client List show you the cache HIT/MISS ratio as %. You should monitor this frequently and tune your cache to get a higher hit %.

Reload Squid Config without restarting

Edit the squid config using “vi” or your favorite editor.

vi /etc/squid/squid.conf

Once you are done editing, reload the new config without restarting Squid.

/usr/sbin/squid -k reconfigure

Clearing Squid Cache

To clear Squid cache:

1) Set the memory cache to 4 MB (or a lower number)

cache_mem 8 MB

2) Set the disk cache to 8 MB (or a lower number). The disk cache must be higher that the memory cache.

cache_dir ufs /var/spool/squid 20 16 256

3) Reload squid config without restart as described in the previous section

4) You may need to wait a few hours for the cache to get cleared. Once the cache is clear, you may restore the previous cache sizes and reload the new config again. You can monitor the cache size through the Squid Manager HTTP interface.

Bypassing Squid

If for some reason you need to bypass Squid, reconfigure Apache to directly send requests to Tomcat. Edit the Apache config file /etc/httpd/conf/httpd.conf

# forward requests directly to Tomcat's proxy connector running on port 8082
ProxyPass        / http://localhost:8082/
ProxyPassReverse / http://localhost:8082/

You will need to restart Apache after making this change.

/etc/init.d/httpd restart

Conclusion

Squid is a very powerful tool for caching. It is not for all applications. Please examine the need of your application and use squid appropriately. I’ve used squid for several years for caching the output from a Java data mashup application and am very satisfied with the ease of use and benefits. Hope you found this tutorial useful. Feel free to post a comment or share your experience with squid.

References

Squid official website

Get Adobe Flash playerPlugin by wpburn.com wordpress themes