×

Warning

EU e-Privacy Directive

This website uses cookies to manage authentication, navigation, and other functions. By using our website, you agree that we can place these types of cookies on your device.

View Privacy Policy

View e-Privacy Directive Documents

View GDPR Documents

You have declined cookies. This decision can be reversed.
In our last session we have choosen an open source software license for our project and applied that together with further documentation to our existing code base. Today, we will focus again on a more technical topic. We will ...
  • explore the various possiblities to build a presentation layer
  • make a strategic decision regarding the UI framework  to use
  • implement the necessary changes
 
This is how the high-level architecture of our application looked like after our latest changes (done in the session Accessing a cloud service):
architecture overview
 
Before we can start, we need to get an understanding of the terms at will be used throughout this article

Disambiguation

The mentioned view technologies in this article are basically software libraries that provide capabilities to build and process the input from user interfaces in JAVA web applications. They can be classified into the following types:
  • web frameworks: provide an API to process the input from user interfaces (below you can find a more detailed desricption regarding two concurrent approaches)
  • template engines: define the presentation of the (meaning: the rendering of webpages)
  • extension libraries: contain reusable widgets that can be incorporated in the templates (see above) and are often added to improve the user experience
 Web frameworks can be futher classified into ...
  • component based frameworks
  • request/action based frameworks
These two classifications represent two concurrent approaches that can co-exits in one application but generally can't be used together. The reason for that lies in the strategy to process the users input from a webpage. While request/action based frameworks completely rely on the underlying request/response model of HTTP, component based frameworks abstract from that request/response model so much that it deals only with components and events of an application. As a result, component based frameworks usually define the entire webpages as components of an application (much like a desktop application based on AWT or SWT). At a first glance, this makes request based frameworks more transparent and easier to understand for the web developers and component based frameworks easier to understand for developers with experience in building desktop applications.
 
The following table lists some common view technologies (that you will inevitably encounter when you explore the various possiblities to build a presentation layer for JAVA web applications) and maps them to the classifications from above:
term classification
Spring MVC request/action based web framework
Apache Struts request/action based web framework
JSF component based web framework (API corresponding to JSR 344)
Apache MyFaces component based web framework (JSF implementation provided by the Apache Software Foundation)
Morraja component based web framework (JSF implementation provided by the Oracle)
Spring WebFlow component based web framework
GWT component based web framework
Vaadin component based web framework
Apache Tapestry component based web framework
Apache Wicket component based web framework
Sitemesh template engine
Thymeleaf template engine
Velocity template engine
Apache Tiles template engine
Primefaces extension library with JSF based widgets
Dojo Toolkit extension library with JavaScript based widgets
The named view technologies from above can be mixed and matched - almost in an arbitrary fashion (a few restrictions like "request based vs. component based" web frameworks have to be considered). Thus, we could create a very high amount of different view technology stacks. Some of them are more common than others.

Definition of view technology stacks

In order to keep efforts low we have to focus on some typical stacks which seem to make sense. On one hand, it will be interesting to see the difference between an implementaion that is based on a request based framework (this is what we actually have implemented with SpringMVC since the beginning of the project) and a component based framework. While GWT or Vaadin seem to be the most radical implementations of a component based framework, Spring WebFlow and JSF offer an interesting middle course. As a result, it makes sense to investigate the following view technology stacks further:
  • Stack 1: Spring WebMVC + Thymeleaf + Spring JS (including Dojo Toolkit)
  • Stack 2: Vaadin Framework
  • Stack 3: Spring WebFlow + Morraja (JSF Impl.) + Primefaces
Of course, there can be other combinations which might be more suitable for you respectively cater better for your needs (there is simply not THE best combination). However, this article focuses only on the stacks listed above.

Evaluation of view technology stacks

At first, we need to define the most important criteria for the selection of a view technology stack. The following criteria is relevant:
  • the used view technologies must not be commercial products - we will continue to follow an open source only approach
  • the used view technologies have to integrate well with existing implementations from our project based on the Spring eco-system (especially Spring Security, Spring Cloud and Spring Data/JPA)
  • the used view technologies have to offer ready to use UI components

