Spring Data Rest: ResourceProcessor configuration is not working properly - spring

I have a strange behaviour with a Spring Data Rest implementation (version 2.5.2.RELEASE).
I'm trying to register a #Bean of ResourceProcessor<Resource<Entity>>, but there is something strange.
I'm trying with two kinds of solutions:
1) Declaring the #Bean in a class:
#Bean
public ResourceProcessor<Resource<Author>> authorProcessor() {
return new ResourceProcessor<Resource<Author>>() {
#Override
public Resource<Author> process(Resource<Author> resource) {
System.out.println("method process of bean ResourceProcessor of class RepositoryBaseConfiguration");
return resource;
}
};
}
2) Implementing the interface ResourceProcessor:
#Component
public class AuthorResourceProcessor implements ResourceProcessor<Resource<Author>> {
#Override
public Resource<Author> process(Resource<Author> resource) {
System.out.println("method process of class AuthorResourceProcessor");
return resource;
}
}
The processors are completely ignored: the message is never printed.
I noticed that the class org.springframework.data.rest.webmvc.ResourceProcessorInvoker has a constructor:
public ResourceProcessorInvoker(Collection<ResourceProcessor<?>> processors) {
//...
}
This constructor is invoked 2 times at the start of the application instead of only one time (as I will expect), and I don't understand why.
The first time, the "processors" variable is solved with the two beans (as expected) and with the bean org.springframework.data.rest.webmvc.ProfileResourceProcessor.
But the second time, the "processors" variable is solved with only the bean org.springframework.data.rest.webmvc.ProfileResourceProcessor.
The second configuration #Override the first one.
Any idea?

The problem depends on the configurations loaded at the startup of the application.
I had this configuration on the web.xml:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/spring-web-config.xml</param-value>
</context-param>
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>org.springframework.data.rest.webmvc.RepositoryRestDispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
So, the ContextLoaderListener loaded the correct configuration in the first time; the "load-on-startup" property of the servlet "RepositoryRestDispatcherServlet" launch a second context configuration load.
I also had a custom class that extended org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration, but this custom class was ignored by the moment that the constructor of RepositoryRestDispatcherServlet load the default RepositoryRestMvcConfiguration, causing the lost of the configurations.
To solve that issue I have created a custom RepositoryRestDispatcherServlet in this way:
public class AppRepositoryRestDispatcherServlet extends DispatcherServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
public AppRepositoryRestDispatcherServlet() {
configure();
}
public AppRepositoryRestDispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
configure();
}
private void configure() {
setContextClass(AnnotationConfigWebApplicationContext.class);
setContextConfigLocation(RepositoryBaseConfiguration.class.getName());
}
}
The class is the same as RepositoryRestDispatcherServlet, with the only difference that in the setContextConfigLocation is passed the custom class that extends RepositoryRestMvcConfiguration (RepositoryBaseConfiguration in this example).
Obviously I had to update the web.xml as follows:
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>my.package.AppRepositoryRestDispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
In this way, the configuration is correctly loaded and mantained.

Related

How to allow GET method for endpoint programmatically?

