1

I have a project to write with JavaEE, but I am new with the technology.

I have EJBs which rely on parameters received from a client front-end.

At the moment, I'm validating parameters within the EJBs, e.g., I have methods in my EJBs to validate the parameters, but I assume this is bad practice because it leads to code duplication, and adds more responsibilities to my EJBs.

I'd like to know what are the best practices to perform parameters validations.

EDIT: My JavaEE back-end is not directly reachable by the client. Instead, we have an interoperability layer written in Java, and following the SOAP architecture, that lies between the client front-end, and the J2E back-end.

An example of my code can be found below :

    // Method inherited from the EJB interface
    // The eventOrganizer variable is another EJB that is injected into this very EJB
    @Override
        public boolean registerEvent(String name, int participantNumber, Calendar date, Coordinator coordinator) {

            l.log(Level.INFO, "Received request for event creation");

            if (!areParametersValid(name, participantNumber, date, coordinator)) {
                return false;
            }

            Calendar cal = Calendar.getInstance();
            cal.setTime(date.getTime());
            cal.add(Calendar.HOUR_OF_DAY, 12);

            Event event = new Event(coordinator, date.getTime(), cal.getTime(), participantNumber, name);

            return eventOrganizer.bookRoom(event);
        }

// Parameters validation methods
/**
     * Returns true if the given name is non null and
     * is not empty
     * @param name the name of the {@link Event}
     * @return true if the name semantically of the {@link Event} is valid
     */
    private boolean nameIsGood(String name) {
        return name != null && !name.trim().equals("");
    }

    /**
     * Returns true if the number of participant is strictly positive
     * @param participantNumber the number of people in the {@link Event}
     * @return true if the number of participant is valid
     */
    private boolean participantNumberIsGood(int participantNumber) {
        return participantNumber > 0;
    }

    /**
     * Checks if the given date is a date in the future,
     * and returns true if it is
     * @param date the date to check
     * @return true if the provided start date for the {@link Event} is valid
     */
    private boolean dateIsGood(Calendar date) {
        return date.after(Calendar.getInstance());
    }

    /**
     * Checks if the given {@link Coordinator} is a valid coordinator,
     * i.e., if he's not null, and returns true if he is not
     * @param coordinator the {@link Coordinator} to check
     * @return true if the {@link Coordinator} is valid
     */
    private boolean coordinatorIsGood(Coordinator coordinator) {
        return coordinator != null;
    }

    /**
     * Checks that all the parameters received for an {@link Event} creation are valid
     * and returns true if they are, or false if they're not
     * @param name the name of the {@link Event}, as a {@link String}
     * @param participantNumber the estimated number of people for the
     *                          {@link Event} as a {@link Integer}
     * @param date the date at which the {@link Event} is scheduled,
     *             as a {@link Calendar}
     * @param coordinator the {@link Coordinator} that created the {@link Event}
     * @return true if all the given parameters are valid, and the event creation shall be processed
     */
    private boolean areParametersValid(String name, int participantNumber, Calendar date, Coordinator coordinator) {
        return nameIsGood(name) && participantNumberIsGood(participantNumber) && dateIsGood(date) && coordinatorIsGood(coordinator);
    }


// Event object 
public class Event {
    private Coordinator coordinator;
    private Date startDate;
    private Date endDate;
    private int nbPeople;
    private String name;
    private List<Room> rooms;
    private List<RoomType> desiredRoomTypes;

    public Event(int nbPeople, String name, List<RoomType> roomTypes) {
        this.nbPeople = nbPeople;
        this.name = name;
        this.desiredRoomTypes = roomTypes;
        this.rooms = new ArrayList<>();
    }

    public Event(Coordinator coordinator, Date startDate, Date endDate, int nbPeople, String name) {
        this.coordinator = coordinator;
        this.startDate = startDate;
        this.endDate = endDate;
        this.nbPeople = nbPeople;
        this.name = name;
        this.rooms = new ArrayList<>();
    }

    public List<Room> getRooms() {
        return rooms;
    }

    public void addRooms(List<Room> rooms) {
        this.rooms.addAll(rooms);
    }

