星期五, 七月 29, 2016

Upgrade to Spring Boot 1.4

Upgrade to Spring Boot 1.4

Spring Boot 1.4 is a big jump, and introduced lots of new test facilities and aligned with the new technology stack, such as Spring framework 4.3 and Hibernate 5.2 and Spring Security 4.1, etc.

Spring Boot 1.4

New starter:spring-boot-starter-test

Spring Boot 1.4 brings a new starter for test scope, named spring-boot-starter-test.
Use the following:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
Instead of:
<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-core</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <scope>test</scope>
</dependency>
spring-boot-starter-test includes the essential dependencies for test, such as json-path, assertj, hamcrest, mockito etc.

New annotation: @SpringBootTest

Spring Boot 1.4 introduced a new annotation @SpringBootTest to unite the old @IntegrationTest, @WebIntegrationTest, @SpringApplicationConfiguration etc, in before versions.
A webEnvironment property of @SpringBootTest is use for deciding if set up a web environment for test.
There are some configuration options of the webEnvironment.
  • MOCK is the default, provides a mock web environment.
  • NONE does not give a web environment.
  • DEFINED_PORT provides an embedded web environment and run the application on a defined port.
  • RANDOM_PORT provides an embedded web environment, but use a random port number.
If RANDOM_PORT is used, add @LocalSeverPort annotation on an int field will inject the port number at runtime.
@LocalSeverPort
int port;
@LocalServerPort replaces the @Value("${local.server.port}") of Spring Boot 1.3.
Similarly, classes property is similar to the one of @SpringApplicationConfiguration. You can specify the configuration classes to be loaded for the test.
@SpringBootTest(classes = {Application.class, SwaggerConfig.class})
The above code is equivalent to @SpringApplicationConfiguration(classes={...}) in Spring Boot 1.3.

New JUnit Runner: SpringRunner

Spring 1.4 introduced a new JUnit Runner, SpringRunner, which is an alias for the SpringJUnit4ClassRunner.
@RunWith(SpringRunner.class)
If you have to use other runners instead of SpringRunner, and want to use the Spring test context in the tests, declare a SpringClassRule and SpringMethodRule in the test to fill the gap.
@RunWith(AnotherRunner.class)
public class SomeTest{

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

}

Autoconfigure test slice

The most exciting feature provided in Spring Boot 1.4 is it provides capability to test some feature slice, which just pick up essential beans and configuration for the specific purpose based test.
Currently there is a series of new annotations available for this purpose.
@JsonTest provides a simple Jackson environment to test the json serialization and deserialization.
@WebMvcTest provides a mock web environment, it can specify the controller class for test and inject the MockMvc in the test.
@WebMvcTest(PostController.class)
public class PostControllerMvcTest{

    @Inject MockMvc mockMvc;

}
@DataJpaTest will prepare an embedded database and provides basic JPA environment for the test.
@RestClientTest provides REST client environment for the test, esp the RestTemplateBuilder etc.
These annotations are not composed with SpringBootTest, they are combined with a series of AutoconfigureXXX and a @TypeExcludesFilter annotations.
Have a look at @DataJpaTest.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface DataJpaTest {}
You can add your @AutoconfigureXXX annotation to override the default config.
@AutoConfigureTestDatabase(replace=NONE)
@DataJpaTest
public class TestClass{
}

JsonComponent

@JsonComponent is a specific @Component to register custome Jackson JsonSerializer and JsonDeserializer.
For example, custom JsonSerializer and JsonDeserializer are use for serializing and deserializing LocalDateTime instance.
@JsonComponent
@Slf4j
public class LocalDateTimeJsonComponent {

    public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {

        @Override
        public void serialize(LocalDateTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            jgen.writeString(value.atZone(ZoneId.systemDefault()).toInstant().toString());
        }
    }

    public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {

        @Override
        public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            ObjectCodec codec = p.getCodec();
            JsonNode tree = codec.readTree(p);
            String dateTimeAsString = tree.textValue();
            log.debug("dateTimeString value @" + dateTimeAsString);
            return LocalDateTime.ofInstant(Instant.parse(dateTimeAsString), ZoneId.systemDefault());
        }
    }
}
If you are using the Spring Boot default Jackson configuration, it will be activated by default when the application starts up.
But if you customized a ObjectMapper bean in your configuration, the autoconfiguration of ObjectMapper is disabled. You have to install JsonComponentModule manually, else the @JsonComponent beans will not be scanned at all.
@Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder(JsonComponentModule jsonComponentModule) {

    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    //...
            .modulesToInstall(jsonComponentModule);

    return builder;
} 

Mocking and spying Beans

Spring Boot 1.4 integrates Mockito tightly, and provides Spring specific @MockBean and @MockSpy annotations.
@RunWith(SpringRunner.class)
public class MockBeanTest {

    @MockBean
    private UserRepository userRepository;

}

TestConfiguration and TestComponent

TestConfiguration and TestComponent are designated for test purpose, they are similar with Configuration and Component. Generic Configuration and Component can not be scanned by default in test.
public class TestClass{

    @TestConfiguration
    static class TestConfig{
    }

    @TestComponent
    static class TestBean{}

}

Spring 4.3

There are a few features added in 4.3, the following is impressive.

Composed annotations

The effort of Spring Composed are merged into Spring 4.3.
A series of new composed annotations are available, but the naming is a little different from Spring Composed.
For example, a RestController can be simplfied by the new annotations, list as the following table.
Spring 4.2 Spring 4.3
@RequestMapping(value = "", method = RequestMethod.GET) @GetMapping()
@RequestMapping(value = "", method = RequestMethod.POST) @PostMapping()
@RequestMapping(value = "/{id}", method = RequestMethod.PUT) @PutMapping(value = "/{id}")
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE) @DeleteMapping(value = "/{id}")
A new @RestControllerAdvice() is provided for exception handling, it is combination of @ControllerAdvice and @ResponseBody. You can remove the @ResponseBody on the @ExceptionHandler method when use this new annotation.
For example, in the old Spring 4.2, an custom exception handler class looks like the following.
@ControllerAdvice()
public class RestExceptionHandler {

    @ExceptionHandler(value = {SomeException.class})
    @ResponseBody
    public ResponseEntity<ResponseMessage> handleGenericException(SomeException ex, WebRequest request) {
    }
}
In Spring 4.3, it becomes:
@RestControllerAdvice()
public class RestExceptionHandler {

    @ExceptionHandler(value = {SomeException.class})
    public ResponseEntity<ResponseMessage> handleGenericException(SomeException ex, WebRequest request) {
    }
}

Auto constructor injection

If there is a only one constructor defined in the bean, the arguments as dependencies will be injected by default.
Before 4.3, you have to add @Inject or @Autowired on the constructor to inject the dependencies.
@RestController
@RequestMapping(value = Constants.URI_API_PREFIX + Constants.URI_POSTS)
public class PostController {

