0
\$\begingroup\$

(See the initial and previous version.)

This time, I have incorporated all the good points made by mtj. See what I have:

com.github.coderodde.weblog.viewcounter.CountViewServlet.java:

package com.github.coderodde.weblog.viewcounter;

import static com.github.coderodde.weblog.viewcounter.Util.objects;
import com.google.gson.Gson;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.time.ZonedDateTime;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * This servlet is responsible for storing the IP-address and the timestamp of 
 * a view in at <a href="http://coderodde.github.io/weblog/">coderodde's weblog</a>.
 *
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Aug 21, 2021)
 * @since 1.6 (Aug 21, 2021)
 */
@WebServlet(name="CountViewServlet", urlPatterns={"/countView"})
public class CountViewServlet extends HttpServlet {

    private static final Gson GSON = new Gson();
    private static final Logger LOGGER =
            Logger.getLogger(CountViewServlet.class.getName());

    @Inject private DataAccessObject dataAccessObject;

    @Override
    protected void doPost(HttpServletRequest httpServletRequest,
                          HttpServletResponse httpServletResponse) 
    throws ServletException, IOException {
        // Allow the weblog page to get the response from this servlet:
        httpServletResponse.setHeader("Access-Control-Allow-Origin", 
                                      "coderodde.github.io");

        JSONResponseObject jsonResponseObject = new JSONResponseObject();
        jsonResponseObject.succeeded = false;

        try {
            dataAccessObject.createTablesIfNeeded();
            ZonedDateTime mostRecentViewTime = 
                    dataAccessObject.getMostRecentViewTime();

            if (mostRecentViewTime != null) {
                jsonResponseObject.mostRecentViewTime =
                        mostRecentViewTime.toString();
            }

            dataAccessObject.addView(httpServletRequest); 
            jsonResponseObject.numberOfViews = dataAccessObject.getViewCount();

            // Mark as successful:
            jsonResponseObject.succeeded = true;
        } catch (SQLException ex) {
            LOGGER.log(
                    Level.SEVERE, 
                    "SQL failed: {0}, caused by: {1}", 
                    objects(ex.getMessage(), ex.getCause()));

        } catch (URISyntaxException ex) {
            LOGGER.log(
                    Level.SEVERE, 
                    "Bad DB URI: {0}, caused by: {1}", 
                    objects(ex.getMessage(), ex.getCause()));
        }

        try (PrintWriter printWriter = httpServletResponse.getWriter()) {
            printWriter.print(GSON.toJson(jsonResponseObject));
        }
    }
}

com.github.coderodde.weblog.viewcounter.DataAccessObject.java:

package com.github.coderodde.weblog.viewcounter;

import static com.github.coderodde.weblog.viewcounter.Util.objects;
import com.github.coderodde.weblog.viewcounter.sql.SQLStatements;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.servlet.http.HttpServletRequest;
import org.apache.tomcat.jdbc.pool.DataSource;

/**
 * This class implements the data access object for the view counter.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Aug 22, 2021)
 */
@RequestScoped
public class DataAccessObject {

    private static final Logger LOGGER = 
            Logger.getLogger(DataAccessObject.class.getName());

    private static final String EUROPE_HELSINKI_ZONE_ID = "Europe/Helsinki";
    private static final ZoneId ZONE_ID = ZoneId.of(EUROPE_HELSINKI_ZONE_ID);

    private static final String DB_URL_ENVIRONMENT_VARIABLE_NAME = 
            "CLEARDB_DATABASE_URL";

    private static final DataSource dataSource = new DataSource();
    private static final DataAccessObject INSTANCE = new DataAccessObject();

    public static DataAccessObject getInstance() {
        return INSTANCE;
    }

    /**
     * Makes sure that the main table is created.
     * 
     * @throws java.sql.SQLException if the SQL layer fails.
     * @throws java.net.URISyntaxException if the DB URI is invalid.
     */
    public void createTablesIfNeeded() throws SQLException, URISyntaxException {

        try (Connection connection = getConnection()) {
            connection.createStatement()
                      .executeUpdate(SQLStatements
                                      .ViewTable
                                      .Create
                                      .CREATE_MAIN_TABLE);

        } 
    }

    /**
     * Adds a new view data to the database.
     * 
     * @param httpServletRequest the request object.
     * 
     * @throws java.sql.SQLException if the SQL layer fails.
     * @throws java.net.URISyntaxException if the DB URI is invalid.
     */
    public void addView(HttpServletRequest httpServletRequest)
            throws SQLException, URISyntaxException {

        String host = httpServletRequest.getRemoteHost();
        int port = httpServletRequest.getRemotePort();
        String remoteAddress = httpServletRequest.getHeader("X-FORWARDED-FOR");

        if (remoteAddress == null) {
            remoteAddress = httpServletRequest.getRemoteAddr();
        }

        try (Connection connection = getConnection();
             PreparedStatement statement =
                     connection.prepareStatement(
                             SQLStatements.ViewTable.Insert.INSERT_VIEW)) {

            statement.setString(1, remoteAddress);
            statement.setString(2, host);
            statement.setInt(3, port);

            ZonedDateTime nowZonedDateTime = ZonedDateTime.now(ZONE_ID);

            Timestamp nowTimestamp = 
                    Timestamp.from(nowZonedDateTime.toInstant());

            statement.setTimestamp(4, nowTimestamp);
            statement.executeUpdate();
        }
    }