For each of the stacks mentioned above I have created a branch in my Github repository:

branches
These are the links to the different branches (feel free to explore them on your own):
 
After playing around and implementing these stacks, I can summarize the following key findings:
  • Stack 1 obviously integrates very well with our current implementation because that was already based on request based framework Spring WebMVC and leveraged Thymeleaf as template engine. The extension library Spring JS (including the Dojo Toolkit) with its capabilities to enrich the web page by decorating the existing HTML with JS widgets enables more user friendly web pages. As good as this sounds in theory as bad it was to implement. This was because of the lack of coherent documentation and properly working examples (in fact, I spent more than 2 days before I finally gave up).
  • Stack 2 with the component based framework Vaadin works completely different to our current imlementation. Instead of multiple entry points (URLs) that get picked up by different controllers and methods there is only one Vaadin Servlet that renders the single components of the web page and handles the events (e. g. navigation from one view to another). Because of that, the integration with Spring Security is not fully supported and has to be tweaked a little (e. g. it is recommended to render the login page without Vaadin because otherwise the whole login mechanism had to be replicated). Despite of that Vaadin integrates pretty well with the remaining Spring-based building blocks of our application. It has a lot of widgets that can take the user experience to another level. For some reason, the Vaadin Designer did not work in my existing IDE (STS) but it seems to be a great tool that makes it possible to build nice UIs in a fraction of the time that is usually needed.
  • Stack 3 - basically based on Spring Webflow and JSF - integrates well with all existing Spring-based building blocks of our application. In comparision to the other stacks it required the most adjustments in configuration. But once this is done adding new or changing existing webflows become really easy. Furthermore, the implementation as webflows forces you to think in process steps and leads to a really clean seperation of concerns in the application. Addtionally, Primefaces offers a lot of JSF based components that help to deliver a richer user experience.

Looking at the initial selection criteria, I have to come to the following conclusions:

criteria Stack 1 Stack 2 Stack 3
open source framework  checked checkbox  checked checkbox checked checkbox
integration with the existing Spring-based eco-system    checked checkbox   checked checkbox
ready to use UI components    checked checkbox checked checkbox
 
Stack 1 has disqualified because I didn't manage to implement a working application within 2 days. I may be that I'm incapable to build something with the Dojo Framework or that the documenation and examples really do not contribute to an understanding. Either way: it has been simply to complicated in comparison the other alternatives.
Although I liked Stack 2 - based on the Vaadin Framework -  it hasn't entirely convinced me either. This is mainly because of the limited integration respecitvely overlap in certain areas with Spring Secturity. Furthermore, it is quite easy to mix different concerns of the application within the view components that are implemented in pure JAVA code. (I have to admit: it is pretty nice not to implement the whole application in only one programming language).
Stack 3 seems to be the best option as it fulfils all my criteria. Furthermore, it is pretty straight-forward to implement a webflow as the required xml can be put together quickly based on an activity diagram. This will come in handy when we go forward and extend the application to support various (sub-)processes. Spring Security is entirely supported by Spring webflow and allows for very fine-grained access control. Addtionally, Primefaces is a very mature and extensive component library that supports everything we will need for a pleasent user experience. This entire setup will (almost inevitably) enforce a nice and clean application architecture.

Adjust the application

At first, we need to do a couple of adjustments in the pom.xml:

1.) include Spring WebFlow, JSF and Primefaces:

<!-- Spring Webflow: spring faces -->   
<dependency>   
	<groupId>org.springframework.webflow</groupId>   
	<artifactId>spring-faces</artifactId>  
	<version>2.4.4.RELEASE</version>   
</dependency>   
  
<!-- JSF implementation: Mojarra -->   
<dependency>   
	<groupId>com.sun.faces</groupId>   
	<artifactId>jsf-api</artifactId>  
	<version>2.2.13</version>   
