Thursday, November 26, 2009

Tomcat PermGenSpace on several redeployments

The PermGenSpace bug has been an issue for a long time. Several people have diagnosed this bug in ClassLoader of the JVM and there are several posts which can easily be searched on google.

This issue typically occurs after several reployments to Tomcat. However there are ways to relieve or extend the number of redeployments you can do without having to do full Tomcat restart everytime. You can add the following options to your JAVA_OPTS environment variable in the catalina.bat or catalina.sh file in the bin folder of tomcat:

-Xmx768m -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m

Windows 7 as a Java, Tomcat, Postgres developer

I've been given a new computer at work on the (ANU campus) just yesterday and it came preinstalled with the new Windows 7. I've been told I am probably the first person at the college to be using Windows 7 so I almost felt like a guinea pig and seeing if it has any compatibility issues especially for my development environment. But after spending some time on it, I can tell you it really wasn't that painful to make the transition.

Java 6

Since I was running a 64 bit machine I needed to download the Windows x64 option when loading from the sun webite:

Download Java 6

Eclipse

Since I was running the 64bit version of Java, I needed to download the 64bit version of eclipse for Windows. I had downloaded version 3.5.1

Eclipse Download page

The direct download for the 64 bit version on windows is here
Direct eclipse download 64bit Windows

The eclipse that was downloaded was not the Java EE bundle. So it was missing a lot of the functionality that I was used to such as HTML editors, JSP editors etc.... To download the plugins for that kind of support,
In Eclipse goto: Help - Install new software
and supply the following URL: http://download.eclipse.org/releases/galileo
Then select the "Web, XML and Java EE Development" package.
Now eclipse is ready to go.

Postgres

Initially I attempted to install the latest Postgres binary Version 8.4 for windows and it immediately came back with an installation error. I followed up on some forums and many others had encountered similar issues. I was told that people had better luck with version 8.3 which I downloaded from here:

Download postgres 8.3

In the default installation I still encountered some errors. In this case, it complained something about a 'Secondary Logon service'. To enable this service I did the following:

I went to the 'Services' windows doing the following: Start - Control Panel - System and Security - Administrative tools - Services


In the services window, search for the 'Secondary Logon service' and enable it.





 I also disabled User Access control (UAC) which seemed to cause problems for other people by following the steps outlined here

Once the above steps were completed I was able to continue with the normal installation procedure for Postgres 8.3.

Wednesday, November 18, 2009

JSTL test condition and trailing spaces

Took me a few minutes to track this one down, but it did catch me by surprise. The trailing spaces in the test conditions for JSTL tags do make a difference. For those that are interested I was using Tomcat 6.

I was getting the following errors in my stacktrace:


An error occurred at line: 40 in the jsp file: /WEB-INF/jsp/genotypingInventory.jsp
The method setTest(boolean) in the type IfTag is not applicable for the arguments (String)
37:                     <td class="right-col">${sp.box.box }</td>
38:                     <td class="right-col">${sp.position }</td>
39:                     <td class="right-col">
40:                             <c:if test="${not empty sp.mouse.strain} ">
41:
42:                             </c:if>
43:                     </td>      


The error was in the line:


<c:if test="${not empty sp.mouse.strain} ">


The problem is the extra space after the end curly bracket. After changing it to the following it worked fine:



<c:if test="${not empty sp.mouse.strain}">

Monday, November 16, 2009

Migration from Tomcat 5.5 to Tomcat 6.0.20

The migration from Tomcat 5.5 to Tomcat 6.0.20 was relatively painless except for the 3 following issues I encountered.


Issue #1: Unable to read TLD issue

Turns out that I was including a copy of jsp-api-2.0.jar in the build of my WEB-INF/lib directory, and Tomcat already had it in its lib folder. Deleting the jar from my WEB-INF/lib directory fixed the problem.

Reference:
http://forums.sun.com/thread.jspa?threadID=580206&am

Issue #2: Missing mail.jar
Add the mail.jar to lib folder of Tomcat 6





Issue #3: Different Enum behaviour with JSTL


Don't use enum values directly in JSTL. Instead provide a getter method to retrieve the value such as getValue() and invoke ${enum.value} rather than just ${enum}. In Tomcat 5.5 the behaviour was if you had ${enum} internally it would return enum.toString(). Now with Tomcat 6 and as per section 1.18.2 of the 2.1 JSP-EL spec, the behaviour now returns enum.name(). So if you want the return the toString() value create and invoke getValue().

Reference:
https://issues.apache.org/bugzilla/show_bug.cgi?id=46920

