|
| 1 | +/* |
| 2 | + * Copyright 2018 Google LLC |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +package com.example.cloudsql; |
| 18 | + |
| 19 | +import java.io.IOException; |
| 20 | +import java.io.PrintWriter; |
| 21 | +import java.sql.Connection; |
| 22 | +import java.sql.PreparedStatement; |
| 23 | +import java.sql.ResultSet; |
| 24 | +import java.sql.SQLException; |
| 25 | +import java.sql.Timestamp; |
| 26 | +import java.util.ArrayList; |
| 27 | +import java.util.Date; |
| 28 | +import java.util.List; |
| 29 | +import javax.servlet.ServletException; |
| 30 | +import javax.servlet.annotation.WebServlet; |
| 31 | +import javax.servlet.http.HttpServlet; |
| 32 | +import javax.servlet.http.HttpServletRequest; |
| 33 | +import javax.servlet.http.HttpServletResponse; |
| 34 | +import javax.sql.DataSource; |
| 35 | + |
| 36 | +@WebServlet(name = "Index", value = "") |
| 37 | +public class IndexServlet extends HttpServlet { |
| 38 | + |
| 39 | + @Override |
| 40 | + public void doGet(HttpServletRequest req, HttpServletResponse resp) |
| 41 | + throws IOException, ServletException { |
| 42 | + // Extract the pool from the Servlet Context, reusing the one that was created |
| 43 | + // in the ContextListener when the application was started |
| 44 | + DataSource pool = (DataSource) req.getServletContext().getAttribute("pool"); |
| 45 | + |
| 46 | + // [START cloud_sql_example_query] |
| 47 | + int tabCt, voteCt; |
| 48 | + List<String> recentVotesTeams = new ArrayList<>(); |
| 49 | + List<Timestamp> recentVotesTimes = new ArrayList<>(); |
| 50 | + // It's important to always close a connection when you are finished with it, so that it is |
| 51 | + // returned to the pool and can be reused by other applications. |
| 52 | + // A try-with-resources statement automatically closes the connection when it exits the scope, |
| 53 | + // even if an error occurs. It's important to always close a connection when you are finished |
| 54 | + // with it, so that it is returned to the pool and can be reused later. |
| 55 | + try (Connection conn = pool.getConnection()){ |
| 56 | + // PreparedStatements are compiled by the database immediately and executed at a later date. |
| 57 | + // Databases can reused already compiled queries, improving efficiency. |
| 58 | + PreparedStatement voteStmt = conn.prepareStatement( |
| 59 | + "SELECT team, ts FROM votes ORDER BY ts DESC LIMIT 5"); |
| 60 | + // Execute the statement |
| 61 | + ResultSet voteResults = voteStmt.executeQuery(); |
| 62 | + // Process the results |
| 63 | + while(voteResults.next()){ |
| 64 | + recentVotesTeams.add(voteResults.getString(1)); |
| 65 | + recentVotesTimes.add(voteResults.getTimestamp(2)); |
| 66 | + } |
| 67 | + |
| 68 | + // PreparedStatements can also be executed multiple times with different arguments. This can |
| 69 | + // improve efficiency, and project a query from being vulnerable to an SQL injection. |
| 70 | + PreparedStatement voteCtStmt = conn.prepareStatement( |
| 71 | + "SELECT COUNT(vote_id) FROM votes WHERE team=?"); |
| 72 | + |
| 73 | + voteCtStmt.setString(1, "tabs"); |
| 74 | + ResultSet tabResult = voteCtStmt.executeQuery(); |
| 75 | + tabResult.next(); // Move to the first result |
| 76 | + tabCt = tabResult.getInt(1); |
| 77 | + |
| 78 | + voteCtStmt.setString(1, "spaces"); |
| 79 | + ResultSet spacesResult = voteCtStmt.executeQuery(); |
| 80 | + spacesResult.next(); // Move to the first result |
| 81 | + voteCt = spacesResult.getInt(1); |
| 82 | + |
| 83 | + } catch (SQLException e) { |
| 84 | + // If something goes wrong, the application needs to react appropriately. This might mean |
| 85 | + // getting a new connection and executing the query again, or it might mean redirecting the |
| 86 | + // user to a different page to let them know something went wrong. |
| 87 | + e.printStackTrace(); |
| 88 | + throw new Error("Unable to successfully complete SQL transaction! \n" + e.toString()); |
| 89 | + } |
| 90 | + // [END cloud_sql_example_query] |
| 91 | + |
| 92 | + // Add variables and render the page |
| 93 | + req.setAttribute("tabVoteCt", tabCt); |
| 94 | + req.setAttribute("spaceVoteCt", voteCt); |
| 95 | + req.setAttribute("winningTeam", tabCt > voteCt ? "TABS" : "SPACES"); |
| 96 | + req.setAttribute("voteDiff", tabCt > voteCt ? tabCt-voteCt: voteCt-tabCt); |
| 97 | + req.setAttribute("votesTeam", recentVotesTeams); |
| 98 | + req.setAttribute("votesTs", recentVotesTimes); |
| 99 | + req.getRequestDispatcher("/index.jsp").forward(req, resp); |
| 100 | + } |
| 101 | + |
| 102 | + @Override |
| 103 | + public void doPost(HttpServletRequest req, HttpServletResponse resp) |
| 104 | + throws IOException { |
| 105 | + // Get the team from the request and record the time of the vote. |
| 106 | + String team = req.getParameter("team"); |
| 107 | + if (team != null) team = team.toLowerCase(); |
| 108 | + Timestamp now = new Timestamp(new Date().getTime()); |
| 109 | + if (team == null || !team.equals("tabs") && !team.equals("spaces")){ |
| 110 | + resp.setStatus(400); |
| 111 | + resp.getWriter().append("Invalid team specified."); |
| 112 | + return; |
| 113 | + } |
| 114 | + |
| 115 | + // Reuse the pool that was created in the ContextListener when the Servlet started. |
| 116 | + DataSource pool = (DataSource) req.getServletContext().getAttribute("pool"); |
| 117 | + |
| 118 | + // Open the connection safely, making sure it will be closed when finished. |
| 119 | + try (Connection conn = pool.getConnection()) { |
| 120 | + |
| 121 | + // A PreparedStatement can be cached by the database, improving it's efficiency. |
| 122 | + PreparedStatement voteStmt = conn.prepareStatement( |
| 123 | + "INSERT INTO votes (ts, team) VALUES (?, ?);"); |
| 124 | + voteStmt.setTimestamp(1, now); |
| 125 | + voteStmt.setString(2, team); |
| 126 | + |
| 127 | + // Finally, execute the statement. If it fails, an error will be thrown. |
| 128 | + voteStmt.execute(); |
| 129 | + |
| 130 | + } catch (SQLException e) { |
| 131 | + // Let the user know the vote wasn't cast correctly. |
| 132 | + throw new Error("Unable to cast vote! \n" + e.toString()); |
| 133 | + } |
| 134 | + PrintWriter out = resp.getWriter(); |
| 135 | + out.printf("Vote successfully cast for '%s' at time %s!\n", team, now); |
| 136 | + } |
| 137 | + |
| 138 | +} |
0 commit comments