Sunday, September 14, 2014

Age implementation in Java

Few days ago I saw a post in StackoverFlow asking for finding date of birth from age in java. I thought that why don't I write a class which can manipulate Age. Here is what I came up with.
This class Age stores age in terms of

  • Years
  • Months
  • Days
  • Hours
  • Minutes
  • Seconds

Followings are the features of this class:

  • It returns Age for a given Date
  • It is Timezone specific
  • It returns age as Seconds, Minutes, Hours, Days, Weeks, Months
  • Prints Age in a pretty format
  • This class is Serializable, Cloneable and Comparable
  • It is written using java.time api

import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.ValueRange;
import java.util.Date;
import java.util.Objects;

/**
 * The class <code>Age</code> represents the length of time that a person has
 * lived or a thing has existed.
 * 
 * @author TapasB
 */
public class Age implements Serializable, Cloneable, Comparable<Age> {

 private static final long serialVersionUID = 210032925430172448L;

 private long years;
 private long months;
 private long days;
 private long hours;
 private long minutes;
 private long seconds;

 // Private Constructor
 private Age() {

 }

 /**
  * Creates and returns a new instance of <code>Age</code> object.
  * 
  * @author TapasB
  * @return a new instance of <code>Age</code>
  */
 public static Age getDefault() {
  return new Age();
 }

 /**
  * Creates and returns a new instance of <code>Age</code> object after
  * setting the given years.
  * 
  * @author TapasB
  * @param years
  *            - years to set
  * @return a new instance of <code>Age</code>
  */
 public static Age of(long years) {
  return getDefault().setYears(years);
 }

 /**
  * Creates and returns a new instance of <code>Age</code> object after
  * setting given years and months to it.
  * 
  * @author TapasB
  * @param years
  *            - years to set
  * @param months
  *            - months to set
  * @return a new instance of <code>Age</code>
  */
 public static Age of(long years, long months) {
  return of(years).setMonths(months);
 }

 /**
  * Creates and returns a new instance of <code>Age</code> object after
  * setting given years, months and days to it.
  * 
  * @author TapasB
  * @param years
  *            - years to set
  * @param months
  *            - months to set
  * @param days
  *            - days to set
  * @return a new instance of <code>Age</code>
  */
 public static Age of(long years, long months, long days) {
  return of(years, months).setDays(days);
 }

 /**
  * Creates and returns a new instance of <code>Age</code> object after
  * setting given years, months, days and hours to it.
  * 
  * @author TapasB
  * @param years
  *            - years to set
  * @param months
  *            - months to set
  * @param days
  *            - days to set
  * @param hours
  *            - hours to set
  * @return a new instance of <code>Age</code>
  */
 public static Age of(long years, long months, long days, long hours) {
  return of(years, months, days).setHours(hours);
 }

 /**
  * Creates and returns a new instance of <code>Age</code> object after
  * setting given years, months, days, hours and minutes to it.
  * 
  * @author TapasB
  * @param years
  *            - years to set
  * @param months
  *            - months to set
  * @param days
  *            - days to set
  * @param hours
  *            - hours to set
  * @param minutes
  *            - minutes to set
  * @return a new instance of <code>Age</code>
  */
 public static Age of(long years, long months, long days, long hours, long minutes) {
  return of(years, months, days, hours).setMinutes(minutes);
 }

 /**
  * Creates and returns a new instance of <code>Age</code> object after
  * setting given years, months, days, hours, minutes and seconds to it.
  * 
  * @author TapasB
  * @param years
  *            - years to set
  * @param months
  *            - months to set
  * @param days
  *            - days to set
  * @param hours
  *            - hours to set
  * @param minutes
  *            - minutes to set
  * @param seconds
  *            - seconds to set
  * @return a new instance of <code>Age</code>
  */
 public static Age of(long years, long months, long days, long hours, long minutes, long seconds) {
  return of(years, months, days, hours, minutes).setSeconds(seconds);
 }

 /**
  * Calculates and returns an instance <code>Age</code> for the given date
  * string and date pattern. The <code>Age</code> will be calculated with
  * respect to the system default time-zone.
  * 
  * @author TapasB
  * @param date
  *            - the birth date as string
  * @param pattern
  *            - date pattern
  * @return an instance of <code>Age</code>
  * @throws ParseException
  *             if the parsing fails
  */
 public static Age fromDateOfBirth(String date, String pattern) throws ParseException {
  Objects.requireNonNull(date, "date");
  Objects.requireNonNull(pattern, "pattern");
  SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
  return fromDateOfBirth(dateFormat.parse(date));
 }

 /**
  * Calculates and returns an instance of <code>Age</code> for the given
  * {@link Date}. The <code>Age</code> will be calculated with respect to the
  * system default time-zone.
  * 
  * @author TapasB
  * @param date
  *            - the birth date
  * @return an instance of <code>Age</code>
  */
 public static Age fromDateOfBirth(Date date) {
  Objects.requireNonNull(date, "date");
  return fromDateOfBirth(date, ZoneId.systemDefault());
 }