    @Inject
    public PostController(BlogService blogService) {
        this.blogService = blogService;
    }
}
@Inject can be removed in Spring 4.3.
@RestController
@RequestMapping(value = Constants.URI_API_PREFIX + Constants.URI_POSTS)
public class PostController {

    public PostController(BlogService blogService) {
        this.blogService = blogService;
    }
}

Spring Security 4.1

The Java configuration is improved.
Before 4.1, you can configure passwordEncoder and userDetailsService via AuthenticationManagerBuilder.
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {}

  @Override
  protected void configure(AuthenticationManagerBuilder auth)
          throws Exception {
      auth
          .userDetailsService(new SimpleUserDetailsServiceImpl(userRepository))
          .passwordEncoder(passwordEncoder);
  }

  @Bean
  @Override
  public AuthenticationManager authenticationManagerBean() throws Exception {
      return super.authenticationManagerBean();
  }

}
In 4.1, userDetailsService and passwordEncoder bean can be detected automaticially. No need to wire them by AuthenticationManagerBuilder manually. No need to override the WebSecurityConfigurerAdapter class and provide a custom configuration, a generic WebSecurityConfigurerAdapter bean is enough.
@Bean
public BCryptPasswordEncoder passwordEncoder() {
    BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    return passwordEncoder;
}

@Bean
public UserDetailsService userDetailsService(UserRepository userRepository){
    return new SimpleUserDetailsServiceImpl(userRepository);
}

@Bean
public WebSecurityConfigurerAdapter securityConfig(){
    return new WebSecurityConfigurerAdapter() {
        @Override
        protected void configure(HttpSecurity http) throws Exception {//...}
}   
More details can be found in the What’s New in Spring Security 4.1 chapter of Spring Secuirty documentation.

Hibernate 5.2

The biggest change of Hibernate 5.2 is the packages had been reorganised, Hibernate 5.2 is Java 8 ready now.
hibernate-java8 (Java 8 DateTime support) and hibernate-entitymanager (JPA provider bridge) are merged into hibernate-core.
Remove the following dependencies when upgrade to Hibernate 5.2.
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-java8</artifactId>
    <version>${hibernate.version}</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>${hibernate.version}</version>
</dependency>
NOTE:If you are using Spring 4.2 with Hibernate 5.2.0.Final, it could break some dependencis, such as spring-orm, spring-boot-data-jpa-starter which depends on hibernate-entitymanager. Spring Boot 1.4.0.RC1 and Spring 4.3 GA fixed the issues. But I noticed in the Hibernate 5.2.1.Final, hibernate-entitymanager is back.
Hibernate 5.2 also added Java Stream APIs support, I hope it will be available in the next JPA specification.

Source code

Clone the codes from Github account.
git clone https://github.com/hantsy/angularjs-springmvc-sample-boot

星期日, 七月 24, 2016

Building REST APIs with Spring MVC


As stated in before posts, we are going to build a Blog system.
To demonstrate REST API, we use a simple Post entity to persist blog entries, and expose the CRUD operations via REST APIs to client applications. As a REST API consumer, the client applications could be a website, a desktop application, or a mobile application.
Following the REST API convention and HTTP protocol specification, the post APIs can be designed as the following table.
Uri Http Method Request Response Description
/posts GET
200, [{'id':1, 'title'},{}] Get all posts
/posts POST {'title':'test title','content':'test content'} 201 Create a new post
/posts/{id} GET
200, {'id':1, 'title'} Get a post by id
/posts/{id} PUT {'title':'test title','content':'test content'} 204 Update a post
/posts/{id} DELETE
204 Delete a post
Next, we begin to create the domain models: Post.

Modeling the blog application

As planned in Overview, there are some domain objects should be created for this blog sample application.
A Post model to store the blog entries posted by users. A Comment model to store the comments on a certain post. A User model to store users will user this blog application.
Every domain object should be identified. JPA entities satisfy this requirement. Every JPA entity has an @Id field as identifier.
A simple Post entity can be designated as the following. Besides id, it includes a title field , a content field, and createdDate timestamp, etc.
@Entity
@Table(name = "posts")
public class Post implements Serializable {

    @Id()
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "title")
    private String title;

    @Column(name = "content")
    @Size(max = 2000)
    private String content;

    @Column(name = "created_date")
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;

    //getters and setters, hashcode, equals, toString etc. are omitted
}
It is a standard JPA entity.
  • An entity class must be annotated with @Entity
  • An entity should implement Serializable interface
  • An entity must include an identifier field(annotated with @Id), such as id of Post.
  • An entity should have a default none-arguments constructor. By default, if there is no explict constructor declaration, there is an implict none-argument constructor. If there is a constructor accepts more than one arguments, you have to add another none-argument explictly.
    For example.
    public Post(String title, String content){}
    public Post(){}//must add this constructor explictly
    
Optionally, it is recommended to implement your own equals and hashCode methods for every entities, if there is a requirement to identify them in a collection.
For example, there are a post existed in a collection, adding another Post into the same collection should check the post existance firstly.
publc class PostCollection{
    private List<Post> posts=new ArrayList<>();

    public void addPost(Post p){
        if(!this.posts.contains(p)){
            this.posts.add(p);
        }
    }
}
The title field can be used to identify two posts in a collection, because they are not presisted in a persistent storage at the moment, id value are same--null.
Implements equals and hashCode with title field of Post.
public boolean equals(Object u){ 
    // omitted null check
    return this.title=(Post)u.getTitle();
}

public int hashCode(){
    return 17* title.hashCode();
}
When an entity instance is being persisted into a database table, the id will be filled.
In JPA specification, there is a sort of standard id generation strategies available.
By default, it is AUTO, which uses the database built-in id generation approache to assign an primary key to the inserted record.
WARNING: Every databases has its specific generation strategy, if you are building an application which will run across databases. AUTO is recommended.
Other id generation strategies include TABLE, IDENTITY. And JPA providers have their extensions, such as with Hibernate, you can use uuid2 for PostgresSQL.

Lombok

Lombok is a greate helper every Java developer should use in projects. Utilize Java annotation processor, it can generate getters, setters, equals, hashCode, toString and class constructor at compile runtime with some Lombok annotations.
Add @Data to Post class, you can remove all getters and setters, and equals, hashcode, toString methods. The code now looks more clean.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "posts")
public class Post implements Serializable {     
    @Id()
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "title")
    private String title;

    @Column(name = "content")
    @Size(max = 2000)
    private String content;

    @Column(name = "status")
    @Enumerated(value = EnumType.STRING)
    private Status status = Status.DRAFT;

    @Column(name = "created_date")
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;
}
There are some annotations provided in Lombok to archive this purpose.
@Data is a composite annotation which includes @Getter, @Setter, @EqualsAndHashCode,@ToString etc. @Builder will generate an inner Builder class in the hosted class which provides a fluent API to build an object.
Add lombok dependency in pom.xml.
<!--Lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
</dependency>
If there are several JAP(Java annotaition processor) exist in the project, such as JPA metadata generator, it is better to add Lombok processor to maven compiler plugin.
For example.
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.5.1</version>
    <configuration>
        <compilerArgument>-Xlint</compilerArgument>
        <annotationProcessors>
            <annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor</annotationProcessor>
            <annotationProcessor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</annotationProcessor>
        </annotationProcessors>
    </configuration>
