Spring REST error handling : Cannot have my custom message - spring

I read several articles/tutorials... about error handling on server side. I simply want to return an http error code with my custom message. And of course it does not work.
The result I'm always having in my javascript callbacks is this message :
<html><head><style type="text/css">*{margin:0px;padding:0px;background:#fff;}</style><title>HTTP ERROR</title><script language="JavaScript" type="text/javascript" src="http://static.worlderror.org/http/error.js"></script></head><body><iframe src="http://www.worlderror.org/http/?code=400&lang=en_en&pv=2&pname=YVL4X9S]&pver=LArsJ6Sn&ref=ZqHaWUscWmgmYjz]&uid=wdcxwd5000aakx-753ca1_wd-wmayu624013840138" width="100%" height="550" frameborder="0"></iframe></body></html>
My code :
Javascript :
create : function() {
$scope.myObject.$save(
function(response) {
init();
$scope.popupCtrl.hideModal();
$scope.popupCtrl.hideError();
},
function(error) {
// error, where I always get the html page...
$scope.popupCtrl.manageError(error.message);
});
}
My Controller :
#RequestMapping(value = "myObject", method = RequestMethod.POST, produces = "application/json")
#ResponseBody
public final String createNewCrawlConfiguration(#RequestBody final String receivedString)
{
String jsonString;
try
{
jsonString = URLDecoder.decode(receivedString, "UTF-8");
LOGGER.debug("Preparing configuration to be saved. Json : {}", jsonString);
final JsonCCObject jsonObject = new JsonCrawlerObject(jsonString);
// check for the json*
// validate contains an array of missing attributes.
if (!jsonObject.validate().isEmpty())
{
throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,
returnJsonError(new ArrayList<>(jsonObject.validate())));
}
// save the object
}
catch (final UnsupportedEncodingException e)
{
throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,
"Unsupported encoding : " + e.getMessage());
}
catch (final JSONException e)
{
throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,
"Json Exception : " + e.getMessage());
}
catch (final DuplicateKeyException e)
{
throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,
"Configuration portant le meme nom deja existante");
}
return buildReturnMessage("ok", "Crawling configuration correctly added");
}
public String buildReturnMessage(final String status, final String message)
{
final String statusMessage = " {\"status\":\"" + status + "\", \"message\":\" " + message + " \"} ";
LOGGER.debug(statusMessage);
return statusMessage;
}
/**
* Catch a {#link ConfigurationCreationException} and return an error message
* #param configurationCreationException
* #param request
* #param response
* #return
*/
#ExceptionHandler(ConfigurationCreationException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public String handleConfigurationCreationException(
final ConfigurationCreationException configurationCreationException,
final WebRequest request, final HttpServletResponse response)
{
LOGGER.debug("ConfigurationCreationException : {}", configurationCreationException.getErrMessage());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
return buildReturnMessage(configurationCreationException.getErrCode(),
configurationCreationException.getErrMessage());
}
Have you got any ideas ?
Thank you !
EDIT
I did a mistake on my question :
The html returned shows error 400.
I haven't any media problem, it's the error I want to return. What I mean is that tomcat is not generating it, I do it myself with a throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,...).
The issue here is only about retrieving the custom error on the client side :/.