</dependency>   
<dependency>   
	<groupId>com.sun.faces</groupId>   
	<artifactId>jsf-impl</artifactId>  
	<version>2.2.13</version>   
</dependency>   
  
<!-- JSF extension: Primefaces -->   
<dependency>   
	<groupId>org.primefaces</groupId>   
	<artifactId>primefaces</artifactId>  
	<version>6.0</version>  
</dependency> 

2.) set packaging type explicitly to war (this is needed to build the application properly):

<packaging>war</packaging>  

3.) add dependency for spring security taglib in order to be able to access Spring Security functionality in JSF facelets:

<dependency>  
	<groupId>org.springframework.security</groupId>  
	<artifactId>spring-security-taglibs</artifactId>  
</dependency> 

Addtionally, we need to store some configuration files for JSF and Spring security. That's why we have to create a webapp containing a WEB-INF directory underneath src/main. This directory will be picked up and included in the application when it is built. A boringly ordinary faces-config.xml has to be placed underneath src/main/webapp/WEB-INF. Furthermore, a Spring Security specific taglib has to be stored at src/main/webapp/WEB-INF/springsecurity.taglib.xml. This will allow us later on to use spring security functionality in JSF faclets.

In our next step, we have to add further JSF configuration parameters to src/main/resources/application.properties:

# javax.faces.* properties to configure JSF behaviour  
#   
# DEFAULT_SUFFIX: file ending for view templates used with Facelets  
# PROJECT_STAGE: enables special Facelets debug output during development   
# FACELETS_REFRESH_PERIOD: causes Facelets to refresh templates during development  
# FACELETS_LIBRARIES: registers addional tag libraries for usage in facelets   
server.context-parameters.javax.faces.DEFAULT_SUFFIX=.xhtml  
server.context-parameters.javax.faces.PROJECT_STAGE=Development  
server.context-parameters.javax.faces.FACELETS_REFRESH_PERIOD=1  
server.context-parameters.javax.faces.FACELETS_LIBRARIES=/WEB-INF/springsecurity.taglib.xml  
  
# set context param 'com.sun.faces.forceLoadConfiguration' in order to force JSF   
# implementation to neglect the need of a FacesServlet(-Mapping) and ConfigureListener  
# in a web.xml and to use annotation based configuration  
server.context-parameters.com.sun.faces.forceLoadConfiguration=true 

We do that by means of server.context-parameters.* which Spring Boot will use to set the acutal context parameters of our web application. Please refer to the comments in the file for further details.

Before we have look at the decisive changes, I want to point out some refactoring that was done to was to as improvement of our application:

  • the interface StockInfo was removed because it was never really needed and only made things more complicated
  • the class StockInfoImpl was renamed to StockInfo
  • the class SecurityConfig.java was moved to package de.dlopes.stocks.facilitator.config
  • the class MyUserDetailsService was moved to package de.dlopes.stocks.facilitator.services.impl
  • the pacakge de.dlopes.stocks.facilitator.security was removed because it is not needed anymore
  • the class PublicController was deleted because it is not needed anymore
  • the class HomeController was deleted because it is not needed anymore
  • the pacakge de.dlopes.stocks.facilitator.contoller was removed because it is not needed anymore

 In order to maintain the actual context path of the application only once, we added the new config parameter dispatcherServletCxtpth in the application.yaml underneath src/main/resources and made it accessable in the class ConfigurationSettings.
