How to Design Jax - RS for two different methods with same path and producing/consuming same media type? - http-post

I want to design a Post service with two methods. Method names will be different. These two methods will take different objects for invocations. But Path and Production/Consumption media types will have to be same. How to do that? Please find this code below. It is giving exception like " A resource model has ambiguous (sub-)resource method for HTTP method POST and input mime-types as defined by"#Consumes" and "#Produces" annotations at Java methods" while being deployed into weblogic.
package com.adac.rest.service;
import javax.ws.rs.Path;
import javax.ws.rs.POST;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import com.adac.rest.model.Response;
import com.adac.rest.model.SalesData;
import com.adac.rest.model.Tax;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
#Path("/rest")
public class StageDataLoadingService {
#POST
#Path("/dataloading")
#Consumes("application/json")
#Produces("application/json")
public String resourceMethodPUTSalesData(SalesData sd) throws Exception {
System.out.println("This method called with Sales Data");
Response resp = new Response();
resp.setStatuscode("0");
resp.setStatusmessage("Success");
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String json_resp = mapper.writeValueAsString(resp);
String str1 = mapper.writeValueAsString(sd);
System.out.println(str1);
//String sd1 = mapper.writeValueAsString(sd);
//System.out.println(sd1);
// JsonNode jsonNodeRoot = mapper.readTree(json_resp);
return json_resp;
}
#POST
#Path("/dataloading")
#Consumes("application/json")
#Produces("application/json") public String resourceMethodPUTTax(Tax tax)
throws Exception {
System.out.println("This method called with Tax");
Response resp = new Response(); resp.setStatuscode("0");
resp.setStatusmessage("Success");
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT); String json_resp =
mapper.writeValueAsString(resp);
String str1 = mapper.writeValueAsString(tax);
System.out.println(str1);
return json_resp;
}
}
Also, suggest web.xml and weblogic.xml configurations for the design you suggest.
Currently web.xml is:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>ADACStageDataLoadingService</display-name>
<servlet>
<servlet-name>rest.application.config.ApplicationConfig</servlet-name>
</servlet>
<servlet-mapping>
<servlet-name>rest.application.config.ApplicationConfig</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
and weblogic.xml is:
<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app
xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.9/weblogic-web-app.xsd">
<wls:weblogic-version>12.2.1.3</wls:weblogic-version>
<wls:context-root>ADACStageDataLoadingService</wls:context-root>
<wls:library-ref>
<wls:library-name>jax-rs</wls:library-name>
<wls:specification-version>2.0</wls:specification-version>
<wls:exact-match>false</wls:exact-match>
</wls:library-ref>
</wls:weblogic-web-app>

When you develop the api I suggest you to follow the rest best practices:
if you want insert a resource use POST method and use the following #Path: /SalesData/{resourceId} (you can omit "dataloading" because is implicit in the POST method
if you want update a resource use PUT method with the same path: /SalesData/{resourceId}
if you want delete a resource use DELETE method

Related

Fail to consume SOAP WS with Spring-WS in Spring boot but works from SOAPUI

