Archive for the Category 'generics'

More fun with Generics – Class.cast()

Tuesday, December 04th, 2007

Continuing the discussion from here and here.

We’ve been making extensive use of generics at work and in the process have discovered a bug in the 1.6 JDK (more on that later). The problem is that we can’t cast to the actual type of some generic instance variables, and also can’t use instanceof. The annoying thing is that it works fine in Eclipse – it’s only an issue in Sun’s JDK, so it fails during a command-line compile. Also it works fine in 1.5, so being one of the few on the team to use 1.6, I end up needing to fix things since it doesn’t affect the rest of the team or the continuous build.

We’ve worked around it by replacing casts with the Class.cast() method:

public T cast(Object obj) {
   if (obj != null && !isInstance(obj))
      throw new ClassCastException();
   return (T) obj;
}

and replace instanceof checks with Class.isAssignableFrom().

But take a look at the cast method. If we were to take the optimistic approach assuming no improper usage and remove the null and isInstance checks, then the method becomes

public T cast(Object obj) {
   return (T) obj;
}

Huh. That’s odd – we’re just replacing a cast with a cast.

So I wrote a simple test method to see why this works:

@SuppressWarnings("unchecked")
public static <T> T cast(@SuppressWarnings("unused") final Class<T> clazz,
       final Object o) {
  return (T)o;
}

(It turns out that the clazz parameter isn’t necessary, but at first I thought it was to justify the T type.)

I wrote some test code:

public static void main(final String... args) {
  List<String> list = new ArrayList<String>();

  ArrayList<String> c1 = cast(ArrayList.class, list);
  System.out.println("c1: " + c1.getClass().getName());

  Object c2 = cast(String.class, list);
  System.out.println("c2: " + c2.getClass().getName());

  String c3 = cast(String.class, list);
  System.out.println("c3: " + c3.getClass().getName());
}

and was surprised that the cast using String.class compiled – I can cast to any class. Of course it fails at runtime – the cast isn’t legal, so the output is

c1: java.util.ArrayList
c2: java.util.ArrayList
Exception in thread "main" java.lang.ClassCastException:
 java.util.ArrayList cannot be cast to java.lang.String

Looking at the decompiled code makes it clear what’s going on – it’s just erasure. The type of T is only used by the compiler to ensure (to the extent possible) that the code is valid, but the runtime type still has to be valid:

public static Object cast(Class clazz, Object o) {
  return o;
}

public static void main(String[] args) {
  List list = new ArrayList();
  ArrayList c1 = (ArrayList)cast(ArrayList.class, list);
  System.out.println((new StringBuilder("c1: ")).append(
         c1.getClass().getName()).toString());
  Object c2 = cast(String.class, list);
  System.out.println((new StringBuilder("c2: ")).append(
         c2.getClass().getName()).toString());
  String c3 = (String)cast(String.class, list);
  System.out.println((new StringBuilder("c3: ")).append(
         c3.getClass().getName()).toString());
}

Of course the Class parameter isn’t necessary:

@SuppressWarnings("unchecked")
public static <T> T cast(final Object o) {
  return (T)o;
}

public static void main(final String... args) {
  List<String> list = new ArrayList<String>();

  ArrayList<String> c1 = cast(list);
  System.out.println("c1: " + c1.getClass().getName());

  Object c2 = cast(list);
  System.out.println("c2: " + c2.getClass().getName());

  String c3 = cast(list);
  System.out.println("c3: " + c3.getClass().getName());
}

would have worked fine.

More generics hacks

Monday, December 03rd, 2007

Continuing on the generics discussion here, I’ve been using a pattern that I’ve seen discussed in the blogosphere to reduce redundancy in creating collections, e.g. some utility methods on a CollectionUtils class:

public static <T> ArrayList<T> newArrayList() {
  return new ArrayList<T>();
}

public static <T> HashSet<T> newHashSet() {
  return new HashSet<T>();
}

public static <K, V> HashMap<K, V> newHashMap() {
  return new HashMap<K, V>();
}

This eliminates the redundancy of specifying the type(s) twice:

List<VerboseTypeName> list = new ArrayList<VerboseTypeName>();