    /**
     * Returns the total number of views. 
     * 
     * @return the total number of views so far.
     * 
     * @throws java.sql.SQLException if the SQL layer fails.
     * @throws java.net.URISyntaxException if the DB URI is invalid.
     */
    public int getViewCount() throws SQLException, URISyntaxException {
        try (Connection connection = getConnection();
             Statement statement = connection.createStatement()) {

            try (ResultSet resultSet =
                    statement.executeQuery(
                            SQLStatements
                                    .ViewTable
                                    .Select
                                    .GET_NUMBER_OF_VIEWS)) {

                if (!resultSet.next()) {
                    throw new IllegalStateException(
                            "Could not read the number of views.");
                }

                return resultSet.getInt(1);
            }
        } 
    }

    /**
     * Returns the most recent view time stamp.
     * @return the most recent view time.
     * 
     * @throws java.sql.SQLException if the SQL layer fails.
     * @throws java.net.URISyntaxException if the DB URI is invalid.
     */
    public ZonedDateTime getMostRecentViewTime() 
            throws SQLException, URISyntaxException {
        try (Connection connection = getConnection();
             Statement statement = connection.createStatement()) {

            try (ResultSet resultSet = 
                    statement.executeQuery(
                            SQLStatements
                                    .ViewTable
                                    .Select
                                    .GET_MOST_RECENT_VIEW_TIME)) {

                if (!resultSet.next()) {
                    return null;
                }

                Timestamp mostRecentViewTimestamp = resultSet.getTimestamp(1);

                if (mostRecentViewTimestamp == null) {
                    return null;
                }

                ZonedDateTime mostRecentViewZonedDateTime =
                        ZonedDateTime.ofInstant(
                                mostRecentViewTimestamp.toInstant(), 
                                ZONE_ID);

                return mostRecentViewZonedDateTime;
            }
        }
    }

    private static void loadJDBCDriverClass() {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
        } catch (ClassNotFoundException ex) {
            LOGGER.log(
                    Level.SEVERE, 
                    "com.mysql.cj.jdbc.Driver class not found: {0}, " + 
                            "caused by: {1}", 
                    objects(ex, ex.getCause()));

            throw new RuntimeException(
                    "com.mysql.cj.jdbc.Driver not found.", 
                    ex);

        } catch (InstantiationException ex) {
            LOGGER.log(
                    Level.SEVERE, 
                    "com.mysql.cj.jdbc.Driver could not be instantiated: {0}," +
                            " caused by: {1}", 
                    objects(ex, ex.getCause()));

            throw new RuntimeException(
                    "com.mysql.cj.jdbc.Driver could not be instantiated.", 
                    ex);

        } catch (IllegalAccessException ex) {
            LOGGER.log(
                    Level.SEVERE, 
                    "com.mysql.cj.jdbc.Driver could not be accessed: {0}, " + 
                            "caused by: {1}", 
                    objects(ex, ex.getCause()));

            throw new RuntimeException(
                    "com.mysql.cj.jdbc.Driver could not be accessed.", 
                    ex);
        }
    }

    static {
        loadJDBCDriverClass();
    }

    private Connection getConnection() throws SQLException, URISyntaxException {
        URI databaseURI = 
                new URI(System.getenv(DB_URL_ENVIRONMENT_VARIABLE_NAME));

        String username = databaseURI.getUserInfo().split(":")[0];
        String password = databaseURI.getUserInfo().split(":")[1];
        String databaseURL = 
                "jdbc:mysql://" + databaseURI.getHost() + databaseURI.getPath();

        return DriverManager.getConnection(databaseURL, username, password);
    }
}

com.github.coderodde.weblog.viewcounter.JSONResponseObject.java:

package com.github.coderodde.weblog.viewcounter;

/**
 * This POJO class type defines a simple object for reporting to the front-end.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Aug 22, 2021)
 * @since 1.6 (Aug 22, 2021)
 */
public class JSONResponseObject {

    public boolean succeeded;
    public int numberOfViews;
    public String mostRecentViewTime;
}

com.github.coderodde.weblog.viewcounter.Util.java:

package com.github.coderodde.weblog.viewcounter;

/**
 * This class contains common utility methods.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.61 (Aug 25, 2021)
 * @since 1.61 (Aug 25, 2021)
 */
public class Util {

    /**
     * Allows writing {@code method(arg1, ..., argn)}.
     * 
     * @param objects the objects.
     * @return same array.
     */
    public static Object[] objects(Object... objects) {
        return objects;
    }
}

The entire repository is here. You can see the servlet in action here (scroll down to the very bottom in order to see the counter).

Critique request

Please, tell me how to further improve my work.

\$\endgroup\$
2
  • \$\begingroup\$ Where can I put the "ready to merge" checkmark on this pull-request? ;-) \$\endgroup\$
    – mtj
    Aug 25, 2021 at 9:48
  • \$\begingroup\$ @mtj Haha, you are funny, Sir. :-D \$\endgroup\$
    – coderodde
    Aug 25, 2021 at 10:51

1 Answer 1

1
\$\begingroup\$

Couple of thing that I've noticed are:

  1. Total views is getting updated every-time when the page is refreshed. So it does not shows number of unique visitors - suppose this is expected behaviour.
  2. Last visit time is showing local time of the visitor. It could have been updated to use UTC format, or the timezone can be displayed.
\$\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.