 /**
  * Calculates and returns an instance of <code>Age</code> for the given
  * {@link Date} with respect to the given {@link ZoneId}.
  * 
  * @author TapasB
  * @param date
  *            - the birth date
  * @param zone
  *            - the zone ID to use
  * @return an instance of <code>Age</code>
  */
 public static Age fromDateOfBirth(Date date, ZoneId zone) {
  Objects.requireNonNull(date, "date");
  Objects.requireNonNull(zone, "zone");
  Instant instant = Instant.ofEpochMilli(date.getTime());
  LocalDateTime dateTime = LocalDateTime.ofInstant(instant, zone);
  return fromDateOfBirth(dateTime, zone);
 }

 /**
  * Calculates and returns an instance of <code>Age</code> for the given
  * {@link LocalDateTime}. The <code>Age</code> will be calculated with
  * respect to the system default time-zone.
  * 
  * @author TapasB
  * @param date
  *            - the birth date
  * @return an instance of <code>Age</code>
  */
 public static Age fromDateOfBirth(LocalDateTime date) {
  Objects.requireNonNull(date, "date");
  return fromDateOfBirth(date, ZoneId.systemDefault());
 }

 /**
  * Calculates and returns an instance of <code>Age</code> for the given
  * {@link LocalDateTime} with respect to the given {@link ZoneId}.
  * 
  * @author TapasB
  * @param date
  *            - the birth date
  * @param zone
  *            - the zone ID to use
  * @return an instance of <code>Age</code>
  */
 public static Age fromDateOfBirth(LocalDateTime date, ZoneId zone) {
  Objects.requireNonNull(date, "date");
  Objects.requireNonNull(zone, "zone");
  ZonedDateTime zonedDate = date.atZone(zone);
  return fromDateOfBirth(zonedDate);
 }

 /**
  * Calculates and returns an instance of <code>Age</code> for the given
  * {@link ZonedDateTime}.
  * 
  * @author TapasB
  * @param date
  *            - the birth date
  * @return an instance of <code>Age</code>
  */
 public static Age fromDateOfBirth(ZonedDateTime date) {
  Objects.requireNonNull(date, "date");
  ZonedDateTime now = ZonedDateTime.now(date.getZone());
  ZonedDateTime tempDate = ZonedDateTime.from(date);

  long years = tempDate.until(now, ChronoUnit.YEARS);
  tempDate = tempDate.plusYears(years);

  long months = tempDate.until(now, ChronoUnit.MONTHS);
  tempDate = tempDate.plusMonths(months);

  long days = tempDate.until(now, ChronoUnit.DAYS);
  tempDate = tempDate.plusDays(days);

  long hours = tempDate.until(now, ChronoUnit.HOURS);
  tempDate = tempDate.plusHours(hours);

  long minutes = tempDate.until(now, ChronoUnit.MINUTES);
  tempDate = tempDate.plusMinutes(minutes);

  long seconds = tempDate.until(now, ChronoUnit.SECONDS);

  Age age = getDefault();

  if (years > 0) {
   age.years = years;
  }

  if (months > 0) {
   age.months = months;
  }

  if (days > 0) {
   age.days = days;
  }

  if (hours > 0) {
   age.hours = hours;
  }

  if (minutes > 0) {
   age.minutes = minutes;
  }

  if (seconds > 0) {
   age.seconds = seconds;
  }

  return age;
 }

 /**
  * Sets the given years to this object.
  * 
  * @author TapasB
  * @param years
  *            - years to set
  * @return this instance for chain call
  */
 public Age setYears(long years) {
  checkValidValue("years", 
    ValueRange.of(0, ChronoField.YEAR.range().getMaximum()), 
    years);
  this.years = years;
  return this;
 }

 /**
  * Sets the given months to this object.
  * 
  * @author TapasB
  * @param months
  *            - months to set
  * @return this instance for chain call
  */
 public Age setMonths(long months) {
  checkValidValue("months", 
    ValueRange.of(0, ChronoField.MONTH_OF_YEAR.range().getMaximum()), 
    months);
  this.months = months;
  return this;
 }

 /**
  * Sets the given days to this object.
  * 
  * @author TapasB
  * @param days
  *            - days to set
  * @return this instance for chain call
  */
 public Age setDays(long days) {
  checkValidValue("days", 
    ValueRange.of(0, ChronoField.DAY_OF_MONTH.range().getMaximum()), 
    days);
  this.days = days;
  return this;
 }

 /**
  * Sets the given hours to this object.
  * 
  * @author TapasB
  * @param hours
  *            - hours to set
  * @return this instance for chain call
  */
 public Age setHours(long hours) {
  checkValidValue("hours", 
    ValueRange.of(0, ChronoField.HOUR_OF_DAY.range().getMaximum()), 
    hours);
  this.hours = hours;
  return this;
 }

