0 Replies Latest reply on Apr 4, 2006 10:13 AM by Dennis Wenger

    GenericCRUDAction with nested Conversations

    Dennis Wenger Newbie

      I've developed a GenericCRUDAction class which can be used as base for many crud actions for different entities.
      Second level CRUD operations are supprted (edit the owned entities of another entity) also sorting by request parameters.
      It uses the GenericDAOPattern.

      I'd like to get some Feedback about this implementation. Does this make sense or is it not flexible enough for many usecases?

      Generic base Class:

      @Interceptors(SeamInterceptor.class)
      @Intercept(InterceptionType.ALWAYS)
      public abstract class GenericCRUDActionImpl<T extends Lookup<ID>, TDAO extends GenericDAO<T, ID>, ID extends Serializable>
       implements GenericCRUDAction<T>, Serializable
      {
       private static final long serialVersionUID = 6854193449714628275L;
       protected static Logger log;
      
      
       private final Class<T> entityClass;
      
       protected List<T> entities;
       protected int entityIndex;
       @Valid
       protected T entity;
      
       @RequestParameter
       protected String by;
       protected String preserveBy;
       @RequestParameter
       protected String order;
       protected String preserveOrder;
       //for refresh first-level, if is second-level CRUD
       protected GenericCRUDAction parentAction;
      
       public GenericCRUDActionImpl ()
       {
       this.entityClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
       log = Logger.getLogger(entityClass.getSimpleName()+"CRUDAction");
       }
       @Begin(join=true)
       public String findAll()
       {
       entities = loadEntities();
       //sort
       if(preserveBy!=null && preserveBy.length()>0){
       log.debug("sortCalled "+preserveBy+" "+preserveOrder+" / List: "+entities);
       Collections.sort(entities,new EntityComperator(preserveBy, preserveOrder));
      
       log.debug("sorted: "+entities);
       }
      
       log.info(entities.size() + " entities found: "+entities);
       if(entity != null)
       log.info(entity.getShowName() + " selected ");
      
       return entityClass.getSimpleName().toLowerCase()+"list";
       }
       //seperate for overloading
       protected List<T> loadEntities(){
       return getEntityDAO().findAll();
       }
      
       @Begin(nested=true)
       public String mknew(){
       log.info("new entity");
       refresh();
       createEntity();
       return entityClass.getSimpleName().toLowerCase()+"edit";
       }
      
       @Begin(nested=true)
       @IfInvalid(outcome=REDISPLAY)
       public String edit(){
       if(entityIndex > -1) entity = entities.get(entityIndex);
       if(entity == null) return null;
       return entityClass.getSimpleName().toLowerCase()+"edit";
       }
      
      
       @SuppressWarnings("unchecked")
       @IfInvalid(outcome=REDISPLAY) //needed?
       public String delete()
       {
       if(entityIndex > -1) entity = entities.get(entityIndex);
       if(entity != null){
       log.info("deleting: " + entity.toString());
       getEntityDAO().makeTransient(entity);
       refresh();
       if(parentAction != null) parentAction.reloadEntity();
       }
       else{
       log.info("deleting: no entities found");
       }
       return null;
       }
       @SuppressWarnings("unchecked")
       @End(ifOutcome="done")
       @IfInvalid(outcome=REDISPLAY)
       public String save() {
       if(entity != null){
       log.info("editing: " + entity.getShowName());
       entity = getEntityDAO().makePersistent(entity);
       refresh();
       if(parentAction != null) parentAction.reloadEntity();
       }else{
       log.info("editing: no entity found");
       }
       return "done";
       }
      
       @End
       public String done(){
       refresh();
       return "done";
       }
      
       public String sortParams(){
       if(by!=null) this.preserveBy=by;
       if(order!=null) this.preserveOrder=order;
       return "done";
       }
      
       public void refresh()
       {
       log.debug("refresh()");
       findAll();
       entity = null;
       entityIndex = -1;
       }
      
       public void reloadEntity(){
       getEntityDAO().refresh(entity);
       }
      
      
       @Destroy @Remove
       public void destroy() {
       log.debug("DESTORYED");
       }
      
       abstract protected void createEntity();
       abstract protected TDAO getEntityDAO();
      
      
       //getter/setter for annotating
       abstract public List<T> getEntities();
       abstract public T getEntity();
       abstract public void setEntity(T entity);
       abstract public void setEntityIndex(int entityIndex);
      
       public int getEntityIndex() {
       return entityIndex;
       }
       public void setEntities(List<T> entities) {
       this.entities=entities;
       }
      
       public String getBy() {
       return preserveBy;
       }
       public String getOrder() {
       return preserveOrder;
       }
      
      
       private class EntityComperator implements Comparator<T>, Serializable{
      
       private static final long serialVersionUID = -5289773927277498419L;
       private String by;
       private String order;
      
       public EntityComperator(String by, String order){
       this.by=by;
       this.order=order;
       }
      
       public int compare(T entity1, T entity2) {
       try {
       String property1 = BeanUtils.getProperty(entity1,by);
       String property2 = BeanUtils.getProperty(entity2,by);
       if("dec".equals(order))
       return property2.compareToIgnoreCase(property1);
       else
       return property1.compareToIgnoreCase(property2);
       } catch (Exception e) {
       return 0;
       }
       }
       }
      }
      



      Concrete Class for first level CRUD:
      (sorry for bad code formatting, but like this i can view the whole class on one screen)
      @Stateful
      @Name("planningPeriodAction")
      @Interceptors(SeamInterceptor.class)
      @Intercept(InterceptionType.ALWAYS)
      @Conversational(ifNotBegunOutcome="main")
      public class PlanningPeriodCRUDActionImpl extends GenericCRUDActionImpl<PlanningPeriod,PlanningPeriodDAO,Integer>
       implements PlanningPeriodCRUDAction{
      
       private static final long serialVersionUID = -1114709254580248864L;
       @EJB private PlanningPeriodDAO planningPeriodDAO;
      
       @Override @Begin(join=true) @Factory("planningperiods")
       public String findAll() {
       return super.findAll();
       }
      
       @Override
       protected void createEntity(){ setEntity(new PlanningPeriod()); }
      
       @Override
       protected PlanningPeriodDAO getEntityDAO(){ return planningPeriodDAO; }
      
       //Overridden methods for annotation
       @Override @DataModel(scope=PAGE, value="planningperiods")
       public List<PlanningPeriod> getEntities(){ return entities; }
      
       @Override @DataModelSelectionIndex(value="planningperiods")
       public void setEntityIndex(int entityIndex){ this.entityIndex=entityIndex; }
      
       @Override @Out(value="planningperiod", scope=CONVERSATION, required=false)
       public PlanningPeriod getEntity(){ return entity; }
      
       @Override @In(value="planningperiod", required=false)
       public void setEntity(PlanningPeriod entity){ this.entity=entity; }
      
      }


      Implementation of a second level crudAction:
      @Stateful
      @Name("timeSlotAction")
      @Interceptors(SeamInterceptor.class)
      @Intercept(InterceptionType.ALWAYS)
      @Conversational(ifNotBegunOutcome="main")
      public class TimeSlotCRUDActionImpl extends GenericCRUDActionImpl<TimeSlot,TimeSlotDAO,Integer>
       implements TimeSlotCRUDAction{
      
       private static final long serialVersionUID = -1114709254580248864L;
       @EJB private TimeSlotDAO timeSlotDAO;
      
       @SuppressWarnings("unused")
       @Out(scope=CONVERSATION, required=false) private List<SelectItem> timeSlotActionplanningFrequencyDaySelect;
      
       @Override @Begin(join=true) @Factory("timeslots")
       public String findAll() {
       log.debug("findAll()");
       return super.findAll();
       }
      
       @Override
       protected void createEntity(){
       setEntity(new TimeSlot(planningperiod));
       }
      
       @Override
       protected TimeSlotDAO getEntityDAO(){ return timeSlotDAO; }
      
       //Overridden methods for annotation
       @Override @DataModel(scope=PAGE, value="timeslots")
       public List<TimeSlot> getEntities(){ return entities; }
      
       @Override @DataModelSelectionIndex(value="timeslots")
       public void setEntityIndex(int entityIndex){ this.entityIndex=entityIndex; }
      
       @Override @Out(value="timeslot", scope=CONVERSATION, required=false)
       public TimeSlot getEntity(){
       if(planningperiod!=null) timeSlotActionplanningFrequencyDaySelect = LookupConverter.getNumbers4SelectOne(planningperiod.getPlanningFrequency());
       return entity;
       }
      
       @Override @In(value="timeslot", required=false)
       public void setEntity(TimeSlot entity){ this.entity=entity; }
      
       //for second level CRUD
       //If @In not required its also usable as first level CRUD
       @In private PlanningPeriod planningperiod;
      
       @In(value="planningPeriodAction")
       public void setParentAction(GenericCRUDAction parentAction){
       this.parentAction = parentAction;
       }
      
       @Override
       protected List<TimeSlot> loadEntities(){
       return planningperiod.getTimeSlots();
       }
      
      
      }


      The DAOs are injected without using Seam, because I want to hold the DAO-layer seam free.

      Next step will be to move the createEntity()-FactoryMethod to the DAO-Layer.

      Has someone used something like this? What do you think about it?

      Any commets and questions welcome!

      Greets Dennis