I'm using Spring Boot to consume a SOAP WS which I generate from a WSDL. I added the spring-ws-security so I can pass the user/password as security header as shown in the configuration:
#Configuration
public class ClientConfig {
public static final String SIEBEL_ENDPOINT = "http://...";
#Bean
public CustomerClient customerClient() {
CustomerClient client = new CustomerClient();
client.setDefaultUri(SIEBEL_ENDPOINT);
client.setWebServiceTemplate(webServiceTemplate(marshaller()));
return client;
}
#Bean
public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller marshaller) {
WebServiceTemplate template = new WebServiceTemplate(marshaller, marshaller);
template.setDefaultUri(SIEBEL_ENDPOINT);
ClientInterceptor[] interceptors = new ClientInterceptor[] {new LogHttpHeaderClientInterceptor(), wsSecurityInterceptor()};
template.setInterceptors(interceptors);
return template;
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.test.dms.gen");
return marshaller;
}
#Bean
public Wss4jSecurityInterceptor wsSecurityInterceptor() {
Wss4jSecurityInterceptor wss4jSecurityInterceptor = new Wss4jSecurityInterceptor();
wss4jSecurityInterceptor.setSecurementActions(WSHandlerConstants.USERNAME_TOKEN);
wss4jSecurityInterceptor.setSecurementPasswordType(WSConstants.PW_TEXT);
wss4jSecurityInterceptor.setSecurementUsername("rf_USER");
wss4jSecurityInterceptor.setSecurementPassword("rf_USER");
return wss4jSecurityInterceptor;
}
}
And the service call:
public class CustomerClient extends WebServiceGatewaySupport {
public CustomerInfoOutput getCustomerInfo(String vin) {
ObjectFactory request = new ObjectFactory();
final CustomerInfoInput custInfoInput = request.createCustomerInfoInput();
custInfoInput.setVINNumber(vin);
return (CustomerInfoOutput) getWebServiceTemplate().marshalSendAndReceive(ClientConfig.SIEBEL_ENDPOINT, custInfoInput);
}
}
everything is well generated, and this logged output:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
SOAP-ENV:mustUnderstand="1">
<wsse:UsernameToken wsu:Id="UsernameToken-e8f183db-44db-4c0b-90d9-ca57e89225fd">
<wsse:Username>rf_USER</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">rf_USER</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns3:CustomerInfo_Input xmlns:ns2="http://siebel.com/testdashboard"
xmlns:ns3="http://test.com/rf/customerinfo" xmlns:ns4="http://test.com/rf"
xmlns:ns5="http://www.siebel.com/xml/IBM%20test%20Dashboard">
<ns3:VINNumber>123456789</ns3:VINNumber>
</ns3:CustomerInfo_Input>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
When I send this request using SOAP UI, it works perfectly. But when it's sent using the generated objects from the WSDL, I have this error:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>There is no active Web Service with operation named
'http://test.com/rf/customerinfo:CustomerInfo_Input'.(SBL-EAI-04313)
</faultstring>
<detail>
<siebelf:siebdetail xmlns:siebelf="http://www.siebel.com/ws/fault">
<siebelf:logfilename>EAIObjMgr_enu_0023_24117286.log</siebelf:logfilename>
<siebelf:errorstack>
<siebelf:error>
<siebelf:errorcode>SBL-EAI-04313</siebelf:errorcode>
<siebelf:errorsymbol>IDS_EAI_WS_OP_NOT_FOUND</siebelf:errorsymbol>
<siebelf:errormsg>There is no active Web Service with operation named
'http://test.com/rf/customerinfo:CustomerInfo_Input'.(SBL-EAI-04313)
</siebelf:errormsg>
</siebelf:error>
</siebelf:errorstack>
</siebelf:siebdetail>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Any ideas please?
PS: please don't focus on URIs because I changed them, but the generated request works fine in SOAPUI.
With the hint of #RanjithR I processed as follow:
I traced the SOAP Call using Wireshark, and I discovered that Spring WS doesn't include the header SOAPAction. The following SOF question talks about this, and how to configure it (it sends you to the spring documentation which explains well the thing). Spring-WS client not setting SOAPAction header
The second thing is that, even if I added the header, it continues to tell me that the endpoint is not sent.
In my WSDL, the SOAPAction is defined as follows:
<soap:operation soapAction="document/http://test.com/rf/customerinfo:CustomerInfo"></soap:operation>
And my Spring ws call was like that:
return getWebServiceTemplate().marshalSendAndReceive(ClientConfig.SIEBEL_ENDPOINT,
customerInfoInput,
webServiceMessage -> ((SoapMessage)webServiceMessage).setSoapAction("document/http://ripl.com/rforce/customerinfo:CustomerInfo"));
In Wireshark, I had:
SOAPAction: "http://ripl.com/rforce/customerinfo:CustomerInfo"
But when I trace the same call from SOAP UI I have:
SOAPAction: "document/http://ripl.com/rforce/customerinfo:CustomerInfo"
So I tried to send a string literal as a SOAPAction:
"\"document/http://ripl.com/rforce/customerinfo:CustomerInfo\"";
Note the \"\" around the action and it works :)
Maybe it can help someone that have to integrate with Jurasik Park systems...
This post saved me a lot of time by pointing me in the right direction. The Action header was not added for me using the method described above, but the following method worked fine:
// add action header
final String ActionHeaderName = "Action";
final String ActionHeaderPrefix = "wsa";
final String ActionHeaderNamespace = "http://www.w3.org/2005/08/addressing";
final String ActionHeaderContent = "http://example/ActionHeader";
var actionName = soapEnvelope.createName(ActionHeaderName, ActionHeaderPrefix, ActionHeaderNamespace);
var actionHeader = soapEnvelope.getHeader().addHeaderElement(actionName);
actionHeader.setTextContent(ActionHeaderContent);
Thanks a lot #Marouane