 /**
  * Sets the given minutes to this object.
  * 
  * @author TapasB
  * @param minutes
  *            - minutes to set
  * @return this instance for chain call
  */
 public Age setMinutes(long minutes) {
  checkValidValue("minutes", 
    ValueRange.of(0, ChronoField.MINUTE_OF_HOUR.range().getMaximum()), 
    minutes);
  this.minutes = minutes;
  return this;
 }

 /**
  * Sets the given seconds to this object.
  * 
  * @author TapasB
  * @param seconds
  *            - seconds to set
  * @return this instance for chain call
  */
 public Age setSeconds(long seconds) {
  checkValidValue("seconds", 
    ValueRange.of(0, ChronoField.SECOND_OF_MINUTE.range().getMaximum()), 
    seconds);
  this.seconds = seconds;
  return this;
 }

 /**
  * Sets the given value to this object to the given field defined by
  * {@link ChronoUnit}.
  * 
  * <p>
  * The accepted list of values are:
  * <ul>
  * <li>ChronoUnit.YEARS</li>
  * <li>ChronoUnit.MONTHS</li>
  * <li>ChronoUnit.DAYS</li>
  * <li>ChronoUnit.HOURS</li>
  * <li>ChronoUnit.MINUTES</li>
  * <li>ChronoUnit.SECONDS</li>
  * </ul>
  * </p>
  * 
  * @author TapasB
  * @param value
  *            - value to set
  * @param unit
  *            - field to which the value needs to be set
  * @return this instance for chain call
  * @throws UnsupportedOperationException
  *             if the unit passed to it is not belongs to the allowed list
  *             of values
  */
 public Age set(long value, ChronoUnit unit) throws UnsupportedOperationException {
  Objects.requireNonNull(unit, "unit");

  switch (unit) {
   case YEARS:
    return setYears(value);

   case MONTHS:
    return setMonths(value);

   case DAYS:
    return setDays(value);

   case HOURS:
    return setHours(value);

   case MINUTES:
    return setMinutes(value);

   case SECONDS:
    return setSeconds(value);

   default:
    throw new UnsupportedOperationException("Unit " + unit + " not supported");
  }
 }

 /**
  * Calculates the <code>Age</code> and returns the birth date as
  * {@link LocalDateTime}.
  * 
  * @author TapasB
  * @return date of birth as <code>LocalDateTime</code>
  */
 public LocalDateTime getDateOfBirth() {
  LocalDateTime now = LocalDateTime.now();
  LocalDateTime dob = now.minus(this.years, ChronoUnit.YEARS).
    minus(this.months, ChronoUnit.MONTHS).
    minus(this.days, ChronoUnit.DAYS).
    minus(this.hours, ChronoUnit.HOURS).
    minus(this.minutes, ChronoUnit.MINUTES).
    minus(this.seconds, ChronoUnit.SECONDS);

  return dob;
 }

 /**
  * Calculates the <code>Age</code> and returns the birth date as
  * {@link ZonedDateTime} with respect to the given {@link ZoneId}.
  * 
  * @author TapasB
  * @param zone
  *            - the zone ID to use
  * @return date of birth as <code>ZonedDateTime</code>
  */
 public ZonedDateTime getDateOfBirth(ZoneId zone) {
  ZonedDateTime now = ZonedDateTime.now(zone);
  ZonedDateTime dob = now.minus(this.years, ChronoUnit.YEARS).
    minus(this.months, ChronoUnit.MONTHS).
    minus(this.days, ChronoUnit.DAYS).
    minus(this.hours, ChronoUnit.HOURS).
    minus(this.minutes, ChronoUnit.MINUTES).
    minus(this.seconds, ChronoUnit.SECONDS);

  return dob;
 }

 /**
  * Returns elapsed seconds from date of birth for this <code>Age</code>.
  * 
  * @author TapasB
  * @return seconds
  */
 public long asSeconds() {
  return asSeconds(ZoneId.systemDefault());
 }

 /**
  * Returns elapsed seconds from date of birth for this <code>Age</code> for
  * the given. zone
  * 
  * @author TapasB
  * @param zone
  *            - the zone ID to use
  * @return seconds
  */
 public long asSeconds(ZoneId zone) {
  return calculateDifference(ChronoUnit.SECONDS, zone);
 }

 /**
  * Returns elapsed minutes from date of birth for this <code>Age</code>.
  * 
  * @author TapasB
  * @return minutes
  */
 public long asMinutes() {
  return asMinutes(ZoneId.systemDefault());
 }

 /**
  * Returns elapsed minutes from date of birth for this <code>Age</code> for
  * the given zone.
  * 
  * @author TapasB
  * @param zone
  *            - the zone ID to use
  * @return minutes
  */
 public long asMinutes(ZoneId zone) {
  return calculateDifference(ChronoUnit.MINUTES, zone);
 }

