Technical blog

November 26, 2010

[Example] Hibernate One-To-Many bidirectional relation

Filed under: database, hibernate, java — Tags: , , — paawak @ 01:59

Let us consider the relation between Semester and Subjects. A Semester has a collection of Subjects. Each Subject, in turn, has a reference to the Semester.

This is illustrated as below:

public class Semester {
 
    private Long semesterId;
 
    private String semesterName;
 
    private Set<Subject> subjects;
 
...
 
}
 
public class Subject {
 
    private Long subjectId;
 
    private String subjectName;
 
    private Semester semester;
...
}

This is how I convert it to Hibernate entities:

@Entity
public class Semester implements Serializable {
 
    private static final long serialVersionUID = -6067841723974478563L;
 
    @Id
    @Column(name = "SEMESTER_ID")
    @SequenceGenerator(name = "seq", allocationSize = 1, initialValue = 1, sequenceName = "SEQ_SEMESTER")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
    private Long semesterId;
 
    @Column(name = "SEMESTER_NAME")
    private String semesterName;
 
    @OneToMany(mappedBy = "semester", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Subject> subjects;
 
...
}
 
@Entity
public class Subject implements Serializable {
 
    private static final long serialVersionUID = 5705981176568667418L;
 
    @Id
    @Column(name = "SUBJECT_ID")
    @SequenceGenerator(name = "seq", allocationSize = 1, initialValue = 1, sequenceName = "SEQ_SUBJECT")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
    private Long subjectId;
 
    @Column(name = "SUBJECT_NAME")
    private String subjectName;
 
    @ManyToOne
    @JoinColumn(name = "SEMESTER_ID_FK")
    private Semester semester;
 
...
}

Further, I should be able to insert it with the following code:

        Set<Subject> subjects = new HashSet<Subject>();
 
        Semester semester = new Semester();
 
        for (int i = 1; i <; 5; i++) {
 
            Subject subject = new Subject();
            subject.setSubjectName("subject_" + i);
            subject.setSemester(semester);
 
            subjects.add(subject);
 
        }
 
        semester.setSemesterName("1st sem");
        semester.setSubjects(subjects);
 
        Transaction tr = session.beginTransaction();
        session.save(semester);
        tr.commit();

The SQL script is:

CREATE SEQUENCE  SEQ_SEMESTER
START WITH 1
INCREMENT BY 1;
 
CREATE TABLE SEMESTER (
 
	SEMESTER_ID NUMBER PRIMARY KEY NOT NULL,
	SEMESTER_NAME VARCHAR(50) NOT NULL
 
);
 
CREATE SEQUENCE  SEQ_SUBJECT
START WITH 1
INCREMENT BY 1;
 
CREATE TABLE SUBJECT (
 
	SUBJECT_ID NUMBER PRIMARY KEY NOT NULL,
	SUBJECT_NAME VARCHAR(50) NOT NULL,
	SEMESTER_ID_FK NUMBER NOT NULL,
	CONSTRAINT CNSTR_SEMESTER_ID_FK FOREIGN KEY(SEMESTER_ID_FK) REFERENCES SEMESTER(SEMESTER_ID)
 
);

The sources can be found here.

November 2, 2010

[How To] Handle Oracle TimeStamp with TimeZone from Java

Filed under: database, hibernate, java — Tags: , , — paawak @ 02:39

Oracle TimeStamp with TimeZone: A Critical Analysis

For storing the TimeZone information in TimeStamp, Oracle (10g) has two options:

  1. TIMESTAMP WITH TIME ZONE data type:  As the name indicates, this stores the TimeZone data along with the TimeStamp. The TimeZone can be either stored in the UTC format (eaxmple: +05:30) or the TimeZoneRegion format (example: Asia/Calcutta or Indian Standard Time).
  2. TIMESTAMP WITH LOCAL TIME ZONE data type: This does not store the TimeZone information per se. While inserting data, it adjusts for the offset of the client’s TimeZone and stores it according to the db server’s local TimeZone, without the actual TimeZone information. Similarly, while querying, the TimeStamp will be offseted against the client’s TimeZone, without returning any explicit TimeZone information. This data type is very confusing and is to be avoided at all costs.

As far as JDBC is concerned, things are plain and simple when we talk about Statement. The trouble starts the moment we think about PreparedStatement. Both these data types map to the java.sql.Timestamp in JDBC. But java.sql.Timestamp does not have any TimeZone information. In this scenario, we should be using the PreparedStatement.setTimestamp(int parameterIndex, Timestamp x, Calendar cal) to pass the TimeZone information to the db driver. There is another twist to this tale. The TIMESTAMP WITH LOCAL TIME ZONE data type needs to adjust the time against the client’s TimeZone. When we open a Oracle client session, the TimeZone is already set. But that is not the case with JDBC Connection. The TimeZone info is not set by the driver, for reasons best known to themselves. So a simple ResultSet.getTimestamp(int columnIndex) will not work. Instead, we have to use the ResultSet.getTimestamp(int columnIndex, Calendar cal) to pass the TimeZone to the db driver.

Simple JDBC example

Consider the following table having both types of TimeStamp with TimeZone:

CREATE TABLE TIMESTAMP_DEMO (
	ID                                                    NUMBER(6) PRIMARY KEY NOT NULL,
	NAME                                             VARCHAR2(20) NOT NULL,
	TIME_WITH_ZONE                     TIMESTAMP(9) WITH TIME ZONE NOT NULL,
	TIME_WITH_ZONE_LOCAL      TIMESTAMP(9) WITH LOCAL TIME ZONE
    ) ;

This is the SQL statement for inserting data:

INSERT INTO TIMESTAMP_DEMO (ID, NAME, TIME_WITH_ZONE, TIME_WITH_ZONE_LOCAL) VALUES (0, ‘manualInsert’, TO_TIMESTAMP_TZ(’2010-09-26 11:30:00 Australia/Adelaide’,'YYYY-MM-DD HH24:MI:SS TZR’), TO_TIMESTAMP_TZ(’2010-09-26 11:30:00 Australia/Adelaide’,'YYYY-MM-DD HH24:MI:SS TZR’));

We will try to insert data from plain JDBC and then read it back to ensure that the information persisted is correct.

INSERT Approach 1

Consider the following code snippet:

        PreparedStatement pStat = con
                .prepareStatement("INSERT INTO TIMESTAMP_DEMO "
                        + " (ID, NAME, TIME_WITH_ZONE, TIME_WITH_ZONE_LOCAL) "
                        + " VALUES " + " (?, ?, ?, ?)");        
 
        pStat.setInt(1, 1);
        pStat.setString(2, "insert_1");
 
        String timeZoneId = "Australia/Adelaide";
 
        TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
 
        Calendar timeWithZone = Calendar.getInstance(timeZone);
 
        timeWithZone.set(Calendar.HOUR_OF_DAY, 11);
        timeWithZone.set(Calendar.MINUTE, 30);
        timeWithZone.set(Calendar.SECOND, 0);
        timeWithZone.set(Calendar.MILLISECOND, 0);
 
