Enabling SSL with JBoss 5 and Spring Security Framework

To enable SSL on JBoss 5 following steps are required:

1. Generate the keystore with following command

keytool -genkey -alias tomcat -keyalg RSA -keystore NAME_OF_KEYSTORE -validity NUMBER_OF_DAYS

It will prompt the user to enter few more details. Please enter all.
At the end it will ask for the key password. It should be same as keystore password.
Press enter to skip. It will take the same password.

This will create a self-signed certificate, but the procedure would be more or less the same even if you are going to use a certificate from a Certification Authority

2. Copy the file into the jboss/server/<Server-Name>/conf/ directory
3. Edit jboss/server/<Server-Name>/deploy/jbossweb.sar/server.xml
[Assuming that HTTP will run in port 80 and HTTPS in 443]

a. Locate HTTP/1.1 Connector block

b. Replace Connector block with the following

<Connector protocol="HTTP/1.1" port="80" address="${jboss.bind.address}" connectionTimeout="20000" redirectPort="443" />

c. Locate SSL/TLS Connector configuration block and Un-comment the block.

d. Replace the Connector element with the following

<Connector protocol="HTTP/1.1" SSLEnabled="true"
port="443" address="${jboss.bind.address}"
scheme="https" secure="true" clientAuth="false"
keystoreFile="${jboss.server.home.dir}/conf/"

keystorePass="" sslProtocol = "TLS" />

4. Open jboss/server/<Server-Name>/conf/bootstrap/bindings.xml

  • Replace all port which has values 8443 to 443
  • Replace all port which has values 8080 to 80

5. Please contact IT support to configure firewall.
6. Edit the application’s acegi-security xml as follows:

a. Add channelProcessingFilter to filterChainProxy bean

