1
\$\begingroup\$

(See the next iteration.)

Introduction

This is my attempt at a simplistic web application just for the sake of practice. I have three main servlets:

  1. CreateDatabaseController: responsible for creating the database and the table in MySQL,
  2. AddPersonController: responsible for adding a new person to the table.
  3. ShowPersonController: this one is funny. It expects show/1/funky, where 1 is the user ID and funky is the first name of the user. If you mistype funky to punky, the filter will fix it and redirect to show/1/funky. When the URI is in order, it pukes out JSON of the selected person.

Code

AddPersonController.java

package net.coderodde.web.db.fun.controllers;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static net.coderodde.web.db.fun.controllers.DBUtils.close;

/**
 * This controller is responsible for creating new persons.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Aug 8, 2017)
 */
@WebServlet(name = "AddPersonController", urlPatterns = {"/add_person"})
public class AddPersonController extends HttpServlet {

    /**
     * The SQL command for inserting a person.
     */
    private static final String INSERT_PERSON_SQL = 
            "INSERT INTO funny_persons (first_name, last_name, email) VALUES " +
            "(?, ?, ?);";

    /**
     * Handles the HTTP <code>GET</code> method.
     *
     * @param request  the servlet request.
     * @param response the servlet response.
     * @throws ServletException if a servlet-specific error occurs.
     * @throws IOException if an I/O error occurs.
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        try (PrintWriter out = response.getWriter()) {
            out.println("Please use the POST method!");
        }
    }

    /**
     * Handles the HTTP <code>POST</code> method.
     *
     * @param request  the servlet request.
     * @param response the servlet response.
     * @throws ServletException if a servlet-specific error occurs.
     * @throws IOException if an I/O error occurs.
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");

        try (PrintWriter out = response.getWriter()) {
            String firstName = request.getParameter("first_name");
            String lastName = request.getParameter("last_name");
            String email = request.getParameter("email");

            if (firstName.isEmpty()) {
                out.println("The first name is empty.");
                return;
            }

            if (lastName.isEmpty()) {
                out.println("The last name is empty.");
                return;
            }

            if (email.isEmpty()) {
                out.println("The email is empty.");
                return;
            }

            MysqlDataSource mysql = DefaultDataSourceCreator.create();
            Connection connection = null;
            PreparedStatement statement = null;

            try {
                connection = mysql.getConnection();
                statement = connection.prepareStatement(INSERT_PERSON_SQL);
                statement.setString(1, firstName);
                statement.setString(2, lastName);
                statement.setString(3, email);
                statement.executeUpdate();
            } catch (SQLException ex) {
                out.println("Error: " + ex.getMessage());
            } finally {
                close(null, statement, connection);
            }
        }
    }

    /**
     * Returns a short description of the servlet.
     *
     * @return a String containing servlet description
     */
    @Override
    public String getServletInfo() {
        return "This servlet adds new persons to the database.";
    }
}

CreateDatabaseController.java

package net.coderodde.web.db.fun.controllers;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static net.coderodde.web.db.fun.controllers.DBUtils.close;

/**
 * This controller creates the database if it is not yet created.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Aug 8, 2017)
 */
@WebServlet(name = "CreateDatabaseController", urlPatterns = {"/create"})
public class CreateDatabaseController extends HttpServlet {

    /**
     * Creates a new database if not already created.
     */
    private static final String CREATE_DATABASE_SQL = 
            "CREATE DATABASE IF NOT EXISTS funny_db;";

    /**
     * Switches to 'funny_db'.
     */
    private static final String USE_DATABASE_SQL = "USE funny_db";

    /**
     * Creates the table if not already created.
     */
    private static final String CREATE_TABLE_SQL =
            "CREATE TABLE IF NOT EXISTS funny_persons (\n" +
                "id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n" +
                "first_name VARCHAR(40) NOT NULL,\n" +
                "last_name VARCHAR(40) NOT NULL,\n" +
                "email VARCHAR(50) NOT NULL,\n" +
                "created TIMESTAMP);";