I am loading a .war file and add it as web app to the embedded Tomcat server.
#Bean
public EmbeddedServletContainerFactory servletContainerFactory() {
LOGGER.info("Adding web app");
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
String appHome = System.getProperty(Environment.APP_HOME);
String targetFileName = "web-0.0.1-SNAPSHOT.war";
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream(targetFileName);
LOGGER.info(System.getProperty("user.name"));
LOGGER.debug("Loading WAR from " + appHome);
File target = new File(Paths.get(appHome, targetFileName).toString());
try {
LOGGER.info(String.format("Copy %s to %s", targetFileName, target.getAbsoluteFile().toPath()));
java.nio.file.Files.copy(resourceAsStream, target.getAbsoluteFile().toPath(), StandardCopyOption.REPLACE_EXISTING);
Context context = tomcat.addWebapp("/", target.getAbsolutePath());
context.setParentClassLoader(getClass().getClassLoader());
} catch (ServletException ex) {
throw new IllegalStateException("Failed to add webapp.", ex);
} catch (Exception e) {
throw new IllegalStateException("Unknown error while trying to load webapp.", e);
}
return super.getTomcatEmbeddedServletContainer(tomcat);
}
};
}
This is working so far but if I access http://localhost:8080/web I am getting
2017-03-04 11:18:59.588 WARN 29234 --- [nio-8080-exec-2] o.s.web.servlet.PageNotFound : Request method 'GET' not supported
and the response
Allow: POST
Content-Length: 0
Date: Sat, 04 Mar 2017 10:26:16 GMT
I am sure all I have to do is to allow the GET method on /web and hopefully the static web content provided from the loaded war file will be accessible via web browser.
How/where can I configure the endpoint such that it allows GET requests?
I tried to introduce a WebController as described in this tutorial.
#Controller
public class WebController {
private final static Logger LOGGER = Logger.getLogger(WebController.class);
#RequestMapping(value = "/web", method = RequestMethod.GET)
public String index() {
LOGGER.info("INDEX !");
return "index";
}
}
In the log output I can see that this is getting mapped correctly:
RequestMappingHandlerMapping : Mapped "{[/web],methods=[GET]}" onto public java.lang.String org.ema.server.spring.controller.dl4j.WebController.index()
but it does not change the fact that I cannot visit the website.
I've also configured a InternalResourceViewResolver:
#Configuration
#EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter {
private final static Logger LOGGER = Logger.getLogger(MvcConfiguration.class);
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
LOGGER.info("configureViewResolvers()");
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setSuffix(".html");
registry.viewResolver(resolver);
}
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
web.xml
Since I configure everything in pure Java, this file does not define a lot:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Easy Model Access Server</display-name>
<listener>
<listener-class>org.ema.server.ServerEntryPoint</listener-class>
</listener>
<context-param>
<param-name>log4j-config-location</param-name>
<param-value>WEB-INF/classes/log4j.properties</param-value>
</context-param>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/web/*.html</url-pattern>
</servlet-mapping>
</web-app>
Reproduce
If you want to reproduce this you can simply checkout the entire code from github. All you need to do this:
mkdir ~/.ema
git clone https://github.com/silentsnooc/easy-model-access
cd easy-model-access/ema-server
mvn clean install
java -jar server/target/server-*.jar
This will clone, build and run the server.
The directory ~/.ema directory is required at the moment. It is where the WAR is being copied as the server starts.
My guess is that your web.xml maps any path to the Spring DispatcherServlet, something like:
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Because of <url-pattern>/</url-pattern> any request must be handled by a Spring controller, for this reason your static files are not served by Tomcat. Also a pattern like /*.html would have same effect.
If you have only a few pages you might add one or more mapping to the predefined default servlet for them, before the mapping of Spring (and also before Spring Security if you use it):
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>index.html</url-pattern>
</servlet-mapping>
You may also use <url-pattern>*.html</url-pattern> or, if your resources are under the web path and there are only static resources there: <url-pattern>/web/*</url-pattern>
Maybe all this is done instead in Java code in the org.ema.server.ServerEntryPoint that you have as a listener in web.xml
I think the mapping I wrote up in web.xml is done in your case in method getServletMappings of class org.ema.server.spring.config.AppInitializer, I changed it to use a more strict pattern /rest-api/* instead than /, not sure pattern is correct and everything else works, but now http://127.0.0.1:8080/index.html works
#Override
protected String[] getServletMappings() {
return new String[] { "/rest-api/*" };
}
as I see the url: http://localhost:8080/web is wrong.
You can try: http://localhost:8080/[name-of-war-file]/web

Dispatcher Servlet in Spring Boot

In my Spring Boot application with packaging type as war, i am configuring Spring MVC. As i understand we dont have to configure Dispatcher Servlet Manually. However, i old style of web.xml i used to configure Dispatcher Servlet and then i used to pass contextClass and contextConfigLocation as follows
<servlet>
<description>
</description>
<display-name>DispatcherServlet</display-name>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<description>contextClass</description>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<description>contextConfigLocation</description>
<param-name>contextConfigLocation</param-name>
<param-value>com.xxx.yyy.jdorderspringmvcweb.config.SpringMvcConfig</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
I belive this was to indicate that SpringMvcConfig (my custom class with spring mvc configuration) is the configuration class for Spring MVC..
However, In spring boot if Dispatcher Servlet is configured Automatically, how can i pass my custom class to dispatcher Servlet ?
In my Spring Boot application, my SpringMvcConfig class extends from WebMvcConfigurerAdapter and is annotated with #Configuration class
Help Needed...
Right in the configuration class which is annotated by #Configuration you could define your dispatcherServlet and pass init-parameter to it.
#Bean
public ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(dispatcherServlet());
registrationBean.addInitParameter("contextClass","org.springframework.web.context.support.AnnotationConfigWebApplicationContext");
registrationBean.addInitParameter("contextConfigLocation","com.xxx.yyy.jdorderspringmvcweb.config.SpringMvcConfig");
return registrationBean;
}
Another way would be to create a paramter map and then set parameter for registration bean. This stream shows how to do it.
I think you have to create a config class as follow:
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MySpringMvcDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
// TODO Auto-generated method stub
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { DemoAppConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}

How to inject a Spring Data JPA Repository into a Servlet Filter?

I'm using the Tuckey UrlRewriteFilter. I want to use a rewrite rule from a database, so using the <class-rule class="com.example.Foo" /> configuration, which lets you get rules at runtime. I created a class extending RewriteRule:
public class Foo extends RewriteRule {
#Autowired
private MyRepository myRepository;
public boolean init(ServletContext servletContext) {
return true;
}
#Override
public RewriteMatch matches(HttpServletRequest request, HttpServletResponse response) {
//myRepository is null
return super.matches(request, response);
}
#Override
public void destroy() {
}
}
I'd like to use a Spring Data JPA Repository inside this Foo class, but it looks the repository is null.
How can I inject it correctly?
Declare your filter in web.xml as usual, except that you will need to provide org.springframework.web.filter.DelegatingFilterProxy as the filter class name instead of your actual class name.
<filter>
<filter-name>urlRewriteFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>urlRewriteFilter</filter-name>
<url-pattern>/</url-pattern>
</filter-mapping>
Finally, in your ROOT application context, declare a bean pointing to your filter class and with the same name as the filter name provided in web.xml in the application context file loaded from web.xml:
<bean id="urlRewriteFilter" class="org.tuckey.web.filters.urlrewrite.UrlRewriteFilter"/>
Since your filter instance is now managed by Spring, you can inject any Spring managed bean into it.

ResourceFilterFactory and non-Path annotated Resources

(I'm using Jersey 1.7)
I am attempting to add a ResourceFilterFactory in my project to select which filters are used per method using annotations.
The ResourceFilterFactory seems to be able to filters on Resources which are annotated with the Path annotation but it would seem that it does not attempt to generate filters for the methods of the SubResourceLocator of the resources that are called.
#Path("a")
public class A {
//sub resource locator?
#Path("b")
public B getB() {
return new B();
}
#GET
public void doGet() {}
}
public class B {
#GET
public void doOtherGet() { }
#Path("c")
public void doInner() { }
}
When ran, the Filter factory will only be called for the following:
AbstractResourceMethod(A#doGet)
AbstractSubResourceLocator(A#getB)
When I expected it to be called for every method of the sub resource.
I'm currently using the following options in my web.xml;
<init-param>
<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
<param-value>com.my.MyResourceFilterFactory</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.my.resources</param-value>
</init-param>
Is my understanding of the filter factory flawed?
You need to use #Path annotation at class level for Class B. When Jersey does the resource scan I bet you that it doesnt pick up this class as a resource.

GWTP using Spring in server side

I am working with gwtp and I would like to use Spring on the server side. I have seen that Spring is include in gwtp, but I don't know how I can use it. Anyone can help me about that?
Will be cool some example.
I have looked for by google, but no way :(
Thanks a lot!!
GWTP is using GIN pattern (Dependency Injection at Client Side) and it's default integration with GUICE at DI server side. for more detail GWTP
Spring is server side DI pattern.
I have seen that Spring is include in gwtp,
It does not include Spring at all. it's default integration with GUICE. but you can use spring with it.
gwtp-sample-basic-spring example
Well, at first you have to configure Spring in your web.xml descriptor:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<servlet>
<servlet-name>springGwtRemoteServiceServlet</servlet-name>
<servlet-class>org.spring4gwt.server.SpringGwtRemoteServiceServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>springGwtRemoteServiceServlet</servlet-name>
<url-pattern>/yourProjectName/springGwtServices/*</url-pattern>
</servlet-mapping>
Notice that you need the Spring4GWT library for this example.
Next, in your RemoteService interfaces you need to specify the RemoteServiceRelativePath like this example:
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
import com.google.gwt.user.client.rpc.RemoteService;
#RemoteServiceRelativePath("springGwtServices/userService")
public interface UserService extends extends RemoteService{
public User getUserByLogin(String name);
public void logout();
public void deleteUserById(Long userId);
}
And now, you just need to implement your service as in any Spring app.
Example, suppose you want an action to delete a User by ID and using the GWTP paradigm:
In server side, here is the Handler:
#Repository("deleteUserHandler")
public class DeleteUserHandler extends AbstractActionHandler<DeleteUserAction, DeleteUserResult> {
#Autowired
private UserService userService;
public DeleteUserHandler(){
super(DeleteUserAction.class);
}
#Override
public DeleteUserResult execute(DeleteUserAction action, ExecutionContext arg1)
throws ActionException {
Long idToDel = action.getUserToDeleteId();
if(idToDel != null){
userService.deleteUserById(idToDel);
}
return new DeleteUserResult();
}
#Override
public void undo(DeleteUserAction arg0, DeleteUserResult arg1,
ExecutionContext arg2) throws ActionException {
// TODO Auto-generated method stub
}
}
The DeleteUserAction is as follows
public class DeleteUserAction extends UnsecuredActionImpl<DeleteUserResult> {
private Long userToDeleteId;
public DeleteUserAction(Long userToDel) {
this.userToDeleteId = userToDel;
}
/**
* For serialization only.
*/
#SuppressWarnings("unused")
private DeleteUserAction() {
}
public Long getUserToDeleteId() {
return userToDeleteId;
}
public void setUserToDeleteId(Long userToDeleteId) {
this.userToDeleteId = userToDeleteId;
}
}
And finally the Result class:
public class DeleteUserResult implements Result {
/**
* For serialization only.
*/
//#SuppressWarnings("unused")
public DeleteUserResult() {
}
}
I hope this helps.
PS: I suppose you can do the Spring things (application context etc..) by yourself, if not, please tell
You can find some good examples on GWTP repository in Github. We recently migrated all of our from Google Code to Github, which hosts the latest version.
Remember you can also use REST communication using the new GWTP-Dispatch-Rest, with that you don't need a lot of configuration code to integrate GWTP with Spring server side.
https://github.com/ArcBees/GWTP-Samples

Resources