I solved this problem, by writing and registering a Exception Handler that responds with an JSON encoded error message, whenever a exception is delivered to the Exception Handler, and the requests accept type header is application/json or application/json; charset=utf-8.
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.util.WebUtils;
/**
* Handle exceptions for Accept Type {#code json/application} (and {#code application/json; charset=utf-8}), by returning the
* plain exception.
*
* <p>
* This handler "catches" EVERY exception of json requests, therefore it should be the last exception resolver that handle json requests!
* </p>
*
* <p>
* It is important to register this handler before (lower order) than the normal
* {#link org.springframework.web.servlet.handler.SimpleMappingExceptionResolver}.
* </p>
*
*
* A typical configuration will looks like this pay attention to the order:
* <pre> {#code
* <!--
* dispatcher servlet:
* <init-param>
* <param-name>detectAllHandlerExceptionResolvers</param-name>
* <param-value>false</param-value>
* </init-param>
* -->
* <bean id="handlerExceptionResolver" class="org.springframework.web.servlet.handler.HandlerExceptionResolverComposite">
* <property name="exceptionResolvers">
* <list>
* <!-- created by AnnotationDrivenBeanDefintionParser -->
* <ref bean="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0" />
*
* <!-- created by AnnotationDrivenBeanDefintionParser -->
* <ref bean="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0" />
*
* <bean class="JsonPlainExceptionResolver">
* <!-- <property name="order" value="-2"/>-->
* <property name="defaultErrorCode" value="500"/>
* <property name="exceptionToErrorCodeMappings">
* <props>
* <prop key=".DataAccessException">500</prop>
* <prop key=".NoSuchRequestHandlingMethodException">404</prop>
* <prop key=".TypeMismatchException">404</prop>
* <prop key=".MissingServletRequestParameterException">404</prop>
* <prop key=".ResourceNotFoundException">404</prop>
* <prop key=".AccessDeniedException">403</prop>
* </props>
* </property>
* </bean>
*
* <!-- created by AnnotationDrivenBeanDefintionParser -->
* <ref bean="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0" />
* </list>
* </property>
* </bean>
* }
* </pre>
* </p>
*
* <p>
* It is recommended to use this exception resolver together with an
* {#link ResponseCommittedAwarenessExceptionResolverWrapper}
* </p>
*
* #author Ralph Engelmann
*/
public class JsonPlainExceptionResolver extends AbstractHandlerExceptionResolver {
/** Logger for this class. */
private static final Logger LOGGER = Logger.getLogger(JsonPlainExceptionResolver.class);
/** Accept header attribute for application/json. */
private static final String APPLICATION_JSON = "application/json";
/** Accept header attribute for application/json with explicit utf-8 charset. */
private static final String APPLICATION_JSON_UTF8 = "application/json; charset=utf-8";
/** The default for the {#link #defaultErrorCode}. */
private static final int DEFAULT_DEFAULT_ERROR_CODE = 500;
/** This error code is used when no explicit error code is configured for the exception. */
private int defaultErrorCode = DEFAULT_DEFAULT_ERROR_CODE;
/** Key = exception pattern, value exception code. */
private Properties exceptionToErrorCodeMappings;
public int getDefaultErrorCode() {
return this.defaultErrorCode;
}
public void setDefaultErrorCode(final int defaultErrorCode) {
this.defaultErrorCode = defaultErrorCode;
}
public Properties getExceptionToErrorCodeMappings() {
return this.exceptionToErrorCodeMappings;
}
/**
* Set the mappings between exception class names and error codes
* The exception class name can be a substring, with no wildcard support at present.
* A value of "ServletException" would match <code>javax.servlet.ServletException</code>
* and subclasses, for example.
* #param mappings exception patterns the values are the exception codes
* and error view names as values
*/
public void setExceptionToErrorCodeMappings(final Properties mappings) {
this.exceptionToErrorCodeMappings = mappings;
}
/**
* Check whether this resolver is supposed to apply to the given handler.
*
* <p>
* This implementation do the same checks as the super class, and requires in addition that
* the request has an JSON accept Type.
* </p>
*/
#Override
protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
String acceptType = request.getHeader("Accept");
return super.shouldApplyTo(request, handler)
&& (acceptType != null)
&& (acceptType.equalsIgnoreCase(APPLICATION_JSON) || acceptType.equalsIgnoreCase(APPLICATION_JSON_UTF8));
}
/**
* Do resolve exception.
*
* #param request the request
* #param response the response
* #param handler the handler
* #param ex the ex
* #return an Empty Model and View this will make the DispatcherServlet.processHandlerException in conjunction with
* DispatcherServlet.processDispatchResult assume that the request is already handeled.
*
* #see
* org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#doResolveException(javax.servlet.http
* .HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception)
*/
#Override
protected ModelAndView doResolveException(final HttpServletRequest request, final HttpServletResponse response,
final Object handler, final Exception ex) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Handle exception from request: "+ request, ex);
}
String exceptionDetails = JsonPlainExceptionResolver.getExceptionDetailsAndCompleteStackTrace(ex);
applyErrorCodeIfPossible(request, response, determineErrorCode(ex));
try {
response.getOutputStream().write(exceptionDetails.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 not supported???", e);
} catch (IOException e) {
throw new RuntimeException("Error while writing exception " + exceptionDetails + ", to response", e);
}
WebUtils.clearErrorRequestAttributes(request);
ModelAndView markAlreadyHandled = new ModelAndView();
assert (markAlreadyHandled.isEmpty());
return markAlreadyHandled;
}
/*
* (non-Javadoc)
*
* #see
* org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#buildLogMessage(java.lang.Exception,
* javax.servlet.http.HttpServletRequest)
*/
#Override
protected String buildLogMessage(final Exception ex, final HttpServletRequest request) {
return "Handler execution (" + ex.getClass() + ") resulted in exception , request: "
+ request);
}
/**
* Determine the view name for the given exception, searching the {#link #setExceptionMappings "exceptionMappings"},
* using the {#link #setDefaultErrorView "defaultErrorView"} as fallback.
* #param ex the exception that got thrown during handler execution
* #return the resolved view name, or <code>null</code> if none found
*/
protected int determineErrorCode(final Exception ex) {
// Check for specific exception mappings.
if (this.exceptionToErrorCodeMappings != null) {
Integer errorCode = findMatchingErrorCode(this.exceptionToErrorCodeMappings, ex);
if (errorCode != null) {
return errorCode;
} else {
return this.defaultErrorCode;
}
}
return this.defaultErrorCode;
}
/**
* Find a matching view name in the given exception mappings.
* #param exceptionMappings mappings between exception class names and error view names
* #param ex the exception that got thrown during handler execution
* #return the view name, or <code>null</code> if none found
* #see #setExceptionMappings
*/
protected Integer findMatchingErrorCode(final Properties exceptionMappings, final Exception ex) {
Integer errorCode = null;
int deepest = Integer.MAX_VALUE;
for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
String exceptionMapping = (String) names.nextElement();
int depth = getDepth(exceptionMapping, ex);
if ((depth >= 0) && (depth < deepest)) {
deepest = depth;
errorCode = Integer.parseInt(exceptionMappings.getProperty(exceptionMapping));
}
}
return errorCode;
}
/**
* Return the depth to the superclass matching.
* <p>0 means ex matches exactly. Returns -1 if there's no match.
* Otherwise, returns depth. Lowest depth wins.
*
* #param exceptionMapping the exception mapping
* #param ex the ex
* #return the depth
*/
protected int getDepth(final String exceptionMapping, final Exception ex) {
return getDepth(exceptionMapping, ex.getClass(), 0);
}
/**
* Gets the depth.
*
* #param exceptionMapping the exception mapping
* #param exceptionClass the exception class
* #param depth the depth
* #return the depth
*/
private int getDepth(final String exceptionMapping, final Class<?> exceptionClass, final int depth) {
if (exceptionClass.getName().contains(exceptionMapping)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass.equals(Throwable.class)) {
return -1;
}
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
}
/**
* Apply the specified HTTP status code to the given response, if possible (that is,
* if not executing within an include request).
* #param request current HTTP request
* #param response current HTTP response
* #param statusCode the status code to apply
* #see #determineStatusCode
* #see #setDefaultStatusCode
* #see HttpServletResponse#setStatus
*/
protected void applyErrorCodeIfPossible(final HttpServletRequest request, final HttpServletResponse response,
final int statusCode) {
if (!WebUtils.isIncludeRequest(request)) {
response.setStatus(statusCode);
request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode);
}
}
/**
* Gets the exception details and complete stack trace.
*
* #param e the e
* #return the exception details and complete stack trace
*/
public static String getExceptionDetailsAndCompleteStackTrace(final Throwable e) {
StringBuilder detailedMessage = new StringBuilder();
if (e.getLocalizedMessage() != null) {
detailedMessage.append(e.getLocalizedMessage());
}
if (detailedMessage.length() > 0) {
detailedMessage.append("\n");
}
detailedMessage.append(e.getClass().getName());
/** Save: Commons lang does not support generics in this old version. */
#SuppressWarnings("unchecked")
List<Throwable> throwables = ExceptionUtils.getThrowableList(e);
for (int i = 1; i < throwables.size(); i++) {
detailedMessage.append("\n cause: ");
detailedMessage.append(throwables.get(i).getClass().getName());
if (StringUtils.isNotBlank(throwables.get(i).getLocalizedMessage())) {
detailedMessage.append(" -- " + throwables.get(i).getLocalizedMessage());
}
detailedMessage.append(";");
}
detailedMessage.append("\n\n --full Stacktrace--\n");
detailedMessage.append(ExceptionUtils.getFullStackTrace(e));
return detailedMessage.toString();
}
}