        Timestamp ts = new Timestamp(timeWithZone.getTimeInMillis());
 
        pStat.setTimestamp(3, ts, timeWithZone);
 
        pStat.setTimestamp(4, ts, timeWithZone);
 
        pStat.execute();
 
        pStat.close();

Now run the following query from any Oracle SQL Client:

SELECT TO_CHAR(TIME_WITH_ZONE, ‘YYYY-MM-DD HH24:MI:SS:FF TZR’) AS TIME_WITH_ZONE FROM TIMESTAMP_DEMO;

You will find that the returned value is:

2010-11-02 11:30:00:000000000 +05:30

Note that the TimeZone info is wrongly stored. The only conclusion that we can have is that the JDBC driver is buggy. How do we get around this problem?

INSERT Approach 2

        String timeZoneId = "Australia/Adelaide";
 
        TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
 
        Calendar timeWithZone = Calendar.getInstance(timeZone);
 
        timeWithZone.set(Calendar.HOUR_OF_DAY, 11);
        timeWithZone.set(Calendar.MINUTE, 30);
        timeWithZone.set(Calendar.SECOND, 0);
        timeWithZone.set(Calendar.MILLISECOND, 0);
 
        String dateFormat = "yyyy-MM-dd HH:mm:ss:SSS";
        DateFormat df = new SimpleDateFormat(dateFormat);
 
        // this is very important
        if (timeZone != null) {
            df.setTimeZone(timeZone);
        }
 
        String dateTime = df.format(timeWithZone.getTime());
        String tzId = timeWithZone.getTimeZone().getID();
        dateTime += " " + tzId;
 
        PreparedStatement pStat = con
                .prepareStatement("INSERT INTO TIMESTAMP_DEMO "
                        + " (ID, NAME, TIME_WITH_ZONE, TIME_WITH_ZONE_LOCAL) "
                        + " VALUES "
                        + " (?, ?, TO_TIMESTAMP_TZ(?, 'YYYY-MM-DD HH24:MI:SS:FF TZR'), ?)");
 
        pStat.setInt(1, 2);
        pStat.setString(2, "insert_2");
 
        Timestamp ts = new Timestamp(timeWithZone.getTimeInMillis());
 
        pStat.setString(3, dateTime);
 
        pStat.setTimestamp(4, ts, timeWithZone);
 
        pStat.execute();
 
        pStat.close();

On running the select statement, the data got back is:

2010-11-02 11:30:00:000000000 AUSTRALIA/ADELAIDE

This is precisely the data which was inserted. The trcik here is to bypass the JDBC data and use Oracle function TO_TIMESTAMP_TZ(timeString, format).

SELECT Approach 1

        Statement stat = con.createStatement();
 
        ResultSet res = stat
                .executeQuery("SELECT * FROM TIMESTAMP_DEMO  ORDER BY ID");
 
        while (res.next()) {
 
            Timestamp timestamp = res.getTimestamp("TIME_WITH_ZONE");
 
            Timestamp timestampLocal = null;
 
            try {
                timestampLocal = res.getTimestamp("TIME_WITH_ZONE_LOCAL");
            } catch (SQLException e) {
                e.printStackTrace();
                break;
            }
 
            System.out.println("TIME=" + timestamp + ", TIME_LOCAL="
                    + timestampLocal);
 
        }
 
        stat.close();
        res.close();

On running the above, we get the following exception:

java.sql.SQLException: The database session time zone is not set
at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:70)
at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:110)

This is got on the line

res.getTimestamp(”TIME_WITH_ZONE_LOCAL”);

as the TimeZone is not set in the JDBC Connection object.

SELECT Approach 2

        Statement stat = con.createStatement();
 
        ResultSet res = stat
                .executeQuery("SELECT * FROM TIMESTAMP_DEMO  ORDER BY ID");
 
        while (res.next()) {
 
            Timestamp timestamp = res.getTimestamp("TIME_WITH_ZONE");
 
            Timestamp timestampLocal = res.getTimestamp("TIME_WITH_ZONE_LOCAL",
                    new GregorianCalendar(TimeZone.getDefault()));
 
            System.out.println(res.getString("NAME") + ": "
                    + res.getString("ID") + ", TIME="
                    + getTimeWithZone(timestamp) + ", TIME_LOCAL="
                    + getTimeWithZone(timestampLocal));
 
        }
 
        stat.close();
        res.close();

And this is how getTimeWithZone() looks like:

    public static String getTimeWithZone(Timestamp timestamp) {
 
        Calendar cal = new GregorianCalendar();
        cal.setTime(timestamp);
 
        return getTimeWithZone(cal);
 
    }
 
    public static String getTimeWithZone(Calendar cal) {
 
        String dateFormat = "HH:mm:ss:SSS zzzz";
        DateFormat df = new SimpleDateFormat(dateFormat);
 
        // this is very important
        TimeZone timeZone = cal.getTimeZone();
 
        if (timeZone != null) {
            df.setTimeZone(timeZone);
        }
 
        String dateTime = df.format(cal.getTime());
 
        return dateTime;
 
    }

We get the following sysouts:

insert_1: 1, TIME=11:30:00:000 India Standard Time, TIME_LOCAL=06:00:00:000 India Standard Time
insert_2: 2, TIME=06:30:00:000 India Standard Time, TIME_LOCAL=06:00:00:000 India Standard Time

Note that in both cases, the returned TimeZone is wrongly fetched. Again, the JDBC driver is buggy.

SELECT Approach 3

As with INSERT, we will try to bypass the JDBC driver as follows:

        Statement stat = con.createStatement();
 
        ResultSet res = stat.executeQuery("SELECT ID, NAME, "
                + " TO_CHAR(TIME_WITH_ZONE, 'HH24:MI:SS:FF TZR'), "
                + " TO_CHAR(TIME_WITH_ZONE_LOCAL, 'HH24:MI:SS:FF TZR') "
                + " FROM TIMESTAMP_DEMO  ORDER BY ID");
 
        while (res.next()) {
 
            String timestamp = res.getString(3);
            String timestampLocal = res.getString(4);
 
            System.out.println(res.getString("NAME") + ": "
                    + res.getString("ID") + ", TIME=" + timestamp
                    + ", TIME_LOCAL=" + timestampLocal);
 
        }
 
        stat.close();
        res.close();

The following sysouts are got:

insert_1: 1, TIME=11:30:00:000000000 +05:30, TIME_LOCAL=11:30:00:000000000 +05:30
insert_2: 2, TIME=11:30:00:000000000 AUSTRALIA/ADELAIDE, TIME_LOCAL=11:30:00:000000000 +05:30

Note that the values returned are exactly as intended.

Hibernate Example

Let us try and translate the above code into Hibernate. The first inference that we can draw from the above discussion is that get() on TIMESTAMP WITH LOCAL TIME ZONE will not work with the usual approach, and we have to define our custom data type to customise get() on PreparedStatement.

