Can we use multiple message factories in Spring WS? - java

I recently solved the problem of the direct streaming with Spring WS, thanks to Arjen for the fast response. The trick was to disable payload caching in the Axiom message factory.
I now suffering from that global decision, we have either to enable or disable payload caching for the whole system. We would like to have webservices with streaming and also some which enables validation (which is not possible if payload caching is disabled)
So is it possible to map an specific endpoint to an specific message factory? It's easy to do with webservice clients, but what about endpoints? Any ideas?
Thank you!

You're right, it's very easy on client side passing the specific WebServiceMessageFactory instance to the WebServiceTemplate constructor.
Anyway, I guess it's also possible on server side.
If you can use different url mappings, the simplest way I found out is based on the configuration of two (or more) differents MessageDispatcherServlet in your web.xml. Then you just have to pass as init parameter the specific bean name of the WebServiceMessageFactory instance you prefer. Something like this:
<servlet>
<servlet-name>ws1</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/conf/ws1-servlet.xml</param-value>
</init-param>
<init-param>
<param-name>messageFactoryBeanName</param-name>
<param-value>defaultMessageFactory</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>ws2</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/conf/ws2-servlet.xml</param-value>
</init-param>
<init-param>
<param-name>messageFactoryBeanName</param-name>
<param-value>streamingMessageFactory</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>ws1</servlet-name>
<url-pattern>/ws1</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ws2</servlet-name>
<url-pattern>/ws2</url-pattern>
</servlet-mapping>
Then, you just have to add your different message factory beans to your spring-ws context.
Hope this helps.

if you want to switch messagefactory within a single endpoint, you can achieve that by extending WebServiceMessageReceiverHandlerAdapter and overriding getMessageFactory method.
public class SwitchableMessageRecieverHandlerAdapter extends WebServiceMessageReceiverHandlerAdapter{
private WebServiceMessageFactory alternateMessageFactory;
private String switchOn;
public void setSwitchOn(String switchOn) {
this.switchOn = switchOn;
}
public String getSwitchOn() {
return switchOn;
}
public WebServiceMessageFactory getAlternateMessageFactory() {
return alternateMessageFactory;
}
public void setAlternateMessageFactory(WebServiceMessageFactory alternateMessageFactory) {
this.alternateMessageFactory = alternateMessageFactory;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(alternateMessageFactory, "alternateMessageFactory is required");
Assert.notNull(switchOn, "switchOn parameter is required");
}
#Override
public WebServiceMessageFactory getMessageFactory() {
TransportContext transport = TransportContextHolder.getTransportContext();
if(transport!=null && transport.getConnection() instanceof HttpServletConnection){
HttpServletConnection httpCon = (HttpServletConnection)(transport.getConnection());
if(getSwitchOn().equals(httpCon.getHttpServletRequest().getHeader("soapAction"))){
return alternateMessageFactory;
}
}
return super.getMessageFactory();
}
}
Now configure SwitchableMessageRecieverHandlerAdapter in your context file by providing alternateMessageFactory and soap actions for which you want to use alternateMessageFactory
<bean id="messageFactory" class="org.springframework.ws.samples.mtom.service.AxiomMtomEnabledSoapMessageFactory">
<property name="payloadCaching" value="true"/>
<property name="attachmentCaching" value="true"/>
</bean>
<bean id="saajMessageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>
<bean id="switchableMessageReciever" class="SwitchableMessageRecieverHandlerAdapter">
<property name="alternateMessageFactory" ref="saajMessageFactory"/>
<property name="switchOn" value="LoadImage"/>
</bean>
Now inform MessageDispatcherServlet to use the extended WebServiceMessageReceiverHandlerAdapter by adding an init-param in web.xml
<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
<init-param>
<param-name>transformWsdlLocations</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>messageReceiverHandlerAdapterBeanName</param-name>
<param-value>switchableMessageReciever</param-value>
</init-param>
</servlet>

Related

Could not find path for REST resource (Wildfly 22.0.0 deployment)

When I deploy my Web Application to Wildfly 22.0.0, I can't seem to find the path for my Rest resource:
Could not find resource for full path: http://127.0.0.1:8080/CourseManagementApplication/rest/api/assignments/list
I'm not sure what I'm doing wrong.
web.xml
<servlet>
<servlet-name>RestApplication</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>impl.rest.application.RestApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>RestApplication</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
RestApplication.java
#ApplicationPath("/api")
public class RestApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(AssignmentResource.class);
return classes;
}
}
AssignmentResource.java
#Path("/assignments")
public class AssignmentResource {
#GET
#Path("/list")
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response getAssignments() {
return ........
}
}
I thought the resource would be accessible through the URI [Root]/rest/api/assignments/list. I can't understand why this path doesn't work.
There were two problems in this code:
1- As mentioned in the comment above, I shouldn't have used servlet-mapping and #ApplicationPath simultaneously. So I removed the #ApplicationPath anotation in RestApplication.java.
2- I wasn't aware that in order to use a url pattern different from /*, you should not only define it in the server-mapping but it is also necessary to add the context-param to the web.xml, like this
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/rest</param-value>
</context-param>
So the endpoint becomes correctly available in http://127.0.0.1:8080/CourseManagementApplication/rest/assignments/list