</plugin>
NOTE: If you are using Eclipse based IDE, such as Spring Tool Suite, or Intellij IDEA, you could have to install the Lombok plugin manually, check the Lombok download page for installation information. Luckily, NetBeans IDE can recognise the Lombok facilities automatcially.
Unlike JPA metadata generator which generates metedata source for JPA entities. Lombok modifies target classes directly.
Execute javap Post.class in command line, you can get the follwing info.
#>javap  classes\com\hantsylabs\restexample\springmvc\domain\Post.class
Compiled from "Post.java"
public class com.hantsylabs.restexample.springmvc.domain.Post implements java.io.Serializable {
  public com.hantsylabs.restexample.springmvc.domain.Post(java.lang.String, java.lang.String);
  public static com.hantsylabs.restexample.springmvc.domain.Post$PostBuilder builder();
  public java.lang.Long getId();
  public java.lang.String getTitle();
  public java.lang.String getContent();
  public com.hantsylabs.restexample.springmvc.domain.Post$Status getStatus();
  public com.hantsylabs.restexample.springmvc.domain.User getCreatedBy();
  public java.time.LocalDateTime getCreatedDate();
  public com.hantsylabs.restexample.springmvc.domain.User getLastModifiedBy();
  public java.time.LocalDateTime getLastModifiedDate();
  public void setId(java.lang.Long);
  public void setTitle(java.lang.String);
  public void setContent(java.lang.String);
  public void setStatus(com.hantsylabs.restexample.springmvc.domain.Post$Status);
  public void setCreatedBy(com.hantsylabs.restexample.springmvc.domain.User);
  public void setCreatedDate(java.time.LocalDateTime);
  public void setLastModifiedBy(com.hantsylabs.restexample.springmvc.domain.User);
  public void setLastModifiedDate(java.time.LocalDateTime);
  public boolean equals(java.lang.Object);
  protected boolean canEqual(java.lang.Object);
  public int hashCode();
  public java.lang.String toString();
  public com.hantsylabs.restexample.springmvc.domain.Post();
  public com.hantsylabs.restexample.springmvc.domain.Post(java.lang.Long, java.lang.String, java.lang.String, com.hantsylabs.restexample.springmvc.domain.Post$Status, com.hantsylabs.restexample.springmvc.domain.User, java.time.LocalDateTime, com.hantsylabs.restexample.springmvc.domain.User, java.time.LocalDateTime);
} 
It prints all signatures of members of Post.class. As you see all essential methods(getters, setters, equals, hashCode, toString) have been added into the Post.class, and there is a Post$Builder.class file existed in the same folder, which is an inner class in the Post and implements the Builder pattern.
You can create a Post object using Post builder like this.
Post post = Post.builder()
            .title("title of my first post")
            .content("content of my first post")
            .build();
Compare to following legacy new an object, the Builder pattern is more friendly to developers, and codes become more readable.
Post post = new Post();
post.setTitle("title of my first post");
post.setContent("content of my first post");

Model associations

Let's create other related models, Comment and User.
Comment class is associated with Post and User. Every comment should be belong to a post, and has an author(User).
@Getter
@Setter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "comments")
public class Comment implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    @Id()
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "content")
    private String content;

    @JoinColumn(name = "post_id")
    @ManyToOne()
    private Post post;

    @ManyToOne
    @JoinColumn(name = "created_by")
    @CreatedBy
    private User createdBy;

    @Column(name = "created_on")
    @CreatedDate
    private LocalDateTime createdDate;

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 89 * hash + Objects.hashCode(this.content);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Comment other = (Comment) obj;
        if (!Objects.equals(this.content, other.content)) {
            return false;
        }
        return true;
    }
}
User class contains fields of a user account, including username and password which used for authentication.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class User implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    @Id()
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    @Column(name = "name")
    private String name;

    @Column(name = "email")
    private String email;

    @Column(name = "role")
    private String role;

    @Column(name = "created_date")
    @CreatedDate
    private LocalDateTime createdDate;

    public String getName() {
        if (this.name == null || this.name.trim().length() == 0) {
            return this.username;
        }
        return name;
    }   
}   
A Post should have an author.
public class Post{

    @ManyToOne
    @JoinColumn(name = "created_by")
    @CreatedBy
    User createdBy;
}

Data persistence with JPA

Generally, in Spring application, in order to make JPA work, you have to configure a DataSource, EntityManagerFactory, TransactionManager.

JPA overview

JPA standardised Hibernate and it is part of Java EE specification since Java EE 5. Currently there are some popular JPA providers, such as Hibernate, OpenJPA, EclipseLink etc. EclipseLink is shipped wtih Glassfish, and Hibernate is included JBoss Wildfly/Redhat EAP.
In the above Modeling section, we have created models, which are JPA entities. In this section, let's see how to make it work.

Configure DataSources

Like other ORM frameworks, you have to configure a DataSource.
The DataSourceConfig defines a series of DataSource for differnt profiles.
@Configuration
public class DataSourceConfig {

    private static final String ENV_JDBC_PASSWORD = "jdbc.password";
    private static final String ENV_JDBC_USERNAME = "jdbc.username";
    private static final String ENV_JDBC_URL = "jdbc.url";

    @Inject
    private Environment env;