 /**
  * Returns elapsed hours from date of birth for this <code>Age</code>.
  * 
  * @author TapasB
  * @return hours
  */
 public long asHours() {
  return asHours(ZoneId.systemDefault());
 }

 /**
  * Returns elapsed hours from date of birth for this <code>Age</code> for
  * the given zone.
  * 
  * @author TapasB
  * @param zone
  *            - the zone ID to use
  * @return hours
  */
 public long asHours(ZoneId zone) {
  return calculateDifference(ChronoUnit.HOURS, zone);
 }

 /**
  * Returns elapsed days from date of birth for this <code>Age</code>.
  * 
  * @author TapasB
  * @return days
  */
 public long asDays() {
  return asDays(ZoneId.systemDefault());
 }

 /**
  * Returns elapsed days from date of birth for this <code>Age</code> for the
  * given zone.
  * 
  * @author TapasB
  * @param zone
  *            - the zone ID to use
  * @return days
  */
 public long asDays(ZoneId zone) {
  return calculateDifference(ChronoUnit.DAYS, zone);
 }

 /**
  * Returns elapsed weeks from date of birth for this <code>Age</code>.
  * 
  * @author TapasB
  * @return weeks
  */
 public long asWeeks() {
  return asWeeks(ZoneId.systemDefault());
 }

 /**
  * Returns elapsed weeks from date of birth for this <code>Age</code> for
  * the given zone.
  * 
  * @author TapasB
  * @param zone
  *            - the zone ID to use
  * @return weeks
  */
 public long asWeeks(ZoneId zone) {
  return calculateDifference(ChronoUnit.WEEKS, zone);
 }

 /**
  * Returns elapsed months from date of birth for this <code>Age</code>.
  * 
  * @author TapasB
  * @return months
  */
 public long asMonths() {
  return asMonths(ZoneId.systemDefault());
 }

 /**
  * Returns elapsed months from date of birth for this <code>Age</code> for
  * the given zone.
  * 
  * @author TapasB
  * @param zone
  *            - the zone ID to use
  * @return months
  */
 public long asMonths(ZoneId zone) {
  return calculateDifference(ChronoUnit.MONTHS, zone);
 }

 /**
  * Returns string representation of the <code>Age</code> in the following
  * format: <br/>
  * <code>A Years B Months C Days D Hours E Minutes F Seconds</code> <br/>
  * Any field except Seconds will be omitted if the value is 0.
  * 
  * @author TapasB
  * @return the string representation
  */
 public String getDisplayString() {
  StringBuilder builder = new StringBuilder();

  setString(years, builder, "Years");
  setString(months, builder, "Months");
  setString(days, builder, "Days");
  setString(hours, builder, "Hours");
  setString(minutes, builder, "Minutes");

  builder.append(seconds);
  builder.append(" Seconds.");

  return builder.toString();
 }

 /**
  * Returns string representation of the <code>Age</code> in the following
  * format: <br/>
  * <code>A Years B Months C Days D Hours E Minutes F Seconds</code> <br/>
  * Any field except Seconds will be omitted if the value is 0.
  * 
  * @author TapasB
  * @return the string representation
  */
 @Override
 public String toString() {
  return getDisplayString();
 }

 /**
  * Compares two <code>Age</code> objects.
  * 
  * @author TapasB
  * @param age
  *            - the other <code>Age</code> to be compared
  * @return the value 0 if the argument <code>Age</code> is equal to this
  *         <code>Age</code>; a value less than 0 if this <code>Age</code> is
  *         before the <code>Age</code> argument; and a value greater than 0
  *         if this <code>Age</code> is after the <code>Age</code> argument.
  */
 @Override
 public int compareTo(Age age) {
  Objects.requireNonNull(age, "age");
  long thisSeconds = asSeconds();
  long otherSeconds = age.asSeconds();
  return Long.compare(thisSeconds, otherSeconds);
 }

 /**
  * Creates and returns a copy of this object with the same years, months,
  * days, hours, minutes and seconds.
  * 
  * @author TapasB
  * @return a clone of this instance of <code>Age</code>
  */
 @Override
 protected Age clone() {
  return of(years, months, days, hours, minutes, seconds);
 }