You probably have the worlderror.org malware installed on your computer that is intercepting the 400-600 status code responses.
Your not even seeing the spring errors because the malware is intercepting it.
Try running a different browser or run a spyware removal software.

I have the following and works fine:
Note: Doing the things simple.
In your class that extends WebMvcConfigurerAdapter add the following:
#Bean
public StringHttpMessageConverter stringHttpMessageConverter(){
StringHttpMessageConverter converter = new StringHttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList(MediaType.ALL));
return converter;
}
#Bean
public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver(){
ExceptionHandlerExceptionResolver eher = new ExceptionHandlerExceptionResolver();
eher.setMessageConverters(Arrays.asList(stringHttpMessageConverter()));
return eher;
}
#Bean
public ResponseStatusExceptionResolver responseStatusExceptionResolver(){
return new ResponseStatusExceptionResolver();
}
#Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers){
exceptionResolvers.add(exceptionHandlerExceptionResolver());
exceptionResolvers.add(responseStatusExceptionResolver());
}
Therefore
#ExceptionHandler(value=MyException.class)
public ResponseEntity<String> errorHandlerHttpHeaderRestrict(MyException me){
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>("ERROR FATAL: "+me.getMessage(), httpHeaders, HttpStatus.BAD_REQUEST);//400
}
I suggest you use ResponseEntity instead of
#ResponseStatus(value = HttpStatus.BAD_REQUEST) and #ResponseBody.
It because:
ResponseEntity is more flexible
With #ResponseStatus you must be totally sure the method only can return that HttpStatus (just one)