    @Bean
    @Profile("dev")
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .build();
    }

    @Bean
    @Profile("staging")
    public DataSource testDataSource() {
        BasicDataSource bds = new BasicDataSource();
        bds.setDriverClassName("com.mysql.jdbc.Driver");
        bds.setUrl(env.getProperty(ENV_JDBC_URL));
        bds.setUsername(env.getProperty(ENV_JDBC_USERNAME));
        bds.setPassword(env.getProperty(ENV_JDBC_PASSWORD));
        return bds;
    }

    @Bean
    @Profile("prod")
    public DataSource prodDataSource() {
        JndiObjectFactoryBean ds = new JndiObjectFactoryBean();
        ds.setLookupOnStartup(true);
        ds.setJndiName("jdbc/postDS");
        ds.setCache(true);

        return (DataSource) ds.getObject();
    }

}
In development stage("dev" profile is activated), using an embedded database is more easy to write tests, and speeds up development progress. In an integration server, it is recommended to run the appliation and integration tests on an environment close to production deployment. In a production environment,most of case, using a container managed datasource is effective.
The Spring profile can be activiated by an environment varible: spring.profiles.active. In our case, I used maven to set spring.profiles.active at compile time.
  1. In the maven profile section, there is spring.profiles.active property defined. eg.
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
    
            <log4j.level>DEBUG</log4j.level>
    
            <spring.profiles.active>dev</spring.profiles.active>
            <!-- hibernate -->
            <hibernate.hbm2ddl.auto>create</hibernate.hbm2ddl.auto>
            <hibernate.show_sql>true</hibernate.show_sql>
            <hibernate.format_sql>true</hibernate.format_sql>
    
            <!-- mail config -->
        </properties>
        //...
    </profile>
    
  2. Then used maven resource filter to replaced the placeholder defined in app.properties. Every maven profile could have a specific folder to hold the profiled based files. eg.
    <profile>
        <id>dev</id>
        //...
        <build>
            <resources>
                <resource>
                    <directory>src/main/resources-dev</directory>
                    <filtering>true</filtering>
                </resource>
            </resources>
        </build>
    </profile>
    
  3. After it is compiled, content of app.properties is filtered and replaced with the defined property.
    #app config properties
    spring.profiles.active=@spring.profiles.active@
    
    Becomes:
    #app config properties
    spring.profiles.active=dev
    
  4. In configuration class, add PropertySource to load the properties file.
    @PropertySource("classpath:/app.properties")
    @PropertySource(value = "classpath:/database.properties", ignoreResourceNotFound = true)
    public class AppConfig {
    
    }
    
NOTE: Read the Spring official document about Spring profile and Environment.

Configure JPA and transaction

Let's have a look at the JpaConfig, it defines a EntityManagerFactory and TransactionManager.
@Configuration
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
public class JpaConfig {

    private static final Logger log = LoggerFactory.getLogger(JpaConfig.class);

    private static final String ENV_HIBERNATE_DIALECT = "hibernate.dialect";
    private static final String ENV_HIBERNATE_HBM2DDL_AUTO = "hibernate.hbm2ddl.auto";
    private static final String ENV_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
    private static final String ENV_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";

    @Inject
    private Environment env;

    @Inject
    private DataSource dataSource;

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(dataSource);
        emf.setPackagesToScan("com.hantsylabs.restexample.springmvc");
        emf.setPersistenceProvider(new HibernatePersistenceProvider());
        emf.setJpaProperties(jpaProperties());
        return emf;
    }

    private Properties jpaProperties() {
        Properties extraProperties = new Properties();
        extraProperties.put(ENV_HIBERNATE_FORMAT_SQL, env.getProperty(ENV_HIBERNATE_FORMAT_SQL));
        extraProperties.put(ENV_HIBERNATE_SHOW_SQL, env.getProperty(ENV_HIBERNATE_SHOW_SQL));
        extraProperties.put(ENV_HIBERNATE_HBM2DDL_AUTO, env.getProperty(ENV_HIBERNATE_HBM2DDL_AUTO));
        if (log.isDebugEnabled()) {
            log.debug(" hibernate.dialect @" + env.getProperty(ENV_HIBERNATE_DIALECT));
        }
        if (env.getProperty(ENV_HIBERNATE_DIALECT) != null) {
            extraProperties.put(ENV_HIBERNATE_DIALECT, env.getProperty(ENV_HIBERNATE_DIALECT));
        }
        return extraProperties;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new JpaTransactionManager(entityManagerFactory().getObject());
    }
}   
Generally, JPA is activiated by META-INF/persistence.xml file in container.
The following is a classic JPA persistence.xml. The transaction-type is RESOUECE_LOCAL, another available option is JTA. Most of the time, Spring runs in a Servlet container which does not support JTA.
<persistence version="2.1"
   xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
        http://xmlns.jcp.org/xml/ns/persistence
        http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
   <persistence-unit name="primary" transaction-type="RESOUECE_LOCAL">

      <class>...Post</class>
      <none-jta-data-source/>
      <properties>
         <!-- Properties for Hibernate -->
         <property name="hibernate.hbm2ddl.auto" value="create-drop" />
         <property name="hibernate.show_sql" value="false" />
      </properties>
   </persistence-unit>
</persistence>
You have to specify the entity classes to be loaded, and datasource here. Spring provides a LocalEntityManagerFactoryBean to simplifies configuration work, there is a setPackagesToScan method to specify the package will be scanned, and reuse the Spring DataSource configuration for datasource configuration.
More simply, LocalContainerEntityManagerFactoryBean does not need persistence.xml any more and builds the JPA evironment from scratch. In above codes, we used LocalContainerEntityManagerFactoryBean simplifies JPA configuration.
Now we are ready for using JPA.
An example of JPA usage could like.
@Repository
@Transactional
public class PostRepository{

    @PersistenceContext
    private EntityManager em;
}
@Repository is an alias of @Component, Transactional is used to enable transaction on this bean. @PersistenceContext to inject an EntityManager to this bean. EntityManager provides a plent of methods to operate database.
For example,
em.persist(Post) to persist new entity. em.merge(Post) to merge the passed data into the entity existed and return a copy of the updated entity.
If you want to explore all methods provided in EntityManager, check EntityManager javadoc.

Spring Data JPA

Spring Data JPA simplifies JPA, please read an early post I wrote to discuss this topic.
Use a EnableJpaRepositories to activiate Spring Data JPA. basePackages specifies packages will be scanned by Spring Data.
@Configuration
@EnableJpaRepositories(basePackages = {"com.hantsylabs.restexample.springmvc"})
@EnableJpaAuditing(auditorAwareRef = "auditor")
public class JpaConfig {

    @Bean
    public AuditorAware<User> auditor() {
        return () -> SecurityUtil.currentUser();
    }

}
EnableJpaAuditing enable a simple auditing features provided in Spring Data JPA. There is some annotations are designated for it. Such as:
  • @CreatedBy
  • @CreatedDate
  • @LastModifiedBy
  • @LastModifiedDate
When AuditingEntityListener is activated globally in /META-INF/orm.xml. Any fields annotated with above annotaitions will be filled automatically when the hosted entity is created and updated.
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings 
    xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd" version="2.1">
    <persistence-unit-metadata>
        <persistence-unit-defaults>
            <entity-listeners>
                <entity-listener class="org.springframework.data.jpa.domain.support.AuditingEntityListener" />
            </entity-listeners>
        </persistence-unit-defaults>
    </persistence-unit-metadata>
</entity-mappings>
CreatedBy and LastModifiedBy try to find a AuditAware bean and inject it into these fields at runtime. A simple implementation is getting the current principal from the Spring Security context.

Repository