Intercept JAX-RS Request: Register a ContainerRequestFilter with tomcat

I am trying to intercept a request to my JAX-RS webservice by a ContainerRequestFilter. I want to use it with a custom annotation, so I can decorate certain methods of the webservice. This should enable me to handle requests to this methods based on the information whether they are made on a secure channel
or not, before the actual method is executed.
I tried different approaches, searched several posts and then implemented mostly based on the answer by Alden in this post.
But I can't get it working.
I have a method test in my webservice decorated with my custom annotation Ssl.
#POST
#Path("/test")
#Ssl
public static Response test(){
System.out.println("TEST ...");
}
The annotation looks like this:
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
public #interface Ssl {}
Then I setup a filter implementation
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.ext.Provider;
#Ssl
#Provider
public class SslInterceptor implements ContainerRequestFilter
{
#Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
System.out.println("Filter executed.");
}
}
But the filter is never executed nor there occur any error messages or warnings. The test method runs fine anyway.
To resolve it, I tried to register the filter in the web.xml as described here.
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.resourceConfigClass</param-name>
<param-value>com.sun.jersey.api.core.PackagesResourceConfig</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.my.packagewithfilter</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
<param-value>com.my.packagewithfilter.SslInterceptor</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.my.packagewithfilter</param-value>
</init-param>
</servlet>
But that also doesn't work. What am I missing? Any ideas how to make that filter work? Any help is really appreciated!
You're using JAX-RS 2.0 APIs (request filters, name binding, ...) in your classes but Jersey 1 proprietary init params in your web.xml (package starting with com.sun.jersey, Jersey 2 uses org.glassfish.jersey). Take a look at this answer and at these articles:
Registering Resources and Providers in Jersey 2
Binding JAX-RS Providers to Resource Methods
Just compiling the answer from Michael Gajdos to help someone who do not want open more tabs:
When you are using Jersey-2 you must use the follow configuration to register your filter into the web.xml
jersey.config.server.provider.classnames
instead of
com.sun.jersey.spi.container.ContainerRequestFilters (jersey-1x)
<!-- This is the config needed -->
<servlet>
//...
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>com.your_package_path.yourClassFilter</param-value>
</init-param>
//...
</servlet>
Have a look at this blog post for the more 'classical' approaches (without using the annotation)

Multiple endpoints with Resteasy