Hibernate Approach 1

This is how my custom data type (to map to TIMESTAMP WITH LOCAL TIME ZONE) looks like:

public class TimestampType implements UserType {
 
    // private static final Logger LOG = Logger.getLogger(TimestampType.class);
 
    @Override
    public int[] sqlTypes() {
        return new int[] { Types.TIMESTAMP };
    }
 
    @Override
    public Class returnedClass() {
        return Calendar.class;
    }
 
    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
 
        if (x == null || y == null) {
            return false;
        }
 
        return x.equals(y);
    }
 
    @Override
    public int hashCode(Object x) throws HibernateException {
 
        if (x != null) {
            return x.hashCode();
        }
 
        return 0;
    }
 
    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
            throws HibernateException, SQLException {
 
        Calendar cal = new GregorianCalendar();
        Timestamp timestamp = rs.getTimestamp(names[0], cal);
 
        if (timestamp != null) {
            cal.setTime(timestamp);
        } else {
            cal = null;
        }
 
        return cal;
    }
 
    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index)
            throws HibernateException, SQLException {
 
        if (value == null) {
            st.setNull(index, Types.DATE);
        } else {
 
            doInstanceCheck(value);
            Calendar cal = (Calendar) value;
            Timestamp timestamp = new Timestamp(cal.getTimeInMillis());
            st.setTimestamp(index, timestamp, cal);
 
        }
 
    }
 
    @Override
    public Object deepCopy(Object value) throws HibernateException {
 
        Calendar clone = null;
 
        if (value != null) {
 
            doInstanceCheck(value);
            Calendar cal = (Calendar) value;
 
            // just copying the timezone and time
            clone = new GregorianCalendar();
            clone.setTimeInMillis(cal.getTimeInMillis());
            TimeZone tz = cal.getTimeZone();
            clone.setTimeZone(TimeZone.getTimeZone(tz.getID()));
        }
 
        return clone;
    }
 
    @Override
    public boolean isMutable() {
        return true;
    }
 
    @Override
    public Serializable disassemble(Object value) throws HibernateException {
 
        Calendar cal = null;
 
        if (value != null) {
 
            doInstanceCheck(value);
            cal = (Calendar) deepCopy(value);
 
        }
 
        return cal;
    }
 
    @Override
    public Object assemble(Serializable cached, Object owner)
            throws HibernateException {
        return disassemble(cached);
    }
 
    @Override
    public Object replace(Object original, Object target, Object owner)
            throws HibernateException {
        return disassemble(original);
    }
 
    protected void doInstanceCheck(Object value) {
 
        if ((value != null) &amp;&amp; !(value instanceof Calendar)) {
            throw new UnsupportedOperationException(value.getClass()
                    + " not supported, expecting type "
                    + Calendar.class.getName());
        }
 
    }
 
}

And this is how my entity looks like:

@Entity
@Table(name = "TIMESTAMP_DEMO")
public class TimestampDemo implements Serializable {
 
    private static final long serialVersionUID = -5902132666472097299L;
 
    private long id;
 
    private String name;
 
    private Calendar timeWithZone;
 
    private Calendar timeWithZoneLocal;
 
    @Id
    @SequenceGenerator(name = "seq", sequenceName = "TIMESTAMP_DEMO_SEQ")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
    public long getId() {
        return id;
    }
 
    @Column
    public String getName() {
        return name;
    }
 
    @Column(name = "TIME_WITH_ZONE")
    @Temporal(TemporalType.TIMESTAMP)
    public Calendar getTimeWithZone() {
        return timeWithZone;
    }
 
    @Column(name = "TIME_WITH_ZONE_LOCAL")
    @Type(type = "com.swayam.demo.oracle.hibernate.custom.TimestampType")
    public Calendar getTimeWithZoneLocal() {
        return timeWithZoneLocal;
    }
 
    public void setId(long id) {
        this.id = id;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public void setTimeWithZone(Calendar timeWithZone) {
        this.timeWithZone = timeWithZone;
    }
 
    public void setTimeWithZoneLocal(Calendar timeWithZoneLocal) {
        this.timeWithZoneLocal = timeWithZoneLocal;
    }
 
}

As expected, this will not save/get correct TimeZone data as it depends on the buggy JDBC driver.

Hibernate Approach 2

Here, as before, we use the TO_TIMESTAMP_TZ() function to circumvent the buggy JDBC driver. We keep the TimestampType for mapping to TIMESTAMP WITH LOCAL TIME ZONE and add TimestampType2 to map to TIMESTAMP WITH TIME ZONE.

public class TimestampType2 extends TimestampType {
 
    private static final Logger LOG = Logger.getLogger(TimestampType2.class);
 
    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
            throws HibernateException, SQLException {
 
        Calendar cal = null;
        String timestamp = rs.getString(names[0]);
 
        LOG.info("raw timestamp=" + timestamp);
 
        if (timestamp != null) {
            cal = parseOracleTimestampWithZone(timestamp);
        }
 
        return cal;
    }
 
    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index)
            throws HibernateException, SQLException {
 
        if (value == null) {
            st.setNull(index, Types.DATE);
        } else {
 
            doInstanceCheck(value);
            Calendar cal = (Calendar) value;
 
            String dateTime = getOracleFormattedTimeWithZone(cal);
 
            LOG.info("dateTime=" + dateTime);
            LOG.info("index=" + index);
 
            st.setString(index, dateTime);
 
        }
 
    }
 
    /**
     * Converts raw time-stamp with time-zone string from Oracle to Calendar
     * containing the time-zone
     *
     * @param rawTimestamp
     *            in the format <em>2010-9-26 11.30.0.0 Australia/Adelaide</em>
     * @return
     */
    private static Calendar parseOracleTimestampWithZone(String rawTimestamp) {
 
        Calendar cal = null;
 
        String dateFormat = "yyyy-MM-dd HH.mm.ss.SSS";
        SimpleDateFormat df = new SimpleDateFormat(dateFormat);
 
        try {
 
            Date date = df.parse(rawTimestamp);
            Calendar tempCal = Calendar.getInstance();
            tempCal.setTime(date);
 
            String timeZoneId = rawTimestamp.split("\\s")[2];
            TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
            cal = new GregorianCalendar(timeZone);
 
            // setting the date and time-zone does not work, as Calendar adjusts
            // for time zone, so the below circus
 
            cal.set(Calendar.YEAR, tempCal.get(Calendar.YEAR));
            cal.set(Calendar.MONTH, tempCal.get(Calendar.MONTH));
            cal.set(Calendar.DATE, tempCal.get(Calendar.DATE));
            cal.set(Calendar.HOUR_OF_DAY, tempCal.get(Calendar.HOUR_OF_DAY));
            cal.set(Calendar.MINUTE, tempCal.get(Calendar.MINUTE));
            cal.set(Calendar.SECOND, tempCal.get(Calendar.SECOND));
            cal.set(Calendar.MILLISECOND, tempCal.get(Calendar.MILLISECOND));
 
        } catch (ParseException e) {
            LOG.error("Could not convert string `" + rawTimestamp
                    + "` to Calendar", e);
        }
 
        return cal;
 
    }
 
