Using Observer Pattern to send messages in a Spring project

In this post we display a usage of the Observer pattern in a Spring project. We need to push a notification to a user, based on some other user actions. We will use a simple action like a dropdown selection. When a user chooses an option from the dropdown, the rest users will get notified for this action and its details.

Notice that this post focuses on the Observer pattern and not the notification system. We simply use the messaging functionality as an example to show the Observer pattern in action. There are better and more elegant solutions for pushing notifications in a Java project.

Technologies Used

  • Spring MVC
  • Design Patterns
  • Spring Security
  • Hibernate
  • MySQL
  • Maven
  • Jsp , Jstl

Database

We will need only two tables for this example. The first one will contain the users. We will keep the minimum details here, as we don’t care about complexity. We only need an auto increment id and a username/password combination for the authentication. The second table will contain the messages. Each message has an id, a title, a body, a creation date and a user id which points to the sender.

CREATE TABLE user
(
  user_id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT ,
  username VARCHAR(400) NOT NULL,
  password VARCHAR(400) NOT NULL
);

CREATE TABLE message (
  message_id bigint(20) NOT NULL PRIMARY KEY AUTO_INCREMENT,
  title varchar(100) DEFAULT NULL,
  body text,
  sender_id bigint(20) DEFAULT NULL,
  creation_date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  FOREIGN KEY (sender_id) REFERENCES user(user_id)
);

INSERT INTO user (username, password) VALUES ("testUser", "$2a$10$04TVADrR6/SPLBjsK0N30.Jf5fNjBugSACeGv1S69dZALR7lSov0y");
INSERT INTO user (username, password) VALUES ("testAdmin", "$2a$10$04TVADrR6/SPLBjsK0N30.Jf5fNjBugSACeGv1S69dZALR7lSov0y");

 

Implementation

dashboard.jsp

<form:form action="/messagapp/chooseColor" commandName="colorForm" method="get" class="form-inline">
    <h2 class="form-signin-heading">Choose color</h2>
    <form:select path="color" cssClass="form-control" cssStyle="width: 30%;">
        <form:option value="green" label="Green" cssClass="form-control"/>
        <form:option value="red" label="Red" cssClass="form-control"/>
    </form:select>
    <button class="btn btn-default" type="submit">OK</button>
</form:form>

When the user makes a choice and clicks ok, the request is handled by the controller. There before we call the Service layer, we add an observer to the observable service class that we will call.

 

 

MainController.java

@Controller
public class MainController {

    @Autowired
    private UserService userService;

    @Autowired
    private ChooseColorNotificationObserver obs;

    @Autowired
    private MessageService messageService;


    ...

    @RequestMapping(value = "chooseColor", method = RequestMethod.GET)
    public String chooseColor(@Valid @ModelAttribute(value = "colorForm") ColorForm colorForm, Principal principal){

        Color colorEnum = (colorForm.getColor().equals("green")) ? Color.GREEN : Color.RED;

        userService.addObserver(obs);
        userService.saveColor(colorEnum, principal);

        return "dashboard";
    }

    ...

}

Inside the observable service method we perform our business logic and we call the persistence layer for data storage. These steps are not implemented cause they are not needed for this example. The important is that after our changes, we call the setChanged() method to state that our value has changed and the notifyObservers() method where we pass the changed value.

UserService.java

@Service("userService")
@EnableTransactionManagement
public class UserService extends Observable {

    @Autowired
    private UserDao userDao;

    @Transactional(readOnly = true)
    public User getUserByUsername(String username) {

        return userDao.getUserByUsername(username);
    }

    public void saveColor(Color color, Principal principal){

        //Apply business logic and save the color

        setChanged();
        notifyObservers(color);

    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

To use the Observer pattern we need a class to implement the java.util.Observer interface. Our class will be the NotificationObserver class which contains one abstract method.

NotificationObserver.java

public abstract class NotificationObserver<T> implements java.util.Observer {
    public abstract void update(Observable o, Object arg);
}

Now we can extend our abstract observer class, to override the method we need. This method will be called everytime we notify the observers.

ChooseColorNotificationObserver.java

@Component("chooseColorNotificationObserver")
public class ChooseColorNotificationObserver extends NotificationObserver {

    @Autowired
    private MessageLogic messageLogic;

    @Override
    public void update(Observable o,  Object arg) {

        messageLogic.buildAndSendColorMessage((Color) arg);
    }
}

The last step is to implement the method that will create and store the message.

MessageLogic.java

@Component("messageLogic")
public class MessageLogic {

    @Autowired
    private MessageService messageService;

    @Autowired
    private UserService userService;

     protected void buildAndSendColorMessage(Color color){

        String buttonColor = color.equals(Color.GREEN) ? "green" : "red";

        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        User sender = userService.getUserByUsername(auth.getName());

        String title = "Button pressed";
        String body = "User "  + sender.getUsername() + " just pressed the " + buttonColor + " button!";

        Message message = new Message();
        message.setTitle(title);
        message.setBody(body);
        message.setSender(sender);
        messageService.insert(message);
    }
}

 

Now it’s time to test that everything works fine. After the dropdown selection that we already made with a user, we logout and login again with another user. We navigate to the Messages page and we can see the message.

 

Installation and Run

  • Run the database/queries.sql  queries to your database.
  • Add your database credentials to resources/db.properties file.
  • Build the project using mvn clean install
  • Run the application using tomcat

Test Credentials

Username Password Role
testUser 123456 testUser
testAdmin 123456 testAdmin