    /**
     * If not yet created, this request creates the database and the table.
     *
     * @param request  the servlet request.
     * @param response the servlet response.
     * @throws ServletException if a servlet-specific error occurs.
     * @throws IOException if an I/O error occurs.
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");

        try (PrintWriter out = response.getWriter()) {
            MysqlDataSource mysql = DefaultDataSourceCreator.create();
            Connection connection = null;
            PreparedStatement statement = null;
            boolean error = false;

            try {
                connection = mysql.getConnection();
                statement = connection.prepareStatement(CREATE_DATABASE_SQL);
                statement.execute();

                statement = connection.prepareStatement(USE_DATABASE_SQL);
                statement.execute();

                statement = connection.prepareStatement(CREATE_TABLE_SQL);
                statement.execute();
            } catch (SQLException ex) {
                error = true;
                out.println("Error: " + ex.getMessage());
            } finally {
                close(null, statement, connection);
            }

            if (!error) {
                out.println("Database 'funny_db' is created!");
            }
        }
    }

    /**
     * Handles the HTTP <code>GET</code> method.
     *
     * @param request  the servlet request.
     * @param response the servlet response.
     * @throws ServletException if a servlet-specific error occurs.
     * @throws IOException if an I/O error occurs.
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Handles the HTTP <code>POST</code> method.
     *
     * @param request  the servlet request.
     * @param response the servlet response.
     * @throws ServletException if a servlet-specific error occurs.
     * @throws IOException if an I/O error occurs.
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Returns a short description of the servlet.
     *
     * @return a String containing servlet description.
     */
    @Override
    public String getServletInfo() {
        return "Creates the database and the table.";
    }
}

DBUtils.java

package net.coderodde.web.db.fun.controllers;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * This class contains some common utilities for dealing with databases.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Aug 8, 2017)
 */
public final class DBUtils {

    /**
     * Creates and sets a MySQL data source.
     * 
     * @param user       the user name.
     * @param password   the password.
     * @param url        the database URL.
     * @param port       the database port.
     * @param serverName the name of the server.
     * 
     * @return the MySQL data source.
     */
    public static  MysqlDataSource getMysqlDataSource(String user,
                                                      String password,
                                                      String url,
                                                      int port,
                                                      String serverName) {
        MysqlDataSource dataSource = new MysqlDataSource();

        if (user != null) {
            dataSource.setUser(user);
        }

        if (password != null) {
            dataSource.setPassword(password);
        }

        if (url != null) {
            dataSource.setURL(url);
        }

        if (port >= 0) {
            dataSource.setPort(port);
        }

        if (serverName != null) {
            dataSource.setServerName(serverName);
        }

        return dataSource;
    }

    /**
     * Closes a {@code ResultSet}.
     * 
     * @param resultSet the result set to close.
     */
    public static void close(ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
        } catch (SQLException ex) {
            throw new RuntimeException("Could not close a ResultSet.", ex);
        }
    }

    /**
     * Closes a {@code PreparedStatement}.
     * 
     * @param preparedStatement the prepared statement to close.
     */
    public static void close(PreparedStatement preparedStatement) {
        try {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
        } catch (SQLException ex) {
            throw new RuntimeException(
                    "Could not close a PreparedStatement.",
                    ex);
        }        
    }

    /**
     * Closes a {@code Connection}.
     * 
     * @param connection the connection to close.
     */
    public static void close(Connection connection) {
        try {
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException ex) {
            throw new RuntimeException(
                    "Could not close a Connection.",
                    ex);
        }        
    }

    /**
     * Closes the data base related resources.
     * 
     * @param resultSet         the result set to close.
     * @param preparedStatement the prepared statement to close.
     * @param connection        the connection to close.
     */
    public static void close(ResultSet resultSet,
                             PreparedStatement preparedStatement, 
                             Connection connection) {
        close(resultSet);
        close(preparedStatement);
        close(connection);
    }
}

DefaultDataSourceCreator.java

package net.coderodde.web.db.fun.controllers;

import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

public class DefaultDataSourceCreator {

    public static MysqlDataSource create() {
        MysqlDataSource mysql =
                DBUtils.getMysqlDataSource(
                        "root",
                        "your_password",
                        "jdbc:mysql://localhost:3306/funny_db", 
                        -1, 
                        null);

        return mysql;
    }
}

MyFilter.java

package net.coderodde.web.db.fun.controllers;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

public class MyFilter implements javax.servlet.Filter {

    private final int id;
    private final String firstName;

    public MyFilter(int id, String firstName) {
        this.id = id;
        this.firstName = firstName;
    }

    @Override
    public void init(FilterConfig filterConfig)
            throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request,
            ServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {
        ((HttpServletResponse) response)
                .sendRedirect("/DBWebFun/show/" + id + "/" + firstName);
    }