    private static String getOracleFormattedTimeWithZone(Calendar timeWithZone) {
 
        String dateFormat = "yyyy-MM-dd HH:mm:ss:SSS";
        DateFormat df = new SimpleDateFormat(dateFormat);
 
        // this is very important
        TimeZone timeZone = timeWithZone.getTimeZone();
 
        if (timeZone != null) {
            df.setTimeZone(timeZone);
        }
 
        String dateTime = df.format(timeWithZone.getTime());
        String tzId = timeWithZone.getTimeZone().getID();
        dateTime += " " + tzId;
 
        return dateTime;
 
    }
 
}

And this is how my modified entity looks like:

@SQLInsert(sql = "INSERT INTO TIMESTAMP_DEMO (NAME, TIME_WITH_ZONE, TIME_WITH_ZONE_LOCAL, ID) values (?, TO_TIMESTAMP_TZ(?, 'YYYY-MM-DD HH24:MI:SS:FF TZR'), ?, ?)")
@Table(name = "TIMESTAMP_DEMO")
@Entity
public class TimestampDemo2 implements Serializable {
 
    private static final long serialVersionUID = 4940963602672391841L;
 
    private long id;
 
    private String name;
 
    private Calendar timeWithZone;
 
    private Calendar timeWithZoneLocal;
 
    @Id
    @SequenceGenerator(name = "seq", sequenceName = "TIMESTAMP_DEMO_SEQ")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
    public long getId() {
        return id;
    }
 
    @Column
    public String getName() {
        return name;
    }
 
    @Column(name = "TIME_WITH_ZONE")
    @Type(type = "com.swayam.demo.oracle.hibernate.custom.TimestampType2")
    public Calendar getTimeWithZone() {
        return timeWithZone;
    }
 
    @Column(name = "TIME_WITH_ZONE_LOCAL")
    @Type(type = "com.swayam.demo.oracle.hibernate.custom.TimestampType")
    public Calendar getTimeWithZoneLocal() {
        return timeWithZoneLocal;
    }
 
    public void setId(long id) {
        this.id = id;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public void setTimeWithZone(Calendar timeWithZone) {
        this.timeWithZone = timeWithZone;
    }
 
    public void setTimeWithZoneLocal(Calendar timeWithZoneLocal) {
        this.timeWithZoneLocal = timeWithZoneLocal;
    }
 
}

This set up gives us the desirable save/get results with the correct TimeZone value.

Conclusion

I take this opportunity to illustrate the advantage of Open Source Software as opposed to proprietary ones. If the ojdbc sources were in public domain, we could have patched the bug instead of having to circumvent them as in the present instance.

Resources

The sources can be found here. There are three test cases which illustrates the above discussion. I have used the ojdbc driver 5 for this example. Since I am using Maven for building this, the libraries are not a part of this distribution. You can grab any Oracle JDBC driver from here.

October 31, 2010

Using custom data type in Hibernate

Filed under: hibernate, java — Tags: , — paawak @ 19:06

Why need custom data type?

I have a java.util.Calendar object which I want to persist in the db, along with the TimeZone. Suppose I am working with a db which does not have support for storing TimeZone. How do I proceed? One of the easiest solutions would be to store the entire timestamp as a VARCHAR. Granted, but how do I instruct Hibernate to use a java.util.Calendar object instead of a String? Its done by annotating with @Type(type=”"), where type should be the fully qualified name of a class implementing the org.hibernate.usertype.UserType interface.

Custom UserType

public class TimeWithZone implements UserType {
 
    private static final Logger LOG = Logger.getLogger(TimeWithZone.class);
 
    /**
     * Define the supported column types
     */
    @Override
    public int[] sqlTypes() {
        return new int[] { Types.VARCHAR };
    }
 
    @Override
    public Class returnedClass() {
        return Calendar.class;
    }
 
    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
 
        if (x == null || y == null) {
            return false;
        }
 
        return x.equals(y);
    }
 
    @Override
    public int hashCode(Object x) throws HibernateException {
 
        if (x != null) {
            return x.hashCode();
        }
 
        return 0;
    }
 
    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
            throws HibernateException, SQLException {
 
        Calendar cal = null;
        String timestampStr = rs.getString(names[0]);
 
        if (timestampStr != null) {
            cal = getTimeWithZone(timestampStr);
        }
 
        return cal;
    }
 
    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index)
            throws HibernateException, SQLException {
 
        if (value == null) {
            st.setNull(index, Types.VARCHAR);
        } else {
 
            doInstanceCheck(value);
            Calendar cal = (Calendar) value;
            st.setString(index, getTimeWithZone(cal));
 
        }
 
    }
 
    @Override
    public Object deepCopy(Object value) throws HibernateException {
 
        Calendar clone = null;
 
        if (value != null) {
 
            doInstanceCheck(value);
            Calendar cal = (Calendar) value;
 
            // just copying the timezone and time
            clone = new GregorianCalendar();
            clone.setTimeInMillis(cal.getTimeInMillis());
            TimeZone tz = cal.getTimeZone();
            clone.setTimeZone(TimeZone.getTimeZone(tz.getID()));
        }
 
        return clone;
    }
 
    @Override
    public boolean isMutable() {
        return true;
    }
 
    @Override
    public Serializable disassemble(Object value) throws HibernateException {
 
        Calendar cal = null;
 
        if (value != null) {
 
            doInstanceCheck(value);
            cal = (Calendar) deepCopy(value);
 
        }
 
        return cal;
    }
 
    @Override
    public Object assemble(Serializable cached, Object owner)
            throws HibernateException {
        return disassemble(cached);
    }
 
    @Override
    public Object replace(Object original, Object target, Object owner)
            throws HibernateException {
        return disassemble(original);
    }
 
    protected void doInstanceCheck(Object value) {
 
        if ((value != null) &amp;&amp; !(value instanceof Calendar)) {
            throw new UnsupportedOperationException(value.getClass()
                    + " not supported, expecting type "
                    + Calendar.class.getName());
        }
 
    }
 
    /**
     * Converts a String <em>2010-09-26 11:30:00 Australia/Adelaide</em> to
     * Calendar
     *
     * @param timeWithZone
     * @return
     */
    private Calendar getTimeWithZone(String timeWithZone) {
 
        String timeZoneId = timeWithZone.split("\\s")[2];
 
        TimeZone tz = TimeZone.getTimeZone(timeZoneId);
 
        String format = "yyyy-MM-dd HH:mm:ss";
        DateFormat df = new SimpleDateFormat(format);
        df.setTimeZone(tz);
 
        try {
            df.parse(timeWithZone);
        } catch (ParseException e) {
            LOG.error("could not parse date string: " + timeWithZone, e);
        }
 
        return df.getCalendar();
 
    }
 