    public void addRoom(Room room) {
        this.rooms.add(room);
    }

    public Coordinator getCoordinator() {
        return coordinator;
    }

    public Date getStartDate() {
        return startDate;
    }

    public Date getEndDate() {
        return endDate;
    }

    public int getNbPeople() {
        return nbPeople;
    }

    public String getName() {
        return name;
    }
}

// Coordinator object 
public class Coordinator {
    private String firstName;
    private String lastName;
    private String email;
    private List<Event> eventsCreated;

    public Coordinator(String firstName, String lastName, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    public List<Event> getEventsCreated() {
        return eventsCreated;
    }

    public void setEventsCreated(List<Event> eventsCreated) {
        this.eventsCreated = eventsCreated;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String getEmail() {
        return email;
    }
}
Maxime Flament
  • 721
  • 1
  • 7
  • 24
  • 1
    Why not add shared validator methods to a helper class? – Gareth O'Connor Mar 26 '18 at 08:53
  • Do you mean a class that would be distributed and shared by my EJBs, and that validates the parameters for each one of them ? Wouldn't this class grow quicly as we add new EJBs ? – Maxime Flament Mar 26 '18 at 08:54
  • Why would I use an helper class for validation, instead of validating the fields directly in my Business Objects, e.g., my `Event` and `Coordinator` objects? What are the differences and what are the advantages of such validation? – Maxime Flament Mar 26 '18 at 09:01
  • 1
    Main benefit of a helper class would be for EJBs with shared validation methods... – Gareth O'Connor Mar 27 '18 at 08:14
  • Okay thank you. I've seen interceptors for validation, is that another way of doing this ? Or is it intended for another validation purpose? – Maxime Flament Mar 27 '18 at 08:16

3 Answers3

0

One way to avoid clutter is to separate the unvalidated (mutable) object from the validated (immutable) object. Don't be afraid to have several immutable objects as needed. The act of validating is figuring out what data that you have, and by extension what types/objects it actually is.

As for the validation it self, if its for user feedback make one method that gives all problems to some object and pass it around. That way you can validate all objects using the same interface.

UserFeedbackObject validate(UserFeedbackObject f){
  if (name.length == 0) f = f.badField("username","expected a username");
  if (date.after(Calendar.getInstance()) == 0) f = f.badField("username","date needs to be in the future");
  return  f;
}
mncl
  • 161
  • 1
  • 5
0

Bean Validation

Once you are looking for a good practice, I strongly encourage you to use Bean Validation instead of writing your own validation mechanism, specially for simple constraints.

Bean Validation is part of Java EE. It is annotation based and comes with a built-in set of constraints. If they don't suit you, you can write your own constraints.

As per your current validation constraints, you would have the following:

Event registerEvent(@NotNull @NotBlank String name, 
                    @Min(1) int participantNumber, 
                    @NotNull @Future Calendar date, 
                    @NotNull @Valid Coordinator coordinator);

Bean Validation is an specification. The actual validation is performed by an implementation such as Hibernate Validator. How to setup Bean Validation or Hibernate Validator is beyond the scope of this answer.


Some useful resources:

cassiomolin
  • 124,154
  • 35
  • 280
  • 359
  • 1
    Good answer. Another way to include those validation is by xml: https://docs.jboss.org/hibernate/validator/4.1/reference/en-US/html/validator-xmlconfiguration.html – Duloren May 22 '18 at 14:43
  • 1
    Thanks for your answer, I used this solution but I had struggle unit testing it. That's why I went for a custom validation helper class. I know this is bad practice, but I found it convenient in my situation and the amount of time I had to realize this project! But for everyone coming here, you should use this answer instead of using a custom validation class. – Maxime Flament May 22 '18 at 15:09
  • I did not do both of what you say. I accepted your answer and upvoted it ;) – Maxime Flament May 22 '18 at 16:55
-1

I decided, for many reasons such as focused on business logic beans or re-usability, to delegate the validation of fields to interceptors.

I extracted the validation methods in a helper class called FieldsValidator, which I'm using in my interceptors.

This decoupage provides 3 things :

