Spring MVC multipart response - spring

I'm trying to write full multipart flow, from the client side sending multipart request using Spring restTemplate and from the server side auto resolve the different parts to objects (I'm using JAXB for objects marshaling) and send a response back in multipart.
I'm was able to implement almost all the flow but I'm unable to send a multipart response with jaxb objects from spring controller.
Here is the contorller code:
#RequestMapping(value="/putuser",method=RequestMethod.POST)
#ResponseBody
public MultiValueMap<String, Object> getUser(#RequestBody User user) throws IOException, JAXBException {
}
user.setName("new");
MultiValueMap<String, Object> form = new LinkedMultiValueMap<String, Object>();
form.add("user", user);
form.add("file", new FileSystemResource("/tmp/1.1"));
return form;
}
This is the exception that I'm getting in the server side:
java.lang.ClassCastException: org.springframework.core.io.FileSystemResource cannot be cast to java.lang.String
at org.springframework.http.converter.FormHttpMessageConverter.writeForm(FormHttpMessageConverter.java:233)
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:197)
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:73)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:148)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:90)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:189)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:69)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:122)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:746)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:687)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:915)
It looks like Spring is trying to convert each part in the response to String instead of the correct content type (file/xml etc/)
I tried to update my spring.xml file like this:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.FormHttpMessageConverter">
<property name="partConverters">
<list>
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter" />
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean class="org.springframework.http.converter.ResourceHttpMessageConverter" />
<bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter" />
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
To try making he FormHttpMessageConverter to write the parts correctly but this did not helped
Is there any way making spring to send multipart responses correctly with different types of parts?

You will need to set Content-Type header of your response to appropriate value. The FormHttpMessageConverter relies on Content-Type to identify if it needs to cast the response to String or some other type.
#RequestMapping(value="/putuser",method=RequestMethod.POST)
#ResponseBody
public MultiValueMap<String, Object> getUser(#RequestBody User user, HttpServletResponse httpResponse) throws IOException, JAXBException {
}
user.setName("new");
MultiValueMap<String, Object> form = new LinkedMultiValueMap<String, Object>();
form.add("user", user);
form.add("file", new FileSystemResource("/tmp/1.1"));
httpResponse.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE); // <-- IMPORTANT
return form;
}

Related

Spring Server Error on unknown Accept header

I have a Spring REST project that uses spring boot. I have declared jackson-dataformat-xml as a dependency in maven to support xml when the accept header is application/xml (and it natively accepts application/json).
The problem I have is when i set the accept header to anything other than those two (ex application/dsfas or text/html, I get a server error with the following exception:
23:36:04.368 [http-nio-8082-exec-5] WARN o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Failure in #ExceptionHandler protected org.springframework.http.ResponseEntity<java.lang.Object> com.mergg.common.web.RestResponseEntityExceptionHandler.handleNotFound(java.lang.RuntimeException,org.springframework.web.context.request.WebRequest)
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
Any idea on how to fix so that I can either ignore the accept header and return json or tell the client it was a bad request?
public class JsonContentNegotiation implements ContentNegotiationStrategy {
#Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
System.out.println("This is your negotation Strategy");
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
String headers = request.getHeader(HttpHeaders.ACCEPT);
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headers);
if(headers.indexOf(MediaType.APPLICATION_JSON_VALUE)==-1){
mediaTypes.add(new MediaType("application","json"));
}
MediaType.sortBySpecificityAndQuality(mediaTypes);
return mediaTypes;
}
}
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="true"/>
<property name="ignoreAcceptHeader" value="true"/>
<property name="defaultContentTypeStrategy">
<bean class="demo2.ContentNegotation.JsonContentNegotiation"/>
</property>
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>
otherwise use this in the controller to tell the client this is wrong header
#ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
#ResponseBody
public String fix(Exception e){
System.out.println("do This");
return "Accept Header may be wrong";
}

Set custom response in Spring interceptor