Wednesday, November 11, 2009

Sharing your Vodafone Mobile Connect internet with Linux

This article will describe the details on how to setup your linux machine to share its internet connection with users connected to a router through your Vodafone Mobile Connection internet connection.

Vodafone Mobile Connect Card driver for Linux is a tool that allows you to establish a connection to the Internet using 3G cards. Details on installing the driver for your Vodafone Mobile connect onto your linux machine click here.

To find your driver click here.

I did have issues with the default DNS server configuration provided by the drivers. If you experience something similar try the following IP addresses for your DNS configuration:

  • 208.67.220.220 
  • 208.67.222.222
Once you have got your internet connection working on the linux machine, set the default gateway to point to your internet connection that will probably have an alias of ppp0. In Fedora linux I would type the following:

route add default gw 0.0.0.0 ppp0
If you type 'route' at the command-line you should see something similar to this:

[root@localhost ~]# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.64.64.64     *               255.255.255.255 UH    0      0        0 ppp0
192.168.1.0     *               255.255.255.0   U     0      0        0 eth0
link-local      *               255.255.0.0     U     0      0        0 eth0
default         *               0.0.0.0         U     0      0        0 ppp0

You also want to give your linux machine a static IP so that users on the router can point to your machine as their gateway. This is the following configuration I used:

IP: 192.168.1.105
subnet mask: 255.255.255.0
gateway: 0.0.0.0
 Now on the client computer that's connected to the router, configure their network settings to have the following:

Gateway: 192.168.1.105
subnet mask: 255.255.255.0
Now you can have multiple computers all sharing the same internet connection going through the Vodafone Mobile Connect card.

Cheers,
Phil

Apache Jetspeed 2.2.0 with Postgres installation

I attempted my first installation of the binary download of JetSpeed 2.2.0. Using the installer I chose postgres as my choice of database. However, during installation I encountered the following error:

Error running the install,The following error occurred while executing this line:
C:\Apache\Jetspeed-2.2\database\bulid.xml:126;Source file does not exist!

In order to get it working I had to edit the following file:

C:\Apache\Jetspeed-2.2.0\database\database.properties

And change the property value from
db.type=postgres
to
db.type=postgresql

Then in the directory C:\Apache\Jetspeed-2.2.0\database\
type ant

Now you can startup the server and verify it works.

Tuesday, November 10, 2009

HSSF Helper classes for producing simple Excel Documents with Enum types

I've created some classes to abstract Excel document creation away from HSSF api and reduce the amount of code needed.

The Enum class will represent the columns headers and the column index for which
data should be allocated. Adding a data to an Excel spreadsheet is as simple as the following:

session.addColumn(EnumClass.EnumConstant, "your data");

The session class will automatically figure out the column index based on the order of the enum constants defined in the enum class. The beauty of this is if you decide to rearrange the order of the columns, then all you have to do is rearrange the order of the enum constants defined in the enum class.

Here are the classes:

This is an example class using the ExcelSession. See how simple it is to use?


package au.edu.apf.phenomebank.report;

import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.springframework.web.servlet.view.document.AbstractExcelView;

import au.edu.apf.phenomebank.appservice.InventoryAppService;
import au.edu.apf.phenomebank.db.StrainFacilityCount;
import au.edu.apf.phenomebank.helper.ExcelSession;
import au.edu.apf.phenomebank.inventory.StorageFacility;
import au.edu.apf.phenomebank.inventory.Technician;
import au.edu.apf.phenomebank.web.ExcelReportingController;
import au.edu.apf.phenomebank.web.ParamNames;
import au.edu.apf.phenomebank.web.Util;

/**
 * Produces an Excel Document for reporting the number of mice frozen down for each Strain
 * at a particular facility
 *
 *
 * @author Philip Wu
 */
public class NumFrozenStrainFacilityExcelView extends AbstractExcelView {

    private InventoryAppService inventoryService;
    
    public InventoryAppService getInventoryService() {
        return inventoryService;
    }
    public void setInventoryService(InventoryAppService inventoryService) {
        this.inventoryService = inventoryService;
    }
    
