I am using Spring-mvc with ContentNegotiatingViewResolver for handle my response objects and parse into a specific format. When i return the object from controller, in response it add my response object but also add the object which i used to map the request parameter. My controller is act as rest controller. Because i want to create rest-services using spring with ContentNegotiatingViewResolver. After debugging, i found InvocableHandlerMethdod class in which invokeForRequest method contain following argument:
public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
...............................................
In ModelAndViewContainer argument contain following detail:
ModelAndViewContainer: View is [null]; default model {oauthClientDetail=com.netsol.entities.OauthClientDetail#9ac35e, org.springframework.validation.BindingResult.oauthClientDetail=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
I am not able to figure, why OauthClientDetail object set in ModelAndViewContainer and how i prevent this.
Follwing is my controller code:
#RequestMapping(value = "/changePassword", method = RequestMethod.POST)
public ApiResponse<UserDetail> changePassword(#Valid OauthClientDetail clientDetail,
BindingResult bindingResult,
#RequestParam(value = "password", required = true) String password) {
logger.info("In changePassword Service...... ");
if(bindingResult.hasErrors()){
throw new InvalidRequestException("Error", bindingResult);
}
UserDetail detail = userService.changeUserPassword(password, clientDetail, encoder);
ApiResponse<UserDetail> response = new ApiResponse<UserDetail>();
if(detail != null){
response.setErrorCode(CommonUtility.API_RESPONSE_STATUS.SUCCESS.getValue());
response.setErrorMessage(CommonUtility.API_RESPONSE_STATUS.SUCCESS.getMessage());
response.setResponse(detail);
}else{
response.setErrorCode(CommonUtility.API_RESPONSE_STATUS.FAIL.getValue());
response.setErrorMessage(CommonUtility.API_RESPONSE_STATUS.FAIL.getMessage());
response.setResponse(null);
}
return response;
}
rest-servlet.xml configuration:
bean id="contentNegotiationManager"
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false" />
<property name="favorParameter" value="true" />
<property name="parameterName" value="mediaType" />
<property name="ignoreAcceptHeader" value="true" />
<property name="useJaf" value="false" />
<property name="defaultContentType" value="application/json" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
</bean>
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="contentNegotiationManager" ref="contentNegotiationManager" />
<property name="order" value="1" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
<entry key="html" value="text/html" />
</map>
</property>
<property name="defaultViews">
<list>
<bean
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<bean class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="autodetectAnnotations" value="true" />
<property name="aliases">
<map>
<entry key="list" value="java.util.List" />
<entry key="string" value="java.lang.String" />
<entry key="hsahmap" value="java.util.HashMap" />
<entry key="object" value="java.lang.Object" />
<entry key="hashSet" value="java.util.HashSet" />
</map>
</property>
<property name="supportedClasses">
<list>
<value>java.util.List</value>
<value>java.lang.String</value>
<value>java.util.Map</value>
<value>java.lang.Object</value>
<value>java.util.Set</value>
<value>java.lang.Long</value>
<value>java.util.Date</value>
</list>
</property>
</bean>
</constructor-arg>
</bean>
</list>
</property>
<property name="defaultContentType" ref="htmlMediaType" />
<property name="ignoreAcceptHeader" value="true" />
</bean>
<bean id="htmlMediaType" class="org.springframework.http.MediaType">
<constructor-arg value="text" />
<constructor-arg value="html" />
</bean>
My request from chrome postman client:
POST /empirecl-api/api/changePassword HTTP/1.1
Host: localhost:8080
Authorization: Bearer b3f46274-b019-4d7b-a3bd-5c19e9660c2f
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
clientId=my-client-with-secret&clientSecret=12345678&clientSecretConfirm=12345678&password=12345678
My Response is:
{
"oauthClientDetail": {
"userId": null,
"clientId": "my-client-with-secret",
"additionalInformation": null,
"authorities": null,
"authorizedGrantTypes": null,
"autoapprove": null,
"clientSecret": "12345678",
"clientSecretConfirm": "12345678",
"resourceIds": null,
"scope": null,
"webServerRedirectUri": null,
"createdOn": null,
"updatedOn": null,
"active": null,
"lastLogin": null
},
"apiResponse": {
"errorCode": 0,
"errorMessage": "Success",
"response": {
"userName": "my-client-with-secret",
"dateCreated": null,
"dateUpdated": null,
"active": "true"
}
}
}
In response the oauthClientDetail object contain values that, i send with request and map into the object. From this, i assume that, my map object is set in the response. How i prevent this object to set in the response.
I found the Solution from this MappingJacksonJsonView return top-level json object link. Need to add #ResponseBody annotation in controller and set produces={"application/json"}. Follwoing is my new code:
#RequestMapping(value = "/changePassword", method = RequestMethod.POST, produces={"application/json"})
public #ResponseBody ApiResponse<UserDetail> changePassword(#Valid OauthClientDetail clientDetail,
BindingResult bindingResult,
#RequestParam(value = "password", required = true) String password) {
logger.info("In changePassword Service...... ");
if(bindingResult.hasErrors()){
throw new InvalidRequestException("Error", bindingResult);
}
UserDetail detail = userService.changeUserPassword(password, clientDetail, encoder);
ApiResponse<UserDetail> response = new ApiResponse<UserDetail>();
if(detail != null){
response.setSuccess(CommonUtility.API_RESPONSE_STATUS.SUCCESS.getValue());
response.setMessage(CommonUtility.API_RESPONSE_STATUS.SUCCESS.getMessage());
response.setResponse(detail);
}else{
response.setSuccess(CommonUtility.API_RESPONSE_STATUS.FAIL.getValue());
response.setMessage(CommonUtility.API_RESPONSE_STATUS.FAIL.getMessage());
response.setResponse(null);
}
return response;
}
Related
I'm using Quartz 2.2.1. My code is like follows.
<bean name="myJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.my.MyJob" />
<property name="durability" value="true" />
<property name="jobDataAsMap">
<map>
<entry key="param" value="myParam" />
</map>
</property>
</bean>
<bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="myJob" />
<property name="cronExpression" value="0 0/1 * * * ?" />
</bean>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobDetails">
<list>
<ref bean="myJob" />
</list>
</property>
<property name="triggers">
<list>
<ref bean="myTrigger" />
</list>
</property>
<property name="quartzProperties" ref="quartzProperties"/>
</bean>
java class
#PersistJobDataAfterExecution
#DisallowConcurrentExecution
public class MyJob extends QuartzJobBean {
private String param;
private final org.slf4j.Logger log = LoggerFactory.getLogger(getClass());
#Override
protected void executeInternal(JobExecutionContext jobContext) throws JobExecutionException {
JobDataMap jobDataMap = jobContext.getJobDetail().getJobDataMap();
log.debug("size of jobdatamap {}", jobDataMap.keySet().size());
log.debug("quartz param {}", param);
// some code
}
public void setParam(String param) {
this.param = param;
}
public String getParam() {
return param;
}
}
but I always get param null and jobdatamap 0. I checked several examples in web and those are same like mine.
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.makeSchedulerThreadDaemon = true
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 1
org.quartz.threadPool.makeThreadsDaemons = true
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.MSSQLDelegate
org.quartz.jobStore.dataSource = myDB
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = false
org.quartz.dataSource.myDB.jndiURL = /jdbc/myDB
what is I am missing?
I have use the SchedulerFactoryBean to pass JobDataMap and it is working for me.
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobDetails">
<list>
<ref bean="myJob" />
</list>
</property>
<property name="triggers">
<list>
<ref bean="myTrigger" />
</list>
</property>
<property name="quartzProperties" ref="quartzProperties"/>
<property name="schedulerContextAsMap">
<map>
<entry key="param" value="myParam" />
</map>
</property>
</bean>
I have bean class as below -
public class Account {
private String strAccNumber = "";
private List<Account> accountList = new ArrayList<Account>();
// getter setter
....
...
#Override
public String toString() {
// code for PassThroughLineAggregator
String data="";
for (int j=0; j<accountList.size(); j++) {
Account bean = accountList.get(j);
data = data + bean.getStrAccNumber();
if (j<(accountList.size()-1)) {
data = data + "\n";
}
}
return data;
}
}
to write data I want to set accountList in my config file.
I'm using below code to set bean property.
<bean id="flatFileReader" class="org.springframework.batch.item.file.FlatFileItemReader"
scope="step">
<property name="resource" value="classpath:springBatch.csv" />
<property name="strict" value="false"></property>
<property name="linesToSkip" value="1" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="names" value="ACC#" />
</bean>
</property>
<property name="fieldSetMapper">
<bean
class="com.abc.reader.AccountDetailsFieldSetMapper" />
</property>
</bean>
</property>
</bean>
<bean id="flatFileProcessor"
class="com.abc.processor.AccountItemProcessor"
scope="step"></bean>
<bean id="flatFileWriter"
class="com.abc.FlatFileItemWriterListener"
scope="step">
<property name="resource" value="classpath:springBatch1.csv" />
<property name="lineAggregator">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<property name="fieldExtractor">
<bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
<property name="names" value="strAccNumber" />
</bean>
</property>
</bean>
</property>
</bean>
AccountItemProcessor -
public class AccountItemProcessor implements ItemProcessor<Account, List<Account>>{
#Override
public List<Account> process(Account accObj) throws Exception {
// logic..
}
After processing single record in the process step I want to write multiple items(list of item) , how can I write multiple item at a time using arraylist. In my case I want to write data from accountlist.
You'll want to implement your own LineAggregator. That is what provides the String to be written to the file. In your case, you'll write one that returns a String that is really multiple lines.
You can read more about the LineAggregator interface in the documentation here: http://docs.spring.io/spring-batch/apidocs/org/springframework/batch/item/file/transform/LineAggregator.html
I am importing a spring xml configuration file from another project using import resource. The resource is apparently found since there is no error message saying it isn't, but none of the beans get loaded. Is there something I'm doing wrong here? I should note that when I run my integration tests from within eclipse it all works fine, but it blows up when running the same integration test from within a maven build.
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="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
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<import resource="classpath*:httpclient-4x.xml" />
<context:component-scan base-package="com.mycompany.data" />
<bean id="applicationProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath*:properties/client.${deployment.env}.properties</value>
</list>
</property>
<property name="ignoreResourceNotFound" value="false" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="searchSystemEnvironment" value="true" />
</bean>
<bean id="jacksonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
</beans>
httpclient-4x.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="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
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="secureRestTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<ref bean="secureClientHttpRequestFactory" />
</constructor-arg>
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<util:constant static-field="org.springframework.http.MediaType.APPLICATION_XML" />
<util:constant static-field="org.springframework.http.MediaType.APPLICATION_JSON" />
<util:constant static-field="org.springframework.http.MediaType.TEXT_PLAIN" />
</list>
</property>
</bean>
<bean class="org.springframework.http.converter.FormHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<util:constant static-field="org.springframework.http.MediaType.MULTIPART_FORM_DATA" />
</list>
</property>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</list>
</property>
</bean>
<bean id="secureClientHttpRequestFactory" class="com.cobalt.inventory.security.PreemptiveBasicAuthClientHttpRequestFactory">
<constructor-arg>
<ref bean="secureCloseableHttpClient" />
</constructor-arg>
</bean>
<bean id="secureCloseableHttpClient" factory-bean="secureHttpClientBuilder" factory-method="build" />
<bean id="secureHttpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder" factory-method="create">
<property name="defaultCredentialsProvider" ref="credentialsProvider" />
<property name="defaultSocketConfig" ref="socketConfig" />
<property name="defaultRequestConfig" ref="requestConfig" />
<property name="userAgent" value="${http.user.agent: CDK Cobalt invJava}" />
<property name="maxConnPerRoute" value="${http.max.connections.per.route:100}" />
<property name="maxConnTotal" value="${http.max.connections:100}" />
</bean>
<bean id="credentialsProvider" class="org.apache.http.impl.client.BasicCredentialsProvider" />
<bean id="credentials" class="org.apache.http.auth.UsernamePasswordCredentials">
<constructor-arg index="0" name="userName" value="validUsername" />
<constructor-arg index="1" name="password" value="validPassword" />
</bean>
<bean id="socketConfigBuilder" class="org.apache.http.config.SocketConfig.Builder">
<property name="soKeepAlive" value="true" />
<property name="soTimeout" value="${http.socket.timeout:10000}" />
</bean>
<bean id="socketConfig" factory-bean="socketConfigBuilder" factory-method="build" />
<bean id="requestConfigBuilder" class="org.apache.http.client.config.RequestConfig.Builder">
<property name="connectTimeout" value="${http.connection.timeout:10000}" />
<property name="socketTimeout" value="${http.socket.timeout:10000}" />
<property name="cookieSpec">
<util:constant static-field="org.apache.http.client.config.CookieSpecs.IGNORE_COOKIES" />
</property>
</bean>
<bean id="requestConfig" factory-bean="requestConfigBuilder" factory-method="build" />
<!-- For accessing common authorization service -->
<bean id="commonAuthzSecureRestTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg>
<ref bean="commonAuthzSecureClientHttpRequestFactory" />
</constructor-arg>
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<util:constant static-field="org.springframework.http.MediaType.APPLICATION_XML" />
<util:constant static-field="org.springframework.http.MediaType.APPLICATION_JSON" />
<util:constant static-field="org.springframework.http.MediaType.TEXT_PLAIN" />
</list>
</property>
</bean>
<bean class="org.springframework.http.converter.FormHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<util:constant static-field="org.springframework.http.MediaType.MULTIPART_FORM_DATA" />
</list>
</property>
</bean>
<bean id="jsonMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter ">
<property name="supportedMediaTypes">
<list>
<bean id="jsonMediaTypeApplicationJson" class="org.springframework.http.MediaType">
<constructor-arg value="application" />
<constructor-arg value="json" />
</bean>
</list>
</property>
</bean>
</list>
</property>
</bean>
<bean id="commonAuthzSecureClientHttpRequestFactory" class="com.mycompany.security.PreemptiveBasicAuthClientHttpRequestFactory">
<constructor-arg>
<ref bean="commonAuthzSecureCloseableHttpClient" />
</constructor-arg>
</bean>
<bean id="commonAuthzSecureCloseableHttpClient" factory-bean="commonAuthzSecureHttpClientBuilder" factory-method="build" />
<bean id="commonAuthzSecureHttpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder" factory-method="create">
<property name="defaultCredentialsProvider" ref="commonAuthzCredentialsProvider" />
<property name="defaultSocketConfig" ref="commonAuthzSocketConfig" />
<property name="defaultRequestConfig" ref="commonAuthzRequestConfig" />
<property name="userAgent" value="${http.user.agent: userAgent}" />
<property name="maxConnPerRoute" value="${http.max.connections.per.route:100}" />
<property name="maxConnTotal" value="${http.max.connections:100}" />
</bean>
<bean id="commonAuthzCredentialsProvider" class="org.apache.http.impl.client.BasicCredentialsProvider" />
<bean id="commonAuthzCredentials" class="org.apache.http.auth.UsernamePasswordCredentials">
<constructor-arg index="0" name="userName" value="${common_services.iam.remoting.user:user" />
<constructor-arg index="1" name="password" value="${common_services.iam.remoting.password:password}" />
</bean>
<bean id="commonAuthzSocketConfigBuilder" class="org.apache.http.config.SocketConfig.Builder">
<property name="soKeepAlive" value="true" />
<property name="soTimeout" value="${http.socket.timeout:10000}" />
</bean>
<bean id="commonAuthzSocketConfig" factory-bean="commonAuthzSocketConfigBuilder" factory-method="build" />
<bean id="commonAuthzRequestConfigBuilder" class="org.apache.http.client.config.RequestConfig.Builder">
<property name="connectTimeout" value="${http.connection.timeout:10000}" />
<property name="socketTimeout" value="${http.socket.timeout:10000}" />
<property name="cookieSpec">
<util:constant static-field="org.apache.http.client.config.CookieSpecs.IGNORE_COOKIES" />
</property>
</bean>
<bean id="commonAuthzRequestConfig" factory-bean="commonAuthzRequestConfigBuilder" factory-method="build" />
<util:list id="spel-configuration">
<value>#{credentialsProvider.setCredentials(T(org.apache.http.auth.AuthScope).ANY, credentials)}</value>
<value>#{commonAuthzCredentialsProvider.setCredentials(T(org.apache.http.auth.AuthScope).ANY, commonAuthzCredentials)} </value>
</util:list>
</beans>
I turned debug logging on for Spring and found the following log entries:
[2015-04-13 10:46:56,818][DEBUG][org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loaded 0 bean definitions from location pattern [classpath*:httpclient-4x.xml]
[2015-04-13 10:46:56,818][DEBUG][org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader] - Imported 0 bean definitions from URL location [classpath*:httpclient-4x.xml]
I do not have control of the content of httpclient-4x.xml I just reference it.
By request, here is CvsDataClientImpl:
package com.mycompany.data;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import com.mycompany.utility.ConditionalUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
#Component
public class CvsDataClientImpl implements CvsDataClient<JsonNode> {
private static final int MAX_PAGE_SIZE = 100;
private static final String V1 = "/rest/v1.0/vehicles";
#Resource(name = "secureRestTemplate")
private RestTemplate restTemplate;
#Resource(name = "jsonVehiclesNodeMerger")
private JsonVehiclesNodeMerger nodeMerger;
#Value("${inv.vehicle-app.context:inventoryWebApp}")
private String vehicleAppContextPath;
#Value("${services.remoting.url:http://localhost:10080}")
private String remotingUrl;
#Override
public JsonNode read(String urlParameters) {
validateUrlParameters(urlParameters);
ResponseEntity<JsonNode> response = restTemplate.getForEntity(buildBaseRestUrl() + "?" + urlParameters, JsonNode.class);
return response.getBody();
}
#Override
public JsonNode readAll(String urlParameters) {
return readAll(urlParameters, null);
}
public JsonNode readAll(String urlParameters, Integer pageSize) {
int offset = 0;
int actualPageSize = pageSize != null ? pageSize : MAX_PAGE_SIZE;
JsonNode latestResult = null;
JsonNode baseResult = null;
do {
String extendedUrlParameters = buildUrlParameters(urlParameters, offset, actualPageSize);
latestResult = read(extendedUrlParameters);
baseResult = merge(latestResult, baseResult);
offset += actualPageSize;
} while (latestResult.get("searchResult").get("vehicles") != null);
return baseResult;
}
private JsonNode merge(JsonNode latestResult, JsonNode baseResult) {
JsonNode merged = null;
if (baseResult == null) {
merged = latestResult;
} else {
merged = nodeMerger.merge(latestResult, baseResult);
}
JsonNode searchResult = merged.get("searchResult");
((ObjectNode) searchResult).remove("summary");
return merged;
}
private String buildUrlParameters(String urlParameters, int offset, int pageSize) {
String parameters = urlParameters;
parameters += "&limit=" + pageSize + "&offset=" + offset;
return parameters;
}
private String buildBaseRestUrl() {
return remotingUrl + vehicleAppContextPath + V1;
}
private void validateUrlParameters(String urlParameters) {
if (ConditionalUtils.isNullOrEmpty(urlParameters) || urlParameters.indexOf("inventoryOwner") < 0 && urlParameters.indexOf("storeId") < 0) {
throw new IllegalArgumentException("Must provide at least an inventory owner or store ID");
}
}
void setVehicleAppContextPath(String contextPath) {
vehicleAppContextPath = contextPath;
}
void setRemotingUrl(String remotingUrl) {
this.remotingUrl = remotingUrl;
}
}
After some discussion on chat, minion and I found that httpclient-4x.xml is not included in the external project's build jar for some reason. Now I need to investigate that, but it's another story. :-)
What would be the best way to set custom parameters to JdbcPagingItemReader query?
My custom JdbcPagingItemReader implementation:
public class CustomItemReader extends JdbcPagingItemReader<Long> {
public CustomItemReader(DataSource dataSource) throws Exception {
SqlPagingQueryProviderFactoryBean queryProvider = new SqlPagingQueryProviderFactoryBean();
queryProvider.setDataSource(dataSource);
queryProvider.setSelectClause("SELECT t1.id");
queryProvider.setFromClause("FROM table1 t1 LEFT JOIN table2 t2 ON t2.fk_table1_id = t1.id");
queryProvider.setWhereClause("WHERE (t1.col1 = :param1) AND ((t2.id IS NULL) OR (t2.col3 = :param2))");
queryProvider.setSortKey("t1.id");
setDataSource(dataSource);
setFetchSize(10);
setRowMapper(new RowMapper<Long>() {
#Override
public Long mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getLong(1);
}
});
setQueryProvider(queryProvider.getObject());
}
}
To pass parameters from the job's ExecutionContext to the JdbcPagingItemReader, you'd configure the writer as follows:
<bean id="itemReader" class="org.springframework.batch.item.database.JdbcPagingItemReader" scope="step">
<property name="dataSource" ref="dataSource" />
<property name="rowMapper">
<bean class="MyRowMapper" />
</property>
<property name="queryProvider">
<bean class="org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="sortKeys">
<map>
<entry key="t1.id" value="ASCENDING"/>
</map>
</property>
<property name="selectClause" value="SELECT t1.id" />
<property name="fromClause" value="FROM table1 t1 LEFT JOIN table2 t2 ON t2.fk_table1_id = t1.id" />
<property name="whereClause" value=""WHERE (t1.col1 = :param1) AND ((t2.id IS NULL) OR (t2.col3 = :param2))" />
</bean>
</property>
<property name="pageSize" value="10" />
<property name="parameterValues">
<map>
<entry key="param1" value="#{jobExecutionContext[param1]}" />
<entry key="param2" value="#{jobExecutionContext[param2]}" />
</map>
</property>
</bean>
You can view a configuration like this in action in the Spring Batch examples here: https://github.com/spring-projects/spring-batch/tree/master/spring-batch-samples
I created a file upload service using Spring MVC with apache commons multipart resolver support which specifies that a file should be attached as part of a multipart HTTP Post request. The request also contains a parameter containing an XML string with meta-data about the object. The XML can be marshalled using JAXB.
Other services that are not multipart handle the marshalling transparently, e.g.:
#RequestMapping(value = "/register", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public ModelAndView createUser(#RequestBody CreateUserDTO createUserDTO) throws Exception {
UserDTO user = userService.createUser(createUserDTO);
return createModelAndView(user);
}
Here CreateUserDTO is a JAXB annotated object which is automatically marshalled.
In the multipart case I'd like to have the same transparency. Ideally I would like to do the following:
RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public ModelAndView createAttachment(#RequestParam AttachmentDTO attachment,
HttpServletRequest request) throws Exception {
final MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
AttachmentDTO attachment = null;
final MultipartFile dataFile = multipartRequest.getFile("data");
AttachmentDTO createdAttachment = attachmentService.createAttachment(attachment,
dataFile);
return createModelAndView(createdAttachment);
}
Unfortunately this does not work. I am able to bind the attachment parameter as String, but the automatic marshalling does not work. My work around is to manually do the marshalling like the following, but I don't like this approach (especially since the parameter may be specified both in JSON and XML form):
#Autowired
private Jaxb2Marshaller jaxb2Marshaller;
#Autowired
private ObjectMapper jacksonMapper;
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public ModelAndView createAttachment(#RequestParam(ATTACHMENT_PARAMETER) String attachmentString,
final HttpServletRequest request) throws Exception {
final MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
AttachmentDTO attachment = null;
try {
attachment = (AttachmentDTO)jaxb2Marshaller.unmarshal(new StreamSource(new StringReader(attachmentString)));
} catch (XmlMappingException e) {
//Try JSON
try {
attachment = jacksonMapper.readValue(attachmentString, AttachmentDTO.class);
} catch (IOException e1) {
throw new BadRequestException("Could not interpret attachment parameter, both JSON and XML parsing failed");
}
}
Does anyone have a better suggestion for resolving this issue?
For completeness I also specify the relevant Spring config here:
<bean id="viewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order" value="1"/>
<property name="favorPathExtension" value="true"/>
<property name="ignoreAcceptHeader" value="false"/>
<property name="mediaTypes">
<map>
<entry key="xml" value="application/xml"/>
<entry key="json" value="application/json"/>
</map>
</property>
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</list>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<property name="modelKey" value="object"/>
<property name="marshaller" ref="jaxbMarshaller"/>
</bean>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<property name="objectMapper" ref="jaxbJacksonObjectMapper"/>
</bean>
</list>
</property>
</bean>
<!--Use JAXB OXM marshaller to marshall/unmarshall following class-->
<bean id="jaxbMarshaller"
class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPath" value="com.behindmedia.btfd.model.dto"/>
</bean>
<bean id="jaxbJacksonObjectMapper" class="org.codehaus.jackson.map.ObjectMapper"/>
<!-- allows for integration of file upload functionality -->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
<property name="maxUploadSize" value="100000000"/>
</bean>