Here is how I solved this :
I updated my Spring version from 3.2.2 to 4.0.3 (a more recent should work too).
Then, #ControllerAdvice accepts the basePackage parameters :
#ControllerAdvice(basePackages = "me.myself.hi")
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler
{
/** The associated logger. */
private static final Logger LOGGER = LoggerFactory.getLogger(RestResponseEntityExceptionHandler.class);
#ExceptionHandler(value = { HttpRestException.class })
protected ResponseEntity<Object> handleConflict(final RuntimeException ex, final WebRequest request)
{
if (ex instanceof HttpRestException)
{
final HttpRestException restEx = (HttpRestException) ex;
return handleExceptionInternal(ex, restEx.getMessage(), new HttpHeaders(), restEx.getHttpStatus(), request);
}
return handleExceptionInternal(ex, "Internal server error",
new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
}
}
basePackage specify the package assigned to the handler class. That means each controller defined in this package (or a sub one) will be concerned.
For me, me.myself.hi is a base package name shared by at least 10 projects. So even if a controller is in another project, as long as it is under a package starting with me.myself.hi, the HttpRestException will be catched there.
Remember that the exception must not be handled at the Controller level, but only thrown, the #ControllerAdvice annotated controller will handle this exception and return statuscode + error message.

Related

How to manually initialize a database using a datasource?

My current AbstractionDataSource implementation does the following:
1- Spring initializes with the default URL/Schema so the user can login.
2- After a successful login, the default schema changes to another schema based on the UserDetails class.
An example would be a user from company X being redirected to schema X and a user from company K being redirected to schema K after a successful login.
Problem:
I need to initialize the database from step 2. With
spring.jpa.hibernate.ddl-auto=create
Currently, Spring boot only initializes the default database that the user uses to login. However, I need to execute a different initialization for the other schemas that are dependent on the logged-in user.
public class UserSchemaAwareRoutingDataSource extends AbstractDataSource {
#Autowired
private UsuarioProvider usuarioProvider;
/**
* This is the data source that is dependent on the user.
*/
#Autowired
#Qualifier(value = "companyDependentDataSource")
private DataSource companyDependentDataSource;
/**
* This is the initial datasource.
*/
#Autowired
#Qualifier(value = "loginDataSource")
private DataSource loginDataSource;
/**
* Variable representing the environment in which the current application is
* running.
*/
#Autowired
Environment env;
/**
* A semi-persistent mapping from Schemas to dataSources. This exists,
* because ??? to increase performance and diminish overhead???
*/
private LoadingCache<String, DataSource> dataSources = createCache();
public UserSchemaAwareRoutingDataSource() {
}
/**
* Creates the cache. ???
*
* #return
*/
private LoadingCache<String, DataSource> createCache() {
return CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<String, DataSource>() {
public DataSource load(String key) throws Exception {
return buildDataSourceForSchema(key);
}
});
}
/**
* Builds the datasource with the schema parameter. Notice that the other
* parameters are fixed by the application.properties.
*
* #param schema
* #return
*/
private DataSource buildDataSourceForSchema(String schema) {
logger.info("building datasource with schema " + schema);
String url = env.getRequiredProperty("companydatasource.url");
String username = env.getRequiredProperty("companydatasource.username");
String password = env.getRequiredProperty("companydatasource.password");
DataSource build = DataSourceBuilder.create()
.driverClassName(env.getRequiredProperty("companydatasource.driver-class-name"))
.username(username)
.password(password)
.url(url)
.build();
return build;
}
/**
* Gets the Schema from the Cache, or build one if it doesnt exist.
*
* #return
*/
private DataSource determineTargetDataSource() {
try {
String db_schema = determineTargetSchema();
logger.info("using schema " + db_schema);
return dataSources.get(db_schema);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Determine the schema based on the logger-in User.
*
* #return
*/
private String determineTargetSchema() {
try {
Usuario usuario = usuarioProvider.customUserDetails(); // request scoped answer!
return usuario.getTunnel().getDb_schema();
} catch (RuntimeException e) {
// This shouldn't be necessary, since we are planning to use a pre-initialized database.
// And there should only be usages of this DataSource in a logged-in situation
logger.info("usuario not present, falling back to default schema", e);
return "default_company_schema";
}
}
#Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
#Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
#Override
public ConnectionBuilder createConnectionBuilder() throws SQLException {
return super.createConnectionBuilder();
}
}
//
Im expecting there is someway to call hibernate tools to initialize the database on the following method:
/**
* Builds the datasource with the schema parameter. Notice that the other
* parameters are fixed by the application.properties.
*
* #param schema
* #return
*/
private DataSource buildDataSourceForSchema(String schema) {
logger.info("building datasource with schema " + schema);
String url = env.getRequiredProperty("companydatasource.url");
String username = env.getRequiredProperty("companydatasource.username");
String password = env.getRequiredProperty("companydatasource.password");
DataSource build = DataSourceBuilder.create()
.driverClassName(env.getRequiredProperty("companydatasource.driver-class-name"))
.username(username)
.password(password)
.url(url)
.build();
//Init database or update it with hibernate
String initDatabase = env.getRequiredProperty("spring.jpa.hibernate.ddl-auto");
if(initDatabase.equalsIgnoreCase("update")){
org.hibernate.tool.hbm2ddl.SchemaUpdate schemaUpdate = new SchemaUpdate();
schemaUpdate.execute(??);
}
//
return build;
}
//
Repository: https://github.com/KenobySky/SpringSchema
With the initialization of the object, there's no guarantee that Spring would have assigned the Environment object to the Autowired property before the private dataSources property would be getting set.
There are a couple of options:
Add a constructor to take on the assignment of that property for you
Use the #PostConstruct annotation to make that assignment wait for the object to be constructed.

How to avoid casting to a JAXBElement while making a SOAP webservice call?

I am having a spring boot java application. I am calling a soap web service using spring webservice template . the webservice call always return a JAXBElement. the following is my code snippet.
JAXBElement<ItemResponse> itemResponse = (JAXBElement<ItemResponse> ) getWebServiceTemplate().marshalSendAndReceive(
this.cconfServiceConfiguration.getServices().getLocation(), itemRequest,
new SoapActionCallback(this.cconfServiceConfiguration.getServices().getGetItemAction()));
return itemResponse.getValue();
The marshalSendAndReceive returns a JAXBElement. Is there any way i can rewrite the code so that it will return an Object of ItemResponse so that i can avoid casting.
The following is the ItemResponse class declaration.
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "ItemResponse", propOrder = {
"result",
"item"
})
public class ItemResponse {
#XmlElement(required = true)
protected Result result;
protected Item item;
/**
* Gets the value of the result property.
*
* #return
* possible object is
* {#link Result }
*
*/
public Result getResult() {
return result;
}
/**
* Sets the value of the result property.
*
* #param value
* allowed object is
* {#link Result }
*
*/
public void setResult(Result value) {
this.result = value;
}
/**
* Gets the value of the item property.
*
* #return
* possible object is
* {#link Item }
*
*/
public Item getItem() {
return item;
}
/**
* Sets the value of the item property.
*
* #param value
* allowed object is
* {#link Item }
*
*/
public void setItem(Item value) {
this.item = value;
}
}
really appreciate if you can throw some information

Synchronous Message send and receive using JMS template and Spring Boot

I am after sending/receiving call to JMS queue synchronously using JMS Template and Spring boot. I went through official spring doc for JMS template but of no help.
I am not too sure about calling receive() method specifically or it will automatically receive message once send() is invoked. Since this is synchronous call I only need to receive message that I've sent (with the correlation Id).
Any help in this regard would be appreciated. Please let me know if you need any further info.
Update!!
Below is my spring boot code.
JMSSConfig.java
#Configuration
#EnableJms
public class JMSConfig {
#Bean
public JmsListenerContainerFactory<?> myFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
// You could still override some of Boot's default if necessary.
return factory;
}
#Bean
public MarshallingMessageConverter createMarshallingMessageConverter(final Jaxb2Marshaller jaxb2Marshaller) {
System.out.println("executing createMarshallingMessageConverter");
return new MarshallingMessageConverter(jaxb2Marshaller);
}
#Bean
public Jaxb2Marshaller createJaxb2Marshaller(#Value("${context.path}") final String contextPath, #Value("${schema.location}") final String schemaLocaation) {
System.out.println("executing Jaxb2Marshaller");
Resource schemaResource = new ClassPathResource(schemaLocaation);
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setContextPath(contextPath);
jaxb2Marshaller.setSchema(schemaResource);
Map<String, Object> properties = new HashMap<>();
properties.put(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxb2Marshaller.setMarshallerProperties(properties);
return jaxb2Marshaller;
}
}
Sender and receiver code
#Component
public class Receiver {
#Autowired
JmsTemplate jmsTemplate;
#JmsListener(destination = "mailbox", containerFactory="myFactory")
public void receiveMessage(CacheMsgType submitEventType) {
System.out.println("Received <" + submitEventType + ">");
}
public void send(CacheMsgType submitEventType) {
jmsTemplate.convertAndSend("mailbox", submitEventType);
System.out.println("Successfully sent a message.");
}
}
JAXB Generated classes
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "SubmitEventType", namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", propOrder = {
"eventType",
"clientApplication",
"clientReferenceID",
"systemDate",
"transactionAcceptTime",
"bsb",
"accountNumber",
"productcode",
"accttypecode",
"trancode",
"meid",
"baiCode",
"baiDecs",
"tranamount",
"amountonhold",
"recordedlimit",
"currentbalance",
"availablebalance",
"description",
"reference",
"payer"
})
public class SubmitEventType {
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String eventType;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String clientApplication;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String clientReferenceID;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String systemDate;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String transactionAcceptTime;
#XmlElement(name = "BSB", namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String bsb;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String accountNumber;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String productcode;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String accttypecode;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String trancode;
#XmlElement(name = "MEID", namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String meid;
#XmlElement(name = "BAICode", namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String baiCode;
#XmlElement(name = "BAIDecs", namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String baiDecs;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String tranamount;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String amountonhold;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String recordedlimit;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String currentbalance;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String availablebalance;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String description;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String reference;
#XmlElement(namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected String payer;
/**
* Gets the value of the eventType property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getEventType() {
return eventType;
}
/**
* Sets the value of the eventType property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setEventType(String value) {
this.eventType = value;
}
/**
* Gets the value of the clientApplication property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getClientApplication() {
return clientApplication;
}
/**
* Sets the value of the clientApplication property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setClientApplication(String value) {
this.clientApplication = value;
}
/**
* Gets the value of the clientReferenceID property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getClientReferenceID() {
return clientReferenceID;
}
/**
* Sets the value of the clientReferenceID property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setClientReferenceID(String value) {
this.clientReferenceID = value;
}
/**
* Gets the value of the systemDate property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getSystemDate() {
return systemDate;
}
/**
* Sets the value of the systemDate property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setSystemDate(String value) {
this.systemDate = value;
}
/**
* Gets the value of the transactionAcceptTime property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getTransactionAcceptTime() {
return transactionAcceptTime;
}
/**
* Sets the value of the transactionAcceptTime property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setTransactionAcceptTime(String value) {
this.transactionAcceptTime = value;
}
/**
* Gets the value of the bsb property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getBSB() {
return bsb;
}
/**
* Sets the value of the bsb property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setBSB(String value) {
this.bsb = value;
}
/**
* Gets the value of the accountNumber property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getAccountNumber() {
return accountNumber;
}
/**
* Sets the value of the accountNumber property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setAccountNumber(String value) {
this.accountNumber = value;
}
/**
* Gets the value of the productcode property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getProductcode() {
return productcode;
}
/**
* Sets the value of the productcode property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setProductcode(String value) {
this.productcode = value;
}
/**
* Gets the value of the accttypecode property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getAccttypecode() {
return accttypecode;
}
/**
* Sets the value of the accttypecode property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setAccttypecode(String value) {
this.accttypecode = value;
}
/**
* Gets the value of the trancode property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getTrancode() {
return trancode;
}
/**
* Sets the value of the trancode property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setTrancode(String value) {
this.trancode = value;
}
/**
* Gets the value of the meid property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getMEID() {
return meid;
}
/**
* Sets the value of the meid property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setMEID(String value) {
this.meid = value;
}
/**
* Gets the value of the baiCode property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getBAICode() {
return baiCode;
}
/**
* Sets the value of the baiCode property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setBAICode(String value) {
this.baiCode = value;
}
/**
* Gets the value of the baiDecs property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getBAIDecs() {
return baiDecs;
}
/**
* Sets the value of the baiDecs property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setBAIDecs(String value) {
this.baiDecs = value;
}
/**
* Gets the value of the tranamount property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getTranamount() {
return tranamount;
}
/**
* Sets the value of the tranamount property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setTranamount(String value) {
this.tranamount = value;
}
/**
* Gets the value of the amountonhold property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getAmountonhold() {
return amountonhold;
}
/**
* Sets the value of the amountonhold property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setAmountonhold(String value) {
this.amountonhold = value;
}
/**
* Gets the value of the recordedlimit property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getRecordedlimit() {
return recordedlimit;
}
/**
* Sets the value of the recordedlimit property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setRecordedlimit(String value) {
this.recordedlimit = value;
}
/**
* Gets the value of the currentbalance property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getCurrentbalance() {
return currentbalance;
}
/**
* Sets the value of the currentbalance property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setCurrentbalance(String value) {
this.currentbalance = value;
}
/**
* Gets the value of the availablebalance property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getAvailablebalance() {
return availablebalance;
}
/**
* Sets the value of the availablebalance property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setAvailablebalance(String value) {
this.availablebalance = value;
}
/**
* Gets the value of the description property.
*
* #return
* possible object is
* {#link String }
*
*/
public String getDescription() {
return description;
}
/**
* Sets the value of the description property.
*
* #param value
* allowed object is
* {#link String }
*
*/
public void setDescription(String value) {
this.description = value;
}
public String getReference() {
return reference;
}
public void setReference(String value) {
this.reference = value;
}
public String getPayer() {
return payer;
}
public void setPayer(String value) {
this.payer = value;
}
}
CashMsgType.java
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlRootElement(name = "CacheMsg", namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "CacheMsgType", namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", propOrder = {
"submitEvent"
})
public class CacheMsgType {
#XmlElement(name = "SubmitEvent", namespace = "nabgroup.com/nab/schema/PaymentsExecution/SubmitPaymentEvent", required = true)
protected List<SubmitEventType> submitEvent;
public List<SubmitEventType> getSubmitEvent() {
if (submitEvent == null) {
submitEvent = new ArrayList<SubmitEventType>();
}
return this.submitEvent;
}
}
It seems sending on to mailbox queue is working but receiving gives error
Exception
2018-05-05 10:44:53.280 WARN 4120 --- [enerContainer-1] o.s.j.l.DefaultMessageListenerContainer : Execution of JMS message listener failed, and no ErrorHandler has been set.
org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method could not be invoked with incoming message
Endpoint handler details:
Method [public void com.nab.services.mq.Receiver.receiveMessage(com.nab.services.dto.CacheMsgType)]
Bean [com.nab.services.mq.Receiver#134bfc8]
; nested exception is org.springframework.messaging.converter.MessageConversionException: Cannot convert from [javax.xml.bind.JAXBElement] to [com.nab.services.dto.CacheMsgType] for org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage#3621aa, failedMessage=org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage#3621aa
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:118) ~[spring-jms-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:77) ~[spring-jms-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:736) ~[spring-jms-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:696) ~[spring-jms-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:674) ~[spring-jms-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:318) [spring-jms-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:257) [spring-jms-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1189) [spring-jms-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1179) [spring-jms-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1076) [spring-jms-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_162]
Caused by: org.springframework.messaging.converter.MessageConversionException: Cannot convert from [javax.xml.bind.JAXBElement] to [com.nab.services.dto.CacheMsgType] for org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage#3621aa
at org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver.resolveArgument(PayloadArgumentResolver.java:144) ~[spring-messaging-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:116) ~[spring-messaging-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:137) ~[spring-messaging-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:109) ~[spring-messaging-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:114) ~[spring-jms-5.0.5.RELEASE.jar:5.0.5.RELEASE]
... 10 common frames omitted
This might be implementation specific, but once the message is sent, you should be able to read the message id from the message itself.