I have two separate handfuls of REST services in one application. Let's say a main "people" service and a secondary "management" service. What I want is to expose them in separate paths on the server. I am using JAX-RS, RESTEasy and Spring.
Example:
#Path("/people")
public interface PeopleService {
// Stuff
}
#Path("/management")
public interface ManagementService {
// Stuff
}
In web.xml I currently have the following set-up:
<listener>
<listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>
<listener>
<listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/public</param-value>
</context-param>
<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/public/*</url-pattern>
</servlet-mapping>
The PeopleService and ManagementService implementations are just Spring beans.
Above web.xml configuration will expose them both on /public (so having /public/people and /public/management respectively).
What I want to accomplish is to expose the PeopleService on /public, so that the full path would become /public/people and expose the ManagementService on /internal, so that its full path would become /internal/management.
Unfortunately, I cannot change the value of the #Path annotation.
How should I do that?
actually you can. After few hours of debugging I came up with this:
1) Declare multiple resteasy servlets in your web.xml (two in my case)
<servlet>
<servlet-name>resteasy-servlet</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
<init-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/openrest</param-value>
</init-param>
<init-param>
<param-name>resteasy.resources</param-name>
<param-value>com.mycompany.rest.PublicService</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>private-resteasy-servlet</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
<init-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/protectedrest</param-value>
</init-param>
<init-param>
<param-name>resteasy.resources</param-name>
<param-value>com.mycompany.rest.PrivateService</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>private-resteasy-servlet</servlet-name>
<url-pattern>/protectedrest/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>resteasy-servlet</servlet-name>
<url-pattern>/openrest/*</url-pattern>
</servlet-mapping>
Please pay attention to the fact that we initialize personal resteasy.servlet.mapping.prefix and resteasy.resources for each our servlet.
Please don't forget to NOT include any botstrap classes as filters or servlets! And disable autoscan as well.
2) Create a filter that cleans up application from the RESTeasy's global information that it saves in context:
public class ResteasyCleanupFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
request.getServletContext().setAttribute(ResteasyProviderFactory.class.getName(), null);
request.getServletContext().setAttribute(Dispatcher.class.getName(), null);
chain.doFilter(request, response);
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
}
Register it for any request to your services (here I used it for all requests for simplisity):
<filter>
<filter-name>CleanupFilter</filter-name>
<filter-class>com.mycompany.ResteasyCleanupFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CleanupFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Thats it!Now you have two different REST services which lays under different prefixes : /openrest which meant to service all public requests and /protectedrest that takes care about all the private stuff in the app.
So why does it work (or why it does not work otherwise)?
When you call openrest instance for the first time it tries to initalize itself and when done saves the state in the global servletContext like this :
servletContext.setAttribute(ResteasyProviderFactory.class.getName(), deployment.getProviderFactory());
servletContext.setAttribute(Dispatcher.class.getName(), deployment.getDispatcher());
And if you will let it be your call to your second /protectedrest will get the SAME configuration! That is why you need to clean up this information some where. That is why we used our CleanupFilter which empty the context so brand new rest servlet could initialize itself with all the init parameters we declared.
This is a hack, but it does the trick.
This solution was tested for RESTEasy 2.3.6
EDITED
Works with 3.0.9.final as well!
AFAIK, you cannot have multiple servlet mappins for your JAX-RS implementation.
What you could do is: map RESTEasy to '/' (or '/api' for example if your application has other resources to serve and you don't want the JAX-RS part to interfere), then have the following #Path annotations:
#Path("/public/people")
public interface PeopleService {
// Stuff
}
#Path("/internal/management")
public interface ManagementService {
// Stuff
}

Jersey doesn't honour required=true

I have following class annotated with JAX-RS:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Blub {
#XmlElement (required = true)
private String author;
with getter/and setter. I am using this object as parameter to a method:
#Path("/createBlub")
#POST
public ReplyObject createBlub(Blub blub) {
try {
...
//process here
return ReplyObject.success("blub", result);
} catch (Exception e) {
throw new WebApplicationException(e);
}
}
I am expecting Jersey to throw an Exception if in the parameter blub object the field author is not set. However, Jersey doesn't seem to care for the required attribute. I remember that it worked in other projects, but don't see the difference.
I am using jersey 1.12 without anything else:
<servlet>
<servlet-name>JerseyServletContainerAdmin</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>xxx.yyy.zzz.admin</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.feature.DisableXmlSecurity
</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JerseyServletContainerAdmin</servlet-name>
<url-pattern>/admin/*</url-pattern>
</servlet-mapping>
thanks in advance
regards
Leon
Jersey is using JAXB for marshaling and unmarshalling which just means transforming the data into a Java object. If you want validation you have to do it yourself.
Proposed solutions for that include creating a custom MessageBodyReader to add the validation on unmarshalling or creating a more reusable implementation by writing a custom ContextResolver as described in this post: Jersey JAX-RS and JAXB Schema Validation.
JSR303 support would have been ideal for this sort of thing (working nicely with JSON data) but looks like that will be available only in 2.x. It should be possible though to adapt the solution from the above post and use JSR303.
And if the implementation gets too complicated you can always let Jersey create the object and then first-thing-first call some validation method of yours on the object, which normally shouldn't be more than a one liner, something like:
#Path("/createBlub")
#POST
public ReplyObject createBlub(Blub blub) {
ValidationUtils.<Blub>validate(blub);
...

Adding multiple servlets in single web.xml

I am trying to run two Servlet-class in a single web.xml but its not working, each servlet-class works fine independently.
web.xml:
<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
<init-param>
<param-name>transformWsdlLocations</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>240</session-timeout>
</session-config>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-ws-servlet.xml
/WEB-INF/health-page-servlet.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>health-page</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>health-page</servlet-name>
<url-pattern>/health.htm</url-pattern>
</servlet-mapping>
Do let me know if you can figure something wrong that i am doing.
I tried the below link but it doesnt work for me
Can I use Spring MVC and Spring WS in one single application?
This isn't going to work. The one which is mapped on /* overtakes all requests. You need to map it on / instead so that it will only intercept on requests which are not matched by all other existing servlets (including the JSP servlet which is implicitly mapped on *.jsp and all "normal" static resources like CSS/JS/image files!). See also Difference between / and /* in servlet mapping url pattern.
If being able to serve static resources is also required, then better map it on a more specific URL pattern like /ws/* and create a Filter which checks the request URI and then forwards accordingly. That filter can in turn safely be mapped on /*. See also this answer for a more concrete code example: How to access static resources when mapping a global front controller servlet on /*.
I am using Java configuration in my project and following code works fine for the same purpose:
public class Initializer implements WebApplicationInitializer {
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(ApplicationConfiguration.class);
ctx.setServletContext(servletContext);
MessageDispatcherServlet messageDispatcherServlet = new MessageDispatcherServlet();
messageDispatcherServlet.setApplicationContext(ctx);
messageDispatcherServlet.setTransformWsdlLocations(true);
Dynamic dynamic = servletContext.addServlet("messageDispatcherServlet", messageDispatcherServlet);
dynamic.addMapping("/ws/*");
dynamic.setLoadOnStartup(1);
dynamic = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
dynamic.addMapping("/");
dynamic.setLoadOnStartup(1);
}
}
you have a mapping for /* in the spring-ws section which is getting the request. you need to
come up with a different strategy... Try putting the /health.htm before the /* mapping.

Categories

Resources