  • Logic of the validation and testing is done for one class only, e.g., FieldsValidator.

  • Interceptors are focused on validation and WebFault exceptions throw

  • Beans now only care about business logic, and parameters integrity/validity are done in re-usable interceptors

Find an example below :

Interceptor that verifies that a String is not null and not empty :

@AroundInvoke
public Object intercept(InvocationContext ctx) throws Exception {
    Object[] parameters = ctx.getParameters();

    // loops through the parameters of the method
    for (Object parameter : parameters) {
        if (parameter == null) {
            throw new InvalidRequestParametersException("One or more parameters of type String in the request are null!");
        }

        // if the parameter is of type String
        if (parameter.getClass().getName().equals(String.class.getName())) {
            // if the string is invalid
            if (!FieldsValidator.isStringValid((String) parameter)) {
                throw new InvalidRequestParametersException("One or more parameters of type String in the request are invalid!");
            }
        }
    }
    return ctx.proceed();
}

Here's a sample of my FieldsValidator class :

/**
 * A static helper class used to validate fields within the EJBs
 *
 * @author Maxime Flament (maxime.flament@etu.unice.fr)
 */
public class FieldsValidator {

    /**
     * Returns true if the given string is non null and
     * is not empty
     *
     * @param str the string to validate
     * @return true if the string is semantically correct
     */
    public static boolean isStringValid(String str) {
        return str != null && !str.equals("");
    }

    /**
     * Returns true if the given value is strictly positive
     *
     * @param value the number to check
     * @return true if the value is strictly positive
     */
    public static boolean isStrictlyPositive(int value) {
        return value > 0;
    }

    /**
     * Checks if the given date is a date in the future,
     * and returns true if it is
     *
     * @param date the date to check
     * @return true if the provided start date is valid
     */
    public static boolean dateIsGood(Calendar date) {
        return date.after(Calendar.getInstance());
    }

    /**
     * Checks if the given {@link Object} is a valid object,
     * i.e., if he's not null, and returns true if he is not
     * @param object the {@link Object} to check
     * @return true if the {@link Object} is valid
     */
    public static boolean isObjectNotNull(Object object) {
        return object != null;
    }

    /**
     * Checks that the provided email is correct, and returns true if it is,
     * or false otherwise
     *
     * @implNote this method doesn't check that the provided email
     *           actually exists, but it only checks if the email
     *           is valid according to the RFC
     *
     * <a href="https://stackoverflow.com/a/26687649/5710894">Inspired by this</a>
     *
     * @param email the email to check
     * @return true if the given email is valid
     */
    public static boolean isValidEmail(String email) {
        return EmailValidator.getInstance().isValid(email);
    }
}

And below, an example of interceptor usage in one of my bean:

@Interceptors({InterceptorStringVerifier.class /* Add more interceptors here */})
Event registerEvent(String name, int participantNumber, Calendar date, Coordinator coordinator)

Note: interceptors should always be declared in the interface of the bean, not the implementation.

Maxime Flament
  • 721
  • 1
  • 7
  • 24
  • 1
    Why don't you use Bean Validation instead of using on your own validation mechanism? See my [answer](https://stackoverflow.com/a/50469641/1426227). – cassiomolin May 22 '18 at 14:04
  • @CassioMazzochiMolin I guess that he want to separate the validation logic of the bean implementation itself. Maybe he want to implement different kinds of validation according to context – Duloren May 22 '18 at 14:28
  • @Duloren Is still don't see the point of using `FieldsValidator`. That's all provided by Bean Validation. – cassiomolin May 22 '18 at 14:30
  • @CassioMazzochiMolin, yes, FieldsValidator is far to be a good practice. There are several issues and smell there. I just say that sometimes it is required to put the validation outside the bean. For example if you need to relax the ```@Future Calendar date``` or ```@Min(1)```constraints according the context or tenant. – Duloren May 22 '18 at 14:36
  • I tried to use Bean Validation but I had many troubles with it, such as unit testing the validation constraints. I decided to export the validation in an helper class, because of this problem, but I'm aware this is bad practice since the API already provides tools for it. – Maxime Flament May 22 '18 at 14:45