    @Override
    protected void buildExcelDocument(Map map, HSSFWorkbook workbook,
            HttpServletRequest req, HttpServletResponse resp) throws Exception {

        // Create the Excel session
        ExcelSession session = new ExcelSession(workbook, FrozenColumn.class);

        // create the table headers
        session.nextRow();
        session.createHeaders();

        // Add the results
        SimpleDateFormat dateFormatter = new SimpleDateFormat("dd/MM/yyyy");        
        for (StorageFacility facility : StorageFacility.values()) {
            List<StrainFacilityCount> results = inventoryService.findNumFrozernByStrainFacility(facility);
                        
            for (StrainFacilityCount result : results) {    
                session.nextRow();
                session.addColumn(FrozenColumn.STRAIN_ID, result.getStrainId());
                session.addColumn(FrozenColumn.STRAIN_NAME, result.getStrainName());
                session.addColumn(FrozenColumn.NUM_MICE, result.getNumFrozen());
                if (result.getDateOfFreezing() != null)
                    session.addColumn(FrozenColumn.DATE_OF_FREEZING, dateFormatter.format(result.getDateOfFreezing()));
                session.addColumn(FrozenColumn.FACILITY, facility);                
            }
            
        }
        
        Util.setAttachmentResponse(resp, ExcelReportingController.excelViews.get(EnumReportType.STRAIN_INVENTORY));
    }

}

enum FrozenColumn {

    STRAIN_ID("Strain ID"),
    STRAIN_NAME("Strain name"),
    NUM_MICE("# Mice"),
    DATE_OF_FREEZING("Date of Freezing"),
    FACILITY("Facility")
    ;
    
    private String value;
    private FrozenColumn(String value) {
        this.value = value;
    }
    
    public String toString() {
        return value;
    }
}



This is the ExcelSession class:


package au.edu.apf.phenomebank.helper;

import java.util.Map;

import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

/**
 * Excel session as a framework for simplifying Excel document creation
 *
 *
 * @author Philip Wu
 */
public class ExcelSession {

    /**
     * Excel workbook
     */
    private HSSFWorkbook workbook;
    private HSSFSheet sheet;
    /**
     * The enum class
     */
    private Class enumClass;
    /**
     * The column index location for each enum value
     */
    private Map<Object, Integer> indexMap;
    
    /**
     * The current row
     */
    private HSSFRow curRow;
    /**
     * The current row number
     */
    private int rowNum;
    
    public ExcelSession (HSSFWorkbook workbook, Class enumClass, String worksheet) {
        
        if (worksheet == null || worksheet.length() == 0)
            worksheet = "worksheet";
        
        this.enumClass = enumClass;
        
        this.workbook = workbook;
        this.sheet = workbook.createSheet(worksheet);
        
        indexMap = EnumHelper.createIndexMap(enumClass);
        
    }
    
    public ExcelSession (HSSFWorkbook workbook, Class enumClass) {
        this(workbook, enumClass, null);
    }
    
    /**
     * Creates the headers for this Excel document based on the enum class
     */
    public void createHeaders() {
        // Create the headers        
        //HSSFRow row = sheet.createRow(rowNum);
        if (curRow == null) {
            nextRow();
        }
        
        ExcelHelper.createHeaders(workbook, curRow, enumClass.getEnumConstants());
        
    }
    
    /**
     * Moves down to the next row
     */
    public void nextRow() {        
        curRow = sheet.createRow(rowNum);
        rowNum++;
    }
    
    /**
     * Adds a column to the current row
     * @param row
     * @param enumColumn
     * @param value
     */
    public void addColumn(Object enumColumn, Object value) {
        if (value != null)
            ExcelHelper.addColumnDefault(curRow, indexMap.get(enumColumn), value.toString() , "");
    }
    
    /**
     * Provide the ability specify the column index of where to add the value
     * @param columnIndex
     * @param value
     */
    public void addColumn(int columnIndex, Object value, HSSFFont font) {
        if (value != null)
            ExcelHelper.addColumn(curRow, columnIndex, value.toString() , font);
        
    }
    
}



Here is the ExcelHelper class:


package au.edu.apf.phenomebank.helper;

import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

/**
 * Helper for Excel objects
 *
 *
 * @author Philip Wu
 */
public class ExcelHelper {

    /**
     * A Helper method to create an excel worksheet
     * @param workbook
     * @param sheetName
     * @param columns
     * @return
     */
    public static HSSFSheet createSheet(HSSFWorkbook workbook, String sheetName, String[] columns) {
        
        HSSFSheet sheet = workbook.createSheet(sheetName);
        
        HSSFRow row = sheet.createRow(0);
        
        createHeaders(workbook, row, columns);
        
        return sheet;
    }
    
