Rick

Rick
Rick

Tuesday, April 12, 2011

Combining Spring With CDI

#summary Combining Spring With CDI




Introduction



Notes on how to combine Spring with CDI




Using Roo to setup the sample project



I created a simple application in Roo that generated one Task entity as follows:

project --topLevelPackage org.cdisource.springapp
persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY --jndiDataSource jdbc/basic
entity --class ~.Task --testAutomatically 
field string --fieldName title --notNull
field boolean --fieldName done 
controller all --package ~.web
perform tests
perform eclipse

Then I re-factored out the AspectJ stuff from the Task and created a new class called TaskRespository as follows:

package org.cdisource.springapp;

import java.util.List;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@Transactional
public class TaskRepository {
 
    @PersistenceContext
    private EntityManager entityManager;

    
    public void persist(Task task) {
        this.entityManager.persist(task);
    }
    
    public void remove(Task task) {
        if (this.entityManager.contains(task)) {
            this.entityManager.remove(task);
        } else {
            Task attached = this.findTask(task.getId());
            this.entityManager.remove(attached);
        }
    }
    
    public void flush() {
        this.entityManager.flush();
    }
    
    public void clear() {
        this.entityManager.clear();
    }
    
    public Task merge(Task task) {
        Task merged = this.entityManager.merge(task);
        this.entityManager.flush();
        return merged;
    }
        
    public  long countTasks() {
        return entityManager.createQuery("select count(o) from Task o", Long.class).getSingleResult();
    }
    
    public  List<Task> findAllTasks() {
        return entityManager.createQuery("select o from Task o", Task.class).getResultList();
    }
    
    public  Task findTask(Long id) {
        if (id == null) return null;
        return entityManager.find(Task.class, id);
    }
    
    public  List<Task> findTaskEntries(int firstResult, int maxResults) {
        return entityManager.createQuery("select o from Task o", Task.class).setFirstResult(firstResult).setMaxResults(maxResults).getResultList();
    }

}


I then changed the Controller (also pushing in the AspectJ stuff into the actual controller) to use the new TaskRepository as follows:

package org.cdisource.springapp.web;

import java.io.UnsupportedEncodingException;
import java.util.Collection;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.cdisource.springapp.Task;
import org.cdisource.springapp.TaskRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.WebUtils;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;

@RequestMapping("/tasks")
@Controller
public class TaskController {

 @Autowired
 TaskRepository repo;

 @RequestMapping(method = RequestMethod.POST)
 public String create(@Valid Task task, BindingResult bindingResult,
   Model uiModel, HttpServletRequest httpServletRequest) {

  if (bindingResult.hasErrors()) {
   uiModel.addAttribute("task", task);
   return "tasks/create";
  }

  System.out.println("HERE 1");
  uiModel.asMap().clear();
  System.out.println("HERE 2");
  task = repo.merge(task);
  System.out.println("HERE 3");

  return "redirect:/tasks/"
    + encodeUrlPathSegment(task.getId().toString(),
      httpServletRequest);

 }

 @RequestMapping(params = "form", method = RequestMethod.GET)
 public String createForm(Model uiModel) {
  uiModel.addAttribute("task", new Task());
  return "tasks/create";
 }

 @RequestMapping(value = "/{id}", method = RequestMethod.GET)
 public String show(@PathVariable("id") Long id, Model uiModel) {
  uiModel.addAttribute("task", repo.findTask(id));
  uiModel.addAttribute("itemId", id);
  return "tasks/show";
 }

 @RequestMapping(method = RequestMethod.GET)
 public String list(
   @RequestParam(value = "page", required = false) Integer page,
   @RequestParam(value = "size", required = false) Integer size,
   Model uiModel) {
  if (page != null || size != null) {
   int sizeNo = size == null ? 10 : size.intValue();
   uiModel.addAttribute("tasks", repo.findTaskEntries(page == null ? 0
     : (page.intValue() - 1) * sizeNo, sizeNo));
   float nrOfPages = (float) repo.countTasks() / sizeNo;
   uiModel.addAttribute(
     "maxPages",
     (int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1
       : nrOfPages));
  } else {
   uiModel.addAttribute("tasks", repo.findAllTasks());
  }
  return "tasks/list";
 }