    private String getTimeWithZone(Calendar timeWithZone) {
 
        String format = "yyyy-MM-dd HH:mm:ss zzzz";
        DateFormat df = new SimpleDateFormat(format);
        df.setTimeZone(timeWithZone.getTimeZone());
 
        return df.format(timeWithZone.getTime());
 
    }
 
}

Hibenate Entity

@Entity
@Table(name = "CUSTOM_DEMO")
public class CustomDemo {
 
    @Id
    @SequenceGenerator(name = "seq", allocationSize = 1, initialValue = 1, sequenceName = "CUSTOM_DEMO_SEQ")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
    private long id;
 
    @Column
    private String name;
 
    @Column(name = "TIME_WITH_ZONE")
    @Type(type = "com.swayam.demo.oracle.hibernate.TimeWithZone")
    private Calendar timeWithZone;
 
    public long getId() {
        return id;
    }
 
    public void setId(long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Calendar getTimeWithZone() {
        return timeWithZone;
    }
 
    public void setTimeWithZone(Calendar timeWithZone) {
        this.timeWithZone = timeWithZone;
    }
 
}

Resources

The sources can be found here.

Id generation in Hibernate with Sequence

Filed under: hibernate, java — Tags: , — paawak @ 18:47

I have the following SQL Script:

CREATE SEQUENCE  MY_SEQ
START WITH 1
INCREMENT BY 1;
 
CREATE TABLE  SEQ_DEMO (
	ID             INTEGER PRIMARY KEY NOT NULL,
	NAME     VARCHAR2(20) NOT NULL
    ) ;

The Hibernate entity for this would be:

@Entity
@Table(name = "SEQ_DEMO")
public class SeqDemo {
 
    @Id
    @SequenceGenerator(name = "seq", allocationSize = 1, initialValue = 1, sequenceName = "MY_SEQ")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
    private long id;
 
    @Column
    private String name;
 
    public long getId() {
        return id;
    }
 
    public void setId(long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}

September 5, 2010

Annotation based Spring MVC, how tos

Filed under: java, spring — Tags: , , — paawak @ 11:26

I will list here the common how to-s for the new Annotation based Spring MVC. Please note that I am using Spring version 3.0.0.RELEASE. This is a bit, only a small bit, different from the version 2.5.x. So, if you are using 2.5.x, you have to tweak things a bit before you get the same effect.

Consider the following screen:

samplehtml

The fantatstic thing about Spring MVC is it lets you take an Object Oriented approach while designing the Model (Form Bean) and lets you bind that seamlessly to the View (JSP Form).  For the above scenario, we can break down the top section into a list of rows. I would denote the row with a class having select, itemName, price… attributes, as shown below (this is an inner class in my ItemBean class, which is my Form Bean):

    public static class ItemRow {
 
        private boolean selected;
        private String itemName;
        private float price;
        private int quantity;
 
        ...
    }

Then, in my ItemBean class, I can have a List of ItemRow, as shown:

public class ItemBean {
 
    private List<ItemRow> items;
    private float totalPrice;
    private Date expectedDelivery;
...
}

How do I display/bind my Model to View?

This is how the View (Item.jsp) looks like:

                                           <c:forEach var="item" items="${command.items}" varStatus="count">
						<tr>
							<td>
								<form:checkbox id="chkSelect" path="items[${count.index}].selected" />
							</td>
							<td>
								<c:out value="${item.itemName}"/>
							</td>
							<td>
								<input id="txtUnitPrice" type="text" disabled="disabled" value="${item.price}" size="4"/>
							</td>
							<td>								
								<form:input id="txtQuantity" path="items[${count.index}].quantity" size="3" disabled="${!item.selected}"/>
							</td>
							<td>
								<input id="txtItemPrice" type="text" readonly="readonly" value="${item.price * item.quantity}" size="6"/>
							</td>
						</tr>
						</c:forEach>
                                                <tr>
							<td style="height:30px;">
							</td>
						</tr>
						<tr>
							<td colspan="3">
								Total Price:
							</td>
							<td>
								<form:input id="txtTotalPrice" path="totalPrice" readonly="true" size="6" />
							</td>
						</tr>

Especially note the

path=”items[${count.index}].selected”

in the form:checkbox tag. This is the key. It binds a particular index of the items in the ItemBean class to the JSP.

How to map a given url to my Controller method

After the introduction of Annotation @Controller, all Spring MVC controllers have become like MultiActionController, that is, you can handle multiple requests in the same controller. Mapping a url is very simple: you should annotate any public method with the @RequestMapping(”/DesiredUrl.do”). Of course, you should make sure:

1.> The class is annotated with @Controller
2.> The following line is present in the Spring context file:

<context:component-scan base-package="com.swayam.demo.web.controller" />

The method can have a motley combination of parameters and return type. Read the javadocs for details. This annotation takes an optional parameter method, which can be any enum RequestMethod type. If you do not specify anything, your method will handle all types of requests like GET, POST, DELETE, etc.

How do I pre-load or customise my Form Bean before it reaches my controller method?

In Spring MVC, the framework creates an instance of the Form Bean and binds it to the fields in JSP in case of a POST. In many cases, I would like to customise the Form Bean before it starts binding to the JSP fields. In older versions of Spring MVC, this was done by overriding the formBackingObject() method in AbstractFormController.
With annotations, you do it by specifying the @ModelAttribute before the FormBean attribute in the request handler method and then again on a public method returning the customised Form Bean as shown below:

    @RequestMapping(value = "/checkout.htm", method = RequestMethod.POST)
    public ModelAndView checkout(
            @ModelAttribute("postBean") ItemBean formBean,
            BindingResult errors) {
 
        ModelAndView model = new ModelAndView();
        model.addObject("command", formBean);
...
        return model;
 
    }
 
    @ModelAttribute("postBean")
    public ItemBean initBeanForPost() {
 
        ItemBean bean = new ItemBean();
        populateBean(bean, true);
        return bean;
 
    }

Note that its always a good practice to specify the name in the @ModelAttribute annotation. This way you can pre-load different beans for different methods. As shown in the example above, the name specified in the @ModelAttribute annotation in the request handler method checkout() and that in the method initBeanForPost() should be an exact match. By the way, this works for GET as well as POST or any other request type.

How do I do the validation?

Spring version 3.x onwards, doing validations have become very easy. I will describe this in the following few steps:

1. Write a Validator

Your validator should implement the Validator interface. It has two methods that you need to override. The supports() method makes sure that the Validator can validate a given FormBean. The validate() method does the actual job of validating. A typical Validator will look like this:

class ItemValidator implements Validator {
 
    @Override
    public void validate(Object target, Errors errors) {
 
        ItemBean bean = (ItemBean) target;
 
        if (bean.getTotalPrice() == 0) {
            errors.rejectValue("totalPrice", "noItemsSelected");
        }
 
    }
 
    @Override
    public boolean supports(Class<?> clazz) {
        return clazz == ItemBean.class;
    }
 
}

2. Set the Validator

You do this in a public method annotated with @InitBinder in the controller.