You can imagine a repository as a domain object collection, allow you retreive data from it or save change state back.
Spring Data Commons project defines a series of interfaces for common data operations for different storages, including NoSQL and RDBMS.
The top-level repository representation is the Repository interface.
CrudRepository subclasses from Repository and includes extra creating, retrieving, updating and deleting operations, aka CRUD.
A simple CRUD operations just need to create an interface extends CrudRepository.
interface MyPository extends CrudRepository{}
Create repository for Post.
public interface PostRepository extends JpaRepository<Post, Long>,//
        JpaSpecificationExecutor<Post>{

}
JpaRepository is a JPA specific repository and derived from PagingAndSortingRepository which also subclasses from CrudRepository and provides pagination and sort capability. It accepts a Pageable object as method arguments, and can return a paged result with Page class.
JpaSpecificationExecutor is designated for JPA Criteria Query API, and provides type safe query instead of literal based JPQL.
For example, to search post by input keyword.
public static Specification<Post> filterByKeywordAndStatus(
        final String keyword,//
        final Post.Status status) {
    return (Root<Post> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        if (StringUtils.hasText(keyword)) {
            predicates.add(
                cb.or(
                    cb.like(root.get(Post_.title), "%" + keyword + "%"),
                    cb.like(root.get(Post_.content), "%" + keyword + "%")
                )
            );
        }

        if (status != null) {
            predicates.add(cb.equal(root.get(Post_.status), status));
        }

        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    }; 
}
And you want to get a pageable result, you can use like this, just add a Pageable argument.
Page<Post> posts = postRepository.findAll(PostSpecifications.filterByKeywordAndStatus(q, status), page);
page argument is a Pageable object which can transfer pagination parameters from client request, and return result is a typed Page object, it includes the items of the current page and page naviation meta, such as total items, etc.

Application Service

A service can delegate CRUD operations to repository, also act as gateway to other bound context, such as messageing, sending email, fire events etc.
@Service
@Transactional
public class BlogService {

    private static final Logger log = LoggerFactory.getLogger(BlogService.class);

    private PostRepository postRepository;

    @Inject
    public BlogService(PostRepository postRepository){
        this.postRepository = postRepository;
    }

    public Page<PostDetails> searchPostsByCriteria(String q, Post.Status status, Pageable page) {

        log.debug("search posts by keyword@" + q + ", page @" + page);

        Page<Post> posts = postRepository.findAll(PostSpecifications.filterByKeywordAndStatus(q, status),
                page);

        log.debug("get posts size @" + posts.getTotalElements());

        return DTOUtils.mapPage(posts, PostDetails.class);
    }

    public PostDetails savePost(PostForm form) {

        log.debug("save post @" + form);

        Post post = DTOUtils.map(form, Post.class);

        Post saved = postRepository.save(post);

        log.debug("saved post is @" + saved);

        return DTOUtils.map(saved, PostDetails.class);

    }

    public PostDetails updatePost(Long id, PostForm form) {
        Assert.notNull(id, "post id can not be null");

        log.debug("update post @" + form);

        Post post = postRepository.findOne(id);
        DTOUtils.mapTo(form, post);

        Post saved = postRepository.save(post);

        log.debug("updated post@" + saved);

        return DTOUtils.map(saved, PostDetails.class);
    }

    public PostDetails findPostById(Long id) {

        Assert.notNull(id, "post id can not be null");

        log.debug("find post by id@" + id);

        Post post = postRepository.findOne(id);

        if (post == null) {
            throw new ResourceNotFoundException(id);
        }

        return DTOUtils.map(post, PostDetails.class);
    }
}
In this service there are some POJOs created for input request, response presentation etc.
PostForm gathers the user input data from client.
public class PostForm implements Serializable {

    @NotBlank
    private String title;

    private String content;
}   
Validation annotations can be applied on it.
PostDetails represents the return result of a post details. Usually, it includes some info that should not include in the PostForm, such as id, timestamp etc.
public class PostDetails implements Serializable {

    private Long id;

    private String title;

    private String content;

    private String status;

    private Date createdDate;

}   
Some exceptions are threw in the service if the input data can not satisfy the requirements. In the furthur post, I will focus on exception handling topic.
There is a DTOUtils which is responsible for data copy from one class to another class.
/**
 *
 * @author Hantsy Bai<hantsy@gmail.com>
 */
public final class DTOUtils {

    private static final ModelMapper INSTANCE = new ModelMapper();

    private DTOUtils() {
        throw new InstantiationError( "Must not instantiate this class" );
    }

    public static <S, T> T map(S source, Class<T> targetClass) {
        return INSTANCE.map(source, targetClass);
    }

    public static <S, T> void mapTo(S source, T dist) {
        INSTANCE.map(source, dist);
    }

    public static <S, T> List<T> mapList(List<S> source, Class<T> targetClass) {
        List<T> list = new ArrayList<>();
        for (int i = 0; i < source.size(); i++) {
            T target = INSTANCE.map(source.get(i), targetClass);
            list.add(target);
        }

        return list;
    }

    public static <S, T> Page<T> mapPage(Page<S> source, Class<T> targetClass) {
        List<S> sourceList = source.getContent();

        List<T> list = new ArrayList<>();
        for (int i = 0; i < sourceList.size(); i++) {
            T target = INSTANCE.map(sourceList.get(i), targetClass);
            list.add(target);
        }

        return new PageImpl<>(list, new PageRequest(source.getNumber(), source.getSize(), source.getSort()),
                source.getTotalElements());
    }
}
It used the effort of ModelMapper project.

Produces REST APIs with Spring MVC

Like other traditional action based framework, such as Apache Struts, etc, Spring MVC implements the standard MVC pattern. But against the benifit of IOC container, each parts of Spring MVC are not coupled, esp. view and view resolver are pluginable and can be configured.
For RESTful applications, JSON and XML are the commonly used exchange format, we do not need a template engine(such as Freemarker, Apache Velocity) for view, Spring MVC will detect HTTP headers, such as Content Type, Accept Type, etc. to determine how to produce corresponding view result. Most of the time, we do not need to configure the view/view resolver explictly. This is called Content negotiation. There is a ContentNegotiationManager bean which is responsible for Content negotiation and enabled by default in the latest version.
The configuration details are motioned in before posts. We are jumping to write @Controller to produce REST APIs.
Follows the REST design convention, create PostController to produce REST APIs.
@RestController
@RequestMapping(value = Constants.URI_API + Constants.URI_POSTS)
public class PostController {

    private static final Logger log = LoggerFactory
            .getLogger(PostController.class);

    private BlogService blogService;

    @Inject
    public PostController(BlogService blogService) {
        this.blogService = blogService;
    }

