MapStruct

MapStruct is a serious alternative for Dozer bean mapping project. It’s a code generator which simplifies the implementation of mappings between Java bean types based on a convention over configuration approach.

MapStruct solves some traditional mapping issues like a lack of performance, difficult mapping code debugging and late errors détection (usually in code execution).

This posts presents how to use MapStruct by dealing with bean mapping case.

Maven configuration

MapStruct is an annotation processor which is plugged into the Java compiler. That why we need to configure maven processor plugin to parse the mapping annotations and generate code.

    <dependencies>
      <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
      </dependency>
      <!-- other project dependencies -->
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.bsc.maven</groupId>
                <artifactId>maven-processor-plugin</artifactId>
                <version>2.2.4</version>
                <configuration>
                    <defaultOutputDirectory>
                        ${project.build.directory}/generated-sources
                    </defaultOutputDirectory>
                    <processors>
                        <processor>org.mapstruct.ap.MappingProcessor</processor>
                    </processors>
                </configuration>
                <executions>
                    <execution>
                        <id>process</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>process</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

Source classes

The source code bellow, presents the source class and its dependencies.

public class User {

    private String fullName;
    private Address address;
    private List<Authority> authorities;

    // Add getters and setters
}


public class Address {

    private String street;
    private String city;
    private String state;
    private String zipCode;

    @Override
    public String toString() {
        return street + ", " + city + ", " + state + ", " + zipCode;
    }
    
    // Add getters and setters
}

public class Authority {
    private String code;
    private String value;
    
    // Add getters and setters
}

Destination classes

The destination class has a String type address and a set of string for authorities values.

The mapper have to do three actions:

  1. Copy name field value.
  2. Convert Address object to String.
  3. Convert List to Set.

public class UserDto {

    private String name;
    private String address;
    private Set<String> authorities;

    // Add getters and setters
}

Mapping code

@Mapper
public abstract class UserMapping {
    public static UserMapping INSTANCE = Mappers.getMapper(UserMapping.class);

    @Mappings({
         @Mapping(source="fullName", target="name"),
         @Mapping(target="address", expression="java(user.getAddress().toString())")
    })
    public abstract UserDto userToDto (User user);

    @IterableMapping(elementTargetType = String.class)
    protected abstract Set<String> mapListToSet(List<Authority> value);

    protected String mapAuthorityToString(Authority authority) {
        return authority.getValue();
    }
}

Generated mapping code

Mapping code will be gerated in the generate-sources phase when executing Maven install goal.

If there is no mapping specification error, you can check mapping code under project-folder/target/generated-sources folder.

@Generated(value = "org.mapstruct.ap.MappingProcessor",  date = "today",
    comments = "version: 1.0.0.CR2, compiler: javac, 
    environment: Java 1.7.0 (Sun Microsystems Inc.)"
)
public class UserMappingImpl extends UserMapping {

    @Override
    public UserDto userToDto(User user) {
        if ( user == null ) {
            return null;
        }

        UserDto userDto = new UserDto();
        userDto.setName( user.getFullName() );
        userDto.setAuthorities( mapListToSet( user.getAuthorities() ) );
        userDto.setAddress( user.getAddress().toString());
        return userDto;
    }

    @Override
    protected Set<String> mapListToSet(List<Authority> value) {
        if ( value == null ) {
            return null;
        }
        Set<String> set_ = new HashSet<String>();
        for ( Authority authority : value ) {
            set_.add( mapAuthorityToString( authority ) );
        }
        return set_;
    }
}

Test beans mapping

public class MappingTest {
    @Test
    public void testMapping() {
        Address address = new Address( "street", "city", "state", "zipCode");
        User user = new User("name", address, Arrays.asList(new Authority("1", "admin"), new Authority("2", "user")));

        UserDto userDto = UserMapping.INSTANCE.userToDto(user);

        Assert.assertEquals("name", userDto.getName());
        Assert.assertEquals("street, city, state, zipCode", userDto.getAddress());
        Assert.assertTrue(userDto.getAuthorities().containsAll(Arrays.asList("admin", "user")));
    }

}

In this posts, we dealt with some MapStruct features like collections mapping and expressions. It has other mapping features that can fit your mapping needs like Decorators, Enum type mapping and customized mappings with Before/After mapping methods.

At the moment when I’m writing this article, Mapstruct is in RC2 version. I think soon we will have a release which is mature enough to be considered for production use.

Advertisements

Dozer – Bean mapping

Mapping frameworks helps to structure beans mapping code and to avoid a mess of nonfunctional code all over the application classes.

Dozer is commonly used mapping framework. It provides deferent beans mappings modes (XML, API and annotation). But if you have a complex beans mapping case, you may need to use Dozer API with a custom mapping class.

    Dozer