    /**
     * Creates headers for the row using the following columns
     * @param workbook
     * @param row
     * @param columns
     */
    public static void createHeaders(HSSFWorkbook workbook, HSSFRow row, Object[] columns) {
        
        HSSFFont bold = workbook.createFont();
        bold.setBoldweight(org.apache.poi.hssf.usermodel.HSSFFont.BOLDWEIGHT_BOLD);
        
        for (int i=0 ; i < columns.length; i++) {
            Object fieldHeader = columns[i];
            HSSFRichTextString headerFieldValue = new HSSFRichTextString(fieldHeader.toString());
            headerFieldValue.applyFont(bold);
            
            row.createCell(i).setCellValue(headerFieldValue);
        }
    }
    
    public static void addColumn(HSSFRow row, int index, String value) {
        
        addColumn(row, index, value, null);
    }    
    
    /**
     * Helper method to add a column
     * @param row
     * @param index
     * @param value
     * @param defaultValue    The default value to use if value is null
     */
    public static void addColumnDefault(HSSFRow row, int index, String value, String defaultValue) {
        String setValue = defaultValue;
        if (value != null)
            setValue = value;
        addColumn(row, index, setValue);
    }
    
    /**
     * Adds a column to the row at the specified index with the given value applying
     * a font to the text if it exists.
     * @param row
     * @param index
     * @param value
     * @param font
     */
    public static void addColumn(HSSFRow row, int index, String value, HSSFFont font) {
        
        if (value == null)
            return;
        
        // Without replacing \r characters, excel produces square boxes
        value = value.replaceAll("\r", "");
        value = value.replaceAll("\n", "");
        
        HSSFRichTextString excelValue = new HSSFRichTextString(value);    
        if (font != null)
            excelValue.applyFont(font);
        
        row.createCell(index).setCellValue(excelValue);
    }        
    
}


The EnumHelper class:


package au.edu.apf.phenomebank.helper;

import java.util.HashMap;
import java.util.Map;

/**
 * Helper class for Enum types
 *
 *
 * @author Philip Wu
 */
public class EnumHelper {

    // The index position of the ExcelColumn in the enumerated constants
    public static int indexOf(Object enumType) {
        int index = 0;
        for (Object constant :  enumType.getClass().getEnumConstants()) {
            if (constant.equals(enumType))
                return index;
            index++;
        }
        
        return -1; // not found
    }
    
    /**
     * Creates a map mapping the Enum constant to its index position
     * @param enumClass
     * @return
     */
    public static Map<Object, Integer> createIndexMap(Class enumClass) {
        HashMap<Object, Integer> map = new HashMap<Object, Integer>();
        
        int index = 0;
        for (Object constant :  enumClass.getEnumConstants()) {
            map.put(constant, index);
            index++;
        }
        return map;
        
    }
    
}

Using Jericho HTML Parser to convert relative paths to absolute paths

Here is some sample Java code for converting all the 'src' attributes in elements from a relative path to an absolute path. It is easy to extend this to 'href' elements as well.



package au.edu.apf.phenomebank.web;

import java.io.IOException;
import java.net.URL;
import java.util.List;

import net.htmlparser.jericho.Attribute;
import net.htmlparser.jericho.Attributes;
import net.htmlparser.jericho.Element;
import net.htmlparser.jericho.MicrosoftTagTypes;
import net.htmlparser.jericho.OutputDocument;
import net.htmlparser.jericho.Source;

/**
 * Handles a Foreign HTML page
 *
 *
 * @author Philip Wu
 */
public class ForeignHtmlParser {

    public static String convertRelativeToAbsolutePaths(String absoluteBasePath, String urlString) throws IOException {
        
        MicrosoftTagTypes.register();
        
        Source source=new Source(new URL(urlString));
        
        OutputDocument outDoc = new OutputDocument(source);
        
        
        List<Element> elementList=source.getAllElements();
        for (Element element : elementList) {
            if (element.getAttributes()!=null) { 
                
                Attributes attributes = element.getAttributes();
                                
                for (Attribute att  : attributes) {
                    if (att.getName().toLowerCase().equals("src")) {
                        // Convert relative paths to absolute paths
                        if (! att.getValue().startsWith("http")) {
                            
                            // Build the Absolute path 
                            StringBuilder sb = new StringBuilder();
                            sb.append(absoluteBasePath);                            
                            if (! att.getValue().startsWith("/"))
                                sb.append("/");
                            sb.append(att.getValue());
                            
                            // Replace the attribute with the new one
                            outDoc.replace(att, "src='"+sb.toString()+"'");                                                         
                        }
 
                    }
                }
                
            }
            
        }

        //System.out.println("outDoc="+outDoc);
        return outDoc.toString();
    }
    
}