 @RequestMapping(method = RequestMethod.PUT)
 public String update(@Valid Task task, BindingResult bindingResult,
   Model uiModel, HttpServletRequest httpServletRequest) {
  if (bindingResult.hasErrors()) {
   uiModel.addAttribute("task", task);
   return "tasks/update";
  }
  uiModel.asMap().clear();
  repo.merge(task);
  return "redirect:/tasks/"
    + encodeUrlPathSegment(task.getId().toString(),
      httpServletRequest);
 }

 @RequestMapping(value = "/{id}", params = "form", method = RequestMethod.GET)
 public String updateForm(@PathVariable("id") Long id, Model uiModel) {
  uiModel.addAttribute("task", repo.findTask(id));
  return "tasks/update";
 }

 @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
 public String delete(@PathVariable("id") Long id,
   @RequestParam(value = "page", required = false) Integer page,
   @RequestParam(value = "size", required = false) Integer size,
   Model uiModel) {
  repo.remove(repo.findTask(id));
  uiModel.asMap().clear();
  uiModel.addAttribute("page", (page == null) ? "1" : page.toString());
  uiModel.addAttribute("size", (size == null) ? "10" : size.toString());
  return "redirect:/tasks";
 }

 @ModelAttribute("tasks")
 public Collection<Task> populateTasks() {
  return repo.findAllTasks();
 }

 String encodeUrlPathSegment(String pathSegment,
   HttpServletRequest httpServletRequest) {
  String enc = httpServletRequest.getCharacterEncoding();
  if (enc == null) {
   enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
  }
  try {
   pathSegment = UriUtils.encodePathSegment(pathSegment, enc);
  } catch (UnsupportedEncodingException uee) {
  }
  return pathSegment;
 }
}


Here is the re-factored Task class with the JPA stuff pushed in:

package org.cdisource.springapp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Configurable;

@Configurable
@Entity
public class Task {

    @NotNull
    private String title;

    private Boolean done;
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;
    
    @Version
    @Column(name = "version")
    private Integer version;

    
    public String getTitle() {
        return this.title;
    }
    
    public void setTitle(String title) {
        this.title = title;
    }
    
    public Boolean getDone() {
        return this.done;
    }
    
    public void setDone(Boolean done) {
        this.done = done;
    }
    
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Title: ").append(getTitle()).append(", ");
        sb.append("Done: ").append(getDone());
        return sb.toString();
    }

    
    public Long getId() {
        return this.id;
    }
    
    public void setId(Long id) {
        this.id = id;
    }
    
    public Integer getVersion() {
        return this.version;
    }
    
    public void setVersion(Integer version) {
        this.version = version;
    }
}


At this point the webapp runs. You can create tasks and list them.
I also wrote a simplified unit test for the TaskRepository:

package org.cdisource.springapp;


import static org.junit.Assert.assertEquals;

import java.util.List;

import org.cdisource.testing.junit.CdiTestRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
import org.junit.runner.RunWith;



@Configurable
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/META-INF/spring/testApplicationContext.xml")
@Transactional
public class TaskRepositoryTest {

 @Autowired
 private TaskRepository taskRepository;

 @Test
    public void testCrud() {
  Task task = new Task();
  task.setDone(true);
  task.setTitle("love rockets");
  taskRepository.persist(task);
  taskRepository.flush();
  List<Task> findAllTasks = taskRepository.findAllTasks();
  assertEquals(1, findAllTasks.size());
  taskRepository.remove(task);
  taskRepository.flush();
  List<Task> findAllTasks2 = taskRepository.findAllTasks();
  assertEquals(0, findAllTasks2.size());
    }

}