 /**
  * Returns a hash code value for the <code>Age</code> object.
  * 
  * @author TapasB
  * @return a hash code value for this object
  */
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  Long thisSeconds = asSeconds();
  int intValue = thisSeconds.intValue();
  result = prime * result + (int) (intValue ^ (intValue >>> 32));
  return result;
 }

 /**
  * Indicates whether some other <code>Age</code> object is same to this one.
  * 
  * @author TapasB
  * @param obj
  *            - the reference object with which to compare
  * @return true if this object is the same as the obj argument; false
  *         otherwise.
  * @see java.lang.Object#equals(java.lang.Object)
  */
 @Override
 public boolean equals(Object obj) {
  if (this == obj) {
   return true;
  }

  if (obj == null) {
   return false;
  }

  if (getClass() != obj.getClass()) {
   return false;
  }

  Age age = (Age) obj;
  return compareTo(age) == 0;
 }

 // Checks the validity of the field value
 private void checkValidValue(String field, ValueRange range, long value) {
  if (!range.isValidValue(value)) {
   throw new DateTimeException(getInvalidFieldMessage(field, range, value));
  }
 }

 // Returns message in case of invalid value which is used by
 // DateTimeException
 private String getInvalidFieldMessage(String field, ValueRange range, long value) {
  return "Invalid value " + value + " for " + field + ". Valid values: (" + range.getMinimum() + ", " + range.getMaximum() + ").";
 }

 // Appends the field value of Age to the StringBuilder with the token
 private void setString(long value, StringBuilder builder, String token) {
  if (value > 0) {
   builder.append(value);
   builder.append(" ").append(token).append(" ");
  }
 }

 // Calculates the amount of time with respect to the given ChronoUnit
 // and ZoneId between the birth date and now
 private long calculateDifference(ChronoUnit chronoUnit, ZoneId zone) {
  Objects.requireNonNull(chronoUnit);
  Objects.requireNonNull(zone);
  ZonedDateTime dob = getDateOfBirth(zone);
  ZonedDateTime now = ZonedDateTime.now(zone);
  return chronoUnit.between(dob, now);
 }
}
Few example code to use:
public static void getDateOfBirthTest() {
 Age age = Age.of(29, 8, 28, 12, 49, 0);
 System.out.println(age.getDateOfBirth());
}

public static void getAgeFromDateTest() {
 Age age = Age.fromDateOfBirth(LocalDateTime.of(1984, Month.DECEMBER, 16, 7, 45, 0));
 System.out.println(age);
}

public static void ageAsUnitsTest() {
 Age age = Age.fromDateOfBirth(LocalDateTime.of(1984, Month.DECEMBER, 16, 7, 45, 0));
 System.out.println(age.asWeeks());
}

@SuppressWarnings("resource")
public static void serializationTest() throws FileNotFoundException, IOException, ClassNotFoundException {
 Age age = Age.fromDateOfBirth(LocalDateTime.of(1984, Month.DECEMBER, 16, 7, 45));
 System.out.println(age);
 
 ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File("C:/tmp")));
 outputStream.writeObject(age);
 outputStream.flush();
 
 ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("C:/tmp")));
 age = Age.class.cast(inputStream.readObject());
 System.out.println(age);
}

public static void equalsTest() {
 Age age = Age.of(29, 8, 28, 12, 49, 0);
 Age clone = age.clone();
 System.out.println(age.equals(clone));
}
While developing this class I was stuck to calculate the difference between to date-time and I got help from one SO user and I really appreciate that.
You comments and suggestion is appreciable to improve it.
Happy coding.

Thursday, September 4, 2014

Convert String to Character Array without calling toCharArray method

One post from LinkedIn came into my sight questioning that how to compare two String objects without using any inbuilt methods of this class. The solution iss trivial, comparing the lengths and every characters of the given strings, but to convert the String into char[] one need to call toCharArray() method and which is not permissible for the OP.
So to solve this problem I came up with a solution which I would like to share with you. String class stores the characters internally within a field called value and the signature of this field is:
private final char value[];
My intention is to deal with this field directly and for this type of cases Java Reflection comes very handy. So I wrote the following code which might come helpful for me in future or for you.
@SuppressWarnings({ "rawtypes", "unchecked" })
public static char[] toCharArray(String str) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
 final Field field = String.class.getDeclaredField("value");
 AccessController.doPrivileged(new PrivilegedAction() {

  @Override
  public Object run() {
   field.setAccessible(true);
   return null;
  }
 });
  
 Object obj = field.get(str);
 char[] charArr = char[].class.cast(obj);
 return charArr;
}
Hope it will help.

Wednesday, January 29, 2014

How to run JSF 1.2 Application in JBoss AS 7.1.1 Final

In one of my earlier post I have described how can we upgrade the JSF version. Yesterday, I was being asked that how can we downgrade the version. So I created a sample project to do this. It is true that many of you have applications developed in older version of Java Server Faces, JSF 1.2 and if you want to run those application in JBoss AS 7.1.1 maybe these post will help you.
JBoss AS 7.1.1 comes with the backward compatibility of JSF 1.2. The required jars can be found in the following path:
  • jsf-impl: <path_to_your_server>/modules/com/sun/jsf-impl/1.2
  • jsf-api: <path_to_your_server>/modules/javax/faces/api/1.2
But you need to inform the server that your application is a JSF 1.2 application. To do this you need to:
  • Declare your webapp version to Servlet 2.5. 
  • Define a context param which would tell JBoss to use JSF 1.2.
Here is my webapp declaration:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> 
</web-app>
And the context param is:
<context-param>
 <param-name>org.jboss.jbossfaces.JSF_CONFIG_NAME</param-name>
 <param-value>Mojarra-1.2</param-value>