The aim of this post is to provide a solution a tricky java mapping case using custom converter.

Add dependency

<dependency>
	<groupId>net.sf.dozer</groupId>
	<artifactId>dozer</artifactId>
	<version>5.5.1</version>
</dependency>

Source classes

public class User {

    private String name;
    private Address address;
    private List<Authority> authorities;

    // Add getters and setters
}


public class Address {

    private String street;
    private String city;
    private String state;
    private String zipCode;

    @Override
    public String toString() {
        return street + ", " + city + ", " + state + ", " + zipCode;
    }
    
    // Add getters and setters
}

public class Authority {
    private String code;
    private String value;
    
    // Add getters and setters
}

Destination classes

The destination class has a string type address and a set of string for authorities values.

The mapper have to do three actions:

  1. Copy name field value.
  2. Convert Address object to String.
  3. Convert List to Set.

public class UserDto {

    private String name;
    private String address;
    private Set<String> authorities;

    // Add getters and setters
}

Mapping class

The code source bellow prentes the mapping class code.

public class UserToDtoMapping extends BeanMappingBuilder {
    @Override
    protected void configure() {
        mapping(User.class, UserDto.class)
                .fields("name", "name")
                .fields("address", "address")
                .fields("authorities", "authorities", 
                        FieldsMappingOptions.customConverter(AuthoritiesConverter.class));
    }

    public static class AuthoritiesConverter implements CustomConverter {
        public Object convert(Object existingDestinationFieldValue, Object sourceFieldValue, Class<?> destinationClass, Class<?> sourceClass) {
            Set<String> values = new HashSet<String>();
            List<Authority> authorities = (List<Authority>)sourceFieldValue;

            if(CollectionUtils.isEmpty(authorities)) {
                return values;
            }

            for(Authority a : authorities) {
                values.add(a.getValue());
            }
            return values;
        }

    }
}

User name field is a basic case; it will be copied from source to destination without any extra code.
For the address, Dozer will call toString method to convert Address bean to String value, that’s why we override the toString method.
Converting the authorities List to a String Set needs a CustomConverter implementation.

Test beans mapping

public class MappingTest {
    private DozerBeanMapper mapper = new DozerBeanMapper();

    @Before
    public void init() {
        mapper.addMapping(new UserToDtoMapping());
    }

    @Test
    public void testMapping() {
        Address address = new Address( "street", "city", "state", "zipCode");
        User a = new User("name", address, Arrays.asList(new Authority("1", "admin"), new Authority("2", "user")));
        UserDto b  = mapper.map(a, UserDto.class);
        Assert.assertEquals("name", b.getName());
        Assert.assertEquals("street, city, state, zipCode", b.getAddress());
        Assert.assertTrue(b.getAuthorities().containsAll(Arrays.asList("admin", "user")));
    }
}

Dozer is great bean mapping framework, but it has some drawbacks like performance issue and mapping error detection at code execution.

If you are looking for Dozer alternative to get better performance and earlier mapping error detection, you have to consider new beans mapping frameworks like MapStruct and Orika.

Spring MVC – Full java based config

Spring MCV is a very powerful and flexible framework for web application development.

It doesn’t just implement the MVC pattern, it allows you to develop Rest web services.

   

This post presents a web project sample with a full java configuration using Spring MVC 4 and Servlet 3 api. The source code is available in this link.

This project implements client side for the persistence sample presented in Spring-Data-JPA article

1 – Add project dependencies

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.0.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>3.0.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.9.13</version>
</dependency>

2 – Spring MVC config class

import org.springframework.context.annotation.*;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@EnableWebMvc
@Configuration
@ComponentScan(basePackages = { "com.mycompany.myproject.web.controller" })
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
    public InternalResourceViewResolver jspViewResolver() {
        InternalResourceViewResolver bean = new InternalResourceViewResolver();
        bean.setPrefix("/WEB-INF/views/");
        bean.setSuffix(".jsp");
        return bean;
    }

    @Bean(name = "multipartResolver")
    public CommonsMultipartResolver getMultipartResolver() {
        return new CommonsMultipartResolver();
    }

    @Bean(name = "messageSource")
    public ReloadableResourceBundleMessageSource getMessageSource() {
        ReloadableResourceBundleMessageSource resource = new ReloadableResourceBundleMessageSource();
        resource.setBasename("classpath:messages");
        resource.setDefaultEncoding("UTF-8");
        return resource;
    }

}


3 – Webapp initializer

With servlet 3 API you can use a class to configure your web project instead of the classic web.xml file.
You have to check if your application server or your servlet container supports servlet 3 spec. For Tomcat you can have this information in this link:
tomcat.apache.org/whichversion