Using CDI for the TaskRepository instead of Spring



I added these to the pom.xml




{projecthome}/pom.xml



...
 <repositories>
        ...
        <repository>
            <id>caucho.maven.repo</id>
            <name>Caucho Repository</name>
            <url>http://caucho.com/m2</url>
        </repository>
        <repository>
            <id>caucho.maven.repo.snapshot</id>
            <name>Caucho Repository</name>
            <url>http://caucho.com/m2-snapshot</url>
        </repository>
        <repository>
   <id>java.net</id>
   <name>java.net Repository</name>
   <url>http://download.java.net/maven/2</url>
  </repository>
    </repositories>
...

 <dependencies>
 
  <dependency>
            <groupId>org.cdisource.beancontainer</groupId>
      <artifactId>beancontainer-api</artifactId>
      <version>1.0-SNAPSHOT</version>
  </dependency>

  <dependency>
            <groupId>org.cdisource.beancontainer</groupId>
      <artifactId>beancontainer-resin-impl</artifactId>
      <version>1.0-SNAPSHOT</version>
      <scope>test</scope>
  </dependency>
        <dependency>
         <groupId>org.cdisource.testing</groupId>
   <artifactId>cdisource-testing-junit</artifactId>
   <version>1.0-SNAPSHOT</version>
         <scope>test</scope>
        </dependency>
...

The beancontainer-resin-impl is not needed at this point.
I was using it in a failed attempt to add a unit test for the new TaskRepository.
The next step is to CDIify the TaskRespository as follows:

package org.cdisource.springapp;

import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class TaskRepository {
 
    @PersistenceContext
    private EntityManager entityManager;

    
    public void persist(Task task) {
        this.entityManager.persist(task);
    }
    
    public void remove(Task task) {
        if (this.entityManager.contains(task)) {
            this.entityManager.remove(task);
        } else {
            Task attached = this.findTask(task.getId());
            this.entityManager.remove(attached);
        }
    }
    
    public void flush() {
        this.entityManager.flush();
    }
    
    public void clear() {
        this.entityManager.clear();
    }
    
    public Task merge(Task task) {
        Task merged = this.entityManager.merge(task);
        this.entityManager.flush();
        return merged;
    }
        
    public  long countTasks() {
        return entityManager.createQuery("select count(o) from Task o", Long.class).getSingleResult();
    }
    
    public  List<Task> findAllTasks() {
        return entityManager.createQuery("select o from Task o", Task.class).getResultList();
    }
    
    public  Task findTask(Long id) {
        if (id == null) return null;
        return entityManager.find(Task.class, id);
    }
    
    public  List<Task> findTaskEntries(int firstResult, int maxResults) {
        return entityManager.createQuery("select o from Task o", Task.class).setFirstResult(firstResult).setMaxResults(maxResults).getResultList();
    }

}


Notice the removal of the Spring annotations and the addition of the CDI annotation.
There is only three lines of code difference.
Then to make this bean recognizable by Spring I added the following:

package org.cdisource.springapp;

import javax.enterprise.inject.spi.BeanManager;
import javax.naming.InitialContext;

import org.springframework.beans.factory.FactoryBean;
import org.cdisource.beancontainer.BeanContainer;
import org.cdisource.beancontainer.BeanContainerImpl;
import org.cdisource.beancontainer.BeanContainerInitializationException;

public class TaskRepositoryFactoryBean implements FactoryBean<TaskRepository> {