check java websocket authentication after login to the application and before #OnOpen connection

I am implementing avax.websocket to update the count for message as below in my application.
I want to secure the websocket endpoint and I used the below in web.xml and weblogic.xml but it did not work.
After that I tried to use custom ServerEndpointConfig.Configurator, now I am able to connect after logged to my application. But the session is coming as null in request.
With authentication in web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>WebSocketPrj</display-name>
<security-constraint>
<web-resource-collection>
<web-resource-name>simple web resources</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>TestUser</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>Basic</auth-method>
<realm-name>file</realm-name>
</login-config>
<security-role>
<role-name>TestUser</role-name>
</security-role>
</web-app>
weblogc.xml
<?xml version='1.0' encoding='UTF-8'?>
<weblogic-web-app xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.0/weblogic-web-app.xsd">
<security-role-assignment>
<role-name>TestUser</role-name>
<principal-name>TestUser</principal-name>
</security-role-assignment>
<container-descriptor></container-descriptor>
</weblogic-web-app>
without security-constraint in web.xml and without weblogic.xml
public class WebSocketConfigurator extends ServerEndpointConfig.Configurator{
private static final String ORIGIN = "http://localhost:7001";
#Override
public boolean checkOrigin(String originHeaderValue) {
return ORIGIN.equals(originHeaderValue);
}
#Override
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
HttpSession httpSession = (HttpSession) request.getHttpSession();
super.modifyHandshake(config, request, response);
if (httpSession == null) {
httpSession = (HttpSession) request.getHttpSession();
}
if (httpSession == null) {
return;
}
config.getUserProperties().put("httpSession", httpSession);
httpSession = (HttpSession) request.getHttpSession();
}
}
#ServerEndpoint(value = "/messageCount", configurator = WebSocketConfigurator.class)
public class NotificationSocket {
private static final Logger logger = LoggerFactory.getLogger(NotificationSocket.class);
private Session wsSession;
private HttpSession httpSession;
/**
*
* #param session
*/
#OnOpen
public void onOpen(Session session, EndpointConfig config){
}
/**
*
*/
#OnClose
public void onClose(){
// implementation not needed
}
/**
*
* #param session
* #param throwable
*/
#OnError
public void error(Session session,Throwable throwable){
// implementation not needed
}
/**
*
* #param user
* #param session
*/
#OnMessage
public void handleMessage(final String user, final Session session) {
synchronized (session) {
int count = 10;
session.getAsyncRemote().sendText("" + count);
}
}
}
I have login page and after login in welcomauth.jsp I am calling the below websocket:
and below is my javascript client:
var webSocket = new WebSocket("ws://localhost:7001/messageCount");
webSocket.onopen=function(){
webSocket.send('Message');
}
I don't want to pass the user information from client side in modifyhandshake I want to get the logged in user session then validate if the user exist then open the connection else throw exception.(like throw new RuntimeException("Not authenticated"))
I am calling websocket after login to the application. Is there any way to check authentication before the websocket connection(i.e #OnOpen)?
I solved it. We need to add one servlet filter and add the url pattern same as the websocket serverendpoint.
In Servlet filter add the authentication logic. So when ever the websocket url called it will hit the filter and validate the authentication.

Spring ajax send list of objects from controller to jsp

I am trying to send send list of objects which I get from database to my JSP. I managed to successfully send data from JSP to my controller. Method inside my controller takes that parameter, fills List (I checked it in debug mode) and controller returns that list.
#RequestMapping(value="/test.html", method=RequestMethod.GET, produces="application/json")
public #ResponseBody List<ModelVechicle> fetchListModelById(#RequestParam Integer number) {
System.out.println(number);
List<ModelVechicle> modelList = vechicleService.fetchModelById(number);
return modelList;
}
When I try to get that List on my JSP, I get
HTTP Status 406 -
type Status report
message
description The resource identified by this request is only capable of generating
responses with characteristics not acceptable according to the request "accept" headers.
Apache Tomcat/8.0.32
Here is my JSP with AJAX code
<script type="text/javascript">
$(document).ready(function(){
$("#brand").change(onSelectChange);
});
function onSelectChange() {
var selected = $("#brand option:selected");
var output = "";
var number = parseInt(selected.val());
$.ajax({
type: "GET",
url: "test.html",
dataType : 'json',
data: ({number: number}),
success: function(response){
$('#result').html("");
var obj = JSON.parse(response);
$('#result').html(obj.modelName);
},
error: function(xhr,e){
alert('Error while request..'+xhr.responseText);
}
});
if(selected.val() != 0){
output = "You selected brand " + selected.text();
}
$("#output").html(number);
}
Also here is my ModelVechicle class, that is the class which objects I am adding into List:
#Entity
#Table(name = "CARDEALERSHIP.MODEL")
public class ModelVechicle implements Serializable {
private static final long serialVersionUID = 7420515051961158192L;
#Id
#Column(name = "ID")
private Integer modelId;
#Column(name = "MODELNAME")
private String modelName;
#ManyToOne
#JoinColumn(name = "BRANDID")
private Brand brand;
public ModelVechicle(Integer modelId, String modelName, Brand brand) {
super();
this.modelId = modelId;
this.modelName = modelName;
this.brand = brand;
}
public ModelVechicle() {}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((brand == null) ? 0 : brand.hashCode());
result = prime * result + ((modelId == null) ? 0 : modelId.hashCode());
result = prime * result + ((modelName == null) ? 0 : modelName.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ModelVechicle other = (ModelVechicle) obj;
if (brand == null) {
if (other.brand != null)
return false;
} else if (!brand.equals(other.brand))
return false;
if (modelId == null) {
if (other.modelId != null)
return false;
} else if (!modelId.equals(other.modelId))
return false;
if (modelName == null) {
if (other.modelName != null)
return false;
} else if (!modelName.equals(other.modelName))
return false;
return true;
}
public Integer getModelId() {
return modelId;
}
public void setModelId(Integer modelId) {
this.modelId = modelId;
}
public String getModelName() {
return modelName;
}
public void setModelName(String modelName) {
this.modelName = modelName;
}
public Brand getBrand() {
return brand;
}
public void setBrand(Brand brand) {
this.brand = brand;
}
Can somebody please explain me what to do in order to get the List dynamically to JSP page, and display List members properly?
EDIT: Here is my web.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>CarDealership</display-name>
<welcome-file-list>
<welcome-file>addVechicle.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>springDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatchers.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/app-config.xml
</param-value>
</context-param>
</web-app>
As of Spring 3.2+, the content negotiation has other facts in account prior to eval Accept header:
From https://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc:
Enabling Content Negotiation in Spring MVC
Spring supports a couple of conventions for selecting the format
required: URL suffixes and/or a URL parameter. These work alongside
the use of Accept headers. As a result, the content-type can be
requested in any of three ways. By default they are checked in this
order:
Add a path extension (suffix) in the URL. So, if the incoming URL is something like http://myserver/myapp/accounts/list.html then HTML
is required. For a spreadsheet the URL should be
http://myserver/myapp/accounts/list.xls. The suffix to media-type
mapping is automatically defined via the JavaBeans Activation
Framework or JAF (so activation.jar must be on the class path).
A URL parameter like this: http://myserver/myapp/accounts/list?format=xls. The name of the
parameter is format by default, but this may be changed. Using a
parameter is disabled by default, but when enabled, it is checked
second.
Finally the Accept HTTP header property is checked. This is how HTTP is > actually defined to work, but, as previously mentioned, it can
be problematic to use.
That actually means that if you map a #Controller method with a .htm(l) suffix, it is intended to return html and won't return json nor any other format even if you sent other format as Accept header.
I allways map my controllers as .htm and had to change the way I used to map #ResponseBody annotated methods when I upgraded to Spring 3.2 and newer.
EDIT:
After seeing your web.xml, as I supposed, you are mapping every .html suffix request to the dispatcher servlet:
`<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>`
I figure that now the #RequestMapping in your controller is like this:
#RequestMapping(value="/test", method=RequestMethod.GET, produces="application/json")
public #ResponseBody List<ModelVechicle> fetchListModelById(#RequestParam Integer number) {
As /test does not match .html suffix, request is not arriving to springDispatcher, and that's exactly why you are getting a 404.
Now, options to fix this:
1) Add a new mapping in the web.xml which matches this controller:
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>*.html</url-pattern>
<url-pattern>*/test</url-pattern>
</servlet-mapping>
This way you would be forced to include any new non html returning method. Not seems usable for me.
2) Map all incoming requests to dispatcherServlet
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
I don't really like this option, I prefer to filter what I really want to reach the dispatcher servlet.
3) Find a new matching pattern for this kind of requests. I allways publish some kind of generic suffix, which will not be catched by JAF, such as *.service:
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>*.html</url-pattern>
<url-pattern>*.service</url-pattern>
</servlet-mapping>
So in Controller methods which return XML or JSON (or any other format, depending only of the Accept header), I map like this:
#RequestMapping(value="/test.service", method=RequestMethod.GET, produces="application/json")
public #ResponseBody List<ModelVechicle> fetchListModelById(#RequestParam Integer number) {
4) You could as well publish all this kind of #ResponseBody controller method using a 'http://com.xxx.yyy/myApp/service/resource' pattern and use /service/ as servlet mapping in web.xml
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>*.html</url-pattern>
<url-pattern>/service/</url-pattern>
</servlet-mapping>

