Service layer

A Service-Oriented Architecture (SOA) can be defined as a group of services, which communicate with each other. The process of communication involves either simple data passing or it could involve two or more services coordinating some activity. A service is a self-contained software module that performs a predetermined task, thus they are the building blocks of service-oriented applications. The services are somewhat analogous to Java objects and components such as EJBs. Unlike objects, services maintain their own state and provide a loosely coupled interface.

In order to stick with best practices, each service is supposed to have its own contract:

public interface SecurityService extends Service {

        ...

        /**
         * Lists users by role.
         * 
         * @param role the role type
         * @return the user list
         */
        List<GFIUser> listUsersByRole(GFIRole role);

        /**
         * Performs login with username and password.
         * 
         * @param username the user name
         * @param password the password
         * @return the user object
         * @throws LoginException when the username and/or password are/is invalid
         */
        GFIUser login(String username, String password) throws LoginException;

        ...

        /**
         * Saves or updates a group.
         * 
         * @param group to persist
         * @return the saved group
         * @throws InvalidGroupException when the user object has invalid or missing data
         * @throws ExistingGroupNameException when attempting to save group with an existing name 
         */
        GFIGroup saveGroup(GFIGroup group) throws InvalidGroupException, ExistingGroupNameException;

}

Notice this service extends directly from fr.gfi.foundation.core.service.Service interface. The concrete service implementation is then responsible for containing the logic for addressing the business' needs. If there isn't any database involved in this process the concrete class doesn't need to extends from anything, otherwise there's currently two options:

1. Extend from fr.gfi.foundation.core.service.AbstractDatabaseService and manage transactions programatically.

2. Extend from fr.gfi.foundation.core.service.TransactionalService and manage transactions declaratively. In this case, don't forget to include the following XML in your Spring configuration (by referencing your Entity Manager):

<core:transaction id="myTransaction"
        entityManagerFactoryRef="myEntityManager" />

Below an example of concrete implementation which extends from TransactionalService:

public final class SecurityServiceImpl extends TransactionalService implements SecurityService {

        ...
        
        /**
         * @see fr.gfi.admc.service.SecurityService#listUsersByRole(GFIRole)
         */
        public List<GFIUser> listUsersByRole(GFIRole role) {
                Assert.notNull(role, "GFIRole must not be null");
                logger.info("Listing GFIUsers [role=" + role + "]");
                Map<String, Object> params = new HashMap<String, Object>();
                params.put("role", role);               
                return getResultList("listUsersByRole", params);
        }
        
        /**
         * @see fr.gfi.admc.service.SecurityService#login(String, String)
         */
        public GFIUser login(String username, String password) throws LoginException {
                GFIUser user = getUserByUsername(username);
                if (user == null || !user.checkPassword(password)) {
                        throw new LoginException("Invalid username and/or password");
                }
                return user;
        }

        /**
         * @see fr.gfi.admc.service.SecurityService#saveGroup(GFIGroup)
         */
        @JpaEntityValidation(targets = { GFIGroup.class }, exception = InvalidGroupException.class)
        public GFIGroup saveGroup(GFIGroup group) throws ExistingGroupNameException {           
                Assert.notNull(group, "GFIGroup must not be null");
                
                // Check whether the given group name is already being used
                GFIGroup groupByName = getGroupByName(group.getName());
                if (groupByName != null && !groupByName.getId().equals(group.getId())) {
                        throw new ExistingGroupNameException("Group name already being used: " + group.getName());
                }
                
                // Handles create operation
                if (group.getId() == null) {
                        logger.info("Saving GFIGroup [name=" + group.getName() + "]");
                        persistEntity(group);
                }
                
                // Handles update operation
                else {
                        logger.info("Updating GFIGroup [name=" + group.getName() + "]");
                        mergeEntity(group);
                }
                
                return group;
        }

        ...
        
}

JPA entity validation

Have you noticed the annotation JpaEntityValidation in saveGroup() method? That's a feature provided by GFI Foundation Core component, which intends to perform automatic validation for JPA-like entities. This is achieved by allowing JpaEntityValidationInterceptor to inspect the persistent fields and validating them according to what's expected. Let's take as example the GFIGroup class, check its persistent fields:

        ...
        
        @Column(unique = true, nullable = false, length = 30)
        private String name;
        
        @Column(length = 200)
        private String description;

        @ManyToMany(fetch = FetchType.LAZY)
        private Set<GFIUser> users;
        
        ...

Here the "name" field is not supposed to be null, hence if the method saveGroup() gets a GFIGroup object with a null name, an InvalidGroupException will be raised. If no exception is defined, by default JpaEntityValidationException is used.