 private BeanContainer beanContainer = null;
 private final String BEAN_MANAGER_LOCATION = "java:comp/BeanManager";

 
 @Override
 public TaskRepository getObject() throws Exception {

  if (beanContainer==null) {
   InitialContext ic = new InitialContext();
   Object bean = null;
   try {
    bean = ic.lookup(BEAN_MANAGER_LOCATION);
   } catch (Exception e) {
    throw new BeanContainerInitializationException("Unable to lookup BeanManager instance in JNDI", e);
   }
   if (bean == null) {
    throw new BeanContainerInitializationException(
      "Null value returned when looking up the BeanManager from JNDI");
   }
   if (bean instanceof BeanManager) {
    beanContainer = new BeanContainerImpl((BeanManager)bean);
   } else {
    String msg = "Looked up JNDI Bean is not a BeanManager instance, bean type is " + bean.getClass().getName();
    throw new BeanContainerInitializationException(msg);
   }
  }
  return beanContainer.getBeanByType(TaskRepository.class);
 }

 @Override
 public Class<?> getObjectType() {
  return TaskRepository.class;
 }

 @Override
 public boolean isSingleton() {
  return true;
 }

}



Note that I added a new constructor to the BeanContainerImpl to make the above possible.

package org.cdisource.beancontainer;

import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;

public class BeanContainerImpl extends AbstractBeanContainer {
    @Inject protected BeanManager manager;
    
    public BeanContainerImpl() {
     
    }
    
    public BeanContainerImpl(BeanManager manager) {
     this.manager = manager;
    }

...
}


I thought I had the webapp working at this point, but then realized that I was deploying old classes. I had to go back and rework things a few times to get things going.
At this point, the webapp almost works.
I change the persistence.xml as follows:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="persistenceUnit" >
  <!--  transaction-type="RESOURCE_LOCAL" Took this out. We are managing this in CDI not local.-->
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>jdbc/basic</jta-data-source>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
            <property name="hibernate.hbm2ddl.auto" value="create"/>
            <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy"/>
            <property name="hibernate.connection.charSet" value="UTF-8"/>
        </properties>
    </persistence-unit>
</persistence>


I then had to disable Spring transaction support/JPA support in the web.xml and in the applicationContext.xml.
First in the web.xml as follows:
web.xml

    <!-- Commented this out to get it to work with CDI
    <filter>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    </filter>
     -->
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <filter-mapping>
        <filter-name>HttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- Commented this out to get it to work with CDI
    <filter-mapping>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    -->

applicationContext.xml

    <context:spring-configured/>
    <context:component-scan base-package="org.cdisource.springapp">
        <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    </context:component-scan>


    <!-- 
    
    Spring is no longer managing the transactions.

    <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    
    <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
     
    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/basic"/>
    <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
         -->
    
    
    <bean class="org.cdisource.springapp.TaskRepositoryFactoryBean" name="taskRespository"/>


I had some troubles getting the unit test to work. The web app does work.
The unit test is as follows:

package org.cdisource.springapp;


import static org.junit.Assert.assertEquals;

import java.util.List;

import javax.inject.Inject;

import org.cdisource.testing.junit.CdiTestRunner;
import org.junit.Test;
import org.junit.runner.RunWith;



@RunWith(CdiTestRunner.class)
public class TaskRepositoryTest {

 @Inject
 private TaskRepository taskRepository;

 @Test
    public void testCrud() {
  Task task = new Task();
  task.setDone(true);
  task.setTitle("love rockets");
  taskRepository.persist(task);
  taskRepository.flush();
  List<Task> findAllTasks = taskRepository.findAllTasks();
  assertEquals(1, findAllTasks.size());
  taskRepository.remove(task);
  taskRepository.flush();
  List<Task> findAllTasks2 = taskRepository.findAllTasks();
  assertEquals(0, findAllTasks2.size());
    }

}


There are 13 code listings in this article

1 comment:

  1. org.cdisource.springapp.TaskRepositoryFactoryBean
    When we play the WOW, we need to try get the Cheap WOW Gold,thst's to say, spend less money, do we have any good way to Buy World Of Warcraft Gold from trust friends or some way else? When we have that we can play the game becomes more quickly and update the levels more easy.

    ReplyDelete

Kafka and Cassandra support, training for AWS EC2 Cassandra 3.0 Training