    @InitBinder
    public void initBinder(WebDataBinder binder, WebRequest webRequest) {
 
        if (binder.getTarget() instanceof ItemBean) {
 
            binder.setValidator(new ItemValidator());
 
        }
 
    }

I am setting the validator inside the if (binder.getTarget() instanceof ItemBean) block as otherwise I get an ugly exception in case there are any validation errors. I am putting the stack trace below:


java.lang.IllegalStateException: Invalid target for Validator [com.swayam.demo.web.controller.ItemController$1@11e7cc6]: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object ‘postBean’ on field ‘totalPrice’: rejected value [0.0]; codes [noItemsSelected.postBean.totalPrice,noItemsSelected.totalPrice,noItemsSelected.float,noItemsSelected]; arguments []; default message [null]] with root cause
java.lang.IllegalStateException: Invalid target for Validator [com.swayam.demo.web.controller.ItemController$1@11e7cc6]: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object ‘postBean’ on field ‘totalPrice’: rejected value [0.0]; codes [noItemsSelected.postBean.totalPrice,noItemsSelected.totalPrice,noItemsSelected.float,noItemsSelected]; arguments []; default message [null]
at org.springframework.validation.DataBinder.setValidator(DataBinder.java:472)
at com.swayam.demo.web.controller.ItemController.initBinder(ItemController.java:63)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.doInvokeMethod(HandlerMethodInvoker.java:710)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.initBinder(HandlerMethodInvoker.java:329)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.updateModelAttributes(HandlerMethodInvoker.java:691)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:417)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:402)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:771)

3. Use @Valid before the Form Bean

This is a new feature added in release 3.x. Now the validation happens with JSR-303 Bean Validation API. You can find the jar here. If you are using Maven, you can add the following dependency:

		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.0.0.GA</version>
		</dependency>

This is how you use it:

    @RequestMapping(value = "/checkout.htm", method = RequestMethod.POST)
    public ModelAndView checkout(
            @Valid ItemBean formBean,
            BindingResult errors) {
 
        ModelAndView model = new ModelAndView();
...
        return model;
 
    }

The Form Bean is validated with the set Validator by the framework. Note that for Spring 2.5.x, you need to make the call to validator manually inside the handler method:

...
        Validator validator = new ItemValidator();
        validator.validate(formBean, errors);    
...

In my Validator, how do I use messages from properties file for i18n?

Lets say, my properties file is Messages.properties located at /com/swayam/demo/web/res/. The contents are:

noItemsSelected=You have not selected any items

I have to create an instance of MessageSource in my Spring conf file as shown:

	<bean id="messageSource"
		class="org.springframework.context.support.ResourceBundleMessageSource">
		<property name="basenames">
			<list>
				<value>com.swayam.demo.web.res.Messages</value>
			</list>
		</property>
	</bean>

Now I can use the property key in my validate() method as shown:

    @Override
    public void validate(Object target, Errors errors) {
 
        ItemBean bean = (ItemBean) target;
 
        if (bean.getTotalPrice() == 0) {
            errors.rejectValue("totalPrice", "noItemsSelected");
        }
 
    }

How to display the Validation messages on my JSP?

First, from the controller, you have to pass an instance of BindingResult to the JSP.

    @RequestMapping(value = "/checkout.htm", method = RequestMethod.POST)
    public ModelAndView checkout(
            @Valid ItemBean formBean,
            BindingResult errors) {
 
        ModelAndView model = new ModelAndView();
        model.addObject("command", formBean);
 
        if (errors.hasErrors()) {
            model.setViewName("Item");
            model.addObject("errors", errors);
        } else {
            model.setViewName("Checkout");
        }
 
        return model;
 
    }

And this is how the JSP looks like:

		<c:if test="${ not empty errors.allErrors }"> 
		<div style="text-align:center; color:red;">
			<ul>
				<c:forEach var="error" items="${errors.allErrors}">
					<li><spring:message code="${error.code}"  text="${error.defaultMessage}"/></li>
				</c:forEach>
			</ul>
		</div>
		</c:if>

How do I bind a complex object to my JSP form field?

Often we have to display a complex object as text. The best example I can take is that of a Date. Its a java.util.Date in the model. How do I map this to a text field in my JSP? You have to extend the PropertyEditorSupport and override the following methods:
1. getAsText(): Converts an Object to its String representation. Used for displaying a model object in the JSP.
2. setAsText(String text): Converts the text from the input field to the complex object that the model understands.
A typical implementation would look like this:

class DateEditorSupport extends PropertyEditorSupport {
 
    private static final Logger LOG = Logger.getLogger(DateEditorSupport.class);
 
    private final Format formatter;
 
    DateEditorSupport(String dateFormat) {
        formatter = new SimpleDateFormat(dateFormat);
    }
 
    public String getAsText() {
 
        String date = null;
 
        Object value = getValue();
 
        if (value instanceof Date) {
 
            date = formatter.format(value);
 
        } else {
            throw new java.lang.IllegalArgumentException("Expecting a "
                    + Date.class.getName() + " class, got "
                    + value.getClass().getName());
        }
 
        return date;
 
    }
 
    public void setAsText(String text) {
 
        try {
 
            Date date = (Date) formatter.parseObject(text);
            setValue(date);
 
        } catch (ParseException e) {
            LOG.fatal("error setting date for String: " + text, e);
        }
 
    }
 
}

Then, in the @InitBinder method, you need to register this against the class:

    @InitBinder
    public void initBinder(WebDataBinder binder, WebRequest webRequest) {
 
        binder.registerCustomEditor(Date.class, new DateEditorSupport(
                "dd/MM/yyyy"));
 
    }

Putting it all together

You will find the sources here. Its an Eclipse project, using Maven. In order to get all the libraries, install Maven and run:
mvn eclipse:clean eclipse:eclipse -DdownloadSources=true -DdownloadJavadocs=true -Dwtpversion=2.0
You can also run the war file directly and go to http://localhost:8080/SpringMVC/ to see it in action.

August 28, 2010

[How To] Convert a Maven Project to Eclipse Web Project

Filed under: java — Tags: — paawak @ 12:18

Eclipse WTP has this nice feature where it allows you to deploy, run and debug Web Applications from the IDE. You start by going to the Server View, adding the target server (like Apache Tomcat in our case) and then adding the Web Project to the server. This is how the view looks like:

addremovewebprojects_1

But I have often faced this problem: when I run mvn eclipse:eclipse on a Maven Web Project to convert it to an Eclipse project, it is not recognised as a Web Project by Eclipse. When I right-click on the server and select Add Remove Projects, it does not appear in the option box. But to me  mvn eclipse:eclipse -DdownloadSources=true -DdownloadJavadocs=true is indispensable as it downloads and links all the sources and JavaDocs, which comes very handy. On the other hand if I cannot add my project as a Web Project in Eclipse, running and debugging becomes a big issue. So how do I reconcile these two?

Solution 1: Specify the WTP Version

While running mvn eclipse:eclipse, specify the WTP Version by passing the -Dwtpversion=2.0 argument. The command looks like:

mvn eclipse:clean eclipse:eclipse -DdownloadSources=true -DdownloadJavadocs=true -Dwtpversion=2.0

Note that as of this writing, the supported versions of WTP are  1.0, 1.5, 2.0 and R7.

Solution 2: Convert it to a Facted Project

Right click on the project, go to Properties, then select Project Facets.

converttofacetedproject

Another dialog appears having the available facets.

selectdynamicwebproject

Select Dynamic Web Module.

Solution 3: Manual editing

I like this best as it gives lot of flexibility around configuration. First you need to modify the .project file as shown:

<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
	<name>MyWebProject</name>
	<comment></comment>
	<projects>
	</projects>
	<buildSpec>
		<buildCommand>
			<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
			<arguments>
			</arguments>
		</buildCommand>
		<buildCommand>
			<name>org.eclipse.jdt.core.javabuilder</name>
			<arguments>
			</arguments>
		</buildCommand>
		<buildCommand>
			<name>org.eclipse.wst.common.project.facet.core.builder</name>
			<arguments>
			</arguments>
		</buildCommand>
		<buildCommand>
			<name>org.eclipse.wst.validation.validationbuilder</name>
			<arguments>
			</arguments>
		</buildCommand>
	</buildSpec>
	<natures>
		<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
		<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
		<nature>org.eclipse.jdt.core.javanature</nature>
		<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
	</natures>
</projectDescription>

Then, inside the .settings directory, you need to create two files called org.eclipse.wst.common.component and org.eclipse.wst.common.project.facet.core.xml.

org.eclipse.wst.common.component

<?xml version="1.0" encoding="UTF-8"?>
<project-modules id="moduleCoreId" project-version="1.5.0">
    <wb-module deploy-name="MyWebProject">
        <wb-resource deploy-path="/" source-path="/src/main/webapp"/>
        <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
        <property name="context-root" value="MyWebProject"/>
        <property name="java-output-path" value="/MyWebProject/build/classes"/>
    </wb-module>
</project-modules>

org.eclipse.wst.common.project.facet.core.xml

<?xml version="1.0" encoding="UTF-8"?>
<faceted-project>
  <runtime name="Apache Tomcat v7.0"/>
  <fixed facet="jst.web"/>
  <fixed facet="wst.jsdt.web"/>
  <fixed facet="java"/>
  <installed facet="java" version="1.6"/>
  <installed facet="jst.web" version="3.0"/>
  <installed facet="wst.jsdt.web" version="1.0"/>
</faceted-project>

August 22, 2010

Creating an EAR with Maven

Filed under: java — Tags: , — paawak @ 00:50

Maven is the build tool of choice for many people due to the simplicity and flexibility of use. The real strength of Maven is vetted when handling real complex projetcs consisting of tens of modules, each requiring elaborate build requirements.

I had faced an uphill task of Mavenising one of our EAR projects. As is the typical case, our project consisted of a EJB module and a WAR module, package together in a EAR module. It was a bit complex as we had to deploy the same application in JBoss and Glassfish. I will write some of that experience here.

Let us consider an enterprise application having a structure as show below:

Project Structure

I will briefly explain what the modules stand for:

  1. swayam-ear: This is the enterprise application
  2. swayam-ejb: Is the EJB module
  3. swayam-war: Is the web module
  4. swayam-shared: Has the ejb remote interfaces. As the name indicates, its shared by the sawaym-ejb and the swayam-war modules
  5. swayam-ear-builder: Used for building all the modules

I am using Netbeans (6.8) and Glassfish for convinience. But you can pretty much use anything.

I have kept things pretty simple. This is how the Remote interface looks like (its a Stateless Session Bean):

@Remote
public interface MySessionBeanRemote {
 
    String sayHello();
 
}

And this is how I access it from the servlet:

...
MySessionBeanRemote remoteBean = InitialContext.doLookup(MySessionBeanRemote.class.getName());
...

Mavenising the EJB

We will use the maven-ejb-plugin for this. This is how the pom looks like:

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.swayam.eardemo</groupId>
    <artifactId>swayam-ejb</artifactId>
    <version>1.0.0</version>
 
    <packaging>ejb</packaging>
    <name>swayam-ejb</name>
 
    <build>
        <sourceDirectory>${basedir}/src</sourceDirectory>
 
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
            </plugin>
            <plugin>
                <artifactId>maven-ejb-plugin</artifactId>
                <configuration>
                    <ejbVersion>3.0</ejbVersion>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
 
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
 
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.swayam.eardemo</groupId>
            <artifactId>swayam-shared</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>
 
</project>

Note that the packaging is ejb and not jar.