</context-param>
You don't need to place JSF 1.2 jars in your WEB-INF/lib. When you run the application if you see:
12:15:34,896 INFO  [javax.enterprise.resource.webcontainer.jsf.config] (MSC service thread 1-9) Initializing Mojarra (1.2_15.jbossorg-1-20111019-SNAPSHOT) for context '/your-app'
in your console you are good to go.
Cheers.
Download.

Wednesday, January 1, 2014

Measuring Execution Time of ADF Faces Lifecycle

Happy New Year first of all to all of you.

Recently in one of my project, I have a requirement to measure the amount of time that my application takes to render the view. Googling not much reveals anything, except one solution[1] which is for applicable for JSF. Although ADF is built upon JSF Framework, the Lifecycle of ADF is much bigger than JSF so I can't really use that approach completely. But the building block of my solution starts from there.

In my solution I dealt with the class oracle.adf.controller.v2.lifecycle.PagePhaseListener. It is an interface implemented by the objects that wish to be notified before and after the processing of each of the ADF Page Lifecycle phase.

First you need to register your custom page phase listener through the adf-settings.xml. Follow the instruction given in the second link of the reference section[2]. Here is my adf-settings.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<adf-settings xmlns="http://xmlns.oracle.com/adf/settings">
 <adfc-controller-config xmlns="http://xmlns.oracle.com/adf/controller/config">
  <lifecycle>
   <phase-listener>
    <listener-id>PhaseProcessingTimeAnalyserListener</listener-id>
    <class>com.myproject.web.lifecycle.PhaseProcessingTimeAnalyserListener</class>
   </phase-listener>
  </lifecycle>
 </adfc-controller-config> 
</adf-settings>
The class PhaseProcessingTimeAnalyserListener pushes the phase which is currently executing into a collection from the method beforePhase and kind of pop it from that collection from the method afterPhase. The code is pretty much self explanatory.
import java.io.Serializable;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.Map;

import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

import oracle.adf.controller.v2.lifecycle.Lifecycle;
import oracle.adf.controller.v2.lifecycle.PagePhaseEvent;
import oracle.adf.controller.v2.lifecycle.PagePhaseListener;
import oracle.adf.share.ADFContext;

import com.mhis.posm.web.lifecycle.helper.PhaseProcessor;

/**
 * Class PhaseProcessingTimeAnalyserListener calculates the execution time of each Phase of ADF
 * @author TapasB
 */
public class PhaseProcessingTimeAnalyserListener implements PagePhaseListener, Serializable {

 private static final long serialVersionUID = 6814928970314659328L;

 /**
  * Method beforePhase notifies the listener before the execution of a specific phase of the ADF Page Lifecycle.
  * @author TapasB
  * @param phaseEvent
  * @see oracle.adf.controller.v2.lifecycle.PagePhaseListener#beforePhase(oracle.adf.controller.v2.lifecycle.PagePhaseEvent)
  */
 @Override
 public void beforePhase(PagePhaseEvent phaseEvent) {
  int phaseId = phaseEvent.getPhaseId();
  String phaseName = Lifecycle.getPhaseName(phaseId);
  PhaseProcessor.getInstance().process(getRequestId(), getURL(), phaseId, phaseName, PhaseProcessor.PhaseType.BEGIN);
 }

 /**
  * Method afterPhase notifies the listener after the execution of a specific phase of the ADF Page Lifecycle.
  * @author TapasB
  * @param phaseEvent
  * @see oracle.adf.controller.v2.lifecycle.PagePhaseListener#afterPhase(oracle.adf.controller.v2.lifecycle.PagePhaseEvent)
  */
 @Override
 public void afterPhase(PagePhaseEvent phaseEvent) {
  int phaseId = phaseEvent.getPhaseId();
  String phaseName = Lifecycle.getPhaseName(phaseId);
  PhaseProcessor.getInstance().process(getRequestId(), getURL(), phaseId, phaseName, PhaseProcessor.PhaseType.END);
 }

 /**
  * Method getRequestId generates and returns an unique ID value for each Request
  * @author TapasB
  * @return requestId
  */
 private Integer getRequestId() {
  @SuppressWarnings("unchecked")
  Map<String, Object> requestScope = ADFContext.getCurrent().getRequestScope();
  Integer requestId = (Integer) requestScope.get("requestId");

  if (requestId == null) {
   requestId = PhaseProcessor.getInstance().getNextRequestId();
   requestScope.put("requestId", requestId);
  }

  return requestId;
 }

