Showing posts with label Spring MVC. Show all posts
Showing posts with label Spring MVC. Show all posts

Saturday, December 30, 2017

GWT and Spring MVC

In this post I'd like to share the code I developed in May of 2011 when started to work with GWT.

The first thing I did not like was the way how it integrated with the server side. The typical usage of server is to work with it through the RPC using the Servlets. Ok good, but what if I use another technology built a top of Servlets? SparkJava, Spring MVC, whatever? Any framework which follows the Front Controller / MVC paradigm.

Check these resources to get know on which patterns Spring MVC is built around:
https://martinfowler.com/eaaCatalog/frontController.html, https://martinfowler.com/eaaCatalog/applicationController.html, https://martinfowler.com/eaaCatalog/pageController.html).

It violates these principles since I need to add some Servlets responsible for the RPC somewhere aside. For example what if I need to get current Locale or cover with Filter security (f.e. Spring Security) in the code of that Servlets? For the Spring MVC Controller I have it all out of the box. Also I do not like mixing Servlet code which is infrastructure code with the logical/business code: when I need to create the RPC endpoint I need to extend the com.google.gwt.user.server.rpc.RemoteServiceServlet class which is servlet and put the business code there.

To solve these problems I developed very elegant integration with Spring MVC (Spring Framework) where you as the developer can focus on business and forget about the infrastructure and utilize all the power of Spring Framework.

Here it is: https://github.com/deventh/gwt.rpc.springmvc. This is upgraded version of the previous version: https://github.com/deventh/gwtrpcspringmvc with some changes: GWT RequestFactory approach is dropped and Async. Servlet 3.0 approach is added. So the performance is must be better now due no blocking nature. Find the framework and examples how to use there.

So now just forget about the Servlets stuff and start to build the software in the Spring way. The Eclipse IDE GWT plugin generates sample demo application with the greeting service (http://www.gwtproject.org/doc/latest/tutorial/create.html) as the demo project. Lets change it using my framework. How it will look like?

Client service looks the same

package com.github.deventh.gwt.rpc.springmvc.application.client;

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("greet/greetingService.gwt")
public interface GreetingService extends RemoteService {
 String greetServer(String name);
}

But it implementation looks different: no need to extend the Servlet anymore and somehow get the reference to the current Spring Context and extract beans form there. So here it is:


package com.github.deventh.gwt.rpc.springmvc.application.server;

import com.github.deventh.gwt.rpc.springmvc.application.client.AnotherService;
import com.github.deventh.gwt.rpc.springmvc.application.client.GreetingService;
import org.springframework.stereotype.Service;

import java.text.MessageFormat;

@Service("greetingService")
public class GreetingServiceImpl implements GreetingService, AnotherService {
 @Override
 public String greetServer(String name) throws IllegalArgumentException {
  return MessageFormat.format("Hello, {0}!", name);
 }
}

How this Remote Service is published and how it takes care about the serving the request - this is what this framework does. Even more as I mentioned above the server performance (capacity) is better due it async. nature. It is built using Java 8, Servlet 3.0 and Spring Framework 4.x. If you need downgrade in some technologies - try to use prev. version or make the modifications you need.

In this repo you can find some examples how to use it. You can also define the the way Application Exceptions are mapped to the client Exceptions and how to sue it with the Spring Security for some situations like session is expired.

Just a few works about how it is working under the hood. The new Handler mapping is registered which is responsible for the serving requests. It uses the  in the com.google.gwt.user.server.rpc.RemoteServiceServlet async way if allowed. But before that is scans for all the available Spring Beans which extend the Remote Service.

So that's is, develop nice apps with the cool logic using GWT and forget about the infrastructure!



Sunday, July 23, 2017

Spring MVC RequestBodyAdvice or how to add a processing step for parameters in Page Controller methods

... There are a few problems when we need to add some additional processing steps for the Spring MVC Page Controller method parameters. Before the Spring 4.2 we needed to add additional Argument Resolver to do that which was a bit problematic if we need to mix our logic with standard Spring MVC existing one like declarative validation using @Valid annotation?

Assume we have the most popular Request Mapping approach (@Controller/@RequestMapping) and want to add some logic deal with with incoming parameters. It could be the problem of HTML-escaping (JavaScript, XML, SQL, whatever) of some fields for incoming POJOs (DTOs).

Here is the Controller code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.example.demo;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;

import javax.validation.Valid;

@Controller
public class ReportController {
    @GetMapping(value = "/content")
    void report(@RequestBody @Valid ContentRequest contentRequest) {
        // process it ...
    }

    @JsonTypeName("content")
     static class ContentRequest {
        @JsonProperty("id")
        long id;

        @JsonProperty("title")
        @Length(max = 10000, message = "{validation.max.length.exceeds}")
        @NotBlank(message = "{validation.not.empty}")
        String content;
    }
}

And we need to add HTML escape for the content field since it could be published somewhere on other pages.

The solution might be to start using RequestBodyAdvice feature available since Spring MVC.

Let's declare our Advice to MVC configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@Configuration
@EnableWebMvc
class MvcConfig extends WebMvcConfigurationSupport {
 @Resource
 private RequestBodyAdvice escapeRequestBodyAdvice;

 @Override
 protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
  // we can avoid adding new custom since we can use RequestBodyAdvices ...
 }

 @Override
 public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
  RequestMappingHandlerAdapter requestMappingHandlerAdapter = super.requestMappingHandlerAdapter();
  // here is the our custom one: escapeRequestBodyAdvice
  requestMappingHandlerAdapter.setRequestBodyAdvice(Collections.singletonList(escapeRequestBodyAdvice));
  return requestMappingHandlerAdapter;
 }
}