Map<VerboseKeyName, VerboseValueName> map =
        new HashMap<VerboseKeyName, VerboseValueName>();

becomes

List<VerboseTypeName> list = newArrayList();

Map<VerboseKeyName, VerboseValueName> map = newHashMap();

assuming we use static imports for the utility methods.

But Groovy and other scripting languages are making me realize how unnecessarily verbose Java is. For example, in Groovy I can create a List and initialize its elements in one line:

def numbers = [1, 1, 2, 3, 5, 8, 13, 21];

I recently realized that I can use VarArgs and amend the utility methods to take optional initializing values:

public static <T> ArrayList<T> newArrayList(final T... elements) {
  return new ArrayList<T>(Arrays.asList(elements));
}

public static <T> HashSet<T> newHashSet(final T... elements) {
  return new HashSet<T>(Arrays.asList(elements));
}

and then

List<String> strings = new ArrayList<String>();
strings.add("foo");
strings.add("bar");
strings.add("baz");

becomes

List<String> strings = newArrayList("foo", "bar", "baz");

Of course this isn’t new – for example Hibernate Shards has a similar implementation but with the various methods split by interface.

Avoiding cast with a generics hack

Monday, December 03rd, 2007

Say you have a method that returns Object that you need to cast to the actual type. Collection classes have always worked with Objects and required casting, but retrofitting with generics helped reduce this. Methods can be generic too, and we can use a quasi-hack to pre-cast the Object return values. The more I work with scripting languages, the more I realize how verbose Java is, and techniques like this help a lot with reduction of repetition and cruft.

One example is the case in a Spring (or other DI framework) app where you don’t have control over the creation of instances that need Spring-managed resources and cannot use DI (for example JSP custom tags, which are created by the container). In this case we need to pull resources instead of having them pushed.

So we create a utility class that has access to the ApplicationContext, e.g.:

import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public final class SpringContext implements ApplicationContextAware {

  private static final SpringContext INSTANCE = new SpringContext();

  private ApplicationContext context;

  private SpringContext() {
    // singleton
  }

  public static SpringContext instance() {
    return INSTANCE;
  }

  public Object getBean(final String name) {
    return context.getBean(name);
  }

  @Required
  public void setApplicationContext(final ApplicationContext c) {
    context = c;
  }
}

and map it in Spring using:

<bean class='com.myco.myapp.spring.SpringContext' factory-method='instance' />

Usage of getBean requires a cast to the actual type, e.g.:

UserDAO dao = (UserDAO)SpringContext.instance().getBean("userDao");

But we’ve already specified that the type is UserDAO, so we can amend getBean to be generic:

@SuppressWarnings("unchecked")
public <T> T getBean(final String name) {
  return (T)context.getBean(name);
}

and then the client code becomes:

UserDAO dao = SpringContext.instance().getBean("userDao");

This is also useful for methods that return a base class. For example, suppose you have a hierarchy of user classes; an abstract User base class with concrete subclasses Subscriber and Admin. If you have this method in UserDAO:

User findUserByUsername(String username);

then usage looks like:

Admin admin = (Admin)dao.findUserByUsername("bob");

As before, we’ve specified Admin twice, so we can change the method to:

<T extends User> T findUserByUsername(String username);

and this simplifies the calling code to:

Admin admin = dao.findUserByUsername("bob");

Of course this is just syntactic sugar – if the cast is invalid it will still fail.