 /**
  * Method getURL returns the URL in which the application is requested
  * @author TapasB
  * @return a String URL
  */
 private String getURL() {
  Enumeration<String> parameterNames = null;
  String parameterName = null;
  StringBuilder urlBuilder = new StringBuilder();
  Object request = FacesContext.getCurrentInstance().getExternalContext().getRequest();

  try {
   if (request instanceof HttpServletRequest) {
    HttpServletRequest servletRequest = (HttpServletRequest) request;
    urlBuilder.append(servletRequest.getRequestURL().toString());
    parameterNames = servletRequest.getParameterNames();

    if (parameterNames.hasMoreElements()) {
     if (!urlBuilder.toString().contains("?")) {
      urlBuilder.append("?");
     } else {
      urlBuilder.append("&");
     }
    }

    while (parameterNames.hasMoreElements()) {
     parameterName = parameterNames.nextElement();

     urlBuilder.append(parameterName);
     urlBuilder.append("=");
     urlBuilder.append(URLEncoder.encode(servletRequest.getParameter(parameterName), "UTF-8"));

     if (parameterNames.hasMoreElements()) {
      urlBuilder.append("&");
     }
    }
   }
  } catch (Exception ex) {
  }

  return urlBuilder.toString();
 }
}
The class PhaseProcessor is Session Singleton.  One instance per session so it is kept in the session scope. Here it is:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import oracle.adf.share.ADFContext;

import com.edifixio.osrd.generic.log.Log;

/**
 * Class PhaseProcessor processes the Phase execution time
 * @author TapasB
 */
public class PhaseProcessor implements Serializable {

 private static final long serialVersionUID = 6658181867505616109L;

 private List<Phase> phases = new ArrayList<Phase>();
 private AtomicInteger nextRequestId = new AtomicInteger(1);

 /**
  * Constructor PhaseProcessor is private
  * @author TapasB
  */
 private PhaseProcessor() {

 }

 /**
  * Class PhaseType
  * @author TapasB
  */
 public static enum PhaseType {
  BEGIN,
  END;
 }

 /**
  * Method getInstance returns a Session Instance of this class 
  * @author TapasB
  * @return an PhaseProcessor instance
  */
 public static PhaseProcessor getInstance() {
  @SuppressWarnings("unchecked")
  Map<String, Object> sessionScope = ADFContext.getCurrent().getSessionScope();
  PhaseProcessor phaseProcessor = (PhaseProcessor) sessionScope.get("phases");

  if (phaseProcessor == null) {
   phaseProcessor = new PhaseProcessor();
   sessionScope.put("phases", phaseProcessor);
  }

  return phaseProcessor;
 }

 /**
  * Method process processes the {@link Phase} 
  * @author TapasB
  * @param requestId - the unique ID for each request
  * @param url - the url in which the application is requested
  * @param phaseId - ADF's Phase ID
  * @param phaseName - ADF's Phase Name
  * @param phaseType - BEGIN or END
  */
 public void process(int requestId, String url, int phaseId, String phaseName, PhaseType phaseType) {
  Phase phase;

  switch (phaseType) {
   case BEGIN:
    phase = new Phase();

    phase.setRequestId(requestId);
    phase.setUrl(url);
    phase.setPhaseId(phaseId);
    phase.setPhaseName(phaseName);
    phase.setStartTime(new Date());

    phases.add(phase);

    Log.info(this, "The Phase: " + phase.getPhaseName(phaseId) + " begins. Requested URL: " + phase.getUrl());
    break;

   case END:
    ListIterator<Phase> phaseIterator = phases.listIterator(phases.size());

    while (phaseIterator.hasPrevious()) {
     phase = phaseIterator.previous();

     if (phase.getRequestId() == requestId && phase.getPhaseId() == phaseId && phase.getPhaseName().equals(phaseName)) {
      phase.setEndTime(new Date());
      Log.info(this, "The Phase: " + phase.getPhaseName(phaseId) + " ends with execution time: '" + (phase.getEndTime().getTime() - phase.getStartTime().getTime()) + "' millisecondes. Requested URL: " + phase.getUrl());
      phaseIterator.remove();
      break;
     }

    }

    break;
  }
 }

 /**
  * Method getNextRequestId returns and increment the unique ID 
  * @author TapasB
  * @return the ID
  */
 public Integer getNextRequestId() {
  return nextRequestId.getAndIncrement();
 }
}
In the method process it switches the control between two cases. This switching is based on the enum value of the type PhaseType. The idea is, when the phase begins, it creates a new instance of Phase, add it into the List<Phase> phases and log it; and when the phase ends it traverse backward the list, matches the phase with the given requestId, phaseId and phaseName; and when the match is found, it set the endTime to that matched instance of Phase, log it and then remove that instance from the list.
Note that the nextRequestId is an AtomicInteger so the value get updated atomically.

Lastly the model class Phase:
import java.io.Serializable;
import java.util.Date;

import oracle.adf.controller.faces.lifecycle.JSFLifecycle;

/**
 * Class Phase represent a phase
 * @author TapasB
 */
public class Phase implements Serializable {

 private static final long serialVersionUID = -461595462579265128L;

 private int requestId;
 private String url;
 private Date startTime;
 private Date endTime;
 private int phaseId;
 private String phaseName;

 /**
  * Constructor Phase is default
  * @author TapasB
  */
 public Phase() {

 }