This can now be used in the SecurityConfig class. There we have to adjust our security configuration to work well together with JSF and Spring Webflow. Therefore CSRF had to be disabled (see https://jira.springsource.org/browse/SEC-2498). Only the last HTTP POST request is saved in order to mitigate the risk of turning of Spring Security's protection against CSRF:

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

		// retrieve configured contextPath for dispatcher servlet
		String cxtpth = config.getDispatcherServletCxtpth();
		
		http.formLogin()
				.loginPage(cxtpth + "/login")
				.loginProcessingUrl(cxtpth + "/loginProcess")
				.defaultSuccessUrl(cxtpth + "/stock-info")
				.failureUrl(cxtpth + "/login?login_error=1")
			.and().logout()
				.logoutUrl(cxtpth + "/logout")
				.logoutSuccessUrl(cxtpth + "/index")
				
		// Disable CSRF (won't work with JSF) but ensure last HTTP POST request is saved
		// See https://jira.springsource.org/browse/SEC-2498

				.and().csrf()
					.disable()
					.requestCache()
						.requestCache(new HttpSessionRequestCache());
		
	}

Do you remember that we have removed the HomeController as part of a clean-up and refactoring task? Of course, the conding that was that class has to go somewhere. Because of the usage of Spring WebFlow, there is no need of custom Controllers anymore because all requests are handled by a generic Spring Webflow controller. Thus our contoller logic is moved to the service class StockInfoServiceImpl:

*****************************************
 * Copyright (c) 2016 Dominique Lopes.
 * All rights reserved. 
 *
 * This Source Code Form is subject to the terms of the Mozilla Public 
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Contributors:
 *     Dominique Lopes - initial API and implementation
 *******************************************************************************/
package de.dlopes.stocks.facilitator.services.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import de.dlopes.stocks.facilitator.config.ConfigurationSettings;
import de.dlopes.stocks.facilitator.data.StockInfo;
import de.dlopes.stocks.facilitator.data.StockInfoRepository;
import de.dlopes.stocks.facilitator.services.StockDataCollector;
import de.dlopes.stocks.facilitator.services.StockInfoService;

@Service("stockInfoService")
public class StockInfoServiceImpl implements StockInfoService {

	@Autowired
	ConfigurationSettings cs;
	
	@Autowired
	StockInfoRepository siRepo;
	
	@Override
	public List<StockInfo> findAll() {
		List<StockInfo> siList = siRepo.findAll();	
		return siList;
	}

	@Override
	public List<StockInfo> loadAll() {
		StockDataCollector dataCollector = cs.getDataCollector();
		List<StockInfo> siList = dataCollector.getData();
		
		for (StockInfo si : siList) {
			siRepo.save(si);	
		}
		siRepo.flush();
		
		return findAll();
	}

}

This service class is very closely aligned with the initial controller logic. However, one thing that is different and very important here is that we register this class as a named service (see line 25). Only if we do that we can use the methods of this service in the flow definition XMLs that describe the possible navigation path and actions in our application.
Additionally, create the class WebFlowServiceUtil - another named service that provides us with access to utility methods.

/*******************************************************************************
 * Copyright (c) 2016 Dominique Lopes.
 * All rights reserved. 
 *
 * This Source Code Form is subject to the terms of the Mozilla Public 
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Contributors:
 *     Dominique Lopes - initial API and implementation
 *******************************************************************************/
package de.dlopes.stocks.facilitator.services.impl;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

@Service("serviceUtil")
public class WebFlowServiceUtil {

	public boolean isNull(Object object) {
		return object == null;
	};
	
	public boolean notNull(Object object) {
		return !isNull(object);
	};
	
	public boolean isEmpty(String string) {
		return StringUtils.isEmpty(string);
	};

	public boolean notEmpty(String string) {
		return !isEmpty(string);
	};
	
	public boolean isEmpty(List<?> list) {
		return CollectionUtils.isEmpty(list);
	};

	public boolean notEmpty(List<?> list) {
		return !isEmpty(list);
	};
	
}

As a next step, we can to create the flow definition file stock-info-flow.xml underneath src/main/webapp/WEB-INF/flows/stock-info:

<?xml version="1.0" encoding="UTF-8"?>
<!--
    Copyright (c) 2016 Dominique Lopes.
    All rights reserved. 
   
    This Source Code Form is subject to the terms of the Mozilla Public 
    License, v. 2.0. If a copy of the MPL was not distributed with this
    file, You can obtain one at http://mozilla.org/MPL/2.0/.
   
    Contributors:
        Dominique Lopes - initial API and implementation
 -->

