PayloadValidatingInterceptor not firing for #Endpoint? - java

I have an application that I would like to automatically validate messages that are received and sent. I've attached the PayloadValidatingInterceptor and set the schema I would like it to use:
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Autowired
private ApplicationContext ctx;
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
// modifies the wsdl to serve the correct locations
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/*");
}
#Bean
protected PayloadValidatingInterceptor getValidatingInterceptor() {
PayloadValidatingInterceptor validatingInterceptor = new PayloadValidatingInterceptor();
validatingInterceptor.setSchema(getResource("classpath:CARetriever.xsd"));
validatingInterceptor.setValidateRequest(true);
validatingInterceptor.setValidateResponse(true);
return validatingInterceptor;
}
private Resource getResource(String resource) {
return ctx.getResource(resource);
}
}
I can see that the interceptor is getting loaded
2016-01-21 14:22:08 INFO org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor,164 - Validating using class path resource [CARetriever.xsd]
However, when I throw an invalid SOAP message against it, I get a NullPointerException rather than a validation message. So, either my configuration or expectations are wrong. Can someone point to which?

I got the same error: a SOAPFault with a NPE inside with no stack trace. While debugging I noticed that the validator instance used in PayloadValidatingInterceptor is null, because setSchema does not initialize it. But there is the method setXsdSchema, which does initialize it. So, I could fix the NPE using that method instead of setSchema as follows:
payloadValidatingInterceptor.setXsdSchema(new SimpleXsdSchema(new ClassPathResource("test.xsd")));
For a complete example one may check this example: http://memorynotfound.com/spring-ws-validate-soap-request-response-xsd-schema/
Hope that helps.

Related

Bean creation errors for request-scoped beans

When a Spring bean is annotated with SCOPE_REQUEST, it is created and destroyed every time a HTTP request is received by the servlet. If this bean creation fails, a server error is sent back to the caller.
In this trivial example, the creation of the MyInputs bean is dependent on the contents of the HTTP request.
#Configuration
class ApplicationConfiguration {
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public MyInputs myInputs(HttpServletRequest request) {
String header1 = request.getHeader("header1");
if (header1 == null) {
throw new MyException("header1 is missing");
}
return new MyInputs(header1);
}
}
If the HTTP request does not contain a required header, a BeanCreationException will be thrown. This is translated into an unhelpful "500 Internal Server Error" response.
I would like to return a more user-friendly response code and body, for example, a "400 Bad Request" with a helpful message. How do I customize this response translation? I cannot find any lifecycle hooks which will allow this.
Note: This is how the request-scoped bean is consumed:
#RestController
public class MyController {
private final Provider<MyInputs> myInputsProvider;
#Autowired
public MyController(Provider<MyInputs> myInputsProvider) {
this.myInputsProvider = myInputsProvider;
}
#GetMapping("/do-stuff")
public void doStuff() {
// Get the inputs for the current request
MyInputs myInputs = myInputsProvider.get();
// ...
}
}
You can use #ControllerAdvice annotation in order to handle exceptions after are thrown.
Also you need to use #ExceptionHandler in order to handle the exception.
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(MyException.class)
public final ResponseEntity<CustomError> handleException(MyException ex, WebRequest request) {
CustomError error = new CustomError();
error.setMessage(ex.getMessage());
error.setStatus(HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(error, null, HttpStatus.BAD_REQUEST);
}
}

How to declare Servlet on root path without overriding Spring MVC Controllers

I have Spring Boot application with REST API mapped on /api. I need to define additional servlet on /. I want all request that match /api was handled by REST API and all others requests by the servlet. How to do this?
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#RestController
#RequestMapping("/api")
public class ApiController {
#GetMapping
public String get() {
return "api";
}
}
#Bean
public ServletRegistrationBean customServletBean() {
return new ServletRegistrationBean<>(new HttpServlet() {
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.getWriter().println("custom");
}
}, "/*");
}
}
In code above I want something like this:
curl http://localhost:8080/api/
> api⏎
curl http://localhost:8080/custom/
> custom
I have tried with filter to redirect requests, but all requests go to custom servlet:
#Bean
public FilterRegistrationBean apiResolverFilter() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter((req, response, chain) -> {
HttpServletRequest request = (HttpServletRequest) req;
String path = request.getRequestURI().substring(request.getContextPath().length());
if (path.startsWith("/api/")) {
request.getRequestDispatcher(path).forward(request, response);
} else {
chain.doFilter(request, response);
}
});
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
This project is available on github: https://github.com/mariuszs/nestedweb
When mapping a servlet to the root path you will override the mapping for the DispatcherServlet which, by default, is mapped to /.
There are basically 3 solutions you could try
Map the DispatcherServlet to /api and modify the mappings in your controllers
Use a ServletForwardingController to forward the request to the configured but unmapped Servlet
Use a ServletWrappingController to wrap a Servlet instance
Number 2 and 3 are almost the same, with this difference that with option 3 Spring also manages the Servlet instance whereas with option 2, the Servlet container manages the Servlet.
Mapping DispatcherServlet to /api
Option 1 can be an option if all of your controllers are mapped under /api, if they aren't this isn't an option. In your application.properties you would set the spring.mvc.servlet.path to /api. Then you would configure your other Servlet like you did in your question.
Use a ServletForwardingController
Spring provides a ServletForwardingController which will lookup a Servlet in the ServletContext given the name of the servlet and forward the request to it. You will still have to register the Servlet but prevent it from being mapped.
Next you would need a SimpleUrlHandlerMapping to map the URLs to this controller or set it as the default handler (basically a catch all).
#Bean
public ServletForwardingController forwarder() {
ServletForwardingController controller = new ServletForwardingController();
controller.setServletName("my-servlet");
return controller;
}
#Bean
public CustomServlet customServlet() {
return new CustomServlet();
}
#Bean
public ServletRegistrationBean customServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(customServlet(), false);
registration.setServletName("customServlet");
return registration;
}
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setDefaultHandler(forwarder());
mapping.setOrder(LOWEST_PRECEDENCE - 2);
return mapping;
}
Use a ServletWrappingController
Spring provides a ServletWrappingController which will internally create and configure a Servlet instance. It acts as an adapter from/to the Servlet to a Spring Controller. You don't have to register the CustomServlet in this case and is thus slightly easier to configure the then ServletForwardingController.
Next you would need a SimpleUrlHandlerMapping to map the URLs to this controller or set it as the default handler (basically a catch all).
#Bean
public ServletWrappingController wrapper() {
ServletWrappingController controller = new ServletWrappingController ();
controller.setServletName("my-servlet");
controller.setServletClass(CustomerServlet.class);
return controller;
}
#Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setDefaultHandler(wrapper());
mapping.setOrder(LOWEST_PRECEDENCE - 2);
return mapping;
}
Depending on your architecture and url structure you might want to go for option 1 or option 3.

Spring boot - rest template and rest template builder

As I know the RestTemplateBuilder is some kind of factory for RestTemplate. I have a few questions about using it:
Very often in examples there is something like this in #Configuration class:
#Bean
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate();
...
return restClient;
}
Shouldn't RestTemplate be instantiated per #Service class ? If so, how to customize it ?
Spring reference says that RestTemplateBuilder should be customized via RestTemplateCustomizer. How to manage many URI's from many IP addresses with one builder ?
How to add BasicAuthentication globaly to all RestTemplates via RestTemplateBuilder, and is it a good practice?
Thanks for help.
UPDATE:
My application calls rest services from many servers at different IP's and urls - so logically for me is the situation when I have many RestTemplates.
I'm trying to have a factory (RestTemplateBuilder) per server - let's say servers A, B, C. I know how to add a basic authentication. But what for example when I want a basic authentication for server A but not for server B ?
I think about having one RestTemplateBuilder per server. I don't want to do this manually - I would prefer to use Spring mechanisms.
Any help ?
No, you don't need to, typically you will have on rest template instance, and you would pass different url, and request parameters accordingly every time.
String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, vars);
Foo foo = restTemplate.getForObject(fooResourceUrl + "/1", Foo.class);
A descriptive example from spring doc, you can add as many customizers to the builder
public class ProxyCustomizer implements RestTemplateCustomizer {
#Override
public void customize(RestTemplate restTemplate) {
HttpHost proxy = new HttpHost("proxy.example.com");
HttpClient httpClient = HttpClientBuilder.create()
.setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {
#Override
public HttpHost determineProxy(HttpHost target,
HttpRequest request, HttpContext context)
throws HttpException {
if (target.getHostName().equals("192.168.0.5")) {
return null;
}
return super.determineProxy(target, request, context);
}
}).build();
restTemplate.setRequestFactory(
new HttpComponentsClientHttpRequestFactory(httpClient));
}
}
Any RestTemplateCustomizer beans will be automatically added to the
auto-configured RestTemplateBuilder. Furthermore, a new
RestTemplateBuilder with additional customizers can be created by
calling additionalCustomizers(RestTemplateCustomizer…​)
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}
I've set up my config like this:
#Bean
public RestTemplateCustomizer restTemplateCustomizer() {
return restTemplate -> {
restTemplate.setRequestFactory(clientHttpRequestFactory());
};
}
#Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();
clientHttpRequestFactory.setConnectTimeout(connectionTimeoutMs);
clientHttpRequestFactory.setReadTimeout(connectionTimeoutMs);
clientHttpRequestFactory.setBufferRequestBody(false);
return clientHttpRequestFactory;
}
Whenever Spring injects a RestTemplateBuilder, it will configure it using this RestTemplateCustomizer to use the ClientHttpRequestFactory. You may need to do some different customizations, or perhaps none in which case don't declare the bean.
To add the authentication header, you will need to know the user name and password, which you probably won't know until run-time. So I've created an Authenticator bean:
#Component
public class Authenticator {
#Autowired
private RestTemplateBuilder restTemplateBuilder;
public void withAuthenticationHeader(String username, String password, Consumer<RestTemplate> doAuthenticated) {
RestTemplate restTemplate =
restTemplateBuilder
.basicAuthorization(username, password)
.build();
try {
doAuthenticated.accept(restTemplate);
} catch (HttpClientErrorException exception) {
// handle the exception
}
}
}
This allows me to handle authentication failures in a standard way for all requests, which is what I need in my application.
It is injected into other beans and used like so:
#Autowired
private Authenticator authenticator;
public void transmit() {
authenticator.withAuthenticationHeader(username, password, restTemplate ->
restTemplate.postForLocation(url, request));
}
So you'd use the Authenticator rather than using the RestTemple directly.
I couldn't find any standard patterns for this sort of thing, but this seems to work.

Spring Web Flow and Spring MVC URL 404

I am currently using Spring MVC 4.0.5 and would like to use Spring Web Flow for some process oriented pages. However, I think there is still some problem with my configuration.
In the server logs:
2014-09-15 20:54:49,280 [localhost-startStop-1] DEBUG org.springframework.webflow.definition.registry.FlowDefinitionRegistryImpl - Registering flow definition 'ServletContext resource [/WEB-INF/flows/registration/registration-flow.xml]' under id 'registration'
However, when accessing it, the log says
2014-09-15 20:54:49,820 [http-bio-8080-exec-2] DEBUG org.springframework.webflow.mvc.servlet.FlowHandlerMapping - No flow mapping found for request with URI '/appContext/registration/'
Here is my configuration for the web flow
#Configuration
public class WebFlowConfig extends AbstractFlowConfiguration {
private Logger logger = Logger.getLogger(WebFlowConfig.class);
#Bean
#Autowired
public FlowExecutor flowExecutor(FlowDefinitionRegistry flowRegistry,
PlatformTransactionManager txManager, SessionFactory sessionFactory) {
return getFlowExecutorBuilder(flowRegistry)
.addFlowExecutionListener(new SecurityFlowExecutionListener(),
"*")
.addFlowExecutionListener(
new HibernateFlowExecutionListener(sessionFactory,
txManager), "*").build();
}
#Bean
#Autowired
public FlowDefinitionRegistry flowRegistry(
FlowBuilderServices flowBuilderServices) {
return getFlowDefinitionRegistryBuilder(flowBuilderServices)
.setBasePath("/WEB-INF/flows")
.addFlowLocationPattern("/**/*-flow.xml").build();
}
#Bean
#Autowired
public FlowBuilderServices flowBuilderServices(
MvcViewFactoryCreator mvcViewFactoryCreator, Validator validator) {
return getFlowBuilderServicesBuilder()
.setViewFactoryCreator(mvcViewFactoryCreator)
.setValidator(validator).setDevelopmentMode(true).build();
}
#Bean
#Autowired
public MvcViewFactoryCreator mvcViewFactoryCreator(
InternalResourceViewResolver viewResolver) {
MvcViewFactoryCreator factoryCreator = new MvcViewFactoryCreator();
factoryCreator.setViewResolvers(Arrays.asList(viewResolver));
return factoryCreator;
}
#Bean
#Autowired
public FlowHandlerMapping flowHandlerMapping(FlowDefinitionRegistry registry) {
FlowHandlerMapping handlerMapping = new FlowHandlerMapping();
handlerMapping.setOrder(-1);
handlerMapping.setFlowRegistry(registry);
return handlerMapping;
}
#Bean
#Autowired
public FlowHandlerAdapter flowHandlerAdapter(FlowExecutor executor) {
FlowHandlerAdapter handlerAdapter = new FlowHandlerAdapter();
handlerAdapter.setFlowExecutor(executor);
handlerAdapter.setSaveOutputToFlashScopeOnRedirect(true);
return handlerAdapter;
}
}
Hope that someone can help. Thanks.
You are specifying FlowHandlerMapping in webflow config:
Implementation of HandlerMapping that follows a simple convention
for creating URL path mappings from the ids of registered flow definitions. This
implementation returns a FlowHandler that invokes a flow if the current request
path matches the id of a flow in the configured FlowDefinitionRegistry.
The default FlowUrlHandler implementation for Spring Web Flow is DefaultFlowUrlHandler.
Expects URLs to launch flow to be of this pattern:
http://<host>/[app context path]/[app servlet path]/<flow path>
The flow id is treated as the path info component of the request URL string.
If the path info is null, the servletPath will be used as the flow id. Also, if
the servlet path ends in an extension it will be stripped when calculating the flow id.
The flow definition URL for the given flow id will be built by appending the
flow id to the base app context and servlet paths.
No flow mapping found for request with URI '/appContext/registration/'
Your path info must have been null and servlet mapping is something like: /appContext/registration/* resulting in flow id as /appContext/registration/ which is not registered.
So check your servlet mapping.
Remove MvcViewFactoryCreator Definition And .setViewFactoryCreator(mvcViewFactoryCreator)

Spring DispatchServlet cannot find resource within Jetty

I saw a lot people has similar problem but no answer works for me. What I'm doing is trying to embed Jetty with Spring 4 with zero configuration. I put some of my code below for better describe my question:
JettyStarter.class
public class JettyStarter {
...
private static final String CONFIG_LOCATION = "com....config";
private static final String DEFAULT_MAPPING_URL = "/*";
private static final String DEFAULT_CONTEXT_PATH = "/";
...
private ServletContextHandler getServletContextHandler() throws IOException {
WebApplicationContext context = getContext();
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
contextHandler.setErrorHandler(null);
DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
ServletHolder servletHolder = new ServletHolder("Servlet Dispatcher", dispatcherServlet);
servletHolder.setInitOrder(1);
contextHandler.addServlet(servletHolder, DEFAULT_MAPPING_URL);
contextHandler.addEventListener(new ContextLoaderListener(context));
contextHandler.setResourceBase(new ClassPathResource("webapp").getURI().toString());
contextHandler.setContextPath(DEFAULT_CONTEXT_PATH);
return contextHandler;
}
private void startJetty() throws Exception
{
...
HandlerCollection handlers = new HandlerCollection();
handlers.addHandler(getServletContextHandler());
...
server.setHandler(handlers);
server.start();
server.join();
}
private WebApplicationContext getContext() throws IOException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation(CONFIG_LOCATION);
...
return context;
}
...
}
WebMvcConfig.class
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com...controllers")
public class WebMvcConfig extends WebMvcConfigurerAdapter{
...
#Bean
public InternalResourceViewResolver getInternalResourceViewResolver()
{
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
internalResourceViewResolver.setPrefix("/WEB-INF/jsp/view");
internalResourceViewResolver.setSuffix(".jsp");
internalResourceViewResolver.setViewClass(org.springframework.web.servlet.view.JstlView.class);
return internalResourceViewResolver;
}
}
HomeController.class
#Controller
public class HomeController {
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(#RequestParam(value="error", required=false) boolean error, ModelMap model)
{
...
return "/pub/login";
}
}
Finally, after the server started, I got 404 error with the following message while trying to access localhost:8080/login
WARNING: No mapping found for HTTP request with URI [/WEB-INF/jsp/view/pub/login.jsp] in DispatcherServlet with name 'Servlet Dispatcher'
I'm pretty sure resource "login.jsp" is under the folder "/webapp/WEB-INF/jsp/view/pub" within the package. But why it keeping saying it can not be found?!
Solution:
Weird restriction that I cannot answer myself question within 8 hours.
By traced in Spring's source code, I eventually got my page display. The reason is I set Jetty ServletContextHandler's ResourceBase as "webapp", and created "WEB-INF" sub folder under it, all resources under "WEB-INF/jsp/view/..." as well. But the folder WEB-INF is unseeable by ResourceHttpRequestHandler as we can see the code there:
protected boolean isInvalidPath(String path) {
return (path.contains("WEB-INF") || path.contains("META-INF") || StringUtils.cleanPath(path).startsWith(".."));
}
So, the solution is change the resource base to "webapp/WEB-INF", and change InternalResourceViewResolver prefix to "/jsp/view" as well. It does work though!
Now my question is, ResourceHttpRequestHandler is known by to be used to restrict directly resource access, a servlet should not use it, why my none-config version load it in? but not for XML-config version? is XML-config using any other handler by pass this restriction or something I'm missing? Appreciate for anybody to forward.
You need to tell Spring that you have annotated your beans. This is usually done in an XML saying annotation-driven. But I believe you'll have to use #AnnotationDrivenConfig alon with your #Configuration element so that your beans get autowired.
EDIT: As OP mentioned #AnnotationDrivenConfig is deprecated.
Can you try https://stackoverflow.com/a/8076045/2231632 ?

Categories

Resources