and our RequestBodyAdvice looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Component
public class EscapeRequestBodyAdvice extends RequestBodyAdviceAdapter {
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        EscapeBeanUtil.escape(body);
        return body;
    }
}

where EscapeBeanUtil is the utility to escape the fields. It is not recursive - just for the demo purposes. It searches for @EscapeHTML and escapes the annotated field.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.apache.commons.lang3.StringEscapeUtils;
import org.springframework.util.ReflectionUtils;

class EscapeBeanUtil {
  /**
   * Only one level of simple String fields supported
   *
   * @param bean
   */
  static void escape(Object bean) {
    ReflectionUtils.doWithFields(
        bean.getClass(),
        field -> {
          if (!field.isAccessible()) {
            field.setAccessible(true);
          }

          Object value = ReflectionUtils.getField(field, bean);

          if (value != null) {
            String escaped = StringEscapeUtils.escapeHtml4(
                String.valueOf(value)
            );

            ReflectionUtils.setField(field, bean, escaped);
          }
        },
        field -> field.getAnnotationsByType(EscapeHTML.class).length > 0 &&
            String.class.isAssignableFrom(field.getType())
    );
  }
}

Annotation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EscapeHTML {

}


Now, after these changes we have our POJO/DTO a bit modified (See at @EscapeHTML)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;

import javax.validation.Valid;

@Controller
public class ReportController {
    @GetMapping(value = "/content")
    void report(@RequestBody @Valid ContentRequest contentRequest) {
        // process it
    }

    @JsonTypeName("content")
     static class ContentRequest {
        @JsonProperty("id") // other fields which require validation...
        long id;

        @JsonProperty("title")
        @Length(max = 100000, message = "{validation.max.length.exceeds}")
        @NotBlank(message = "{validation.not.empty}")
        @EscapeHTML
        String content;
    }
}

That's it!

Also please note that now validation is broken a bit: after HTML escaping limits for the content length could not fit since more chars are added. To overcome it you can pre-calculate the field size on UI or relax a bit the size or add some custom validator for that field. But very often for the big text inputs where the escape is required there are no limits


Design Patterns Wrapper revisited

 Now lots of software designed and developed following microservice architecture. Components need to communicate with each other via VO (val...