Spring RestTemplate not accepting JAXBElement

I am trying to make a simple POST using RestTemplate to a service. The XSD that I have, does not generate the Root Element but instead has the Root Element Type. But ObjectFactory.createFoo(FooType) gives me the JAXBElement which I am trying to post but failing to do so with below exception:
org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [com.foo.FooType] and content type [application/xml]
This is my code which calls the rest service
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
HttpEntity<FooType> entity = new HttpEntity<FooType>(request.getValue(),headers);
JAXBElement<ResponseType> response = restTemplate.postForObject(esbReplaceNumberListURI, entity, JAXBElement.class);
I tried adding Jaxb2Marshaller(of Spring O/X jar ) to RestTemplate with setSupportJaxbElementClass set as true after going through an answer in StackOverflow. That did not help.
I am building my request object as below which gives me JAXBElement
JAXBElement<FooType> request = ObjectFactory.createFoo(FooType);
and then while posting I do a request.getValue() as below:
HttpEntity<FooType> entity = new HttpEntity<FooType>(request.getValue(),headers);
My Spring Config as below:
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(getMarshallingHttpMessageConverter());
return restTemplate;
}
#Bean
public MarshallingHttpMessageConverter getMarshallingHttpMessageConverter() {
MarshallingHttpMessageConverter marshallingConverter = new MarshallingHttpMessageConverter();
marshallingConverter.setMarshaller(jaxb2Marshaller());
marshallingConverter.setUnmarshaller(jaxb2Marshaller());
marshallingConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.TEXT_XML));
return marshallingConverter;
}
#Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setSupportJaxbElementClass(Boolean.TRUE);
marshaller.setPackagesToScan("com.foo.domain.*");
return marshaller;
}
Using Spring-Boot 1.3.5.RELEASE which in turn uses Spring 4.2.6.RELEASE.
I am at my wits end in figuring this out. Any help is much appreciated.
I'm about 75% sure that you should have marshaller.setPackagesToScan("com.foo.domain");, not marshaller.setPackagesToScan("com.foo.domain.*");. Note the absence of the * - I believe that it's attempting to find a package whose name literally is *, which obviously doesn't exist.
Source:
Jaxb2Marshaller#setPackagesToScan:
This is using a Spring-bases search and therefore analogous to Spring's component-scan feature ({#link org.springframework.context.annotation.ClassPathBeanDefinitionScanner})
That suggests it's probably the same as described in the Spring Reference, which says:
The following is an alternative using 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">
<context:component-scan base-package="org.example"/>
</beans>
Which has <context:component-scan base-package="org.example"/>.