<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/j_acegi_security_check*=httpSessionContextIntegrationFilter,
authenticationProcessingFilter,channelProcessingFilter
/**=httpSessionContextIntegrationFilter,rememberMeProcessingFilter,
logoutFilter,authenticationProcessingFilter,
securityContextHolderAwareRequestFilter,anonymousProcessingFilter,
exceptionTranslationFilter,filterInvocationInterceptor,channelProcessingFilter
</value>
</property>
</bean>

b. Configure the section that will be SSL enabled as SECURE CHANNEL

<bean id="channelProcessingFilter" class="org.acegisecurity.securechannel.ChannelProcessingFilter">
<property name="channelDecisionManager"><ref bean="channelDecisionManager"/></property>
<property name="filterInvocationDefinitionSource">
<value> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/secure/**=REQUIRES_SECURE_CHANNEL
/**=REQUIRES_INSECURE_CHANNEL
</value>
</property>
</bean>

<bean id="secureChannelProcessor"  class="org.acegisecurity.securechannel.SecureChannelProcessor" />
<bean id="insecureChannelProcessor"  class="org.acegisecurity.securechannel.InsecureChannelProcessor" />

7. Edit web.xml as follows:

a. Add context-param for acegi-security

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>

b. Add the security filter for acegi-security

<filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<filter-class> org.acegisecurity.util.FilterToBeanProxy
</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value> org.acegisecurity.util.FilterChainProxy </param-value>
</init-param>
</filter>


Advertisements

Comparing Java XML Parsers

Over the years after using few parsers for the job, I tried to find out the best possible parser in terms of ease-of-use and performance.

I found out that JAXB is by far the clear winner with XPATH can be it’s deputy.
I have compared DOM, SAX, JAXP, JAXB, StAX and XPATH.
The comparison is detailed in the attached powerpoint presentation.
Comparing Java XML parsers

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:

Problem
I had two objects Contacts and Groups having many-to-many association.
A Contact can belong to multiple Groups and a Group can contain multiple Contacts.
My objective was to return a set of Contacts along with the Groups they were associated with.

But if the Contacts-Groups association is set to Lazy load then the above exception will be thrown.

Solution
1. FetchType can be set to EAGER and everything will work fine, the Groups will be loaded; but in a more general case, where the graph of java objects can grow very large, the use of eager fetch may cause unnecessary data to be loaded and the application will be slowed down.

2. Another solution is to explicitly load any lazy-loading the required fields actually hitting a method on those properties
   ex: contact.getEmailSet().size(). But this will result in subsequent sql firing which are required to load Groups (Child)
   Will also slow down the system.

3. Another solution is to include "fetch join" which allows associations or collections of values to be initialized along with their parent objects using a single select. This is particularly useful in the case of a collection. It effectively overrides the outer join and lazy declarations of the mapping file for associations and collections. 

Contacts Domain Object:
@Entity
@Table(name = "Contacts")
public class Contacts  implements java.io.Serializable
{
    …..
    …..
    private Set<EmailList> emailSet = new HashSet<EmailList>(0);
    ….
    ….

    @ManyToMany(targetEntity = EmailList.class, cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch= FetchType.LAZY)
    @JoinTable(name="Contacts_EmailGroup", joinColumns={@JoinColumn(name="contactsId")}, inverseJoinColumns={@JoinColumn(name="emailGroupId")})
    public Set<EmailList> getEmailSet() {
        return this.emailSet;
    }
    public void setEmailSet(Set<EmailList> emailSet) {
        this.emailSet = emailSet;
    }
}

EmailList Domain Object:
@Entity
@Table(name = "EmailGroup")
public class EmailList  implements java.io.Serializable
{
    ….
    ….
    private Set<Contacts> contacts = new HashSet<Contacts>(0);
    ….
    ….

    @ManyToMany(targetEntity = Contacts.class, cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy="emailSet")
    public Set<Contacts> getContacts() {
        return this.contacts;
    }
    public void setContacts(Set<Contacts> contacts) {
        this.contacts = contacts;
    }
}

DAO layer :
private EntityManager contactsEntity;


public List<Contacts> findAllContacts()
{
   Query qry = contactsEntity.createQuery("FROM Contacts c left join fetch c.emailSet");
   return qry.getResultList();
}

 

Hope the solution will be of some use 🙂

Thanks

Sasanka

Ajax validation in Struts 2

Problem

Check the availability of ‘Username’ during registration in J2EE web application using Struts 2 and Ajax.

Solution

1. Create the following JavaScript functions for Ajax implementation:

function GetXmlHttpObject()
{
    var xmlHttp=null;
    try
      {
          // Firefox, Opera 8.0+, Safari
          xmlHttp=new XMLHttpRequest();
      }
    catch (e)
      {
          // Internet Explorer
          try
        {
            xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");
        }
          catch (e)
        {
            xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");
        }
      }
    return xmlHttp;
}

function AJAXInteraction(url, callback) 
{
    var xmlHttp = GetXmlHttpObject();
    if (xmlHttp==null)
    {
          alert ("Your browser does not support AJAX!");
          return;
    }
    
    xmlHttp.onreadystatechange = processRequest;
        
    
    function processRequest () 
    {
      // readyState of 4 signifies request is complete
      if (xmlHttp.readyState == 4) 
      {
        // status of 200 signifies sucessful HTTP call
        if (xmlHttp.status == 200) 
        {
             if (callback) callback(xmlHttp.responseXML);
        }
      }
    }
    
    this.doGet = function() 
    {
      xmlHttp.open("GET", url, true);
      xmlHttp.send(null);
    }
}

function validateCallback(responseXML) 
{
    var msg = responseXML.getElementsByTagName("valid")[0].firstChild.nodeValue;
    if (msg == "false")
    {
        var mdiv = document.getElementById("userIdMessage");
        // set the style on the div to invalid
        document.getElementById("showErrLabel").style.display="block";                
        document.getElementById("showErr").style.display="block";                                
        mdiv.className = "contentformlabel_error";
        mdiv.innerHTML = "Username already exists.";
    } 
    else if(msg == "true")
    {
        var mdiv = document.getElementById("userIdMessage");
        // set the style on the div to valid
        document.getElementById("showErrLabel").style.display="none";                
        document.getElementById("showErr").style.display="none";                                
        mdiv.className = "contentformlabel";
        mdiv.innerHTML = "Username available";
    }
    else
    {
        var mdiv = document.getElementById("userIdMessage");
           // set the style on the div to valid
        document.getElementById("showErrLabel").style.display="block";                
        document.getElementById("showErr").style.display="block";                                
        mdiv.className = "contentformlabel_error";
        mdiv.innerHTML = "Please enter a username";
    }            
}

2. Create another JavaScript function which will be called by the input html element

function validateUserId() 
{
    // /marketer/validate.action = namespace/action
    var url = '<%=request.getContextPath()%>'+"/marketer/validate.action?userId="+document.getElementById("marketer.username_id").value;    
    var ajax = new AJAXInteraction(url, validateCallback);
    ajax.doGet();
    return false;
}

3. Create the HTML

<table>
    <tr>
        <td class="contentformlabel">
            <table cellpadding="0" cellspacing="0" align="right">
                <tr>
                    <td style="vertical-align: middle; padding-right: 6px">
                        <div id="showErrLabel" style="display:none;">
                          <img src="../images/alert_icon.gif">
                        </div>
                    </td>
                    <td style="white-space: nowrap">
                        <font class="contentformlabel_error">*</font> Username:
                    </td>
                </tr>
            </table>        
        </td>
        <td class="contentformfield">
            <s:textfield id="marketer.username_id" cssClass="contentforminput-smallhgt" name="marketer.username" size="40" maxlength="25"/>
        </td>
        <td colspan="2" style="vertical-align: middle">
            <table cellpadding="0" cellspacing="0" align="left">
                <tr>
                    <td style="vertical-align: middle; padding-top: 3px; padding-right: 3px">
                        <input type="image" src="../images/check_availability.gif" onclick="return validateUserId();">
                    </td>
                    <td style="vertical-align: middle; padding-top: 3px; padding-right: 3px">
                        <div id="showErr" style="display:none;" align="left">
                            <img src="../images/left_arrow_red.gif" align="left">                    
                        </div>
                    </td>
                    <td style="vertical-align: middle; padding-top: 3px; padding-right: 3px">
                        <div id="userIdMessage" align="left"></div>
                    </td>
                </tr>
            </table>    
        </td>                                                
    </tr>
</table>

4. Create the relevent stylesheets and call it in html

5. Create the Action which will validate the value of “username”

    Response object is abstracted in Struts 2 hence will not be able to access it directly  from action. So we need to create a custom result object and get the response from it.

package com.util;

import java.io.PrintWriter;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.dispatcher.StrutsResultSupport;

import com.ajaxproj.action.MarketerAction;
import com.ajaxproj.domain.account.Marketer;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;

public class ValidateUsername extends StrutsResultSupport
{
    private static final Log log = LogFactory.getLog(ValidateUsername.class);
    
    @Override
    protected void doExecute(String arg0, ActionInvocation arg1) throws Exception 
    {
        HttpServletRequest request   = (HttpServletRequest) ActionContext.getContext().get(org.apache.struts2.StrutsStatics.HTTP_REQUEST);        
        ActionContext context        = arg1.getInvocationContext();
        HttpServletResponse response = (HttpServletResponse) context.get(HTTP_RESPONSE);

        HttpSession userSession   = request.getSession(true);
        Marketer formMarketer     = null;
        
        MarketerAction  action    = ((MarketerAction) arg1.getAction());
        formMarketer              = action.getMarketerService).checkDuplicateUsername(username);

        String str                = null;
        PrintWriter out           = response.getWriter();
        StringBuffer buffer       = new StringBuffer(255);
        
        // check duplicate username
        if((username != null) && formMarketer != null)
        {
            response.setContentType("text/xml");
            response.setHeader("Cache-Control", "no-cache");
            response.getWriter().write("<valid>false</valid>");
        }
        else if(username != null && formMarketer == null)
        {
            response.setContentType("text/xml");
            response.setHeader("Cache-Control", "no-cache");
            response.getWriter().write("<valid>true</valid>");            
        }
        else
        {
            response.setContentType("text/xml");
            response.setHeader("Cache-Control", "no-cache");
            response.getWriter().write("<valid>Empty</valid>");
        }
    }    
}

6.  Create the default Action

7. Edit Struts.xml (can be struts-<namespace>)

<!DOCTYPE struts PUBLIC 
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="marketer" namespace="/marketer" extends="struts-default">
    <result-types>
        <result-type name="validateUserName" class="com.util.ValidateUsername" />
    </result-types>

    <action name="validate" class="com.action.MarketerAction">
        <result type="validateUserName">/pages/userregistration/marketerUserInfo.jsp</result>
    </action>
</package>
</struts>

How to implement Hover buttons?

1. Create a stylesheet

a #login {
  background: url(../skin/images/login_button.gif) no-repeat left top transparent;
  width: 53px;
  height: 24px;
  border: 0px;
}
 
a:hover #login {
  background-image: url(../skin/images/login_button_hover.gif);
}

2. Call the css in JSP

<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<link href="../path/stylesButton.css" type="text/css" rel="stylesheet">
</head>

3. Concerned Button – Login in this case

<a href="#">
<input type="image" id="login" src="../path/spacer.gif" align="left"alt="Login">
</a>


spacer.gif should be transparent.

How to set browsers Timezone in a Web Application ?

1. Create a javascript function called getTimezoneName()

function getTimezoneName() 
{
    tmSummer = new Date(Date.UTC(2005, 6, 30, 0, 0, 0, 0));
    sumTime = -1 * tmSummer.getTimezoneOffset();

    tmWinter = new Date(Date.UTC(2005, 12, 30, 0, 0, 0, 0));
    winTime = -1 * tmWinter.getTimezoneOffset();
    //alert("Summer :"+sumTime+" Winter :"+winTime);        
    if(sumTime == -720 && winTime == -720) return 'Dateline Standard Time';
    if(sumTime == -660 && winTime == -660) return 'Samoa Standard Time';
    if(sumTime == -600 && winTime == -600) return 'Hawaiian Standard Time';
    if(sumTime == -480 && winTime == -480) return 'Alaskan Standard Time';
    if(sumTime == -420 && winTime == -480) return 'Pacific Standard Time';
    if(sumTime == -420 && winTime == -420) return 'Mountain Standard Time';
    if(sumTime == -360 && winTime == -420) return 'Mexico Standard Time';
    if(sumTime == -360 && winTime == -360) return 'Central America Standard Time';
    if(sumTime == -300 && winTime == -360) return 'Central Standard Time';
    if(sumTime == -300 && winTime == -300) return 'Eastern Standard Time';
    if(sumTime == -240 && winTime == -300) return 'Eastern Standard Time';
    if(sumTime == -180 && winTime == -240) return 'Atlantic Standard Time';
    if(sumTime == -240 && winTime == -240) return 'SA Western Standard Time';
    if(sumTime == -240 && winTime == -180) return 'Pacific SA Daylight Time';
    if(sumTime == -150 && winTime == -210) return 'New Foundland Standard Time';
    if(sumTime == -180 && winTime == -120) return 'E. South America Standard Time';
    if(sumTime == -180 && winTime == -180) return 'SA Eastern Standard Time';
    if(sumTime == -120 && winTime == -180) return 'Greenland Standard Time';
    if(sumTime == -60 && winTime == -120) return 'Mid-Atlantic Standard Time';
    if(sumTime == 0 && winTime == -60) return 'Azores Standard Time';
    if(sumTime == -60 && winTime == -60) return 'Cape Verde Standard Time';
    if(sumTime == 0 && winTime == 0) return 'Greenwich Standard Time';
    if(sumTime == 60 && winTime == 0) return 'GMT Standard Time';
    if(sumTime == 120 && winTime == 60) return 'Europe Standard Time';
    if(sumTime == 60 && winTime == 60) return 'W. Central Africa Standard Time';
    if(sumTime == 180 && winTime == 120) return 'Egypt Standard Time';
    if(sumTime == 120 && winTime == 120) return 'South Africa Standard Time';
    if(sumTime == 240 && winTime == 180) return 'Russian Standard Time';
    if(sumTime == 180 && winTime == 180) return 'E. Africa Standard Time';
    if(sumTime == 270 && winTime == 210) return 'Iran Standard Time';
    if(sumTime == 240 && winTime == 240) return 'Arabian Standard Time';
    if(sumTime == 300 && winTime == 240) return 'Caucasus Standard Time';
    if(sumTime == 270 && winTime == 270) return 'Afghanistan Standard Time';
    if(sumTime == 360 && winTime == 300) return 'Ekaterinburg Standard Time';
    if(sumTime == 300 && winTime == 300) return 'West Asia Standard Time';
    if(sumTime == 330 && winTime == 330) return 'India Standard Time';
    if(sumTime == 345 && winTime == 345) return 'Nepal Standard Time';
    if(sumTime == 420 && winTime == 360) return 'N. Central Asia Standard Time';
    if(sumTime == 360 && winTime == 360) return 'Sri Lanka Standard Time';
    if(sumTime == 390 && winTime == 390) return 'Myanmar Standard Time';
    if(sumTime == 420 && winTime == 420) return 'SE Asia Standard Time';
    if(sumTime == 480 && winTime == 420) return 'North Asia Standard Time';
    if(sumTime == 480 && winTime == 480) return 'China Standard Time';
    if(sumTime == 540 && winTime == 480) return 'North Asia East Standard Time';
    if(sumTime == 540 && winTime == 540) return 'Tokyo Standard Time';
    if(sumTime == 600 && winTime == 540) return 'Yakutsk Standard Time';
    if(sumTime == 570 && winTime == 630) return 'Cen. Australia Daylight Time';
    if(sumTime == 570 && winTime == 570) return 'AUS Central Standard Time';
    if(sumTime == 600 && winTime == 600) return 'E. Australia Standard Time';
    if(sumTime == 600 && winTime == 660) return 'AUS Eastern Standard Time';
    if(sumTime == 660 && winTime == 600) return 'Vladivostok Standard Time';
    if(sumTime == 660 && winTime == 660) return 'Central Pacific Standard Time';
    if(sumTime == 720 && winTime == 780) return 'New Zealand Daylight Time';
    if(sumTime == 720 && winTime == 720) return 'Fiji Standard Time';
    if(sumTime == 780 && winTime == 780) return 'Tonga Standard Time';
}

2. Create a lookuptable with all the Timezone return values as above.

CREATE TABLE [dbo].[lookuptable](
    [idLookup] [bigint] IDENTITY(1,1) NOT NULL,
    [groupName] [varchar](20) NOT NULL,
    [lookupName] [varchar](100) NOT NULL,
 CONSTRAINT [PK_lookuptable] PRIMARY KEY (idLookup)
)
GO

INSERT INTO lookupTable (groupName, lookupName) VALUES ('Timezone', 'Dateline Standard Time')
INSERT INTO lookupTable (groupName, lookupName) VALUES ('Timezone', 'Samoa Standard Time')
INSERT INTO lookupTable (groupName, lookupName) VALUES ('Timezone', 'Hawaiian Standard Time')
INSERT INTO lookupTable (groupName, lookupName) VALUES ('Timezone', 'Alaskan Standard Time')
INSERT INTO lookupTable (groupName, lookupName) VALUES ('Timezone', 'Pacific Standard Time')

3. From action/servlet return those values from step 2 to the jsp

4. Create another JavaScript function SetTimeZoneValue()

function setTimeZoneValue()
{
    var currTimeZone = getTimezoneName();
    document.getElementById('timezone_id').value=currTimeZone;
}

 

5. In JSP call setDefaultTimezone() from body onload

<body onload="SetTimezoneName();">

 

Deploy and test the application. Change the timezone from windows date and time properties to see the effect.

Hope the solution helps all 🙂

jboss Deployment Problem : java.sql.SQLException: User not found: SA

Some times while deploying J2EE applications in jBoss server the following error message will be encounter (partial trace) :

[org.jboss.system.ServiceController] Problem starting service jboss:service=Hypersonic,database=localDB
java.sql.SQLException: User not found: SA
    at org.hsqldb.jdbc.Util.sqlException(Unknown Source)
    at org.hsqldb.jdbc.jdbcConnection.<init>(Unknown Source)
    at org.hsqldb.jdbcDriver.getConnection(Unknown Source)
    at org.hsqldb.jdbcDriver.connect(Unknown Source)
    at java.sql.DriverManager.getConnection(DriverManager.java:525)
    at java.sql.DriverManager.getConnection(DriverManager.java:171)
    at org.jboss.jdbc.HypersonicDatabase.getConnection(HypersonicDatabase.java:768)

Please follow the following steps to rectify the problem

  1. stop jboss server
  2. delete tmp, work and data folder from your <jBoss-home>/server/<servername>/

Hope the solution works for all 🙂

Cheers

Sasanka