import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;

public class WebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        // Create the 'root' Spring application context
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(ServiceConfig.class, JPAConfig.class, SecurityConfig.class);

        // Manage the lifecycle of the root application context
        container.addListener(new ContextLoaderListener(rootContext));

        // Create the dispatcher servlet's Spring application context
        AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
        dispatcherServlet.register(MvcConfig.class);
            
        // Register and map the dispatcher servlet
        ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
        
    }

 }


4 – Model class.

public class User {
 
    private Long id;

    private String firstName;

    private String familyName;

    private String email;

    // add extra attributes
    	
    // add getters and setters
	
}

5 – Add view as jsp page
We create a usersView.jsp page with code source bellow to display users list.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<div class="col-sm-offset-1 col-sm-10">

    <legend>
        <spring:message code="table.user.title" />
    </legend>

    <div>
        <table id="dataTable" class="table table-striped table-bordered">
            <thead>
                <tr>
                    <th><spring:message code="table.user.id" /></th>
                    <th><spring:message code="table.user.firstName" /></th>
                    <th><spring:message code="table.user.falilyName" /></th>
                    <th><spring:message code="table.user.email" /></th>
                </tr>
            <thead>
            <tbody>
                <c:forEach var="u" items="${usersModel}">
                    <tr>
                        <td>${u.id}</td>
                        <td>${u.firstName}</td>
                        <td>${u.familyName}</td>
                        <td>${u.email}</td>
                    <tr>
                </c:forEach>
            </tbody>
        </table>
    </div>
</div>

6 – Develop your controller

The @Controller annotation indicates that a particular class serves the role of a controller.
The @RequestMapping annotation is used to map a URL to either an entire class or a particular handler method.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
public class UserController {

    @Autowired
    private UserService userService;
 
    /**
    * Request mapping for user
    */
    @RequestMapping(value = "users", method = RequestMethod.GET)
    public ModelAndView getUsersView() {
        ModelAndView mv= new ModelAndView("usersView");
        mv.addObject("usersModel", userService.findAll());
        return mv;
    }
    
    /**
    * Rest web service
    */
    @RequestMapping(value = "/usersList", method = RequestMethod.GET)
    public @ResponseBody List<UserDto> getUsersRest() {
        return userService.findAll();
    }
}

7 – Test web project
When building your project you may have maven error telling you that the web.xml file is missing.
You can fix it by configuring the maven war plugin as presented bellow.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <configuration>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </configuration>
</plugin>

  • Test display users
    http://localhost:8080/myproject/users
    spring_mvc_test

  • Test rest web service
    http://localhost:8080/myproject/usersList
    You will get json reponse.

    [
        {
            "id": 1,
            "firstName": "adminName",
            "familyName": "adminFamily",
            "email": "admin@maycompany.com",
            "phone": "0033 1 23 45 67 89",
            "language": "en",
            "login": "admin",
            "password": "admin",
            "burthDate": null,
            "authorities": [
                {"id": 3, "name": "user"},
                {"id": 2, "name": "technical user"},
                {"id": 1, "name": "admin"}
            ],
            "enabled": true,
            "pictureId": null,
            "authoritiesAsString": "user, technical user, admin"
        },
        ..........
    ]
    

Here’s the how to video created by Webucator.com. Check out their Spring training classes.

Logback – Dynamic creation of log files

Some project have some log constraints that are difficult to implements with Log4j.

Logback is an alternative to log4j. It gives you more possibilities and it can easily implements your log policies.

    log1

Origami by Brian Chan

In this post, I present a simple of Logback configuration for a maven project. This configuration implements a dynamic log files generation.

Sample project description

This post example is inspired from a batch log policy I dealt with. The batch processes N files and generates two logs for each processed file. The first one is a summary of logs with no stack trace for functional users and the second one is a detailed log for programmers.

The picture bellow presents this case of study.

logback
Below the summary of log policies to implement:

  1. Logging errors without stacktrace.
  2. Dynamic file logging (switch to new log file).
  3. Read logging directory from application properties file.
  4. Create log files name with timestamp.

1. Add Maven dependency

First we need to add Slf4j and logback dependency to the project.

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.5</version>
</dependency>

<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
	<version>1.0.13</version>
</dependency>

<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-core</artifactId>
	<version>1.0.13</version>
</dependency>

<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-access</artifactId>
	<version>1.0.13</version>
</dependency>

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>jcl-over-slf4j</artifactId>
	<version>1.7.5</version>
</dependency>

2. Create a costom layout Class

To customise logging layout, we create a class which extends LayoutBase and override the doLayout method.