<flow xmlns="http://www.springframework.org/schema/webflow"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/webflow
        http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

	<!-- only registered users can access this flow -->
	<secured attributes="ROLE_USER" />

	<on-start>
		<evaluate expression="stockInfoService.findAll()" result="flowScope.stocks" />
	</on-start>

	<decision-state id="isStockInfoLoaded">
		<if test="serviceUtil.notEmpty(flowScope.stocks)" then="viewStockInfo" else="loadStockInfo" />
	</decision-state>

	<view-state id="loadStockInfo">
		<transition on="load" to="viewStockInfo">
			<evaluate expression="stockInfoService.loadAll()" result="flowScope.stocks" />
 		</transition>
	</view-state>

	<view-state id="viewStockInfo">
		<transition on="end" to="finish" />
	</view-state>

	<end-state id="finish" />
	
</flow>

The flow definition file finally contains the process flow of our application in decerative way:

  • we have made this specific process flow only accessible to users with the USER role  by means of a <secured> tag (see line 20)
  • when the process flow starts we invoke the findAll method from the named service that we have registered in the last steps and store its result in the variable stocks within the flowscope - the flowscope makes the variable available also in other steps of the flow (see line 22)
  • we use a <decision-state> tag to check if the stocks variable is empty and either forward to the loadStockInfo or viewStockInfo view state (see line 26)
  • the view state loadStockInfo supports one transition to the viewStockInfo view state - during that transation the loadAll method of our named service is invoked and updates the stocks variable before the acutal view is rendered (see line 30)
  • the view state loadStockInfo supports only the transition to a final end state (see line 36)

Finally, we have to create the JSF facelets that will be used to render the HTML web pages. Spring Webflow automatically looks for views with exactly the same name as the ID of the view states that we have defined in the flow definition file. The possibility of using JSF faclets allows us to define only the relevant parts of the web pages. Therefore we create a JSF faclet at first that wraps all common parts of the web pages underneath src/main/webapp/WEB-INF/layout and name it standard.xhtml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:p="http://primefaces.org/ui"
	xmlns:sec="http://www.springframework.org/security/tags">
	
<f:view contentType="text/html">
<h:head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>
		Stock Facilitator: <ui:insert name="title"/>
	</title>
	<link rel="stylesheet" href="/${request.contextPath}/styles/blueprint/screen.css" type="text/css" media="screen, projection" />
	<link rel="stylesheet" href="/${request.contextPath}/styles/blueprint/print.css" type="text/css" media="print" />
	<!--[if lt IE 8]>
		<link rel="stylesheet" href="/${request.contextPath}/styles/blueprint/ie.css" type="text/css" media="screen, projection" />
	<![endif]-->
	
</h:head>
<h:body>
	<div>
		<sec:authorize ifAnyGranted="ROLE_USER">
			<p:menubar>
			<f:facet name="options">
			       Welcome, ${currentUser.name} | <a href="/logout">Logout</a>
		        </f:facet>
			</p:menubar>
		</sec:authorize>
	</div>
	<div>
		<ui:insert name="content"/>
	</div>
</h:body>
</f:view>
</html>

The JSF facelet from above contains the general layout of our website and defines two slots: "title" (see line 14) and "content" (see line 34). The content of these slots are only determined in the views that correspond to the view states that we have defined in the flow definition file.