Here is my interceptor method where i want to set custom response to tell the UI what happened
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HttpSession session = request.getSession(false);
if (session != null)
return true;
else{
response.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT)
return false;
}
}
And in web.xml
<error-page>
<error-code>408</error-code>
<location>/error.html</location>
</error-page>
spring-servlet.xml
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/login" />
<bean class="com.example.CustomInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
When the session is timed out its not sending any response after return false.
Even the below is not working
response.sendRedirect("http://localhost:8080/home");
You can try very simple thing. Change your mvc:interceptros structure to
<mvc:interceptors>
<bean class="com.example.CustomInterceptor" />
</mvc:interceptors>
This essentially mean apply the interceptor to all applicable requests. I will come in a moment to why I say applicable in a moment. If above works then the issue is with your mapping.
Now as you know interceptors are configured at the level of HandlerMapping and it will be RequestMappingHandlerMapping (Spring 3.1+ with mvc:annotation-driven) or DefaultAnnotationHandlerMapping in your case.
Now as you have use <mvc:mapping path="/**" /> will map to all requests (including subpaths) as long as they are valid mappings. So lets say you have controller
#RequestMapping(value="/home", method = RequestMethod.GET)
public String welcome() {
return "welcome";
}
you cannot hit http://localhost:8080/yourProjectName/home/test and expect it to hit the interceptor. So you have to hit http://localhost:8080/yourProjectName/home as that is a valid HandlerMapping.
As to to response first debug if your interceptor is getting hit for any requests. If it does work then
response.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT);
should redirect you to error.html as you have used
<error-page>
<error-code>408</error-code>
<location>/error.html</location>
</error-page>

How to generate an oauth token using spring without the password

I am trying to create an additional access token for a user within my spring boot application. I do not know their password but the user is authorized to the application. From what I can see I would need to call something like
OAuth2AccessToken accessToken = tokenServices.createAccessTokenForUser(authenticationRequest, user);
Where tokenServices is presumably an instance of DefaultTokenServices. The question is how do I get a reference to the configured token services? I see that this is wired into AuthorizationServerEndpointsConfigurer but I cannot autowire this in. I am using JWT for authentication so really looking for a way to generate a JWT token.
Was trying to implement the flow outlined in Spring OAuth2 - Manually creating an access token in the token store
In my spring-security-config.xml i have this:
<bean id="tokenServices" class="de.hybris.platform.ycommercewebservices.oauth2.token.provider.HybrisOAuthTokenServices">
<property name="tokenStore" ref="tokenStore" />
<property name="supportRefreshToken" value="true" />
<property name="refreshTokenValiditySeconds" value="2592000" />
<!-- 60*60*24*30 = 30d -->
<property name="accessTokenValiditySeconds" value="43200" />
<!-- 60*60*12 = 12h -->
</bean>
Where i can configure my customized tokenServices
public class HybrisOAuthTokenServices extends DefaultTokenServices{
#Override
public OAuth2AccessToken createAccessToken(final OAuth2Authentication authentication) throws AuthenticationException{
try{
return super.createAccessToken(authentication);
}catch (final ModelSavingException e) {
//in case when other client was faster in saving access token - try to get token again
return super.createAccessToken(authentication);
}catch (final ModelRemovalException e) {
//in case when other client was faster in removing expired token - try to get token again
return super.createAccessToken(authentication);
}
}
}

Spring Integration Http with Spring Boot and #RequestMapping