public class CustomLogLayout extends LayoutBase<ILoggingEvent>{

	@Override
	public String doLayout(ILoggingEvent event) {
		StringBuffer sbuf = new StringBuffer(128);
	    sbuf.append(new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date(event.getTimeStamp())));
	    sbuf.append(" ");
	    sbuf.append(String.format("%-6s", event.getLevel()));
	    sbuf.append(" ");
	    sbuf.append(event.getFormattedMessage());
	    if((event.getLevel() == Level.ERROR || event.getLevel() == Level.WARN) && event.getThrowableProxy() !=null) {
	    	sbuf.append(" - ");
	    	sbuf.append(StringUtils.substringBefore(event.getThrowableProxy().getMessage(), IOUtils.LINE_SEPARATOR));
	    }
	    sbuf.append(CoreConstants.LINE_SEPARATOR);
	    return sbuf.toString();
	}

}

3. Logback configuration

In this step we create Logback.xml config file that will use the costomLayout we created in the previous step and will implements the logs policies.

  • Line 3 indicates the path to properties file where it will look for some values. In our case the path is a system property.
  • Line 5 specifies a key for the timestamp value.
  • STIF appender creates a custom log files with the CustomLogLayout class.
  • loggerFileName is a MDC (Mapped Diagnostic Contexts) property. Changing this value will generate a new file.
  • SIFT-TRACE appender creates a verbose log file and it uses logback.classic.PatternLayout.

<configuration>

	<property file="${appli.config.path}" />

	<timestamp key="bySecond" datePattern="yyyyMMddHHmmss" />

	<appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">

		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<level>INFO</level>
		</filter>
		<discriminator>
			<Key>loggerFileName</Key>
			<DefaultValue>unknown</DefaultValue>
		</discriminator>
		<sift>
			<appender name="FILE-${loggerFileName}" class="ch.qos.logback.core.FileAppender">
				<File>${log.dir}/${loggerFileName}_${bySecond}.log</File>
				<Append>false</Append>
				<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
					<layout class="com.mycompany.log.CustomLogLayout" />
				</encoder>
			</appender>
		</sift>
	</appender>

	<appender name="SIFT-TRACE" class="ch.qos.logback.classic.sift.SiftingAppender">
		<discriminator>
			<Key>loggerFileName</Key>
			<DefaultValue>unknown</DefaultValue>
		</discriminator>
		<sift>
			<appender name="TRACE-${loggerFileName}" class="ch.qos.logback.core.FileAppender">
				<File>${log.dir}/TRACE_${loggerFileName}_${bySecond}.log</File>
				<Append>false</Append>
				<layout class="ch.qos.logback.classic.PatternLayout">
					<Pattern>%d %-5level %logger{5} - %msg%n</Pattern>
				</layout>
			</appender>
		</sift>
	</appender>

	<logger name="org.springframework" level="ERROR" />
	
	<root level="DEBUG">
		<appender-ref ref="SIFT" />
		<appender-ref ref="SIFT-TRACE" />
	</root>

</configuration>

4. Test config

In this step we test the logback configuration.

setup() method set system property for the properties file path where logback will look for loging directory path.

The test simulates processing N files and at the end it verifies that we have 2xN log files.

public class LogTest {

	private static final int TEST_FILE_NUM = 5;

	private static final File TEST_DIR = new File(System.getProperty("java.io.tmpdir"), "logbak-test-dir");

	@Before
	public void setUp() throws IOException {
		System.setProperty("appli.config.path", ClassLoader.getSystemClassLoader().getResource("logback.properties").getPath());
		TEST_DIR.mkdirs();
		FileUtils.cleanDirectory(TEST_DIR);
	}


	@Test
	public void testLogbackFileConfig() throws InterruptedException {

		Logger logger = LoggerFactory.getLogger(LogTest.class);

		// assume SLF4J is bound to logback in the current environment
		LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
		JoranConfigurator configurator = new JoranConfigurator();
		configurator.setContext(context);
		context.reset();

		try {
			configurator.doConfigure(ClassLoader.getSystemClassLoader().getResource("logback.xml").getFile());
		} catch (JoranException je) {
			StatusPrinter.print(context);
			Assert.fail("Erreur de chargement des paramètre logback");
		}

		for (int i = 1; i <= TEST_FILE_NUM; i++) {
			MDC.put("loggerFileName", "file" + i);
			logger.info("log in File{}", i);
			logger.debug("log in File{}", i);
			logger.error("log in File" + i, new Exception("My test exception", new Exception("my cause of exception")));
		}

		Assert.assertEquals(TEST_FILE_NUM * 2, TEST_DIR.listFiles().length);
	}
	
}