Mavenising the WAR

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.swayam.eardemo</groupId>
    <artifactId>swayam-war</artifactId>
    <version>1.0.0</version>
 
    <packaging>war</packaging>
    <name>swayam-war</name>
 
    <pluginRepositories>
        <pluginRepository>
            <id>maven.java.net</id>
            <name>Java.net Maven2 Repository</name>
            <url>http://download.java.net/maven/2</url>
        </pluginRepository>
    </pluginRepositories>
 
    <build>
        <finalName>swayam-war</finalName>
        <sourceDirectory>${basedir}/src/java</sourceDirectory>
 
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.0</version>
                <configuration>
                    <webResources>
                        <resource>
                            <directory>${basedir}/web</directory>
                            <excludes>
                                <exclude>CVS/**</exclude>
                                <exclude>WEB-INF/lib/*</exclude>
                            </excludes>
                        </resource>
                    </webResources>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
    <dependencies>
        <dependency>
            <groupId>com.swayam.eardemo</groupId>
            <artifactId>swayam-shared</artifactId>
            <version>1.0.0</version>
        </dependency>                
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.4</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>    
 
</project>

Mavenising the EAR

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.swayam.eardemo</groupId>
    <artifactId>swayam-ear</artifactId>
    <version>1.0.0</version>
 
    <packaging>ear</packaging>
    <name>swayam-ear</name>
 
    <pluginRepositories>
        <pluginRepository>
            <id>maven.java.net</id>
            <name>Java.net Maven2 Repository</name>
            <url>http://download.java.net/maven/2</url>
        </pluginRepository>
    </pluginRepositories>
 
    <build>
        <finalName>swayam-ear</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-ear-plugin</artifactId>
                <version>2.4.1</version>
                <configuration>
                    <modules>
                        <webModule>
                            <groupId>com.swayam.eardemo</groupId>
                            <artifactId>swayam-war</artifactId>
                            <bundleFileName>swayam-war.war</bundleFileName>
                            <contextRoot>/swayam-war</contextRoot>
                        </webModule>
                        <ejbModule>
                            <groupId>com.swayam.eardemo</groupId>
                            <artifactId>swayam-ejb</artifactId>
                            <bundleFileName>swayam-ejb.jar</bundleFileName>
                        </ejbModule>
                    </modules>
                </configuration>
            </plugin>
 
        </plugins>
    </build>
 
    <dependencies>
        <dependency>
            <groupId>com.swayam.eardemo</groupId>
            <artifactId>swayam-war</artifactId>
            <version>${swayam-version}</version>
            <type>war</type>
        </dependency>
        <dependency>
            <groupId>com.swayam.eardemo</groupId>
            <artifactId>swayam-ejb</artifactId>
            <version>${swayam-version}</version>
            <type>ejb</type>
        </dependency>
    </dependencies>
 
    <properties>
        <swayam-version>1.0.0</swayam-version>
    </properties>
 
</project>

The most important thing here is the type tag inside the dependency tag. Without this, it will not work.

Putting it all together

Its often very cumbersome to build these modules one by one manually when one of them changes. This is more so in a development environment. So, I will conclude with one pom for building all the modules at one go. This pom is present in the swayam-ear-builder module.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.swayam.eardemo</groupId>
  <artifactId>swayam-ear-builder</artifactId>
  <packaging>pom</packaging>
  <version>1.0.0</version>
  <name>swayam-ear-builder</name>
 
  <modules>
    <module>../swayam-ear</module>
    <module>../swayam-ejb</module>
    <module>../swayam-war</module>
    <module>../swayam-shared</module>
  </modules>
 
</project>

Note that the path of the modules are relative to the pom.
You can build the EAR project and deploy it on Glassfish or JBoss. Once successfully deployed, you can open http://localhost:8080/swayam-war/EjbInvoker This is how it looks like:

screenshot-servlet-ejbinvoker-google-chrome

Resources

  1. Sources
  2. Binaries

August 21, 2010

[How To] Configure GTalk on Kopete

Filed under: linux — Tags: , — paawak @ 01:10

Kopete is one of the most versatile and cool Instant Messenger for Linux. Its lot better and more secure and feature rich than Pidgin. GTalk is based on the open source XMPP Protocol, Jabber compatible.

While entering the new account details, select Jabber:

Select type as "Jabber"

Enter your full email id.

Go to the connection tab. Check the Override default server information check-box and enter the server as talk.google.com, the port as 5223. Also check these check boxes:

  1. Use protocol encryption

  2. Allow plain text password authentication

Connection details

Save this information and you should be done.

Tweaking MySQL on Fedora

Filed under: database, linux — Tags: , — paawak @ 00:57

MySQL is installed on Fedora and most Linuxes by default. Its just about some tweaking before you can use it. I am detailing some of the rather useful commands.

To Install MySQL and start it

mysql_install_db
mysqld_safe &

Make MySQL case insensitive

This is useful when the DB Script is also expected to run on Windows server.

vi /etc/my.cnf

[mysqld]
lower_case_table_names=1

To change the root password

mysql>

update user set password=password(”newPassword”)  where user=’root’;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0

grant all on *.* to ‘root’@'192.168.%’ identified by ‘newPassword’;

FLUSH PRIVILEGES;

Adding a user

mysql>

insert into user (host, user, password) values(’localhost’,'newUser’,password(’xx123′));

insert into  host(host,db,Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv)  values(’localhost’,'dbName’,'Y’,'Y’,'Y’,'Y’,'Y’,'Y’);

grant all on dbName.* to ‘newUser’@'localhost’ identified by ‘xx123′;

FLUSH PRIVILEGES;

[How To] Enable VPN on Linux by PPTP

Filed under: linux — Tags: , , — paawak @ 00:41

PPTP is an wonderful utility to enable VPN on a Linux box. Its secure and compatible with Windows network. I first heard about PPTP from (this wonderful guy called) Nikolaj. I have learnt how to set it up from him.

I am detailing the steps here:

1> Install PPTP

yum install pptp

2> vi /etc/ppp/options.pptp

###############################################################################

# $Id: options.pptp,v 1.2 2005/08/20 13:16:38 quozl Exp $

#

# Sample PPTP PPP options file /etc/ppp/options.pptp

# Options used by PPP when a connection is made by a PPTP client.

# This file can be referred to by an /etc/ppp/peers file for the tunnel.

# Changes are effective on the next connection. See “man pppd”.

#

# You are expected to change this file to suit your system. As

# packaged, it requires PPP 2.4.2 or later from http://ppp.samba.org/

# and the kernel MPPE module available from the CVS repository also on

# http://ppp.samba.org/, which is packaged for DKMS as kernel_ppp_mppe.

###############################################################################


# Lock the port

lock

# Authentication

# We don’t need the tunnel server to authenticate itself

noauth

persist

debug

# We won’t do EAP, CHAP, or MSCHAP, but we will accept MSCHAP-V2

refuse-eap

refuse-chap

refuse-mschap


# Compression

# Turn off compression protocols we know won’t be used

nobsdcomp

nodeflate


# Encryption

# (There have been multiple versions of PPP with encryption support,

# choose with of the following sections you will use. Note that MPPE

# requires the use of MSCHAP-V2 during authentication)


# http://ppp.samba.org/ the PPP project version of PPP by Paul Mackarras

# ppp-2.4.2 or later with MPPE only, kernel module ppp_mppe.o

# {{{

# Require MPPE 128-bit encryption

#require-mppe-128

# }}}


# http://polbox.com/h/hs001/ fork from PPP project by Jan Dubiec

# ppp-2.4.2 or later with MPPE and MPPC, kernel module ppp_mppe_mppc.o

# {{{

# Require MPPE 128-bit encryption

#mppe required,stateless

# }}}


lcp-echo-failure 36

lcp-echo-interval 5

lcp-max-failure 0

3> vi /etc/ppp/chap-secrets

# Secrets for authentication using CHAP

# client                  server        secret                        IP addresses


VPNUserName      PPTP       VPNPassword         *

4> Create a file called /etc/ppp/peers/my-company-vpn

#pty “pptp my-company.com –nolaunchpppd”

name VPNUserName

remotename PPTP

require-mppe-128

file /etc/ppp/options.pptp

ipparam my-company-vpn

5> Then on the prompt:

pptp my-company.com call my-company-vpn

6> After 10/15 seconds, on the prompt:

route -n

You should see something like:

Kernel IP routing table

Destination Gateway Genmask Flags Metric Ref Use Iface

192.168.1.162 0.0.0.0 255.255.255.255 UH 0 0 0 ppp0

87.61.21.102 192.168.1.1 255.255.255.255 UGH 0 0 0 eth0

192.168.1.0 0.0.0.0 255.255.255.0 U 1 0 0 eth0

If you see ppp0, it means you have successfully connected to the VPN.

7> Add required routes

route add -host 192.168.1.30 gw 192.168.1.162

Where 192.168.1.30 is my internal company IP.

8> After that, to resolve the domains by names

vi /etc/resolv.conf

#to use when connected to the VPN my-company.com

domain my-office.my-company.com

#this, is the most important line: courtesy: Nikolaj

search my-office.my-company.com

#nameserver 192.168.1.1

nameserver 192.168.1.4

nameserver 192.168.1.5

Further reading:

Older Posts »

Powered by WordPress