File upload in Struts2 along with the Spring CSRF token

I use,
Spring Framework 4.0.0 RELEASE (GA)
Spring Security 3.2.0 RELEASE (GA)
Struts 2.3.16
In which, I use an in-built security token to guard against CSRF attacks.
<s:form namespace="/admin_side"
action="Category"
enctype="multipart/form-data"
method="POST"
validate="true"
id="dataForm"
name="dataForm">
<s:hidden name="%{#attr._csrf.parameterName}"
value="%{#attr._csrf.token}"/>
</s:form>
It is a multipart request in which the CSRF token is unavailable to Spring security unless MultipartFilter along with MultipartResolver is properly configured so that the multipart request is processed by Spring.
MultipartFilter in web.xml is configured as follows.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext.xml
/WEB-INF/spring-security.xml
</param-value>
</context-param>
<filter>
<filter-name>MultipartFilter</filter-name>
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>MultipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>AdminLoginNocacheFilter</filter-name>
<filter-class>filter.AdminLoginNocacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AdminLoginNocacheFilter</filter-name>
<url-pattern>/admin_login/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>NoCacheFilter</filter-name>
<filter-class>filter.NoCacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>NoCacheFilter</filter-name>
<url-pattern>/admin_side/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<description>Description</description>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>struts.devMode</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
And in applicationContext.xml, MultipartResolver is registered as follows.
<bean id="filterMultipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="-1" />
</bean>
The CSRF token is now received by Spring security but doing so incurs another problem in Struts.
Uploaded file(s) is now null in Struts action classes like as follows.
#Namespace("/admin_side")
#ResultPath("/WEB-INF/content")
#ParentPackage(value="struts-default")
public final class CategoryAction extends ActionSupport implements Serializable, ValidationAware, ModelDriven<Category>
{
private File fileUpload;
private String fileUploadContentType;
private String fileUploadFileName;
private static final long serialVersionUID = 1L;
//Getters and setters.
//Necessary validators as required.
#Action(value = "AddCategory",
results = {
#Result(name=ActionSupport.SUCCESS, type="redirectAction", params={"namespace", "/admin_side", "actionName", "Category"}),
#Result(name = ActionSupport.INPUT, location = "Category.jsp")},
interceptorRefs={
#InterceptorRef(value="defaultStack", "validation.validateAnnotatedMethodOnly", "true"})
})
public String insert(){
//fileUpload, fileUploadContentType and fileUploadFileName are null here after the form is submitted.
return ActionSupport.SUCCESS;
}
#Action(value = "Category",
results = {
#Result(name=ActionSupport.SUCCESS, location="Category.jsp"),
#Result(name = ActionSupport.INPUT, location = "Category.jsp")},
interceptorRefs={
#InterceptorRef(value="defaultStack", params={ "validation.validateAnnotatedMethodOnly", "true", "validation.excludeMethods", "load"})})
public String load() throws Exception{
//This method is just required to return an initial view on page load.
return ActionSupport.SUCCESS;
}
}
This happens because to my guess, the multipart request is already processed and consumed by Spring hence, it is not available to Struts as a multipart request and therefore, the file object in a Struts action class is null.
Is there a way to get around this situation? Otherwise, I have now left with the only option to append the token to a URL as a query-string parameter which is highly discouraged and not recommended at all.
<s:form namespace="/admin_side"
action="Category?%{#attr._csrf.parameterName}=%{#attr._csrf.token}"
enctype="multipart/form-data"
method="POST"
validate="true"
id="dataForm"
name="dataForm">
...
<s:form>
Long story short : How to get files in a Struts action class, if Spring is made to process a mulipart request? On the other hand, if Spring is not made to process a multipart request then, it lakes the security token. How to overcome this situation?
It seems your best bet is to create a custom MultiPartRequest implementation that delegates to Spring's MultipartRequest. Here is an example implementation:
sample/SpringMultipartParser.java
package sample;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.util.WebUtils;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
public class SpringMultipartParser implements MultiPartRequest {
private static final Logger LOG = LoggerFactory.getLogger(MultiPartRequest.class);
private List<String> errors = new ArrayList<String>();
private MultiValueMap<String, MultipartFile> multipartMap;
private MultipartHttpServletRequest multipartRequest;
private MultiValueMap<String, File> multiFileMap = new LinkedMultiValueMap<String, File>();
public void parse(HttpServletRequest request, String saveDir)
throws IOException {
multipartRequest =
WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
if(multipartRequest == null) {
LOG.warn("Unable to MultipartHttpServletRequest");
errors.add("Unable to MultipartHttpServletRequest");
return;
}
multipartMap = multipartRequest.getMultiFileMap();
for(Entry<String, List<MultipartFile>> fileEntry : multipartMap.entrySet()) {
String fieldName = fileEntry.getKey();
for(MultipartFile file : fileEntry.getValue()) {
File temp = File.createTempFile("upload", ".dat");
file.transferTo(temp);
multiFileMap.add(fieldName, temp);
}
}
}
public Enumeration<String> getFileParameterNames() {
return Collections.enumeration(multipartMap.keySet());
}
public String[] getContentType(String fieldName) {
List<MultipartFile> files = multipartMap.get(fieldName);
if(files == null) {
return null;
}
String[] contentTypes = new String[files.size()];
int i = 0;
for(MultipartFile file : files) {
contentTypes[i++] = file.getContentType();
}
return contentTypes;
}
public File[] getFile(String fieldName) {
List<File> files = multiFileMap.get(fieldName);
return files == null ? null : files.toArray(new File[files.size()]);
}
public String[] getFileNames(String fieldName) {
List<MultipartFile> files = multipartMap.get(fieldName);
if(files == null) {
return null;
}
String[] fileNames = new String[files.size()];
int i = 0;
for(MultipartFile file : files) {
fileNames[i++] = file.getOriginalFilename();
}
return fileNames;
}
public String[] getFilesystemName(String fieldName) {
List<File> files = multiFileMap.get(fieldName);
if(files == null) {
return null;
}
String[] fileNames = new String[files.size()];
int i = 0;
for(File file : files) {
fileNames[i++] = file.getName();
}
return fileNames;
}
public String getParameter(String name) {
return multipartRequest.getParameter(name);
}
public Enumeration<String> getParameterNames() {
return multipartRequest.getParameterNames();
}
public String[] getParameterValues(String name) {
return multipartRequest.getParameterValues(name);
}
public List getErrors() {
return errors;
}
public void cleanUp() {
for(List<File> files : multiFileMap.values()) {
for(File file : files) {
file.delete();
}
}
// Spring takes care of the original File objects
}
}
Next you need to ensure that Struts is using it. You can do this in your struts.xml file as shown below:
struts.xml
<constant name="struts.multipart.parser" value="spring"/>
<bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest"
name="spring"
class="sample.SpringMultipartParser"
scope="default"/>
WARNING: It is absolutely necessary to ensure that a new instance of MultipartRequest is created for every multipart request by properly setting the scope of the bean otherwise you will see race conditions.
After doing this, your Struts actions will have the file information added just as it was before. Keep in mind that validation of file (i.e. file size) is now done with filterMultipartResolver instead of Struts.
Using Themes to auto include the CSRF token
You might consider creating a custom theme so that you can automatically include the CSRF token in forms. For more information on how to do this see http://struts.apache.org/release/2.3.x/docs/themes-and-templates.html
Complete Example on Github
You can find a complete working sample on github at https://github.com/rwinch/struts2-upload
The form encoding multipart/formdata is meant to be used for file upload scenarios, this is according to the W3C documentation:
The content type "multipart/form-data" should be used for submitting
forms that contain files, non-ASCII data, and binary data.
The MultipartResolver class expects a file upload only, and not other form fields, this is from the javadoc:
/**
* A strategy interface for multipart file upload resolution in accordance
* with RFC 1867.
*
*/
So this is why adding the CSRF as a form field would not work, the usual way to secure file upload requests against CSRF attacks is to send the CSRF token in a HTTP request header instead of the POST body. For that you need to make it an ajax POST.
For a normal POST there is no way to do this, see this answer. Either make the POST an ajax request and add the header with some Javascript, or send the CSRF token as a URL parameter as you mentioned.
If the CSRF token is frequently regenerated as it should ideally be between requests, then sending it in as request parameter is less of a problem and might be acceptable.
On the server side, you would need to configure the CSRF solution to read the token from the header, this is usually foreseen by the CSRF solution being used.
At a first glance your configuration looks correct to me. I therefore think that the problem might be some tiny bit of misconfiguration somewhere.
I faced a similar problem with Spring MVC instead of Struts, which I was able to solve with help from the Spring Security team. For full details see this answer.
You may also compare your set up with a working sample available on Github. I have tested this on Tomcat 7, JBoss AS 7, Jetty and Weblogic.
If these do not work, it will be helpful if you can create a single controller, single page application with your configuration that demonstrates the problem and upload it somewhere.
I'm not a Struts user, but I think you can use the fact that the Spring MultipartFilter wraps the request in a MultipartHttpServletRequest.
First get a hold of the HttpServletRequest, in Struts I think you can do it something like this:
ServletRequest request = ServletActionContext.getRequest();
Then retreive the MultipartRequest out of it, wrapping up wrappers if necessary:
MultipartRequest multipart = null;
while (multipart == null)
{
if (request instanceof MultipartRequest)
multipart = (MultipartRequest)request;
else if (request instanceof ServletRequestWrapper)
request = ((ServletRequestWrapper)request).getRequest();
else
break;
}
If this request was a multipart, get the file by the form input name:
if (multipart != null)
{
MultipartFile mf = multipart.getFile("forminputname");
// do your stuff
}

Resources