Saturday, December 26, 2009

CRUD controller with JSF

(belongs to this series of articles)

This is a proposal for a lightweight controller layer that would be built on top or the CRUD services layer described in the previous article. Just like we could generalize the DAO and Services layer for CRUD (create-retrieve-update-delete) operations on domain objects (or entity objects, extending DomainObject and its subclasses), we can create a parametrised framework class that implements the CRUD functionality for presentation layer. This class would have the role of Controller in the Model-View-Controller pattern; thus, the main functions of such a class would be calling the corresponding Service when a CRUD operation on a domain object is being performed. This class would be the common part of all controllers of an application that perform CRUD on domain objects, and contain all the functionality that can be generalized for all domain objects: retrieving, saving, and deleting an entity, and returning a collections of entities to create scrolling lists.

Most of the functionality is implemented by simply passing control to the Services layer; however, the scrolling lists and error handling require some logic on the controller level.

One possible approach is using NetBeans JSF CRUD generator. The disadvantages of that approach would be the dependence on one-way generator (re-generating loses all changes) and NetBeans (it's best when the project does not depend on any IDE). I submit that creating a framework class (possibly complemented by facelets and custom tags) is a more flexible approach.

The root class of the proposed controller hierarchy is trying to stay agnostic of the particular view technology being used. It passes control for most of the real operations to the service layer (via injected reference to the Service), and delegates view framework dependent operations (like displaying error messages) to its subclasses:

package com.example.crud.controller;

import java.io.Serializable;
import java.io.ObjectInputStream.GetField;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Callable;

import javax.management.RuntimeErrorException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.example.crud.service.GenericCrudService;
import com.example.domain.DomainObject;

/**
 * Common session controller to implement CRUD actions. Can be used with
 * different view technologies.
 * 
 * Does <b>not</b> make the assumption that the object type is
 * {@link DomainObject}, so needs subclasses to implement
 * {@link #getCurrentId()}: only subclasses know how to extract the ID.
 * 
 * @author maxim
 * 
 * @param <T>
 *            domain object type
 * @param <PK>
 *            primary key
 */
public abstract class GenericCrudController<T extends DomainObject<PK>, PK extends Serializable> {

 public static final String SUCCESS = "success";

 public static final String ERROR = "error";

 public static final String EDIT = "edit";

 public static final String CANCEL = "cancel";

 protected static Log logger = LogFactory
   .getLog(GenericCrudController.class);

 protected Class<T> type;
 protected T currentRecord;

 protected transient GenericCrudService<T, PK> service;

 private int pgSize = 5;

 private String order;

 private boolean sortAscending;

 private int firstRow = 0;

 /**
  * Default constructor. Tries to discover Class<T>
  */
 @SuppressWarnings("unchecked")
 public GenericCrudController() {
  type = (Class<T>) ((ParameterizedType) getClass()
    .getGenericSuperclass()).getActualTypeArguments()[0];
  if (logger.isDebugEnabled())
   logger.debug("creating new " + this.getClass().getSimpleName());
 }

 /**
  * Subclasses add end user messages according to view technology
  * 
  * @param message
  *            text message
  * @param e
  *            exception or null if none
  */
 protected abstract void addMessage(String message, Throwable e);

 /**
  * Subclasses add end user messages according to view technology
  * 
  * @param message
  *            text message
  */
 protected abstract void addMessage(String message);

 /**
  * Subclass will reset the page view (e.g. after delete)
  */
 public abstract void resetPage();

 public Collection<T> getList() {
  try {
   return service.getAll();
  } catch (Exception e) {
   addMessage("Error retrieving page", e);
   logger.error("error retrieving page:", e);
   return new ArrayList<T>();
  }
 }

 /**
  * This is where the controller operations are really performed. Single spot
  * to implement error handling and returning standard outcomes and messages.
  * 
  * @param controllerAction
  *            a {@link Callable} that does the actions
  * @return string outcome
  */
 protected String successAndErrorAction(Callable<String> controllerAction) {
  try {
   String ret = controllerAction.call();
   logger.info("Action succeeded: " + this.getClass().getSimpleName());
   return null == ret ? SUCCESS : ret;
  } catch (Exception e) {
   logger.error("Exception in controller "
     + this.getClass().getSimpleName(), e);
   addMessage(e.getMessage());
   return ERROR;
  }
 }

 public GenericCrudService<T, PK> getService() {
  return service;
 }

 public void setService(GenericCrudService<T, PK> service) {
  this.service = service;
 }

 public void setCurrentRecord(T update) {
  currentRecord = update;
 }

 /**
  * @return id of the current record
  */
 protected PK getCurrentId() {
  return null==getCurrentRecord()?null:getCurrentRecord().getId();
 }

 /**
  * 
  * @return new current record (just uses the default constructor of T)
  */
 protected T newCurrentRecord() {
  try {
   if (logger.isDebugEnabled())
    logger.debug("new current record" + type.getSimpleName());
   return (T) type.newInstance();
  } catch (Exception e) {
   logger.error("exception creating CRUD currentRecord", e);
   return null;
  }
 }

 /**
  * Getting records to display on a page
  * 
  * @param pageSize
  *            page size
  * @param firstRecord
  *            first record number (starts from 0)
  * @param order
  *            field name for order, or null
  * @param asc
  *            true if ascending order, else descending
  * @return collection, size <= pageSize
  */
 protected Collection<T> getRecords(int pageSize, int firstRecord,
   String order, boolean asc) {
  if (pageSize <= 0) {
   return service.getAll();
  } else {
   return service.getPage(pageSize, firstRecord, order, asc);
  }
 }

 /**
  * 
  * @return collection of records based on current pagination helper
  */
 public Collection<T> getRecords() {
  return getRecords(getPageSize(), getFirstRow(), getOrder(),
    isSortAscending());
 }

 /**
  * 
  * @return the current record (e.g. the one being edited)
  */
 public T getCurrentRecord() {
  return this.currentRecord;
 }

 /**
  * Called from the list view to delete selected record
  * 
  * @return standard outcome from {@link #successAndErrorAction(Callable)}
  * @see #successAndErrorAction(Callable)
  */
 public String delete() {
  return successAndErrorAction(new Callable<String>() {
   @Override
   public String call() throws Exception {
    setCurrentRecord(getSelectedRecord());
    service.delete(getCurrentId());
    addMessage("Record deleted successfully");
    resetPage();
    return null;
   }
  });
 }

 /**
  * 
  * @return record selected in subclass
  */
 protected abstract T getSelectedRecord();

 /**
  * Called from list view to show the editing view; subclasses can override
  * to do something more interesting.
  * 
  * @return "edit" to show the editing view (without curent record this means
  *         create new)
  * @see #edit()
  */
 public String create() {
  currentRecord = newCurrentRecord();
  return EDIT;
 }

 public int getPageSize() {
  return pgSize;
 }

 public void setPageSize(int pageSize) {
  pgSize = pageSize;
 }

 private boolean reReadOnEdit=false;
 
 /**
  * Called from the list view to display the record for editing: "edit" means
  * show the editing view
  * 
  * The current record is pointed to by {@link #dataTable}.
  * 
  * The content of the record is (optionally) re-read, so that the lazy loading
  * collections could be loaded by the DAO on findById(), if the DAO implements this
  * method.
  * 
  * 
  * @return
  */
 public String edit() {
  T currentRecord = getSelectedRecord();
  if (null != currentRecord) {
   if(reReadOnEdit){
    currentRecord = service.get(currentRecord.getId());
   }
   setCurrentRecord(currentRecord);
  }
  return EDIT;
 }

 /**
  * Save action called from edit record view to add or save a record
  * 
  * @see #successAndErrorAction(Callable)
  * @return standard outcome from {@link #successAndErrorAction(Callable)}
  */
 public String save() {
  return successAndErrorAction(new Callable<String>() {
   @Override
   public String call() throws Exception {
    if (null != getCurrentId()) {
     setCurrentRecord(service.update(getCurrentRecord()));
    } else {
     service.insert(getCurrentRecord());
    }
    addMessage("Record saved successfully");
    return null;
   }
  });
 }

 public String getOrder() {
  return order;
 }

 public void setOrder(String order) {
  this.order = order;
 }

 public boolean isSortAscending() {
  return sortAscending;
 }

 public int getFirstRow() {
  return firstRow;
 }

 public void setFirstRow(int firstRow) {
  this.firstRow = firstRow;
 }

 public void setSortAscending(boolean sortAscending) {
  this.sortAscending = sortAscending;
 }

 public void setReReadOnEdit(boolean reReadOnEdit) {
  this.reReadOnEdit = reReadOnEdit;
 }

 public boolean isReReadOnEdit() {
  return reReadOnEdit;
 }

} 

The choice of view technology: why JSF?

JSF seems to be a natural choice of view technology for a framework that implements CRUD using JPA (or any other object-relational mapping framework, e.g. Hibernate), because it provides for easy mapping of properties of entities to view UI. This is especially important when the properties in question are collections of dependent beans (master-detail relationship) and we want to build a table-type edit form  (as shown, for example, here). As opposed to other technologies, JSF requires surprisingly little coding to map a collection of objects to an (editable and scrolling) data table on the web page.

Hence, an extension of GenericCrudController that uses JSF to implement abstract methods:


package com.example.crud.controller.jsf;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIData;
import javax.faces.component.html.HtmlDataTable;
import javax.faces.context.FacesContext;
import javax.faces.model.DataModel;

import org.apache.myfaces.renderkit.html.util.AddResource;
import org.apache.myfaces.renderkit.html.util.AddResourceFactory;

import com.example.crud.controller.GenericCrudController;
import com.example.crud.service.GenericCrudService;
import com.example.domain.DomainObject;

/**
 * Common controller type for CRUD using {@link GenericCrudService}.
 * 
 * Extending this class guarantees consistency in JFS outcomes and error
 * handling in controller operations.
 * 
 * Standard actions used by list and edit page templates are {@link #edit()},
 * {@link #create()}, {@link #save()}, {@link #delete()}.
 * 
 * {@link #getList()} or {@link #getPage()} is used to show the scrolling table.
 * 
 * {@link #getDataTable()} is the scrolling table model.
 * 
 * @author maxim
 * 
 * @param <T>
 *            currentRecord type that the controller works with
 * @param <PK>
 *            primary key type
 * 
 * @see GenericCrudService
 */
public class GenericCrudControllerJsf<T extends DomainObject<PK>, PK extends Serializable>
  extends GenericCrudController<T, PK> implements Serializable {

 private static final long serialVersionUID = -1;

 private Map<T, Boolean> recordSelections = new HashMap<T, Boolean>();

 private String popupOptions = "dependent=yes, menubar=no, toolbar=no, height=400, width=600";

 private transient UIData dataTable;
 private transient FacesContext facesContext;

 public GenericCrudControllerJsf() {
  if (logger.isDebugEnabled())
   logger.debug("constructed " + this.getClass());
 }

 public void submit() {
  logger.debug("void submit method");
 }

 /**
  * @return the dataTable
  */
 public UIData getDataTable() {
  return dataTable;
 }

 /**
  * @param dataTable
  *            the dataTable to set
  */
 public void setDataTable(UIData dataTable) {
  this.dataTable = dataTable;
 }

 /**
  * Add message via {@link FacesContext}
  * 
  * @param message
  * @param e
  */
 protected void addMessage(String message, Throwable e) {
  getFacesContext().addMessage(
    "controller",
    new FacesMessage(FacesMessage.SEVERITY_ERROR, message,
      null == e ? message : e.getMessage()));
 }

 /**
  * Add message via {@link FacesContext}
  * 
  * @param message
  */
 protected void addMessage(String message) {
  getFacesContext().addMessage("controller",
    new FacesMessage(FacesMessage.SEVERITY_INFO, message, message));
 }

 public void setFacesContext(FacesContext context) {
  this.facesContext = context;
 }

 public FacesContext getFacesContext() {
  if (null != facesContext) {
   return facesContext;
  } else {
   return FacesContext.getCurrentInstance();
  }
 }

 private AddResource addResource;

 public void setAddResource(AddResource addResource) {
  this.addResource = addResource;
 }

 public AddResource getAddResource() {
  if (null != addResource) {
   return addResource;
  } else {
   return AddResourceFactory.getInstance(getFacesContext());
  }
 }

 private List<ChoiceListener> choiceListeners = new ArrayList<ChoiceListener>();

 /**
  * Add listener to be called when we make a selection choice (e.g. check
  * some records and click "choose selected" or some such)
  * 
  * @param listener
  */
 public void addChoiceListener(ChoiceListener listener) {
  choiceListeners.add(listener);
 }

 /**
  * That's where we get control when the choice has been made in a pop-up
  * registered listeners are called; if the parent window exists and has a
  * "submitMainForm" element, we will close the current window and "click"
  * that element in the parent window.
  */
 public void choose() {
  for (T record : recordSelections.keySet()) {
   if (null != recordSelections.get(record)
     && recordSelections.get(record)) {
    for (ChoiceListener listener : choiceListeners) {
     listener.processChoice(record);
    }
   }
  }
  recordSelections.clear();
  dataTable = null;
  // The following kludge fires click event for "submitMainForm" element
  // in the main window,
  // if one is defined.
  String javaScriptText = "   window.close(); "
    + "   if(window.opener && window.opener.document.getElementById('submitMainForm')){"
    + "    var fireOnThis = window.opener.document.getElementById('submitMainForm');"
    + "    if (window.opener.document.createEvent) {"
    + "        var evObj = window.opener.document.createEvent('MouseEvents');"
    + "        evObj.initEvent( 'click', true, false );"
    + "        fireOnThis.dispatchEvent(evObj);"
    + "    } else if (window.opener.document.createEventObject){"
    + "         fireOnThis.fireEvent('onclick');" + "      }"
    + "   }";
  getAddResource().addInlineScriptAtPosition(getFacesContext(),
    AddResource.HEADER_BEGIN, javaScriptText);
 }

 /**
  * Method called by views and other controllers to open selection pop-up for
  * the domain objects in question
  * 
  * @param event
  */
 public void openPopup(String actionUrl) {
  dataTable = null;
  String javaScriptText = "window.open('" + actionUrl
    + "', 'popupWindow', '" + popupOptions + "');";
  getAddResource().addInlineScriptAtPosition(getFacesContext(),
    AddResource.HEADER_BEGIN, javaScriptText);
 }

 /**
  * @return
  */
 @Override
 @SuppressWarnings("unchecked")
 public T getSelectedRecord() {
  return null != dataTable ? ((T) dataTable.getRowData()) : null;
 }

 public Map<T, Boolean> getRecordSelections() {
  return recordSelections;
 }

 public void setRecordSelections(Map<T, Boolean> recoordSelections) {
  this.recordSelections = recoordSelections;
 }

 public void setPopupOptions(String popupOptions) {
  this.popupOptions = popupOptions;
 }

 public String getPopupOptions() {
  return popupOptions;
 }

 public void setChoiceListeners(List<ChoiceListener> choiceListeners) {
  this.choiceListeners = choiceListeners;
 }

 public List<ChoiceListener> getChoiceListeners() {
  return choiceListeners;
 }

 private transient DataModel pagedList;

 @Override
 public void resetPage() {
  dataPage = null;
  pagedList = null;
  setDataTable(null);
 }

 private DataPage<T> dataPage;
 
 /**
  * 
  * @return paginating lazy-reading collection
  */
 public DataModel getPage() {
  if (null == pagedList) {
   pagedList = new PagedListDataModel<T>(getPageSize(), getFirstRow()) {
    @Override
    public DataPage<T> fetchPage(int startRow, int pageSize) {
     final int start = getFirstRow();
     setFirstRow(startRow);
     if (logger.isDebugEnabled())
      logger.debug("new page: " + startRow + " old=" + start
        + " pageSize=" + pageSize);
     if (null == dataPage || startRow != dataPage.getStartRow()) {
      dataPage = new DataPage<T>(getService().getTotal(),
        startRow, (List<T>) getService().getPage(
          pageSize, startRow, getOrder(),
          isSortAscending()));
     }
     return dataPage;
    }
   };
  }
  return pagedList;
 }
}

The interesting part is the pagination logic that uses special class to implement lazy reading of records to fill pages.

Another part that needs clarification is how to code facelets or JSPs using this controller as a backing bean.

To be continued...

Thursday, December 17, 2009

Service Layer and Transactions: Sprin...

Service Layer and Transactions: Spring AOP used to apply declarative transactions 


(belongs to this series of articles)

Building on top of the reusable DAO framework that introduces generic DAOs for the simplest CRUD operations (create, update, delete), we are now implementing the Service layer. Why do we need the service layer at all? Indeed, as the framework does not, and can not, implement any non-trivial business methods in the service layer, why would one want to create it at all? After all, as would be seen, the service layer does simple pass-through to DAOs for the generic CRUD operations. The answer, or answers, are as follows:

  • we need to set the transaction boundary somewhere, and the DAOs seem to be the wrong place to do that, because giving them the extra concern of managing transactions would mean they wouldn't be easily usable when we have to aggregate several data object operations in one transaction (e.g. updating the main record and dependent records in a one-to-many would have a difficulty re-using the DAO for the other part of the relationship, because it would then be used within a transaction. This could be handled via declarative transaction (e.g. setting transaction level to "required"), but making DAOs transaction-agnostic seems to be a more flexible approach

  • setting the transaction boundary is better done where we define the business operations, because they are more coarse-grained than the "retrieve-save-delete" level for single domain objects; business operations (like "create invoice" as opposed to "create update invoice item") would tend to group several data access operations and would, in the most wide-spread case, be the natural place to demarcate transactions (this is especially true with Spring's declarative transactions that typically are set at the method level) 
In fact, what is being proposed is just one of the possible transaction strategies (more on this in this series of articles); however, this -- defining the transactions declaratively in the service layer using Spring configuration -- seems to be the simplest and most flexible option.

Here's how we could define the Generic Crud Service interface:

/**
 * 
 */
package com.example.crud.service;

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

import com.example.domain.DomainObject;

/**
 * Generic CRUD service. Methods throw unchecked exceptions for errors; no
 * assumptions as to the type of the exception is made.
 * 
 * @author maxim
 * 
 * @param <T>
 *            domain object type
 * @param <PK>
 *            primary key type
 */
public interface GenericCrudService<T extends DomainObject<PK>, PK extends Serializable> {
 /**
  * 
  * @return a collection of all the entities (can be large)
  * @throws @{@link RuntimeException} unchecked exception for any error
  */
 public Collection<T> getAll();

 /**
  * Update persistent entity
  * 
  * @param emp
  *            data object (contains primary key)
  * @return updated entity (contains fields that could change during update,
  *         e.g. version)
  * @throws @{@link RuntimeException} unchecked exception for any error
  */
 public T update(T emp);

 /**
  * Deletes by primary key
  * 
  * @param id
  *            primary key
  * @throws @{@link RuntimeException} unchecked exception if doesn't exist or
  *         error
  */
 public void delete(PK id);

 /**
  * 
  * @param id
  *            primary key value
  * @return one object
  * @throws @{@link RuntimeException} unchecked exception if doesn't exist or
  *         error
  */
 public T get(PK id);

 /**
  * Inserts new entity
  * 
  * @param emp
  * @throws @{@link RuntimeException} unchecked exception for any error (e.g.
  *         integrity constraint violation)
  */
 public void insert(T emp);

 /**
  * Generic pagination
  * 
  * @param pageSize
  *            page size
  * @param firstRecord
  *            first record (starts with 0)
  * @param order
  *            field name to indicate order to the persistence layer
  *            (typically an entity field name)
  * @param asc
  *            true if ascending, else descending sort order
  * @return a collection containing single page of data, size <= pageSize
  * @throws @{@link RuntimeException} unchecked exception for errors
  */
 public Collection<T> getPage(int pageSize, int firstRecord, String order,
   boolean asc);
 /**
  * Generic pagination
  * 
  * @param pageSize
  *            page size
  * @param firstRecord
  *            first record (starts with 0)
  * @param order
  *            field name to indicate order to the persistence layer
  *            (typically an entity field name)
  * @param asc
  *            true if ascending, else descending sort order
  * @return a collection containing single page of data, size <= pageSize
  * @throws @{@link RuntimeException} unchecked exception for errors
  */
 public Collection<T> getPage(int pageSize, int firstRecord, String order,
   boolean asc, String criteria, Object ... objects );

 /**
  * Get total number of entities in persistent store
  * 
  * @return number
  * @throws @{@link RuntimeException} unchecked exception
  */
 public Long getTotal();

}


... and the implementation:

/**
 * 
 */
package com.example.crud.service;

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

import com.example.crud.dao.GenericDao;
import com.example.crud.dao.GenericDaoConsumer;
import com.example.domain.DomainObject;

/**
 * Implements {@link GenericCrudService} with a {@link GenericDao}
 * 
 * @author maxim
 * 
 */
public class GenericCrudDaoService<T extends DomainObject<PK>, PK extends Serializable>
  implements GenericCrudService<T, PK>, GenericDaoConsumer<T, PK> {

 protected GenericDao<T, PK> dao;

 /*
  * (non-Javadoc)
  * 
  * @see com.example.prototype.service.TService#deleteT(java.lang.PK)
  */
 @Override
 public void delete(PK id) {
  dao.remove(id);
 }

 /*
  * (non-Javadoc)
  * 
  * @see com.example.prototype.service.TService#getAllTs()
  */
 @Override
 public Collection<T> getAll() {
  return dao.findAll();
 }

 /*
  * (non-Javadoc)
  * 
  * @see com.example.prototype.service.TService#getT(java.lang.PK)
  */
 @Override
 public T get(PK id) {
  return dao.findById(id);
 }

 /*
  * (non-Javadoc)
  * 
  * @see
  * com.example.prototype.service.TService#insertT(com.example.prototype
  * .entities.T)
  */
 @Override
 public void insert(T m) {
  dao.create(m);
 }

 /*
  * (non-Javadoc)
  * 
  * @see
  * com.example.prototype.service.TService#updateT(com.example.prototype
  * .entities.T)
  */
 @Override
 public T update(T m) {
  return dao.update(m);
 }

 /**
  * @see GenericCrudService
  */
 @Override
 public Collection<T> getPage(int pageSize, int firstRecord, String order,
   boolean asc) {
  return dao.getPage(pageSize, firstRecord, order, asc);
 }

 @Override
 public Long getTotal() {
  return dao.getTotalEntities();
 }

 @Override 
 public void setDao(GenericDao<T, PK> dao) {
  this.dao = dao;
 }

 public GenericDao<T, PK> getDao() {
  return dao;
 }

 @Override
 public Collection<T> getPage(int pageSize, int firstRecord, String order,
   boolean asc, String criteria, Object... objects) {
  return dao.getPage(pageSize, firstRecord, order, asc, criteria, objects);
 }

}


A simple class extending GenericCrudDaoService does not need to implement any operations (provided all it does is simple create-retrieve-update-delete:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.example.project.service;

import com.example.crud.service.GenericCrudDaoService;
import com.example.project.domain.UserData;

/**
 *
 * @author maxim
 */
public class UserDetailsDaoService extends GenericCrudDaoService<UserData, String> implements UserDetailsService {

}

As can be seen, we don't need to add any operations if we only need a CRUD service. This class is referenced in the Spring configuration quoted below.

Note that the source code doesn't have any transaction declarations either! This is because, instead of annotations, we are using Spring's AOP to apply transaction handling at the method level:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
 xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xsi:schemaLocation="
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">


 <!--
  Service layer. Defines transaction processing. Services are
  implemented directly using GenericDaoService or its subclasses
 -->

 <context:annotation-config />

 <tx:annotation-driven />


       
 <bean id="userSvc" class="com.example.project.service.UserDetailsDaoService" >
  <property name="dao" ref="myUserDetailsService"></property>
 </bean>


 <!--
  These pointcuts work for the services that subclass generic services
  from com.example.crud.service
 -->
 <aop:config>
  <aop:pointcut id="otherServiceMethods"
   expression="execution(* com.example.project.service.*.*(..))" />
  <aop:pointcut id="updateServiceMethods"
   expression="execution(* com.example.project.service.*.update*(..))" />
  <aop:pointcut id="deleteServiceMethods"
   expression="execution(* com.example.project.service.*.delete*(..))" />
  <aop:pointcut id="insertServiceMethods"
   expression="execution(* com.example.project.service.*.insert*(..))" />
  <aop:pointcut id="otherGenericServiceMethods"
   expression="execution(* com.example.crud.service.*.*(..))" />
  <aop:pointcut id="updateGenericServiceMethods"
   expression="execution(* com.example.crud.service.*.update*(..))" />
  <aop:pointcut id="deleteGenericServiceMethods"
   expression="execution(* com.example.crud.service.*.delete*(..))" />
  <aop:pointcut id="insertGenericServiceMethods"
   expression="execution(* com.example.crud.service.*.insert*(..))" />
   
  <aop:advisor advice-ref="txAdvice" pointcut-ref="updateServiceMethods" />
  <aop:advisor advice-ref="txAdvice" pointcut-ref="updateGenericServiceMethods" />
  <aop:advisor advice-ref="txAdvice" pointcut-ref="deleteServiceMethods" />
  <aop:advisor advice-ref="txAdvice" pointcut-ref="deleteGenericServiceMethods" />
  <aop:advisor advice-ref="txAdvice" pointcut-ref="insertServiceMethods" />
  <aop:advisor advice-ref="txAdvice" pointcut-ref="insertGenericServiceMethods" />
  <aop:advisor advice-ref="supportsAdvice" pointcut-ref="otherServiceMethods" />
  <aop:advisor advice-ref="supportsAdvice" pointcut-ref="otherGenericServiceMethods" />
  
 </aop:config>

 <tx:advice id="txAdvice" transaction-manager="transactionManager">
  <tx:attributes>
   <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"
    read-only="false" />
  </tx:attributes>
 </tx:advice>

 <tx:advice id="supportsAdvice" transaction-manager="transactionManager">
  <tx:attributes>
   <tx:method name="*" propagation="SUPPORTS" read-only="true" />
  </tx:attributes>
 </tx:advice>


</beans>