As one of our last steps we have to define the two missing views that correspond to the view states. At first, the view loadStockInfo.xhtml underneath src/main/webapp/WEB-INF/flows/stock-info:

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!--/* 
 Copyright (c) 2016 Dominique Lopes.
 All rights reserved. 

 This Source Code Form is subject to the terms of the Mozilla Public 
 License, v. 2.0. If a copy of the MPL was not distributed with this
 file, You can obtain one at http://mozilla.org/MPL/2.0/.

 Contributors:
     Dominique Lopes - initial API and implementation
*/-->

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:p="http://primefaces.org/ui"
	template="/WEB-INF/layouts/standard.xhtml">
	
	<ui:define name="title">Load Stock Info</ui:define>

	<ui:define name="content">

		Stock info not loaded yet. 
		Click here to refresh stock information: 
		<h:form>
			<p:commandLink action="load" value="Load" />
		</h:form>
	</ui:define>

</ui:composition>

As you can see, this view template contains just the relevant content and does not care about any common items on the web page (e. g. corporate logo, header, footer, etc.). The second view template viewStockInfo.xhtml underneath src/main/webapp/WEB-INF/flows/stock-info is not much different in this regard (although it is a little more complex):

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!--/* 
 Copyright (c) 2016 Dominique Lopes.
 All rights reserved. 

 This Source Code Form is subject to the terms of the Mozilla Public 
 License, v. 2.0. If a copy of the MPL was not distributed with this
 file, You can obtain one at http://mozilla.org/MPL/2.0/.

 Contributors:
     Dominique Lopes - initial API and implementation
*/-->

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:p="http://primefaces.org/ui"
	template="/WEB-INF/layouts/standard.xhtml">

	<ui:define name="title">Overview</ui:define>

	<ui:define name="content">

		<p>The following table lists all loaded stock information:</p>
		
		<p:dataTable id="stocks" var="si" value="#{stocks}">
			<!-- later:
			paginator="true" dynamic="true" rows="#{stocks.pageSize}" page="#{stocks.currentPage}" lazy="true"> -->

			<f:facet name="header">Index: DAX</f:facet>
			<p:column sortBy="#{si.name}">
				<f:facet name="header">Name</f:facet>
			#{si.name}
			</p:column>
			<p:column>
				<f:facet name="header">WKN/ISIN</f:facet>
			#{si.WKN}/#{si.ISIN}
			</p:column>
			<p:column>
				<f:facet name="header">Price</f:facet>
			#{si.price}
			</p:column>
			<p:column>
				<f:facet name="header">Bid</f:facet>
			#{si.bid}
			</p:column>
			<p:column>
				<f:facet name="header">Ask</f:facet>
			#{si.ask}
			</p:column>
			<p:column>
				<f:facet name="header">Change (abs./%)</f:facet>
			#{si.changeAbsolute}/#{si.changePercentage}%
			</p:column>
			<p:column>
				<f:facet name="header">Last update</f:facet>
			#{si.time}
			</p:column>
		</p:dataTable>

	</ui:define>

</ui:composition>

In lines 27 to 60 we use a primefaces data table to render the content of the flowScope variable stocks. The table is defined in a column-wise manner by means of a <p:column> tag. The captions of these columns are defined within a faclet facet (<f:facet> tag).

As a last step, we move all existing HTML files to src/main/resources/templates to src/main/webapp/WEB-INF/views and rewrite them to JSF faclets (no further details specified here because this is considered trival).

This conludes the changes that we need to do.

Recap

Today, we had a look at JAVA based view technologys in general and compared 3 different approaches to one another. We finally decided to use Spring Webflow together with JSF and Primefaces - a JSF based extension library with modern UI compents. Finally, we made the necessary adjustments to our application in order to implement the new view technologies.

In the past iterations of this software project I found a lot of interesting web-based tools and services which make the life of application developers easier. That is why I want to dedicate the next session to a walk trough of my current project setup with all those freely accessible web tools.

This is the new high-level architecture of our application after switching to the implementation based on Spring Webflow, JSF and Primefaces:

architecture overview

This blog entry was inspired by the following sources:

About me

Profile picture
 

Dominique Lopes is a Senior SAP Consultant at MHP with more than 10 years experience in Software Development in various programming languages. In his leisure time he enjoys to try out new IT trends in his private software projects.

 

Popular Tags

JSN Mini template designed by JoomlaShine.com