i try to develop a SpringBoot Rest Server with Spring Integration HTTP -> inboundGateway.
I've an Controller, annotated with "#Controller" and "#RequestMapping" and try to create following flow:
GET Request "/" -> Channel: httpRequestChannel -> Run IndexController -> Channel: httpReplyChannel -> back to Browser
But it's not working.
My Integration Xml:
<int:channel id="httpRequestChannel">
<int:interceptors>
<int:wire-tap channel="logHttpRequestChannel" />
</int:interceptors>
</int:channel>
<int:channel id="httpReplyChannel">
<int:interceptors>
<int:wire-tap channel="logHttpReplyChannel" />
</int:interceptors>
</int:channel>
<int:logging-channel-adapter id="logHttpRequestChannel" level="INFO" logger-name="httpRequestChannel" log-full-message="true" />
<int:logging-channel-adapter id="logHttpReplyChannel" level="INFO" logger-name="httpReplyChannel" log-full-message="true" />
<int-http:inbound-gateway id="inboundGateway"
request-channel="httpRequestChannel" reply-channel="httpReplyChannel"
auto-startup="true" supported-methods="GET" path="/">
<int-http:request-mapping produces="application/json" />
</int-http:inbound-gateway>
The error is:
Dispatcher has no subscribers
But in my opinion, the controller should be an subscriber via the RequestMapping Annotation...
I upload a sample github project: https://github.com/marcelalburg/spring-boot-integration-rest-server
Thanks for you help
Marcel
UPDATE
Hello,
i see something in the documentation:
The parsing of the HTTP Inbound Gateway or the HTTP Inbound Channel Adapter registers an integrationRequestMappingHandlerMapping bean of type IntegrationRequestMappingHandlerMapping, in case there is none registered, yet. This particular implementation of the HandlerMapping delegates its logic to the RequestMappingInfoHandlerMapping. The implementation provides similar functionality as the one provided by the org.springframework.web.bind.annotation.RequestMapping annotation in Spring MVC.
So, i changed following:
<int-http:inbound-gateway id="indexGateway"
request-channel="httpRequestChannel" reply-channel="httpReplyChannel"
auto-startup="true" supported-methods="GET" path="/, /test" reply-timeout="100" />
and my controller
#ServiceActivator( inputChannel = "httpRequestChannel", outputChannel = "httpReplyChannel" )
#RequestMapping( value = "/", method = RequestMethod.GET, produces = "application/json" )
public String testGateway( LinkedMultiValueMap payload, #Headers Map<String, Object> headerMap )
{
// IntegrationRequestMappingHandlerMapping
System.out.println( "Starting process the message [reciveing]" );
return "{HelloMessage: \"Hello\"}";
}
#ServiceActivator( inputChannel = "httpRequestChannel", outputChannel = "httpReplyChannel" )
#RequestMapping( value = "/test", method = RequestMethod.GET, produces = "application/json" )
public String testGateway2( LinkedMultiValueMap payload, #Headers Map<String, Object> headerMap )
{
// IntegrationRequestMappingHandlerMapping
System.out.println( "Starting process the message [reciveing]" );
return "{HelloMessage: \"Test\"}";
}
now, i get an response but it returns randomized "Test" and "Hello" ...
Thanks
No; you seem to have a basic misunderstanding.
With Spring Integration, the inbound-gateway replaces the#Controller, and sends the inbound (possibly converted) object as the payload of a message to the requestChannel.
Some other component (not a controller) subscribes to that channel to receive the message.
So, instead of configuring an #Controller you can either configure your POJO as a <service-activator input-channel="httpRequestChannel" .../> or annotate the method as a #ServiceActivator.
It will then consume the message and, optionally, send the reply to the output channel (omitting the output channel will cause it to be routed back to the gateway).
See the http sample for an example.

Camel Transactional client with Spring, how to rollback route changes?

Consider the following two test. The findOne() has no side effect, the delete() has a side effect on the underlying h2 database. My problem is the #Transactional does not rollback the changes for the delete() method.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:app-context.xml")
public class AccountProcessorTest extends BaseRouteTest {
private static final String ACCOUNTS_ENDPOINT = "seda:bar";
#Test
#Transactional
public void findOne() {
final Map<String, Object> headers = new HashMap<String, Object>();
headers.put("id", 1);
headers.put(Exchange.HTTP_METHOD, "GET");
final String response = template.requestBodyAndHeaders(ACCOUNTS_ENDPOINT, null, headers, String.class);
assertEquals("Checking account",JsonPath.read(response, "name"));
}
#Test
#Transactional
public void delete() {
final Map<String, Object> headers = new HashMap<String, Object>();
headers.put("id", 1);
headers.put(Exchange.HTTP_METHOD, "DELETE");
final String response = template.requestBodyAndHeaders(ACCOUNTS_ENDPOINT, null, headers, String.class);
assertEquals(200, JsonPath.read(response, "code"));
}
}
The BaseRouteTest is just a utility where I get a reference to the Camel ProducerTemplate
public class BaseRouteTest implements InitializingBean {
#Autowired
private ApplicationContext applicationContext;
protected ProducerTemplate template;
#Override
public void afterPropertiesSet() throws Exception {
template = getCamelContext().createProducerTemplate();
}
private CamelContext getCamelContext() {
return applicationContext.getBean("foo", CamelContext.class);
}
}
I have marked the route as transacted using the transacted tag.
<!-- camel-context -->
<camel:camelContext id="foo">
<camel:route>
<camel:from uri="seda:bar"/>
<camel:transacted />
<camel:process ref="accountProcessor"/>
</camel:route>
</camel:camelContext>
My spring configuration file:
<context:component-scan base-package="com.backbase.progfun"/>
<!-- embedded datasource -->
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:data/schema.ddl"/>
<jdbc:script location="classpath:data/insert.sql"/>
</jdbc:embedded-database>
<!-- spring jdbc template -->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
<!-- transaction management start -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- transaction management end -->
<!-- camel-context -->
<camel:camelContext id="foo">
<camel:route>
<camel:from uri="seda:bar"/>
<camel:transacted />
<camel:process ref="accountProcessor"/>
</camel:route>
</camel:camelContext>
You can try it out quickly if you clone my github repo found here:
https://github.com/altfatterz/camel-transaction
If you run the AccountProcessorTest it the findOne() test case fails because the side effect of delete() test case is not rolled back.
Any suggestion would be greatly appreciated.
Thank you.
Transactions aren't carried across SEDA queues.
Therefore the transaction started by your test is a different transaction from the transaction in your route. So the changes made by your route won't be rolled back when the transaction started by your test is rolled back.

Resources