    @Override
    public void destroy() {

    }
}

ShowPersonController.java

package net.coderodde.web.db.fun.controllers;

import com.google.gson.Gson;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.coderodde.web.db.fun.model.FunnyPerson;

import static net.coderodde.web.db.fun.controllers.DBUtils.close;

/**
 * This controller is responsible for viewing persons.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Aug 8, 2017)
 */
@WebServlet(name = "ShowPersonController", urlPatterns = {"/show/*"})
public class ShowPersonController extends HttpServlet {

    private static final String GET_USER_BY_ID_SQL = 
            "SELECT * FROM funny_persons WHERE id = ?;";

    /**
     * Processes requests for both HTTP <code>GET</code> and <code>POST</code>
     * methods.
     *
     * @param request  the servlet request.
     * @param response the servlet response.
     * @throws ServletException if a servlet-specific error occurs.
     * @throws IOException if an I/O error occurs.
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");

        try (PrintWriter out = response.getWriter()) {
            MysqlDataSource mysql = DefaultDataSourceCreator.create();
            String path = request.getPathInfo();

            if (path.startsWith("/")) {
                path = path.substring(1);
            }

            String[] tokens = path.split("/");

            if (tokens.length == 0) {
                out.println("At least the user ID is required.");
                return;
            }

            String idString = tokens[0];
            int id = -1;

            try {
                id = Integer.parseInt(idString);
            } catch (NumberFormatException ex) {
                out.println(idString + ": not an integer.");
                return;
            }

            FunnyPerson person = new FunnyPerson();

            Connection connection = null;
            PreparedStatement statement = null;
            ResultSet resultSet = null;

            try {
                connection = mysql.getConnection();
                statement = connection.prepareStatement(GET_USER_BY_ID_SQL);
                statement.setInt(1, id);
                resultSet = statement.executeQuery();

                if (!resultSet.next()) {
                    close(resultSet, statement, connection);
                    out.println("{\"status\": \"error\"}");
                    return;
                }

                String matchFirstName = null;

                if (tokens.length == 2) {
                    matchFirstName = tokens[1];
                }

                if (!matchFirstName.equals(resultSet.getString("first_name"))) {
                    MyFilter myFilter =
                            new MyFilter(id, resultSet.getString("first_name"));

                    myFilter.doFilter(request, response, null);
                    return;
                }

                person.setId(resultSet.getInt("id"));
                person.setFirstName(resultSet.getString("first_name"));
                person.setLastName(resultSet.getString("last_name"));
                person.setEmail(resultSet.getString("email"));
                person.setCreated(resultSet.getDate("created"));

                Gson gson = new Gson();
                out.println(gson.toJson(person));
            } catch (SQLException ex) {
                throw new RuntimeException("SQLException thrown.", ex);
            } finally {
                close(resultSet, statement, connection);
            }
        }
    }

    private void handleInvalidRequest(HttpServletRequest request,
                                      HttpServletResponse response) {

    }

    /**
     * Handles the HTTP <code>GET</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Handles the HTTP <code>POST</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Returns a short description of the servlet.
     *
     * @return a String containing servlet description
     */
    @Override
    public String getServletInfo() {
        return "Shows the user info via ID/first_name";
    }
}

FunnyPerson.java

package net.coderodde.web.db.fun.model;

import java.sql.Date;

public final class FunnyPerson {

    private int id;
    private String firstName;
    private String lastName;
    private String email;
    private Date created;

    public int getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String getEmail() {
        return email;
    }

    public Date getCreated() {
        return created;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setCreated(Date created) {
        this.created = created;
    }
}

Other

The Maven project is here.

Critique request

Please tell me anything that comes to mind.

\$\endgroup\$
2
\$\begingroup\$

1) You have DB logic in servlet classes. Better if you have a service/DAO layer where implemented all DB related logic. In this case it is easy to write unit tests.

2) Connection, ResultSet, PreparedStatement, etc all implement AutoCloseable interface. If so, you may use try-with-resource feature. In this case you don't need implement own close() method. BTW, you already use this feature here:

try (PrintWriter out = response.getWriter())

3) Even if you still need own close() methods, have only one with argument of AutoCloseable type.

4) I am not sure but try without the checking for null. The main idea is: you have blank dataSource where user is null initially and it is not critical if you set null again:

if (user != null) {                    // <--- not needed
    dataSource.setUser(user);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.