Using Generics to Simplify Hibernate/Spring DAOs (update #2 for JPA)

Thursday, March 22nd, 2007

I’ve been working with JPA and Hibernate Annotations and needed to update my base DAO interface and implementation class from its native Hibernate implementation.

The interface is a little different from the one I’d been using so I’m including the new one:

package com.foo.bar.dao;

import java.io.Serializable;
import java.util.Collection;

import com.foo.bar.model.DatabaseObject;

/**
 * Base DAO.
 * @author Burt
 * @param <T> the entity type
 */
public interface BaseDAO<T extends DatabaseObject> {

  /**
   * Get the total number of instances.
   * @return  the count
   */
  long getTotalCount();

  /**
   * Find all.
   * @return  all instances
   */
  Collection<T> findAll();

  /**
   * Find an instance by primary key.
   * @param pk  the primary key
   * @return  the instance
   */
  T findByPrimaryKey(Serializable pk);

  /**
   * Insert a new instance.
   * @param t  the new instance
   * @return  the instance updated with its primary key
   */
  T create(T t);

  /**
   * Save changes to an existing instance.
   * @param t  the instance
   * @return  the updated & refreshed instance
   */
  T update(T t);

  /**
   * Delete an existing instance.
   * @param t  the instance
   */
  void delete(T t);
}

and the new base implementation class:

package com.foo.bar.dao.jpa;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Date;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.foo.bar.annotation.DependencyInjection;
import com.foo.bar.dao.BaseDAO;
import com.foo.bar.model.DatabaseObject;

/**
 * Abstract base class for Hibernate DAOs.
 *
 * @author Burt
 * @param <T> the entity type
 */
@Transactional
@Repository
public abstract class BaseDaoImpl<T extends DatabaseObject>
       implements BaseDAO<T> {

  protected final Logger _log = Logger.getLogger(getClass());

  private Class _entityClass;

  private EntityManager _entityManager;

  /**
   * Inject the entity manager.
   * @param entityManager
   */
  @PersistenceContext
  @DependencyInjection
  public void setEntityManager(final EntityManager entityManager) {
    _entityManager = entityManager;
  }
  protected EntityManager getEntityManager() {
    return _entityManager;
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#getTotalCount()
   */
  public long getTotalCount() {
    return (Long)getEntityManager().createQuery(
      "SELECT COUNT(*) FROM " + getEntityClass().getSimpleName())
      .getSingleResult();
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findAll()
   */
  @SuppressWarnings("unchecked")
  public Collection<T> findAll() {
    return getEntityManager().createQuery(
        "FROM " + getEntityClass().getName())
        .getResultList();
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByPrimaryKey(java.io.Serializable)
   */
  @SuppressWarnings("unchecked")
  public T findByPrimaryKey(final Serializable pk) {
    return (T)getEntityManager().find(getEntityClass(), pk);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#create(com.foo.bar.model.DatabaseObject)
   */
  public T create(final T t) {
    getEntityManager().persist(t);
    return t;
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#update(com.foo.bar.model.DatabaseObject)
   */
  public T update(final T t) {
    t.setModified(new Date());
    getEntityManager().merge(t);
    return findByPrimaryKey(t.getPk());
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#delete(com.foo.bar.model.DatabaseObject)
   */
  public void delete(final T t) {
    getEntityManager().remove(t);
  }

  protected synchronized Class getEntityClass() {

    if (_entityClass == null) {
      Type type = getClass().getGenericSuperclass();
      loop:
      while (true) {
        if (type instanceof ParameterizedType) {
          Type[] arguments =1) {
              _entityClass = (Class)argument;
              break loop;
            }
          }
        }
        type = ((Class)type).getGenericSuperclass();
        if (type == Object.class) {
          throw new RuntimeException(
              "Could not find a DatabaseObject subclass parameterized type");
        }
      }
    }
    return _entityClass;
  }
}
  1. ParameterizedType)type).getActualTypeArguments(); for (Type argument : arguments) { if (argument instanceof Class && DatabaseObject.class.isAssignableFrom(((Class)argument[back]

Using Generics to Simplify Hibernate/Spring DAOs (update)

Sunday, July 16th, 2006

Tim noted in the comments of my previous post that it’s possible to use reflection to determine the parameterized type of the entity class. It’s a great enhancement and drops simple DAOs that only use the standard base DAO methods down to zero LOC (other than the class definition and imports). Cool.

The updated base DAO implementation class:

(see getEntityClass())

package com.foo.bar.dao.hibernate;

import java.sql.Timestamp;
import java.util.HashSet;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Expression;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.foo.bar.dao.BaseDAO;
import com.foo.bar.model.DatabaseObject;
import com.foo.bar.model.User;

/**
 * Abstract base class for Hibernate DAOs.
 *
 * @author Burt
 * @param <T> the entity type
 */
public abstract class BaseDaoImpl<T extends DatabaseObject>
       extends HibernateDaoSupport
       implements BaseDAO<T> {

  private Class _entityClass;

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findAll()
   */
  public Set<T> findAll() {
    return findAll(false);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findAll()
   */
  @SuppressWarnings("unchecked")
  public Set<T> findAll(final boolean includeDeleted) {
    return (Set<T>)getHibernateTemplate().execute(new HibernateCallback() {
        public Object doInHibernate(final Session session) {
          Criteria criteria = session.createCriteria(getEntityClass());
          if (!includeDeleted) {
            criteria.add(Expression.isNull("deleted"));
          }
          return new HashSet<T>(criteria.list());
        }
      });
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByPrimaryKey(int)
   */
  public T findByPrimaryKey(final int pk) {
    return findByPrimaryKey(pk, false);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByPrimaryKey()
   */
  @SuppressWarnings("unchecked")
  public T findByPrimaryKey(final int pk, final boolean includeDeleted) {

    return (T)getHibernateTemplate().execute(new HibernateCallback() {
      public Object doInHibernate(final Session session) {
        T instance = (T)session.get(getEntityClass(), pk);
        if (instance != null && (includeDeleted || !instance.isDeleted())) {
          return initializeIfNotNull(instance);
        }
        return null;
      }
    });
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByName()
   */
  public T findByName(final String name) {
    return findByName(name, false);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByName()
   */
  @SuppressWarnings("unchecked")
  public T findByName(final String name, final boolean includeDeleted) {

    return (T)getHibernateTemplate().execute(new HibernateCallback() {
      public Object doInHibernate(final Session session) {
        Criteria criteria = session.createCriteria(getEntityClass());
        criteria.add(Expression.eq(getNameFieldName(), name));
        if (!includeDeleted) {
          criteria.add(Expression.isNull("deleted"));
        }
        return initializeIfNotNull(criteria.uniqueResult());
      }
    });
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#create()
   */
  public T create(final T t) {
    getHibernateTemplate().saveOrUpdate(t);
    return t;
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#update()
   */
  public void update(final T t, final User updatedBy) {
    t.setModified(new Timestamp(System.currentTimeMillis()));
    t.setModifiedBy(updatedBy);
    getHibernateTemplate().update(t);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#delete()
   */
  public void delete(final T t, final User deletedBy) {
    getHibernateTemplate().delete(t);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#softDelete()
   */
  public void softDelete(final T t, final User deletedBy) {
    t.setDeleted(new Timestamp(System.currentTimeMillis()));
    update(t, deletedBy);
  }

  @SuppressWarnings("unchecked")
  private Object initializeIfNotNull(final Object instance) {
    if (instance != null) {
      initialize((T)instance);
    }
    return instance;
  }

  protected void initialize(@SuppressWarnings("unused") T instance) {
    // let subclass do special initialization
  }

  protected synchronized Class getEntityClass() {

    if (_entityClass == null) {
      Type type = getClass().getGenericSuperclass();
      loop:
      while (true) {
        if (type instanceof ParameterizedType) {
          Type[] arguments = ( (ParameterizedType)type).getActualTypeArguments();
          for (Type argument : arguments) {
            if (argument instanceof Class &&
                DatabaseObject.class.isAssignableFrom(((Class)argument))) {
              _entityClass = (Class)argument;
              break loop;
            }
          }
        }
        type = ((Class)type).getGenericSuperclass();
        if (type == Object.class) {
          throw new RuntimeException(
              "Could not find a DatabaseObject subclass parameterized type");
        }
      }
    }
    return _entityClass;
  }

  /**
   * Each entity has a 'name' but not necessarily
   * called 'name'.
   * @return  the name's field name
   */
  protected String getNameFieldName() {
    return "name";
  }
}

And the updated sample implementation class:

package com.foo.bar.dao.hibernate;

import com.foo.bar.dao.hibernate.BaseDaoImpl;
import com.foo.bar.dao.ErrorAnnotationDAO;
import com.foo.bar.model.ErrorAnnotation;

/**
 * Hibernate implementation of
 * <code>ErrorAnnotationDAO</code>.
 *
 * @author Burt
 */
public class ErrorAnnotationDaoImpl
       extends BaseDaoImpl<ErrorAnnotation>
       implements ErrorAnnotationDAO {
}

Using Generics to Simplify Hibernate/Spring DAOs

Saturday, July 15th, 2006

Database access always involves a significant amount of boilerplate code, especially for standard CRUD operations. Complex relationships and queries make things interesting, but the bulk of the database code involves reading individual instances (looked up by primary key, ‘name’, etc.), storing new instances, editing instances and deleting. I’ve become a big fan of the Hibernate approach to ORM, and Spring‘s helper classes significantly reduce the amount of coding required. But for each entity there’s still the same nearly identical code for each DAO implementation class, and for their interfaces too.

So I’ve developed a base interface and base implementation class that use Java5 generics to reduce the code for each DAO to almost nothing for DAOs that only perform the “standard” operations.

The DAOs manage DatabaseObjects which extend DataObject and contain fields and getters and setters for primary key, user who created, time created, user who last edited, time last edited, and the user and time that the instance was “soft” deleted, i.e. flagged as deleted to hide from the UI but still in the database.

The base interface:1

package com.foo.bar.dao;

import java.util.Set;

import com.foo.bar.model.DatabaseObject;
import com.foo.bar.model.User;

/**
 * Base DAO.
 * @author Burt
 * @param <T> the entity type
 */
public interface BaseDAO<T extends DatabaseObject> {

  /**
   * Find all.
   * @param includeDeleted
   * @return  all instances
   */
  Set<T> findAll(boolean includeDeleted);

  /**
   * Find all.
   * @return  all instances
   */
  Set<T> findAll();

  /**
   * Find an instance by primary key.
   * @param pk  the primary key
   * @param includeDeleted
   * @return  the instance
   */
  T findByPrimaryKey(int pk, boolean includeDeleted);

  /**
   * Find an instance by primary key.
   * @param pk  the primary key
   * @return  the instance
   */
  T findByPrimaryKey(int pk);

  /**
   * Find an instance by name.
   * @param name  the name
   * @param includeDeleted
   * @return  the instance
   */
  T findByName(String name, boolean includeDeleted);

  /**
   * Find an instance by name.
   * @param name  the name
   * @return  the instance
   */
  T findByName(String name);

  /**
   * Insert a new instance.
   * @param t  the new instance
   * @return  the instance updated with its primary key
   */
  T create(T t);

  /**
   * Save changes to an existing instance.
   * @param t  the instance
   * @param updatedBy
   */
  void update(T t, User updatedBy);

  /**
   * Delete an existing instance.
   * @param t  the instance
   * @param deletedBy
   */
  void delete(T t, User deletedBy);

  /**
   * Soft delete (hide) an existing instance.
   * @param t  the instance
   * @param deletedBy
   */
  void softDelete(T t, User deletedBy);
}

And the base implementation class:

package com.foo.bar.dao.hibernate;

import java.sql.Timestamp;
import java.util.HashSet;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Expression;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.foo.bar.dao.BaseDAO;
import com.foo.bar.model.DatabaseObject;
import com.foo.bar.model.User;

/**
 * Abstract base class for Hibernate DAOs.
 *
 * @author Burt
 * @param <T> the entity type
 */
public abstract class BaseDaoImpl<T extends DatabaseObject>
       extends HibernateDaoSupport
       implements BaseDAO<T> {

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findAll()
   */
  public Set<T> findAll() {
    return findAll(false);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findAll()
   */
  @SuppressWarnings("unchecked")
  public Set<T> findAll(final boolean includeDeleted) {
    return (Set<T>)getHibernateTemplate().execute(new HibernateCallback() {
        public Object doInHibernate(final Session session) {
          Criteria criteria = session.createCriteria(getEntityClass());
          if (!includeDeleted) {
            criteria.add(Expression.isNull("deleted"));
          }
          return new HashSet<T>(criteria.list());
        }
      });
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByPrimaryKey(int)
   */
  public T findByPrimaryKey(final int pk) {
    return findByPrimaryKey(pk, false);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByPrimaryKey()
   */
  @SuppressWarnings("unchecked")
  public T findByPrimaryKey(final int pk, final boolean includeDeleted) {
    return (T)getHibernateTemplate().execute(new HibernateCallback() {
      public Object doInHibernate(final Session session) {
        T instance = (T)session.get(getEntityClass(), pk);
        if (instance != null && (includeDeleted || !instance.isDeleted())) {
          return initializeIfNotNull(instance);
        }
        return null;
      }
    });
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByName()
   */
  public T findByName(final String name) {
    return findByName(name, false);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#findByName()
   */
  @SuppressWarnings("unchecked")
  public T findByName(final String name, final boolean includeDeleted) {
    return (T)getHibernateTemplate().execute(new HibernateCallback() {
      public Object doInHibernate(final Session session) {
        Criteria criteria = session.createCriteria(getEntityClass());
        criteria.add(Expression.eq(getNameFieldName(), name));
        if (!includeDeleted) {
          criteria.add(Expression.isNull("deleted"));
        }
        return initializeIfNotNull(criteria.uniqueResult());
      }
    });
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#create()
   */
  public T create(final T t) {
    getHibernateTemplate().saveOrUpdate(t);
    return t;
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#update()
   */
  public void update(final T t, final User updatedBy) {
    t.setModified(new Timestamp(System.currentTimeMillis()));
    t.setModifiedBy(updatedBy);
    getHibernateTemplate().update(t);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#delete()
   */
  public void delete(final T t, final User deletedBy) {
    getHibernateTemplate().delete(t);
  }

  /** (non-Javadoc)
   * @see com.foo.bar.dao.BaseDAO#softDelete()
   */
  public void softDelete(final T t, final User deletedBy) {
    t.setDeleted(new Timestamp(System.currentTimeMillis()));
    update(t, deletedBy);
  }

  @SuppressWarnings("unchecked")
  private Object initializeIfNotNull(final Object instance) {
    if (instance != null) {
      initialize((T)instance);
    }
    return instance;
  }

  protected void initialize(@SuppressWarnings("unused") T instance) {
    // let subclass do special initialization
  }

  /**
   * Can't determine T.getclass() due to erasure,
   * so must declare the class redundantly.
   * @return  the entity class
   */
  protected abstract Class getEntityClass();

  /**
   * Each entity has a 'name' but not necessarily
   * called 'name'.
   * @return  the name's field name
   */
  protected String getNameFieldName() {
    return "name";
  }
}

And a sample DAO:

package com.foo.bar.dao;

import com.foo.bar.dao.BaseDAO;
import com.foo.bar.model.ErrorAnnotation;

/**
 * DAO for <code>ErrorAnnotation</code>.
 * @author Burt
 */
public interface ErrorAnnotationDAO extends BaseDAO<ErrorAnnotation> {
  // no other methods
}

And its implementation:

(uses only base interface methods, so the only required method is getEntityClass)

package com.foo.bar.dao.hibernate;

import com.foo.bar.dao.hibernate.BaseDaoImpl;
import com.foo.bar.dao.ErrorAnnotationDAO;
import com.foo.bar.model.ErrorAnnotation;

/**
 * Hibernate implementation of
 * <code>ErrorAnnotationDAO</code>.
 *
 * @author Burt
 */
public class ErrorAnnotationDaoImpl
       extends BaseDaoImpl<ErrorAnnotation>
       implements ErrorAnnotationDAO {

  @Override
  protected Class getEntityClass() {
    return ErrorAnnotation.class;
  }
}

Of course client usage is the same as if I’d copy/pasted the boilerplate code thanks to the generic types, e.g.:

ErrorAnnotationDAO dao = ...
ErrorAnnotation errorAnnotation = dao.findByPrimaryKey(12345); // no need to cast
...
Collection<ErrorAnnotation> allErrorAnnotations = dao.findAll(); // no need to cast
...
dao.create(new ErrorAnnotation(...));
...
ErrorAnnotation errorAnnotation = ...
errorAnnotation.setFoo('foo');
errorAnnotation.setBar('bar');
User user = ...
dao.update(errorAnnotation, user);

The one downside is the need to suppress unchecked warnings, but Hibernate guarantees that all instances are of the appropriate class so that’s just to get past the compiler.

  1. Note that the update and delete methods take a User parameter to record the user performing the action. create() doesn’t though because DatabaseObject have by convention a User parameter in their constructors.[back]

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.