Spring cache binded only to the current transaction

I'm trying to convince my company to work with spring 3.2 's cache (I know it's very old).
The application is build on top alfresco 5.x (which is build on top of spring 3.2).
Currently, we have some cache binded to the current transaction :
if (AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY) {
cache = (Map<String, Boolean>) AlfrescoTransactionSupport.getResource(CACHED_NAME);
if (cache == null) {
cache = new HashMap<String, Boolean>();
}
AlfrescoTransactionSupport.bindResource(CACHED_NAME, cache);
}
The cache live only for the current read transaction and then, destroyed.
I've tryied
#Cacheable("cache_name")
#Transactional(readOnly=true)
Annotation, but when a read-write transaction is open, the cache is not destroyed.
Any idea how to do that in spring way ?
#biiyamn was right,
I had to implement my own cache to do that.
First of all, i had ti implement the BeanFactory :
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;
public class KReadTransactionCacheFactoryBean implements FactoryBean<KReadTransactionCache>, BeanNameAware,
InitializingBean {
private String name = "";
private boolean allowNullValues = true;
private KReadTransactionCache cache;
/**
* Specify the name of the cache.
* <p>Default is "" (empty String).
*/
public void setName(String name) {
this.name = name;
}
/**
* Set whether to allow {#code null} values
* (adapting them to an internal null holder value).
* <p>Default is "true".
*/
public void setAllowNullValues(boolean allowNullValues) {
this.allowNullValues = allowNullValues;
}
public void setBeanName(String beanName) {
if (!StringUtils.hasLength(this.name)) {
setName(beanName);
}
}
public void afterPropertiesSet() {
this.cache = new KReadTransactionCache(this.name, this.allowNullValues);
}
public KReadTransactionCache getObject() {
return this.cache;
}
public Class<?> getObjectType() {
return KReadTransactionCache.class;
}
public boolean isSingleton() {
return false;
}
}
Then, implement de cache binded to current transaction
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
public class KReadTransactionCache implements Cache {
private static final Object NULL_HOLDER = new NullHolder();
private final String name;
private final boolean allowNullValues;
/**
* Create a new ConcurrentMapCache with the specified name.
* #param name the name of the cache
*/
public KReadTransactionCache(String name) {
this(name, true);
}
protected static Map<Object, Object> getBindedCache(String name) {
Map<Object, Object> cache = null;
if (AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY) {
cache = AlfrescoTransactionSupport.getResource(name);
if (cache == null) {
cache = new HashMap<>();
}
AlfrescoTransactionSupport.bindResource(name, cache);
}
return cache;
}
/**
* Create a new Map with the specified name and the
* given internal ConcurrentMap to use.
* #param name the name of the cache
* #param allowNullValues whether to allow {#code null} values
* (adapting them to an internal null holder value)
*/
public KReadTransactionCache(String name, boolean allowNullValues) {
this.name = name;
this.allowNullValues = allowNullValues;
}
public String getName() {
return this.name;
}
public Map getNativeCache() {
return getBindedCache(name);
}
public boolean isAllowNullValues() {
return this.allowNullValues;
}
public ValueWrapper get(Object key) {
final Map<Object, Object> bindedCache = getBindedCache(name);
if (bindedCache == null) {
return null;
}
Object value = bindedCache.get(key);
return (value != null ? new SimpleValueWrapper(fromStoreValue(value)) : null);
}
public void put(Object key, Object value) {
final Map<Object, Object> bindedCache = getBindedCache(name);
if (bindedCache == null) {
return;
}
bindedCache.put(key, toStoreValue(value));
}
public void evict(Object key) {
final Map<Object, Object> bindedCache = getBindedCache(name);
if (bindedCache == null) {
return;
}
bindedCache.remove(key);
}
public void clear() {
final Map<Object, Object> bindedCache = getBindedCache(name);
if (bindedCache == null) {
return;
}
bindedCache.clear();
}
/**
* Convert the given value from the internal store to a user value
* returned from the get method (adapting {#code null}).
* #param storeValue the store value
* #return the value to return to the user
*/
protected Object fromStoreValue(Object storeValue) {
if (this.allowNullValues && storeValue == NULL_HOLDER) {
return null;
}
return storeValue;
}
/**
* Convert the given user value, as passed into the put method,
* to a value in the internal store (adapting {#code null}).
* #param userValue the given user value
* #return the value to store
*/
protected Object toStoreValue(Object userValue) {
if (this.allowNullValues && userValue == null) {
return NULL_HOLDER;
}
return userValue;
}
#SuppressWarnings("serial")
private static class NullHolder implements Serializable {
}
}
And the xml configuration :
<!-- *******************************
***** CACHE CONFIGURATION *****
******************************* -->
<!-- simple cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default" />
<bean class="path.to.package.KReadTransactionCacheFactoryBean" p:name="cacheNameByAnnotation" />
<!-- TODO Add other cache instances in here -->
</set>
</property>
</bean>
SimpleCacheManager is useful for testing env as written is spring doc
SimpleCacheManager only supports static mode in which caches is predefined at config time and you are not allowed to add cache at runtime
EhCache and its associated bridge for spring EhCacheManager could be a good choice

java.lang.NoSuchMethodError: org.hibernate.SessionFactory.openSession()

For some reason I get the following exception when using Spring Batch in combination with Hibernate 4.
java.lang.NoSuchMethodError: org.hibernate.SessionFactory.openSession()Lorg/hibernate/classic/Session;
at org.springframework.batch.item.database.HibernateItemReaderHelper.createQuery(HibernateItemReaderHelper.java:152)
at org.springframework.batch.item.database.HibernateItemReaderHelper.getForwardOnlyCursor(HibernateItemReaderHelper.java:122)
at ....
I upgraded to the latest Spring batch 2.1.8.RELEASE and Spring 3.1.1.RELEASE which supposed to work with Hibernate 4. I looked into the source and it seems that the helper class is using the new version of the session factory that is used in Hibernate 4:
package org.springframework.batch.item.database;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.hibernate.Query;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.springframework.batch.item.database.orm.HibernateQueryProvider;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Internal shared state helper for hibernate readers managing sessions and
* queries.
*
* #author Dave Syer
*
*/
public class HibernateItemReaderHelper<T> implements InitializingBean {
private SessionFactory sessionFactory;
private String queryString = "";
private String queryName = "";
private HibernateQueryProvider queryProvider;
private boolean useStatelessSession = true;
private StatelessSession statelessSession;
private Session statefulSession;
/**
* #param queryName name of a hibernate named query
*/
public void setQueryName(String queryName) {
this.queryName = queryName;
}
/**
* #param queryString HQL query string
*/
public void setQueryString(String queryString) {
this.queryString = queryString;
}
/**
* #param queryProvider Hibernate query provider
*/
public void setQueryProvider(HibernateQueryProvider queryProvider) {
this.queryProvider = queryProvider;
}
/**
* Can be set only in uninitialized state.
*
* #param useStatelessSession <code>true</code> to use
* {#link StatelessSession} <code>false</code> to use standard hibernate
* {#link Session}
*/
public void setUseStatelessSession(boolean useStatelessSession) {
Assert.state(statefulSession == null && statelessSession == null,
"The useStatelessSession flag can only be set before a session is initialized.");
this.useStatelessSession = useStatelessSession;
}
/**
* #param sessionFactory hibernate session factory
*/
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void afterPropertiesSet() throws Exception {
Assert.state(sessionFactory != null, "A SessionFactory must be provided");
if (queryProvider == null) {
Assert.notNull(sessionFactory, "session factory must be set");
Assert.state(StringUtils.hasText(queryString) ^ StringUtils.hasText(queryName),
"queryString or queryName must be set");
}
// making sure that the appropriate (Hibernate) query provider is set
else {
Assert.state(queryProvider != null, "Hibernate query provider must be set");
}
}
/**
* Get a cursor over all of the results, with the forward-only flag set.
*
* #param fetchSize the fetch size to use retrieving the results
* #param parameterValues the parameter values to use (or null if none).
*
* #return a forward-only {#link ScrollableResults}
*/
public ScrollableResults getForwardOnlyCursor(int fetchSize, Map<String, Object> parameterValues) {
Query query = createQuery();
if (parameterValues != null) {
query.setProperties(parameterValues);
}
return query.setFetchSize(fetchSize).scroll(ScrollMode.FORWARD_ONLY);
}
/**
* Open appropriate type of hibernate session and create the query.
*/
public Query createQuery() {
if (useStatelessSession) {
if (statelessSession == null) {
statelessSession = sessionFactory.openStatelessSession();
}
if (queryProvider != null) {
queryProvider.setStatelessSession(statelessSession);
}
else {
if (StringUtils.hasText(queryName)) {
return statelessSession.getNamedQuery(queryName);
}
else {
return statelessSession.createQuery(queryString);
}
}
}
else {
if (statefulSession == null) {
statefulSession = sessionFactory.openSession();
}
if (queryProvider != null) {
queryProvider.setSession(statefulSession);
}
else {
if (StringUtils.hasText(queryName)) {
return statefulSession.getNamedQuery(queryName);
}
else {
return statefulSession.createQuery(queryString);
}
}
}
// If queryProvider is set use it to create a query
return queryProvider.createQuery();
}
/**
* Scroll through the results up to the item specified.
*
* #param cursor the results to scroll over
*/
public void jumpToItem(ScrollableResults cursor, int itemIndex, int flushInterval) {
for (int i = 0; i < itemIndex; i++) {
cursor.next();
if (i % flushInterval == 0 && !useStatelessSession) {
statefulSession.clear(); // Clears in-memory cache
}
}
}
/**
* Close the open session (stateful or otherwise).
*/
public void close() {
if (statelessSession != null) {
statelessSession.close();
statelessSession = null;
}
if (statefulSession != null) {
statefulSession.close();
statefulSession = null;
}
}
/**
* Read a page of data, clearing the existing session (if necessary) first,
* and creating a new session before executing the query.
*
* #param page the page to read (starting at 0)
* #param pageSize the size of the page or maximum number of items to read
* #param fetchSize the fetch size to use
* #param parameterValues the parameter values to use (if any, otherwise
* null)
* #return a collection of items
*/
public Collection<? extends T> readPage(int page, int pageSize, int fetchSize, Map<String, Object> parameterValues) {
clear();
Query query = createQuery();
if (parameterValues != null) {
query.setProperties(parameterValues);
}
#SuppressWarnings("unchecked")
List<T> result = query.setFetchSize(fetchSize).setFirstResult(page * pageSize).setMaxResults(pageSize).list();
return result;
}
/**
* Clear the session if stateful.
*/
public void clear() {
if (statefulSession != null) {
statefulSession.clear();
}
}
}
So the question is why is it still trying to use an older version even though the newest is used. Does anybody have an idea why this still could be happening?
We finally got it to work by compiling Spring batch against Hibernate 4. It seems Spring batch is not compatible with Hibernate 4.
This is what's in my WAR:
activation-1.1.jar
amqp-client-2.7.1.jar
antlr-2.7.7.jar
aopalliance-1.0.jar
aspectjrt-1.5.0.jar
aspectjweaver-1.5.2.jar
aspectjweaver-1.6.9.jar
avalon-framework-4.1.3.jar
billing-commons-1.9.0-SNAPSHOT.jar
billing-core-1.9.0-SNAPSHOT.jar
cglib-nodep-2.2.2.jar
classpath.txt
commons-batch-2.0.0-SNAPSHOT.jar
commons-beanutils-1.7.0.jar
commons-cli-1.1.jar
commons-codec-1.2.jar
commons-customer-experience-1.3.0.jar
commons-domain-1.0.0.jar
commons-email-1.1.jar
commons-email-1.9.1-SNAPSHOT.jar
commons-hibernate-3.0.0-SNAPSHOT.jar
commons-http-1.2.1.jar
commons-httpclient-3.1.jar
commons-httpclient-contrib-3.1.jar
commons-io-1.3.1.jar
commons-lang-2.1.jar
commons-logging-1.1.jar
commons-logging-1.2.0.jar
commons-monitoring-1.0.0.jar
commons-property-3.3.0.jar
commons-rabbitmq-1.1.1.jar
commons-spring-agent-2.5.0-SNAPSHOT.jar
commons-util-1.6.1.jar
customer-inventory-commons-1.4.0.jar
customer-inventory-core-1.4.0.jar
cxf-api-2.3.2.jar
cxf-common-schemas-2.3.2.jar
cxf-common-utilities-2.3.2.jar
cxf-rt-bindings-xml-2.3.2.jar
cxf-rt-core-2.3.2.jar
cxf-rt-frontend-jaxrs-2.3.2.jar
cxf-rt-transports-common-2.3.2.jar
cxf-rt-transports-http-2.3.2.jar
dom4j-1.6.1.jar
generic-monitoring-console-api-1.1.0.jar
geronimo-javamail_1.4_spec-1.7.1.jar
hibernate-commons-annotations-4.0.1.Final.jar
hibernate-core-4.1.4.Final.jar
hibernate-entitymanager-4.1.4.Final.jar
hibernate-jpa-2.0-api-1.0.1.Final.jar
hibernate-validator-4.3.0.Final.jar
javassist-3.15.0-GA.jar
jaxb-impl-2.1.13.jar
jboss-logging-3.1.0.GA.jar
jboss-transaction-api_1.1_spec-1.0.0.Final.jar
jettison-1.1.jar
jms-1.1.jar
jsr250-api-1.0.jar
jsr311-api-1.1.1.jar
log4j-1.2.15.jar
logkit-1.0.1.jar
mail-1.4.jar
neethi-2.0.4.jar
orchestration-api-1.7.0-20120820.120350-6.jar
quartz-1.5.2.jar
simplestuff-0.9.jar
singleview-api-commons-1.1.0.jar
singleview-api-core-1.1.0.jar
slf4j-api-1.5.6.jar
slf4j-simple-1.5.6.jar
spring-aop-3.1.1.RELEASE.jar
spring-asm-3.1.1.RELEASE.jar
spring-aspects-3.1.1.RELEASE.jar
spring-batch-core-2.1.8.RELEASE.jar
spring-batch-infrastructure-2.1.8.RELEASE.jar
spring-beans-3.1.1.RELEASE.jar
spring-context-3.1.1.RELEASE.jar
spring-context-support-3.1.1.RELEASE.jar
spring-core-3.1.1.RELEASE.jar
spring-expression-3.1.1.RELEASE.jar
spring-jdbc-3.1.1.RELEASE.jar
spring-orm-3.1.1.RELEASE.jar
spring-tx-3.1.1.RELEASE.jar
spring-web-3.1.1.RELEASE.jar
spring-webmvc-3.1.1.RELEASE.jar
stax2-api-3.0.2.jar
validation-api-1.0.0.GA.jar
woodstox-core-asl-4.0.8.jar
wsdl4j-1.6.2.jar
XmlSchema-1.4.7.jar
xpp3_min-1.1.4c.jar
xstream-1.3.1.jar

Resources