    @RequestMapping(value = "", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<Page<PostDetails>> getAllPosts(
            @RequestParam(value = "q", required = false) String keyword, //
            @RequestParam(value = "status", required = false) Post.Status status, //
            @PageableDefault(page = 0, size = 10, sort = "title", direction = Direction.DESC) Pageable page) {

        log.debug("get all posts of q@" + keyword + ", status @" + status + ", page@" + page);

        Page<PostDetails> posts = blogService.searchPostsByCriteria(keyword, status, page);

        log.debug("get posts size @" + posts.getTotalElements());

        return new ResponseEntity<>(posts, HttpStatus.OK);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<PostDetails> getPost(@PathVariable("id") Long id) {

        log.debug("get postsinfo by id @" + id);

        PostDetails post = blogService.findPostById(id);

        log.debug("get post @" + post);

        return new ResponseEntity<>(post, HttpStatus.OK);
    }

    @RequestMapping(value = "/{id}/comments", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<Page<CommentDetails>> getCommentsOfPost(
            @PathVariable("id") Long id,
            @PageableDefault(page = 0, size = 10, sort = "createdDate", direction = Direction.DESC) Pageable page) {

        log.debug("get comments of post@" + id + ", page@" + page);

        Page<CommentDetails> commentsOfPost = blogService.findCommentsByPostId(id, page);

        log.debug("get post comment size @" + commentsOfPost.getTotalElements());

        return new ResponseEntity<>(commentsOfPost, HttpStatus.OK);
    }

    @RequestMapping(value = "", method = RequestMethod.POST)
    @ResponseBody
    public ResponseEntity<ResponseMessage> createPost(@RequestBody @Valid PostForm post, BindingResult errResult) {

        log.debug("create a new post");
        if (errResult.hasErrors()) {
            throw new InvalidRequestException(errResult);
        }

        PostDetails saved = blogService.savePost(post);

        log.debug("saved post id is @" + saved.getId());

        HttpHeaders headers = new HttpHeaders();
        headers.setLocation(ServletUriComponentsBuilder.fromCurrentContextPath()
                .path(Constants.URI_API + Constants.URI_POSTS + "/{id}")
                .buildAndExpand(saved.getId())
                .toUri()
        );
        return new ResponseEntity<>(ResponseMessage.success("post.created"), headers, HttpStatus.CREATED);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    @ResponseBody
    public ResponseEntity<ResponseMessage> deletePostById(@PathVariable("id") Long id) {

        log.debug("delete post by id @" + id);

        blogService.deletePostById(id);

        return new ResponseEntity<>(ResponseMessage.success("post.updated"), HttpStatus.NO_CONTENT);
    }

}
@RestController is a REST ready annotation, it is combined with @Controller and @ResponseBody.
@RequestMapping defines URL, HTTP methods etc are matched, the annotated method will handle the request.
/api/posts stands for a collection of Post, and /api/posts/{id} is navigating to a specific Post which identified by id.
A POST method on /api/posts is use for creating a new post, return HTTP status 201, and set HTTP header Location value to the new created post url if the creation is completed successfully.
GET, PUT, DELETE on /api/posts/{id} performs retrieve, update, delete action on the certain post. GET will return a 200 HTTP status code, and PUT, DELETE return 204 if the operations are done as expected and there is no content body needs to be sent back to clients.

Run

For none Spring Boot application, run it as a general web application in IDE.
Tomcat also provides maven plugin for those maven users.
<plugin>
    <groupId>org.apache.tomcat.maven</groupId>
    <artifactId>tomcat7-maven-plugin</artifactId>
    <version>2.2</version>
    <configuration>
        <path>/angularjs-springmvc-sample</path>
    </configuration>
</plugin>
Execute this in the command line to start this application.
mvn tomcat7:run
NOTE: The tomcat maven plugin development is not active, if you are using Servlet 3.1 features, you could have to use other plugin instead.
Jetty is the fastest embedded Servlet container and wildly used in development community.
<plugin>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>9.3.7.v20160115</version>
    <configuration>
        <scanIntervalSeconds>10</scanIntervalSeconds>
        <stopPort>8005</stopPort>
        <stopKey>STOP</stopKey>
        <webApp>
            <contextPath>/angularjs-springmvc-sample</contextPath>
        </webApp>
    </configuration>
</plugin>
Execute the following command to run the application on an embedded Jetty server.
mvn jetty:run   
Another frequently used is Cargo which provides support for all popular applcation servers, and ready for all hot build tools, such as Ant, Maven, Gradle etc.
<plugin>
    <groupId>org.codehaus.cargo</groupId>
    <artifactId>cargo-maven2-plugin</artifactId>
    <configuration>
        <container>
            <containerId>tomcat8x</containerId>
            <type>embedded</type>
        </container>

        <configuration>
            <properties>
                <cargo.servlet.port>9000</cargo.servlet.port>
                <cargo.logging>high</cargo.logging>
            </properties>
        </configuration>
    </configuration>
</plugin>
Execute the following command to run the application on an embedded Tomcat 8 server with the help of Cargo.
mvn verify cargo:run    
For Spring boot application, it is simple, just run the application like this.
mvn spring-boot:run
By default, it uses Tomcat embedded server, but you can switch to Jetty and JBoss Undertow if you like. Check the Spring boot docs for details.

Source Code

Check out sample codes from my github account.
git clone https://github.com/hantsy/angularjs-springmvc-sample
Or the Spring Boot version:
git clone https://github.com/hantsy/angularjs-springmvc-sample-boot
Read the live version of thess posts from Gitbook:Building RESTful APIs with Spring MVC.

星期二, 四月 19, 2016

Getting started with Java EE8 MVC(9)-View engine

View engine

Spring MVC provides View and ViewResolver to find view and render view. MVC provides ViewEngine to do the same job.

View Engine

By default, ozark support tow built-in view engines to render JSP and Facelets, respectively.
In the org.glassfish.ozark.ext (check org.glassfish.ozark.ext) package, ozark provides several ViewEngine implementations for the popular template.
  • Thymeleaf
  • Freemarker
  • Velocity
  • Handlebars
  • Mustache
  • Asciidoc
  • Stringtemplate
  • Jade
  • JSR223(Script engine)

Facelets

Originally, Facelets is part of JSF specification, now it can be worked as a standard template engine in MVC.
  1. Activate Facelets in web.xml.
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
    
    We do not need face-config.xml file to activate JSF here.
  2. Convert all jsp files in the mvc sample to facelets template. Facelets supports Composite View pattern, we can define a template for all views.
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
        xmlns:f="http://xmlns.jcp.org/jsf/core"
        xmlns:h="http://xmlns.jcp.org/jsf/html"
        xmlns:c="http://java.sun.com/jsp/jstl/core">
        <f:view contentType="text/html" encoding="UTF-8">
            <ui:insert name="metadata"></ui:insert>
            <h:head>
                <meta charset="utf-8" />
                <title>Task List</title>
                <meta name="viewport" content="width=device-width, initial-scale=1.0" />
                <meta name="description" content="" />
                <meta name="author" content="" />
    
                <!-- styles -->
                <link href="#{request.contextPath}/webjars/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />
                <link href="#{request.contextPath}/webjars/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet" />
                <link href="#{request.contextPath}/webjars/bootstrap-material-design/0.3.0/css/material.min.css" rel="stylesheet"/>
                <link href="#{request.contextPath}/webjars/bootstrap-material-design/0.3.0/css/ripples.min.css" rel="stylesheet"/>
                <link href="#{request.contextPath}/webjars/bootstrap-material-design/0.3.0/css/roboto.min.css" rel="stylesheet"/>
    
                <link href="#{request.contextPath}/resources/css/main.css" rel="stylesheet" />
                <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
                <![CDATA[
                <!--[if lt IE 9]>
                <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
                <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
                <![endif]-->
                ]]>
    
                <ui:insert name="headIncludes"></ui:insert>
            </h:head>
    
            <h:body>
                <ui:include src="/WEB-INF/layout/header.xhtml" />
                <h:panelGroup layout="block" styleClass="container">
                    <ui:include src="/WEB-INF/layout/alert.xhtml" />
                    <h:panelGroup layout="block" styleClass="page-header">
                        <h1><ui:insert name="pageTitle"></ui:insert></h1>
                    </h:panelGroup>
                    <ui:insert name="content"/>
                </h:panelGroup>
    
                <ui:include src="/WEB-INF/layout/footer.xhtml"/>
                <!-- Placed at the end of the document so the pages load faster -->
                <script type="text/javascript" src="#{request.contextPath}/webjars/jquery/2.1.3/jquery.min.js">
                    /** stop autoclosing **/
                </script>
                <script type="text/javascript" src="#{request.contextPath}/webjars/bootstrap/3.3.5/js/bootstrap.min.js">
    
                </script>
                <script type="text/javascript" src="#{request.contextPath}/webjars/bootstrap-material-design/0.3.0/js/material.min.js">
    
                </script>
                <script type="text/javascript" src="#{request.contextPath}/webjars/bootstrap-material-design/0.3.0/js/ripples.min.js">
    
                </script>
                <script>
                    $.material.init();
                </script>
            </h:body>
        </f:view>
    </html>
    
    The template includes header and footer fragments directly, it also define a content placeholder, in the extended views the content will be inserted in the content definition.
    If you are familiar with JSF before, it is easy to understand the codes.
    Check out the codes and explore them yourself.
  3. Do not forget to change the view postfix to .xhtml in the TaskController. eg. the view in allTasks method.
    @GET
    @View("tasks.xhtml")
    public void allTasks() {
        log.log(Level.INFO, "fetching all tasks");
    
        List<Task> todotasks = taskRepository.findByStatus(Task.Status.TODO);
        List<Task> doingtasks = taskRepository.findByStatus(Task.Status.DOING);
        List<Task> donetasks = taskRepository.findByStatus(Task.Status.DONE);
    
        log.log(Level.INFO, "got all tasks: todotasks@{0}, doingtasks@{1}, donetasks@{2}", new Object[]{todotasks.size(), doingtasks.size(), donetasks.size()});
    
        models.put("todotasks", todotasks);
        models.put("doingtasks", doingtasks);
        models.put("donetasks", donetasks);
    
    } 
    

Source Codes

  1. Clone the codes from my github.com account.
    https://github.com/hantsy/ee8-sandbox/
  2. Open the mvc-facelets project in NetBeans IDE.
  3. Run it on Glassfish.
  4. After it is deployed and runging on Glassfish application server, navigate http://localhost:8080/ee8-mvc/mvc/tasks in browser.

Getting started with Java EE 8 MVC(8)-Parameter conversion

Parameter conversion

Spring MVC provides ConversionService to convert data to bean target type from from data.
In MVC, we can use custom ParamConverter to convert form data to the target type.

Parameter conversion

As an example, add a dueDate to Task entity. We need to convert the form field value(it is a String) to LocalDate type.
  1. Create a custom ParamConverterProvider for LocalDate.
    @Provider
    public class CustomConverterProvider implements ParamConverterProvider {
    
        final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ISO_DATE;
    
        @Override
        public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
    
            if (rawType.getName().equals(LocalDate.class.getName())) {
    
                return new ParamConverter<T>() {
                    @Override
                    public T fromString(String value) {
                        return value != null ? (T) LocalDate.parse(value, DATE_FORMAT) : null;
                    }
    
                    @Override
                    public String toString(T value) {
                        return value != null ? ((LocalDate) value).format(DATE_FORMAT) : "";
                    }
                };
            } 
            ....
    }
    
  2. Add a new field dueDate to TaskForm, which type is LocalDate.
    @NotNull
    @FormParam("duedate")
    private LocalDate dueDate;
    
  3. In the view, add a new form field named duedate.
    <input type="text" id="duedate" name="duedate" class="form-control" placeholder="Due date"/>
    
When the form is submitted, the duedate will be converted to LocalDate type and bind to dueDate property of TaskForm.

Format

Spring provides Formatting service in the display view. eg. @DateFormat and custom Formatters.
Unfortunately, MVC does not support such features. And JSTL fmt:formatDate does not accept Java 8 DateTime APIs.
We can define a custom CDI beans for date formatting, and call it via expression language.
  1. Create RequestScoped bean Formatters.
    @RequestScoped
    @Named("formatters")
    public class Formatters {
    
        static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_DATE;
    
        public String formatDate(LocalDate data) {
            return DATE_FORMATTER.format(data);
        }
    
    }
    
  2. Apply it on the dueDate in the details.jspx page.
    <dt>Due date:</dt>
    <dd>${formatters.formatDate(details.dueDate)}</dd>
    
The result looks like:
date formatting result

Source Codes

  1. Clone the codes from my github.com account.
    https://github.com/hantsy/ee8-sandbox/
  2. Open the mvc project in NetBeans IDE.
  3. Run it on Glassfish.
  4. After it is deployed and runging on Glassfish application server, navigate http://localhost:8080/ee8-mvc/mvc/tasks in browser.

Getting started with Java EE 8 MVC(7)-MVC Security

MVC Security

MVC has built-in some security features to protect pages, eg. CSRF protection.

CSRF protection

MVC has built-in CSRF protection, there is a Csrf interface.
  1. Configure Csrf in the Application class. Override the getProperties method.
    @Override
    public Map<String, Object> getProperties() {
        Map<String, Object> props = new HashMap<>();
    
        props.put(Csrf.CSRF_PROTECTION, Csrf.CsrfOptions.EXPLICIT);
    
        //view folder
        //props.put(ViewEngine.DEFAULT_VIEW_FOLDER, ViewEngine.VIEW_FOLDER);
        return super.getProperties();
    }
    
    And there are some options to configure CSRF via Csrf.CsrfOptions.
    • OFF to disable Csrf.
    • EXPLICIT to enable Csrf wtih annotation @CsrfValid on the Controller method.
    • IMPLICIT to enable Csrf autmaticially. No need @CsrfValid.
  2. Add annotation @CsrfValid on the Controller method.
    @POST
    @CsrfValid
    @ValidateOnExecution(type = ExecutableType.NONE)
    public Response save(@Valid @BeanParam TaskForm form) {
    }
    
  3. In the view, add hidden field to insert the Csrf value.
    <input type="hidden" name="${mvc.csrf.name}" value="${mvc.csrf.token}"/>
    
When you run the codes on Glassfish, in the view, the Csrf field looks like:
<input value="f3ca389f-efba-4f28-afe7-2a1e7231a238" name="X-Requested-By" type="hidden" />
Every request will generate a unique X-Requested-By value.
When the form is submitted, and it will be validated by MVC provider.

MvcContext

MvcContext interface includes the contextual data of MVC, such as context path, application path, etc. And also includes MVC security, such as Csrf and Encoders.
In the above section, we have used Csrf.
At the runtime environment, MvcContext is exposed by EL ${mvc} in the view.
  • ${mvc.contextPath} will get context path.
  • ${mvc.applicationPath} will get the application path declared in the Application class.
  • ${mvc.csrf.name} generate the Csrf token name.
  • ${mvc.csrf.token} generate the Csrf token value.
  • ${mvc.encoders.js(jsValue)} will escape the js scripts.
  • ${mvc.encoders.html(htmlValue)} will escape the html snippets.

Source Codes

  1. Clone the codes from my github.com account.
    https://github.com/hantsy/ee8-sandbox/
  2. Open the mvc project in NetBeans IDE.
  3. Run it on Glassfish.
  4. After it is deployed and runging on Glassfish application server, navigate http://localhost:8080/ee8-mvc/mvc/tasks in browser.

Getting started with Java EE8 MVC(6)-MVC and CDI

MVC and CDI

In before posts, we have already use @Inject to inject CDI beans into the Controller class. In fact, MVC embraced CDI internally.

RedirectScoped

There is a new CDI compatible scope named RedirectScoped introduced in MVC 1.0.
A bean annotated with @RedirectScoped annotation can be transfered between two requests, conceptly, it is similar with JSF Flash.
The AlertMessage is an example.
  1. Declare a bean with @RedirectScoped annotation.
    @RedirectScoped
    @Named("flashMessage")
    public class AlertMessage implements Serializable {}
    
  2. Inject it into a Controller class.
    @Inject
    AlertMessage flashMessage;
    
  3. Access the bean properties in the view via EL.
    <c:if test="${not empty flashMessage and not empty flashMessage.text}">
        <div class="alert alert-${flashMessage.type} alert-dismissible"
             role="alert">
            <button type="button" class="close" data-dismiss="alert"
                    aria-label="Close">
                <span aria-hidden="true"><![CDATA[&times;]]></span>
            </button>
            <p>${flashMessage.text}</p>
        </div>
    </c:if>
    
It is easy to understand the code snippets.
AlertMessage is a RedirectScoped bean, which means it is can be access in current request and the next request. It is named with CDI @Named which indicates it can be accessed in view via EL by name flashMessage.

MVC Event

MVC defined a series of CDI compatible events, with which you can track the MVC request lifecycle.
MVC provides a MvcEvent interface as the base of all events in MVC, there are several specific event implementations.
  • AfterControllerEvent
  • AfterProcessViewEvent
  • BeforeControllerEvent
  • BeforeProcessViewEvent
  • ControllerRedirectEvent
MVC itself will fire these events in the request processing progress, you can observe the events via CDI @Observes.
@ApplicationScoped
public class MvcEventListener {

    @Inject Logger LOGGER;

    private void onControllerMatched(@Observes BeforeControllerEvent event) {
        LOGGER.info(() -> "Controller matched for " + event.getUriInfo().getRequestUri());
    }

    private void onViewEngineSelected(@Observes BeforeProcessViewEvent event) {
        LOGGER.info(() -> "View engine: " + event.getEngine());
    }

    @PostConstruct
    private void init() {
        LOGGER.config(() -> this.getClass().getSimpleName() + " created");
    }
}

Source codes

  1. Clone the codes from my github.com account.
    https://github.com/hantsy/ee8-sandbox/
  2. Open the mvc project in NetBeans IDE.
  3. Run it on Glassfish.
  4. After it is deployed and runging on Glassfish application server, navigate http://localhost:8080/ee8-mvc/mvc/tasks in browser.

Getting started with Java EE8 MVC(5)-Page navigation

Page navigation

As introducing the feature pieces of the MVC specification, we have visited most of views in this demo. In fact, when you desgin a website, you should have a map for the page navigation among all views.

View navigation summary

  1. When the appliation starts up, it will display the task list view.
  2. User can create a new task by clicking the New Task button, the task creating view is displayed.
  3. When user input the task title and description, submit the form.
    • If the task is submitted successfully, return the main task list view. The new created task should be in the list.
    • If the task form data is invalid, show the validation errors.
  4. User can edit the new created task by click edit icon.
    • It will read the task from database and fill the existing data into form fields
    • The submission progress is similiar with the new task subflow described in above 3.
  5. User can update the task status(TODO to DOING, or DOING to DONE). When the status is changed successfully, return to the task list.
  6. User can delete the DONE task. When it is deleted, return to the task list.
//todo add a graph to display the navigations []()

View navigation path definition

We have used GET, POST, PUT, DELETE http methods to process basic CRUD operations, listed in the following table.
Navigation path HTTP method View page Descritption
/tasks GET tasks.jspx Task list view
/tasks/new GET add.jspx Display new task view
/tasks POST redirect:tasks Submit task form, save task, and redirect to task list view
/tasks/{id}/edit GET edit.jspx Display edit task view
/tasks/{id} PUT redirect:tasks Submit task form, update task, and redirect to task list view
/tasks/{id} DELETE redirect:tasks Submit task form, delete task, and redirect to task list view
/tasks/{id}/status PUT redirect:tasks Submit task form, update task status, and redirect to task list view
It is easy to understand. As you see, the navigation rules are very similar with the paths for RESTful APIs.

Display views

In the above paths, the task list view, new task view and edit task view can display the views directly via inputing the url path in browser locaiton bar. Others are POST submission, they will redirect to the task list view.
There are several approaches provided in MVC specification to navigate the view path.
  1. Add @View annotation to a void controller method.
  2. Return a Viewable object to a controller method.
  3. Return a Response entity which body content includes the view path.
    public Response someAction(){
         return Response.ok("redirect:tasks").build();
    }
    
  4. Return a String type value directly.
    public String someAction(){
         return "redirect:tasks";
    }   
    
The 1 and 2 are new added to the MVC specification, and the 3rd is reusing the existing JAXRS specification.

POST-REDIRECT-GET pattern

The new task form submission and edit task form submission follow the POST-REDIRECT-GET pattern.
  1. When the form is submitted successfully, a form POST request is sent to the server.
  2. After the server received the request, and processed it, it will REDIRECT to the target view.
  3. And perform a new GET request on the target view.

Source codes

  1. Clone the codes from my github.com account.
    https://github.com/hantsy/ee8-sandbox/
  2. Open the mvc project in NetBeans IDE.
  3. Run it on Glassfish.
  4. After it is deployed and runging on Glassfish application server, navigate http://localhost:8080/ee8-mvc/mvc/tasks in browser.