In EHCache, is there a way to implement some kind of a db listener where the cahce will auto update if the data is out of sync? (e.g as soon as the user requests for data, the cahce checks if the data is out of sync, if yes...updates itself and returns the data, if not...just return the data from the cache)If someone can point me which part of the specification highlights this use, that would be awesome!
The goal is to always provide the latest data to the user. So I am guessing a timed refresh mechanism will not do as the data can change anytime.
EHCAche is not mandatory to use in my case, so any mechanism that satisfies this will be most welcome...
Thanks!!
For EhCache this is what I believe you are looking for. If you would not like to do timed refresh (even though it is a simple solution), triggers or a message bus based update would be the way to go. You can perform some statistics and see update frequency once the triggering has been established and switch to a timed update with sufficient frequency to satisfy Nyquist.
I did that using ehcache-spring-annotations. These are my dependencies in the maven pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.googlecode.ehcache-spring-annotations</groupId>
<artifactId>ehcache-spring-annotations</artifactId>
<version>1.2.0-M1</version>
</dependency>
#Cacheable works, but unfortunately #TriggersRemove doesn't work. The workaround is to invalidate the cache manually.
Here is my example of usage:
package com.company.project.dao;
import java.util.List;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.company.project.domain.Parent;
import com.company.project.domain.Child;
import com.googlecode.ehcache.annotations.Cacheable;
import com.googlecode.ehcache.annotations.KeyGenerator;
import com.googlecode.ehcache.annotations.Property;
#Component("Example")
public class EhcacheExample {
#Autowired
#Qualifier("ehCacheManager")
private FactoryBean<CacheManager> ehCacheManager;
public void createParen(Parent parent) {
cleanCache(parent);
create(parent);
}
private void cleanCache(Parent parent) {
try {
CacheManager cacheManager = ehCacheManager.getObject();
Ehcache ehcache = cacheManager.getEhcache("myCache");
ehcache.remove(parent.getChild().hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
#Cacheable
( cacheName = "myCache",
keyGenerator = #KeyGenerator (
name = "com.company.project.util.ChildCacheKeyGenerator",
properties = #Property( name="includeMethod", value="false" )
)
)
public List<SerieRecording> getParentsByChild(Child child) {
return ...;
}
#Override
public void deleteParentById(long id) {
Parent parent = findById(id);
cleanCache(parent);
delete(parent);
}
...
}
The KeyGenerator implementation can be:
package com.company.project.util;
import java.io.Serializable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.company.project.domain.Child;
import com.googlecode.ehcache.annotations.key.AbstractCacheKeyGenerator;
public class ChildCacheKeyGenerator extends AbstractCacheKeyGenerator<Serializable> {
Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public Serializable generateKey(Object... data) {
if (data[0] instanceof Child) {
Child child = (Child)data[0];
return child.hashCode();
}
new IllegalArgumentException();
return null;
}
}
In the Spring configuration:
<bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" >
<property name="configLocation" value="classpath:config/ehcache-methods.xml"/>
</bean>
ehcache-methods.xml:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd">
<cache name="myCache" eternal="false"
maxElementsInMemory="12600" overflowToDisk="false" diskPersistent="false"
timeToIdleSeconds="0" timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU" />
</ehcache>
I hope it is useful.
Related
API Requests using Postman
I tried to send HTTP requests to the java backend web server application and perform CRUD operations. GET operations are working fine and gave back the proper response but save, update and delete operations gave the following error.
Please note that there are three customer records in my database and I tried to send HTTP requests as follows;
GET http://localhost:8080/tms/api/v1/customers ---> Successful ✔
GET http://localhost:8080/tms/api/v1/customers/3 ---> Successful ✔
DELETE http://localhost:8080/tms/api/v1/customers/3 ---> Unsuccessful ❌
Error Log
Customer ID: 2
02-Feb-2022 09:59:11.271 SEVERE [http-nio-8080-exec-2] com.myapp.web.api.exceptionhandler.AppWideExceptionHandler.globalExceptionHandler null
javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:295)
at com.sun.proxy.$Proxy80.remove(Unknown Source)
at com.myapp.web.dal.CrudDAOImpl.delete(CrudDAOImpl.java:67)
at com.myapp.web.business.custom.impl.CustomerBOImpl.deleteCustomer(CustomerBOImpl.java:79)
at com.myapp.web.api.CustomerController.deleteCustomer(CustomerController.java:116)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doDelete(FrameworkServlet.java:931)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:359)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1735)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
API Layer
CustomerController.java
package com.myapp.web.api;
import com.myapp.web.api.util.ApiUtil;
import com.myapp.web.business.custom.CustomerBO;
import com.myapp.web.dto.CustomerDTO;
import com.myapp.web.exception.IdFormatException;
import com.myapp.web.exception.RecordNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#CrossOrigin(origins = "http://localhost:8080")
#RequestMapping("/api/v1/customers")
#RestController
public class CustomerController {
#Autowired
private CustomerBO customerBO;
/**
* Get all customers list.
*
* #return List<CustomerDTO> customersList.
*/
#GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public List<CustomerDTO> getAllCustomers() throws Exception {
return customerBO.getAllCustomers();
}
/**
* Get customer by customer ID.
*
* #return CustomerDTO customer object.
* #throws IdFormatException if the ID is not an Integer.
* #throws RecordNotFoundException if matching customer record not found,
*/
#GetMapping(produces = MediaType.APPLICATION_JSON_VALUE,
value = "/{id:\\d}")
public CustomerDTO getCustomerByID(#PathVariable(name = "id") String id) throws Exception {
System.out.println("CustomerID: " + id);
Integer customerID = ApiUtil.getIntegerId(id);
CustomerDTO customer = customerBO.getCustomerByID(customerID);
System.out.println("Customer Result: " + customer);
/* If customer not found. */
if (customer == null) throw new RecordNotFoundException();
return customer;
}
/**
* Delete customer by Customer ID.
*/
#ResponseStatus(HttpStatus.NO_CONTENT)
#DeleteMapping(value = "/{id:\\d}")
public void deleteCustomer(#PathVariable String id) throws Exception {
Integer customerID = ApiUtil.getIntegerId(id);
System.out.println("Customer ID: " + customerID);
customerBO.deleteCustomer(customerID);
}
}
Business Layer
SuperBO.java
package com.myapp.web.business;
public interface SuperBO {
}
CustomerBOImpl.java
package com.myapp.web.business.custom.impl;
import com.myapp.web.business.custom.CustomerBO;
import com.myapp.web.business.custom.util.mapper.CustomerDTOMapper;
import com.myapp.web.dal.custom.CustomerDAO;
import com.myapp.web.dto.CustomerDTO;
import com.myapp.web.entity.Customer;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
#NoArgsConstructor
#Transactional
#Service
public class CustomerBOImpl implements CustomerBO {
#Autowired
private CustomerDAO customerDAO;
#Autowired
private CustomerDTOMapper mapper;
#Override
public void deleteCustomer(int customerID) throws Exception {
/* delete. */
this.customerDAO.delete(customerID);
}
#Transactional(readOnly = true)
#Override
public CustomerDTO getCustomerByID(int customerID) throws Exception {
/* get customer by customer ID. */
return this.mapper.getCustomerDTO(this.customerDAO.get(customerID));
}
#Transactional(readOnly = true)
#Override
public List<CustomerDTO> getAllCustomers() throws Exception {
/* get all customers. */
return this.mapper.getCustomerDTOs(this.customerDAO.getAll());
}
}
CustomerDTOMapper.java
package com.myapp.web.business.custom.util.mapper;
import com.myapp.web.dto.CustomerDTO;
import com.myapp.web.entity.Customer;
import org.mapstruct.Mapper;
import java.util.List;
#Mapper(componentModel = "spring")
public abstract class CustomerDTOMapper {
/* -------------------- Customer -------------------- */
public abstract Customer getCustomer(CustomerDTO customerDTO);
public abstract CustomerDTO getCustomerDTO(Customer customer);
public abstract List<CustomerDTO> getCustomerDTOs(List<Customer> customerList);
}
Data Access Layer
SuperDAO.java
package com.myapp.web.dal;
import javax.persistence.EntityManager;
public interface SuperDAO {
EntityManager getEntityManager();
}
CrudDAO.java
package com.myapp.web.dal;
import com.myapp.web.entity.SuperEntity;
import java.io.Serializable;
import java.util.List;
public interface CrudDAO<T extends SuperEntity<Serializable>, PK extends Serializable> extends SuperDAO {
/**
* saving an entity and return the entity.
*
* #return T SuperEntity. */
T save(T entity) throws Exception;
void update(T entity) throws Exception;
void delete(PK key) throws Exception;
T get(PK key) throws Exception;
List<T> getAll() throws Exception;
}
CrudDAOImpl.java
package com.myapp.web.dal;
import com.myapp.web.entity.SuperEntity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;
public class CrudDAOImpl<T extends SuperEntity<Serializable>, K extends Serializable> implements CrudDAO<T, K> {
private final Class<T> entityClass;
#PersistenceContext
private EntityManager entityManager;
public CrudDAOImpl() {
this.entityClass =
(Class<T>) (((ParameterizedType) (this.getClass().getGenericSuperclass())).getActualTypeArguments()[0]);
}
/**
* This method is used to pass the EntityManager to the lower level classes that extend the CrudDAOImpl class.
*/
public EntityManager getEntityManager() {
return this.entityManager;
}
#Override
public void delete(K key) throws Exception {
this.entityManager.remove(key);
}
#Override
public T get(K key) throws Exception {
return this.entityManager.find(this.entityClass, key);
}
#Override
public List<T> getAll() throws Exception {
List<T> resultList = (List<T>) this.entityManager.
createQuery("SELECT e FROM " + this.entityClass.getName() + " e").getResultList();
return resultList;
}
}
CustomerDAO.java
package com.myapp.web.dal.custom;
import com.myapp.web.dal.CrudDAO;
import com.myapp.web.entity.Customer;
public interface CustomerDAO extends CrudDAO<Customer, Integer> {
}
CustomerDAOImpl.java
package com.myapp.web.dal.custom.impl;
import com.myapp.web.dal.CrudDAOImpl;
import com.myapp.web.dal.custom.CustomerDAO;
import com.myapp.web.entity.Customer;
import org.springframework.stereotype.Repository;
#Repository
public class CustomerDAOImpl extends CrudDAOImpl<Customer, Integer> implements CustomerDAO {
}
Here's what I have tried.
I tried unit testing. BO layer and DAO layer unit test case worked as intended but when I check the API by sending HTTP request using Postman, save, update and delete cannot be performed using the API.
CustomerDAOImplTest.java ----> TEST PASSED ! ✔
package com.myapp.web.dal.custom.impl;
import com.myapp.web.WebAppConfig;
import com.myapp.web.WebRootConfig;
import com.myapp.web.dal.custom.CustomerDAO;
import com.myapp.web.entity.Customer;
import com.myapp.web.entity.enumeration.GenderTypes;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import static org.junit.Assert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = {WebRootConfig.class, WebAppConfig.class})
public class CustomerDAOImplTest {
#Autowired
private CustomerDAO customerDAO;
#Test
public void checkCustomerDAO() {
assertNotNull(customerDAO);
}
#Transactional
#Test
public void getCustomerByID() throws Exception {
assertNotNull(customerDAO);
Customer customer = this.customerDAO.get(10);
System.out.println("GET Customer Entity: " + customer);
assertNotNull(customer);
}
#Test
public void getAllCustomers() throws Exception {
List<Customer> customersList = this.customerDAO.getAll();
assertEquals(2, customersList.size());
}
#Transactional
#Test
public void deleteCustomer() throws Exception {
assertNotNull(this.customerDAO);
/* delete */
this.customerDAO.delete(3);
Customer customerFromDB = this.customerDAO.get(10);
assertNull(customerFromDB);
}
}
CustomerBOImpl.java ----> TEST PASSED ! ✔
package com.myapp.web.business.custom.impl;
import com.myapp.web.WebAppConfig;
import com.myapp.web.WebRootConfig;
import com.myapp.web.business.custom.CustomerBO;
import com.myapp.web.dto.CustomerDTO;
import com.myapp.web.entity.enumeration.GenderTypes;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = {WebRootConfig.class, WebAppConfig.class})
public class CustomerBOImplTest {
#Autowired
private CustomerBO customerBO;
#Test
public void checkCustomerBO() {
assertNotNull(customerBO);
}
// #Transactional
#Test
public void deleteCustomer() throws Exception {
assertNotNull(this.customerBO);
/* delete */
this.customerBO.deleteCustomer(3);
CustomerDTO customerDTOFromDB = this.customerBO.getCustomerByID(10);
assertNull(customerDTOFromDB);
}
}
pom.xml dependencies
<dependencies>
<!-- Servlet-API -->
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- MySQL connector -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
<scope>compile</scope>
</dependency>
<!-- SLF4J for logging-->
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-jdk14 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>2.0.0-alpha1</version>
<scope>compile</scope>
</dependency>
<!-- Junit4 for unit testing -->
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- Apache Commons Validator -->
<!-- https://mvnrepository.com/artifact/commons-validator/commons-validator -->
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.7</version>
</dependency>
<!-- mapstruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
<!-- Hibernate -->
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.3.Final</version>
</dependency>
<!-- Apache Commons Codecs -->
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<!-- Spring Data Access ==================================================================== -->
<!--Spring ORM -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.14</version>
</dependency>
<!-- Apache Commons DBCP -->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.9.0</version>
</dependency>
<!-- Spring Data Access - Spring AOP - Aspectj ========================================== -->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
<scope>runtime</scope>
</dependency>
<!-- Spring Web MVC =================================================================== -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.15</version>
</dependency>
<!-- JACKSON DATABIND -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
<!--Jackson Datatype-->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.1</version>
</dependency>
<!--Spring TestContext Framework-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.15</version>
<scope>test</scope>
</dependency>
</dependencies>
Development Enviroment
Java 8
Tomcat 9
I refer to these following questions and answers but, I could not fix the issue. I would appreciate some help to fix the
error and, understand the error.
https://stackoverflow.com/a/32552558/12898581
javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread
https://www.wilfriedbarth.com/til/2018-03-31-spring-transactional-annotation/
I have created a simple job in Spring Batch with Spring Boot to be executed as a task with Spring Cloud Task (all in STS4). If I execute it as a Spring Boot App, te execution is correct and without problems, but if I compile the project or try to launch the test, the execution of the job is correct:
o.s.c.t.b.l.TaskBatchExecutionListener : The job execution id 19 was run within the task execution 56
o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
com.example.demo.MyTasklet : Executing Tasklet: STEP 1
o.s.batch.core.step.AbstractStep : Step: [step1] executed in 16ms
o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]
com.example.demo.MyTasklet : Executing Tasklet: STEP 2
o.s.batch.core.step.AbstractStep : Step: [step2] executed in 7ms
o.s.b.c.l.support.SimpleJobLauncher : Job: [FlowJob: [name=job2]] completed with the following parameters: [{run.id=7, -spring.output.ansi.enabled=always}] and the following status: [COMPLETED] in 52ms
com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Shutdown initiated...
com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Shutdown completed.
But after that, it happends an exception:
o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener#576d5deb] to prepare test instance [com.example.demo.DemoJobApplicationTests#173ed316]
java.lang.IllegalStateException: The ApplicationContext loaded for [[MergedContextConfiguration#e350b40 testClass = DemoJobApplicationTests, locations = '{}', classes = '{class com.example.demo.DemoJobApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer#5e82df6a, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer#50a638b5, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer#0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer#130161f7, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer#0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer#479d31f3], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]] is not active. This may be due to one of the following reasons: 1) the context was closed programmatically by user code; 2) the context was closed during parallel test execution either according to #DirtiesContext semantics or due to automatic eviction from the ContextCache due to a maximum cache size policy.
at org.springframework.util.Assert.state(Assert.java:94) ~[spring-core-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:127) ~[spring-test-5.2.3.RELEASE.jar:5.2.3.RELEASE]
[...]
This makes that the compilation ends in fail, but the execution in the database is recorded as COMPLETED.
Reviewing the code where the error is generated, in the Spring class called DefaultTestContext:
#Override
public ApplicationContext getApplicationContext() {
ApplicationContext context = this.cacheAwareContextLoaderDelegate.loadContext(this.mergedContextConfiguration);
if (context instanceof ConfigurableApplicationContext) {
#SuppressWarnings("resource")
ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context;
Assert.state(cac.isActive(), () ->
"The ApplicationContext loaded for [" + this.mergedContextConfiguration +
"] is not active. This may be due to one of the following reasons: " +
"1) the context was closed programmatically by user code; " +
"2) the context was closed during parallel test execution either " +
"according to #DirtiesContext semantics or due to automatic eviction " +
"from the ContextCache due to a maximum cache size policy.");
}
return context;
}
... the problem is that "context" is not active.
These are my files:
ArqDatasourceConfiguration.java
package com.example.demo;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
#Configuration
public class ArqDatasourceConfiguration {
#Bean
#ConfigurationProperties("spring.batch.datasource")
public DataSource dataSource() throws SQLException {
return DataSourceBuilder.create().build();
}
#Bean
public JdbcTemplate jdbcTemplateBatch(final DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
#Bean(name = "taskDataSource")
#ConfigurationProperties("spring.task.datasource")
public DataSource taskDataSource() throws SQLException {
return DataSourceBuilder.create().build();
}
#Bean
public JdbcTemplate jdbcTemplateTask(#Qualifier("taskDataSource") final DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
CustomTaskConfigurer.java
package com.example.demo;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.task.configuration.DefaultTaskConfigurer;
import org.springframework.stereotype.Component;
#Component
public class CustomTaskConfigurer extends DefaultTaskConfigurer {
#Autowired
public CustomTaskConfigurer(#Qualifier("taskDataSource") DataSource dataSource) {
super(dataSource);
}
}
DemoJobApplication.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.task.configuration.EnableTask;
#SpringBootApplication
#EnableTask
public class DemoJobApplication {
public static void main(String[] args) {
SpringApplication.run(DemoJobApplication.class, args);
}
}
MyJob.java
package com.example.demo;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
#EnableBatchProcessing
public class MyJob {
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Bean
public Job job2(Step step1, Step step2) {
return jobBuilderFactory.get("job2")
.incrementer(new RunIdIncrementer())
.flow(step1)
.next(step2)
.end()
.build();
}
#Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.tasklet(new MyTasklet("STEP 1"))
.build();
}
#Bean
public Step step2() {
return stepBuilderFactory.get("step2")
.tasklet(new MyTasklet("STEP 2"))
.build();
}
}
MyTasklet.java
package com.example.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
public class MyTasklet implements Tasklet {
private static final Logger log = LoggerFactory.getLogger(MyTasklet.class);
private String msg;
public MyTasklet(String msg) {
this.msg = msg;
}
#Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) {
log.info("Executing Tasklet: " + msg);
return RepeatStatus.FINISHED;
}
}
application.properties
spring.batch.initialize-schema=always
spring.main.allow-bean-definition-overriding=true
spring.batch.datasource.jdbc-url=jdbc:postgresql://localhost:5432/postgres
spring.batch.datasource.username=postgres
spring.batch.datasource.password=*****
spring.batch.datasource.driver-class=org.postgresql.Driver
spring.batch.datasource.schema=springbatch
spring.task.datasource.jdbc-url=jdbc:postgresql://localhost:5432/postgres
spring.task.datasource.username=postgres
spring.task.datasource.password=*****
spring.task.datasource.driver-class=org.postgresql.Driver
spring.task.datasource.schema=springtask
DemoJobApplicationTests.java
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
#SpringBootTest
class DemoJobApplicationTests {
#Test
void contextLoads() {
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demoJob</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demoJob</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Cloud Task -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-task</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-task-core</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>
<!-- ================================== -->
<!-- Database -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- ================================== -->
<!-- Warning #ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- ================================== -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
I have tried several annotations like #DirtiesContext, #EnableAutoConfiguration, #SpringBatchTest... but nothing seems to solve the issue, so I would appreciate any idea or help.
Well, after some head banging we have found the solution, ant it is really stu...simply. It is necessary to update the version of the dependencies from Spring Cloud Task in the pom.xml, changing 1.2.2.RELEASE with 2.2.2.RELEASE
I have an XML file that I am trying to read that has elements with attributes. I have tried from multiple examples, but the fields in my class always end up as null as shown below:
Data [type=null, value=null]
Data [type=null, value=null]
Data [type=null, value=null]
Below is the cut down example code of my issue.
Here is an example XML file located in the src/main/resources/data directory (data.xml):
<?xml version="1.0" encoding="UTF-8"?>
<list>
<data type="shopping" value="milk" />
<data type="shopping" value="eggs" />
<data type="TODO" value="return books to library" />
</list>
Below is my domain class for the XML data (Data.java):
package com.example.demo.springbatchtest.domain;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "data")
public class Data {
private final String type;
private final String value;
public Data(String type, String value) {
this.type = type;
this.value = value;
}
#XmlAttribute(name = "type")
public String getType() {
return type;
}
#XmlAttribute(name = "value")
public String getValue() {
return value;
}
#Override
public String toString() {
return "Data [type=" + type + ", value=" + value + "]";
}
}
Here is my Spring Batch job configuration file (JobConfiguration.java):
package com.example.demo.springbatchtest.configuration;
import java.util.HashMap;
import java.util.Map;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.xml.StaxEventItemReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.xstream.XStreamMarshaller;
import com.example.demo.springbatchtest.domain.Data;
#Configuration
public class JobConfiguration {
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
StepBuilderFactory stepBuilderFactory;
#Bean
public StaxEventItemReader<Data> dataItemReader() {
XStreamMarshaller unmarshaller = new XStreamMarshaller();
Map<String, Class> aliases = new HashMap<>();
aliases.put("data", Data.class);
unmarshaller.setAliases(aliases);
StaxEventItemReader<Data> reader = new StaxEventItemReader<>();
reader.setResource(new ClassPathResource("/data/data.xml"));
reader.setFragmentRootElementName("data");
reader.setUnmarshaller(unmarshaller);
return reader;
}
#Bean
public ItemWriter<Data> dataItemWriter() {
return items -> {
for (Data item : items) {
System.out.println(item.toString());
}
};
}
#Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.<Data, Data>chunk(10)
.reader(dataItemReader())
.writer(dataItemWriter())
.build();
}
#Bean
public Job job() {
return jobBuilderFactory
.get("job")
.start(step1())
.build();
}
}
Here is my main Spring Boot class (SpringBatchTestApplication.java):
package com.example.demo.springbatchtest;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
#EnableBatchProcessing
public class SpringBatchTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBatchTestApplication.class, args);
}
}
Here is my pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>SpringBatchTestSpringBoot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBatchTestSpringBoot</name>
<description>Spring Batch Test with Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
It sounds like JAX-B is unable to set the Data.type and value attributes.
By default it will be looking for a getter/setter pair for each attribute, otherwise they will be treated as read-only.
The alternative would be to use field level access - #XmlAccessorType(XmlAccessType.FIELD)
Found this tutorial which helped me get the example code to work.
https://walkingtechie.blogspot.com/2017/03/spring-batch-xml-file-to-mysql-example.html
The key was to a Converter class to parse out the attributes. So in my example, I added a call set a Converter class in the dataItemReader() in the JobConfiguration class.
import com.example.demo.springbatchtest.converter.DataConverter;
...
#Autowired
private DataConverter dataConverter;
...
#Bean
public StaxEventItemReader<Data> dataItemReader() {
XStreamMarshaller unmarshaller = new XStreamMarshaller();
Map<String, Class> aliases = new HashMap<>();
aliases.put("data", Data.class);
unmarshaller.setAliases(aliases);
unmarshaller.setConverters(dataConverter); // from Walking Techie
StaxEventItemReader<Data> reader = new StaxEventItemReader<>();
reader.setResource(new ClassPathResource("/data/data.xml"));
reader.setFragmentRootElementName("data");
reader.setUnmarshaller(unmarshaller);
return reader;
}
And then I extended the Converter class to handle the attributes.
package com.example.demo.springbatchtest.converter;
import org.springframework.stereotype.Component;
import com.example.demo.springbatchtest.domain.Data;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
#Component
public class DataConverter implements Converter {
#Override
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
}
#Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
String type = reader.getAttribute("type");
String value = reader.getAttribute("value");
return new Data(type, value);
}
#Override
public boolean canConvert(Class type) {
return type.equals(Data.class);
}
}
The program now outputs the following:
Data [type=shopping, value=milk]
Data [type=shopping, value=eggs]
Data [type=TODO, value=return books to library]
I am trying to do a simple cache of data but everytime I test it. It never seems to cache the data.
package app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
#EnableAutoConfiguration
#SpringBootApplication
#EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("getUserInfo");
}
}
package app.cache;
import app.repo.User.User;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
#Service
public class UserService {
private User userInfo;
#CacheEvict("getUserInfo")
public void setUserInfo(User userInfo) {
this.userInfo = userInfo;
}
#Cacheable("getUserInfo")
public User getUserInfo() {
return userInfo;
}
}
package app.controllers;
import app.cache.UserService;
import app.repo.User.Player;
import app.repo.User.User;
import app.repo.User.UserRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
#RestController
public class UserController {
#Autowired
private UserService userCache;
#RequestMapping("/test-user-cache")
#ResponseBody
public User testUserCache() {
if(userCache.getUserInfo() != null) {
return userCache.getUserInfo();
}
User user = new User("joejoe","wordpass");
user.setId(44444444);
userCache.setUserInfo(user);
return userCache.getUserInfo();
}
}
Dependencies
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-server-jndi</artifactId>
<version>1.5.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
...
It doesnt seem to save the data when I try to save it. And when I do a request again, no data is never cached.
You get that behavior because the cache works. The data is saved, but null is already cached: if(userCache.getUserInfo()
In order to clear the cache when you set the value you can annotate the setUserInfo Method with #CacheEvict(cacheNames="books", allEntries=true).
I'm trying to configure mail in Spring and keep getting a null pointer exception. My suspicion is that it can't find the "mailSender" bean but I could be way off. My configuration is as follows:
web.xml
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>logVerbosityLevel</param-name>
<param-value>ERROR</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<beans:bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<beans:property name="host" value="mail.company.com"/>
</beans:bean>
</beans:beans>
SendMailTest.java
package company.test;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.activation.CommandMap;
import javax.activation.MailcapCommandMap;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mock.web.MockServletContext;
import company.AppListener;
public class SendMailTest {
private static final Logger logger = AppListener.getLogger();
#Mock
private ServletContextEvent mockServletContextEvent;
private final Map<String, Object> map = new HashMap<String, Object>();
#Mock
private ServletContext mockServletContext;
#Before
public void setUp() {
System.setProperty("java.net.preferIPv4Stack" , "true");
initMocks(this);
map.clear();
when(mockServletContextEvent.getServletContext()).thenReturn(mockServletContext);
Mockito.doAnswer(new Answer<Object>() {
public Object answer(final InvocationOnMock invocation)
throws Throwable {
final String key = (String) invocation.getArguments()[0];
final Object value = invocation.getArguments()[1];
return map.put(key, value);
}
}).when(mockServletContext).setAttribute(Mockito.anyString(), Mockito.anyObject());
Mockito.doAnswer(new Answer<Object>() {
public Object answer(final InvocationOnMock invocation)
throws Throwable {
final Object value = invocation.getArguments()[0];
return map.remove(value);
}
}).when(mockServletContext).removeAttribute(Mockito.anyString());
}
#Autowired
private MailSender mailSender;
#Test
public void testSendSimpleEmaill() {
SimpleMailMessage message = new SimpleMailMessage();
String name = "John Doe";
message.setFrom("john.doe#company.com");
message.setTo("jane.doe#company2.com");
message.setSubject("Test Email from Spring");
message.setText("Will email ever work?");
try {
mailSender.send(message);
}
catch (Exception e) {
e.printStackTrace();
System.err.println(e.getMessage());
}
}
}
Stack Trace
java.lang.NullPointerException
at company.test.SendMailTest.testSendSimpleEmaill(SendMailTest.java:85)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
null
When you're running the test with JUnit, Spring isn't loaded at all, so the autowired fields aren't being set. To run spring in a test, look into the SpringJUnit4ClassRunner.
You can also create your own instance of JavaMailSenderImpl within the setUp method and assign it to mailSender
yu need to put the configurations in your test class, also you whould like to copy your context file to the root of your test class (just file resolution, when the spring runners starts needs to load your context file)..your test class should look like this...
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "applicationContext.xml" })
#WebAppConfiguration
public class FacadeTest {
private String phoneNumber = "111111115";
#Autowired
private BussinesDelegate bussinesDelegate;
#Test
public void runTest(){
this.fullTest(phoneNumber, "aaaaa");
}
public void fullTest(String phoneNumber, String answer){
try {
bmoaBussinesDelegate.processMessage("127.0.0.1", phoneNumber, "camicase" + System.currentTimeMillis());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
and dont forget to check your dependencies!
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- Testng -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.8.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
The cool thing about spring, its that its IOC container can probide the same behavior as a full Java EE compliant server without needing a server, and basically what the runner does is to load the context manually, the same way you could do ir in a standole app with spring ;)