 /**
  * Method getRequestId returns requestId
  * @author TapasB
  * @return requestId
  */
 public int getRequestId() {
  return requestId;
 }

 /**
  * Method setRequestId sets the requestId
  * @author TapasB
  * @param requestId the requestId to set
  */
 public void setRequestId(int requestId) {
  this.requestId = requestId;
 }

 /**
  * Method getUrl returns url
  * @author TapasB
  * @return url
  */
 public String getUrl() {
  return url;
 }

 /**
  * Method setUrl sets the url
  * @author TapasB
  * @param url the url to set
  */
 public void setUrl(String url) {
  this.url = url;
 }

 /**
  * Method getStartTime returns startTime
  * @author TapasB
  * @return startTime
  */
 public Date getStartTime() {
  return startTime;
 }

 /**
  * Method setStartTime sets the startTime
  * @author TapasB
  * @param startTime the startTime to set
  */
 public void setStartTime(Date startTime) {
  this.startTime = startTime;
 }

 /**
  * Method getEndTime returns endTime
  * @author TapasB
  * @return endTime
  */
 public Date getEndTime() {
  return endTime;
 }

 /**
  * Method setEndTime sets the endTime
  * @author TapasB
  * @param endTime the endTime to set
  */
 public void setEndTime(Date endTime) {
  this.endTime = endTime;
 }

 /**
  * Method getPhaseId returns phaseId
  * @author TapasB
  * @return phaseId
  */
 public int getPhaseId() {
  return phaseId;
 }

 /**
  * Method setPhaseId sets the phaseId
  * @author TapasB
  * @param phaseId the phaseId to set
  */
 public void setPhaseId(int phaseId) {
  this.phaseId = phaseId;
 }

 /**
  * Method getPhaseName returns phaseName
  * @author TapasB
  * @return phaseName
  */
 public String getPhaseName() {
  return phaseName;
 }

 /**
  * Method setPhaseName sets the phaseName
  * @author TapasB
  * @param phaseName the phaseName to set
  */
 public void setPhaseName(String phaseName) {
  this.phaseName = phaseName;
 }

 /**
  * Method getPhaseName returns the name of the Phase
  * @author TapasB
  * @param phaseId
  * @return the phase name
  */
 public String getPhaseName(int phaseId) {
  if (phaseId == JSFLifecycle.INIT_CONTEXT_ID) {
   return "INIT_CONTEXT";
  } else if (phaseId == JSFLifecycle.PREPARE_MODEL_ID) {
   return "PREPARE_MODEL";
  } else if (phaseId == JSFLifecycle.APPLY_INPUT_VALUES_ID) {
   return "APPLY_INPUT_VALUES";
  } else if (phaseId == JSFLifecycle.VALIDATE_INPUT_VALUES_ID) {
   return "VALIDATE_INPUT_VALUES";
  } else if (phaseId == JSFLifecycle.PROCESS_UPDATE_MODEL_ID) {
   return "PROCESS_UPDATE_MODEL";
  } else if (phaseId == JSFLifecycle.VALIDATE_MODEL_UPDATES_ID) {
   return "VALIDATE_MODEL_UPDATES";
  } else if (phaseId == JSFLifecycle.PROCESS_COMPONENT_EVENTS_ID) {
   return "PROCESS_COMPONENT_EVENTS";
  } else if (phaseId == JSFLifecycle.METADATA_COMMIT_ID) {
   return "METADATA_COMMIT";
  } else if (phaseId == JSFLifecycle.PREPARE_RENDER_ID) {
   return "PREPARE_RENDER";
  } else if (phaseId == JSFLifecycle.JSF_RESTORE_VIEW_ID) {
   return "RESTORE_VIEW";
  } else if (phaseId == JSFLifecycle.JSF_APPLY_REQUEST_VALUES_ID) {
   return "JSF_APPLY_REQUEST_VALUES";
  } else if (phaseId == JSFLifecycle.JSF_PROCESS_VALIDATIONS_ID) {
   return "JSF_PROCESS_VALIDATIONS";
  } else if (phaseId == JSFLifecycle.JSF_UPDATE_MODEL_VALUES_ID) {
   return "JSF_UPDATE_MODEL_VALUES";
  } else if (phaseId == JSFLifecycle.JSF_INVOKE_APPLICATION_ID) {
   return "JSF_INVOKE_APPLICATION";
  } else {
   return "JSF_RENDER_RESPONSE";
  }
 }
}
It is a simple POJO and pretty much self explanatory. Though I would like to explain the method public String getPhaseName(int phaseId). As you can see this method uses a class oracle.adf.controller.faces.lifecycle.JSFLifecycle; it is kind of constant class used by ADF Controllers define the phase ids for the set of phases of the ADF Page Lifecycle that match the JSF lifecycle.

I would greatly appreciate your comments and share.

References:
  1. Ideas on page load time measurements
  2. Understanding the Fusion Page Lifecycle
  3. How to configure an ADF Phase Listener and where to put the file