Skip to content

Commit 4bdab22

Browse files
authored
Update cloudsql/ samples to address spotbugs issues. (GoogleCloudPlatform#2678)
* Fix lint errors. * Lint fixes for postgres. * Update maven plugin to use gcloud config.
1 parent e6fcdaa commit 4bdab22

File tree

10 files changed

+209
-132
lines changed

10 files changed

+209
-132
lines changed

cloud-sql/mysql/servlet/pom.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<parent>
2828
<groupId>com.google.cloud.samples</groupId>
2929
<artifactId>shared-configuration</artifactId>
30-
<version>1.0.15</version>
30+
<version>1.0.16</version>
3131
</parent>
3232

3333
<properties>
@@ -91,6 +91,10 @@
9191
<groupId>com.google.cloud.tools</groupId>
9292
<artifactId>appengine-maven-plugin</artifactId>
9393
<version>2.2.0</version>
94+
<configuration>
95+
<deploy.projectId>GCLOUD_CONFIG</deploy.projectId>
96+
<deploy.version>GCLOUD_CONFIG</deploy.version>
97+
</configuration>
9498
</plugin>
9599
</plugins>
96100
</build>

cloud-sql/mysql/servlet/src/main/java/com/example/cloudsql/ConnectionPoolContextListener.java

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,33 @@
1818

1919
import com.zaxxer.hikari.HikariConfig;
2020
import com.zaxxer.hikari.HikariDataSource;
21+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
2122
import java.sql.Connection;
2223
import java.sql.PreparedStatement;
2324
import java.sql.SQLException;
24-
import java.util.logging.Logger;
25+
import javax.servlet.ServletContext;
2526
import javax.servlet.ServletContextEvent;
2627
import javax.servlet.ServletContextListener;
2728
import javax.servlet.annotation.WebListener;
2829
import javax.sql.DataSource;
2930

31+
@SuppressFBWarnings(
32+
value = {"HARD_CODE_PASSWORD", "WEM_WEAK_EXCEPTION_MESSAGING"},
33+
justification = "Extracted from environment, Exception message adds context.")
3034
@WebListener("Creates a connection pool that is stored in the Servlet's context for later use.")
3135
public class ConnectionPoolContextListener implements ServletContextListener {
3236

33-
private static final Logger LOGGER = Logger.getLogger(IndexServlet.class.getName());
34-
3537
// Saving credentials in environment variables is convenient, but not secure - consider a more
3638
// secure solution such as https://cloud.google.com/kms/ to help keep secrets safe.
37-
private static final String CLOUD_SQL_CONNECTION_NAME = System.getenv(
38-
"CLOUD_SQL_CONNECTION_NAME");
39+
private static final String CLOUD_SQL_CONNECTION_NAME =
40+
System.getenv("CLOUD_SQL_CONNECTION_NAME");
3941
private static final String DB_USER = System.getenv("DB_USER");
4042
private static final String DB_PASS = System.getenv("DB_PASS");
4143
private static final String DB_NAME = System.getenv("DB_NAME");
4244

45+
@SuppressFBWarnings(
46+
value = "USBR_UNNECESSARY_STORE_BEFORE_RETURN",
47+
justification = "Necessary for sample region tag.")
4348
private DataSource createConnectionPool() {
4449
// [START cloud_sql_mysql_servlet_create]
4550
// The configuration object specifies behaviors for the connection pool.
@@ -54,7 +59,6 @@ private DataSource createConnectionPool() {
5459
// See https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory for details.
5560
config.addDataSourceProperty("socketFactory", "com.google.cloud.sql.mysql.SocketFactory");
5661
config.addDataSourceProperty("cloudSqlInstance", CLOUD_SQL_CONNECTION_NAME);
57-
config.addDataSourceProperty("useSSL", "false");
5862

5963
// ... Specify additional connection properties here.
6064
// [START_EXCLUDE]
@@ -102,12 +106,13 @@ private DataSource createConnectionPool() {
102106
private void createTable(DataSource pool) throws SQLException {
103107
// Safely attempt to create the table schema.
104108
try (Connection conn = pool.getConnection()) {
105-
PreparedStatement createTableStatement = conn.prepareStatement(
109+
String stmt =
106110
"CREATE TABLE IF NOT EXISTS votes ( "
107111
+ "vote_id SERIAL NOT NULL, time_cast timestamp NOT NULL, candidate CHAR(6) NOT NULL,"
108-
+ " PRIMARY KEY (vote_id) );"
109-
);
110-
createTableStatement.execute();
112+
+ " PRIMARY KEY (vote_id) );";
113+
try (PreparedStatement createTableStatement = conn.prepareStatement(stmt); ) {
114+
createTableStatement.execute();
115+
}
111116
}
112117
}
113118

@@ -124,16 +129,19 @@ public void contextDestroyed(ServletContextEvent event) {
124129
public void contextInitialized(ServletContextEvent event) {
125130
// This function is called when the application starts and will safely create a connection pool
126131
// that can be used to connect to.
127-
DataSource pool = (DataSource) event.getServletContext().getAttribute("my-pool");
132+
ServletContext servletContext = event.getServletContext();
133+
DataSource pool = (DataSource) servletContext.getAttribute("my-pool");
128134
if (pool == null) {
129135
pool = createConnectionPool();
130-
event.getServletContext().setAttribute("my-pool", pool);
136+
servletContext.setAttribute("my-pool", pool);
131137
}
132138
try {
133139
createTable(pool);
134140
} catch (SQLException ex) {
135-
throw new RuntimeException("Unable to verify table schema. Please double check the steps"
136-
+ "in the README and try again.", ex);
141+
throw new RuntimeException(
142+
"Unable to verify table schema. Please double check the steps"
143+
+ "in the README and try again.",
144+
ex);
137145
}
138146
}
139147
}

cloud-sql/mysql/servlet/src/main/java/com/example/cloudsql/IndexServlet.java

Lines changed: 68 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.example.cloudsql;
1818

19+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1920
import java.io.IOException;
2021
import java.sql.Connection;
2122
import java.sql.PreparedStatement;
@@ -25,15 +26,20 @@
2526
import java.util.ArrayList;
2627
import java.util.Date;
2728
import java.util.List;
29+
import java.util.Locale;
2830
import java.util.logging.Level;
2931
import java.util.logging.Logger;
32+
import javax.annotation.Nullable;
3033
import javax.servlet.ServletException;
3134
import javax.servlet.annotation.WebServlet;
3235
import javax.servlet.http.HttpServlet;
3336
import javax.servlet.http.HttpServletRequest;
3437
import javax.servlet.http.HttpServletResponse;
3538
import javax.sql.DataSource;
3639

40+
@SuppressFBWarnings(
41+
value = {"SE_NO_SERIALVERSIONID", "WEM_WEAK_EXCEPTION_MESSAGING"},
42+
justification = "Not needed for IndexServlet, Exception adds context")
3743
@WebServlet(name = "Index", value = "")
3844
public class IndexServlet extends HttpServlet {
3945

@@ -46,44 +52,48 @@ public void doGet(HttpServletRequest req, HttpServletResponse resp)
4652
// in the ContextListener when the application was started
4753
DataSource pool = (DataSource) req.getServletContext().getAttribute("my-pool");
4854

49-
int tabCount;
50-
int spaceCount;
55+
int tabCount = 0;
56+
int spaceCount = 0;
5157
List<Vote> recentVotes = new ArrayList<>();
5258
try (Connection conn = pool.getConnection()) {
5359
// PreparedStatements are compiled by the database immediately and executed at a later date.
5460
// Most databases cache previously compiled queries, which improves efficiency.
55-
PreparedStatement voteStmt = conn.prepareStatement(
56-
"SELECT candidate, time_cast FROM votes ORDER BY time_cast DESC LIMIT 5");
57-
// Execute the statement
58-
ResultSet voteResults = voteStmt.executeQuery();
59-
// Convert a ResultSet into Vote objects
60-
while (voteResults.next()) {
61-
String candidate = voteResults.getString(1);
62-
Timestamp timeCast = voteResults.getTimestamp(2);
63-
recentVotes.add(new Vote(candidate, timeCast));
61+
String stmt1 = "SELECT candidate, time_cast FROM votes ORDER BY time_cast DESC LIMIT 5";
62+
try (PreparedStatement voteStmt = conn.prepareStatement(stmt1); ) {
63+
// Execute the statement
64+
ResultSet voteResults = voteStmt.executeQuery();
65+
// Convert a ResultSet into Vote objects
66+
while (voteResults.next()) {
67+
String candidate = voteResults.getString(1);
68+
Timestamp timeCast = voteResults.getTimestamp(2);
69+
recentVotes.add(new Vote(candidate, timeCast));
70+
}
6471
}
6572

6673
// PreparedStatements can also be executed multiple times with different arguments. This can
6774
// improve efficiency, and project a query from being vulnerable to an SQL injection.
68-
PreparedStatement voteCountStmt = conn.prepareStatement(
69-
"SELECT COUNT(vote_id) FROM votes WHERE candidate=?");
70-
71-
voteCountStmt.setString(1, "TABS");
72-
ResultSet tabResult = voteCountStmt.executeQuery();
73-
tabResult.next(); // Move to the first result
74-
tabCount = tabResult.getInt(1);
75-
76-
voteCountStmt.setString(1, "SPACES");
77-
ResultSet spaceResult = voteCountStmt.executeQuery();
78-
spaceResult.next(); // Move to the first result
79-
spaceCount = spaceResult.getInt(1);
80-
75+
String stmt2 = "SELECT COUNT(vote_id) FROM votes WHERE candidate=?";
76+
try (PreparedStatement voteCountStmt = conn.prepareStatement(stmt2); ) {
77+
voteCountStmt.setString(1, "TABS");
78+
ResultSet tabResult = voteCountStmt.executeQuery();
79+
if (tabResult.next()) { // Move to the first result
80+
tabCount = tabResult.getInt(1);
81+
}
82+
83+
voteCountStmt.setString(1, "SPACES");
84+
ResultSet spaceResult = voteCountStmt.executeQuery();
85+
if (spaceResult.next()) { // Move to the first result
86+
spaceCount = spaceResult.getInt(1);
87+
}
88+
}
8189
} catch (SQLException ex) {
8290
// If something goes wrong, the application needs to react appropriately. This might mean
8391
// getting a new connection and executing the query again, or it might mean redirecting the
8492
// user to a different page to let them know something went wrong.
85-
throw new ServletException("Unable to successfully connect to the database. Please check the "
86-
+ "steps in the README and try again.", ex);
93+
throw new ServletException(
94+
"Unable to successfully connect to the database. Please check the "
95+
+ "steps in the README and try again.",
96+
ex);
8797
}
8898

8999
// Add variables and render the page
@@ -93,16 +103,29 @@ public void doGet(HttpServletRequest req, HttpServletResponse resp)
93103
req.getRequestDispatcher("/index.jsp").forward(req, resp);
94104
}
95105

106+
// Used to validate user input. All user provided data should be validated and sanitized before
107+
// being used something like a SQL query. Returns null if invalid.
108+
@Nullable
109+
private String validateTeam(String input) {
110+
if (input != null) {
111+
input = input.toUpperCase(Locale.ENGLISH);
112+
// Must be either "TABS" or "SPACES"
113+
if (!"TABS".equals(input) && !"SPACES".equals(input)) {
114+
return null;
115+
}
116+
}
117+
return input;
118+
}
119+
120+
@SuppressFBWarnings(
121+
value = {"SERVLET_PARAMETER", "XSS_SERVLET"},
122+
justification = "Input is validated and sanitized.")
96123
@Override
97-
public void doPost(HttpServletRequest req, HttpServletResponse resp)
98-
throws IOException {
124+
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
99125
// Get the team from the request and record the time of the vote.
100-
String team = req.getParameter("team");
101-
if (team != null) {
102-
team = team.toUpperCase();
103-
}
126+
String team = validateTeam(req.getParameter("team"));
104127
Timestamp now = new Timestamp(new Date().getTime());
105-
if (team == null || (!team.equals("TABS") && !team.equals("SPACES"))) {
128+
if (team == null) {
106129
resp.setStatus(400);
107130
resp.getWriter().append("Invalid team specified.");
108131
return;
@@ -116,28 +139,29 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp)
116139
try (Connection conn = pool.getConnection()) {
117140

118141
// PreparedStatements can be more efficient and project against injections.
119-
PreparedStatement voteStmt = conn.prepareStatement(
120-
"INSERT INTO votes (time_cast, candidate) VALUES (?, ?);");
121-
voteStmt.setTimestamp(1, now);
122-
voteStmt.setString(2, team);
123-
124-
// Finally, execute the statement. If it fails, an error will be thrown.
125-
voteStmt.execute();
142+
String stmt = "INSERT INTO votes (time_cast, candidate) VALUES (?, ?);";
143+
try (PreparedStatement voteStmt = conn.prepareStatement(stmt); ) {
144+
voteStmt.setTimestamp(1, now);
145+
voteStmt.setString(2, team);
126146

147+
// Finally, execute the statement. If it fails, an error will be thrown.
148+
voteStmt.execute();
149+
}
127150
} catch (SQLException ex) {
128151
// If something goes wrong, handle the error in this section. This might involve retrying or
129152
// adjusting parameters depending on the situation.
130153
// [START_EXCLUDE]
131154
LOGGER.log(Level.WARNING, "Error while attempting to submit vote.", ex);
132155
resp.setStatus(500);
133-
resp.getWriter().write("Unable to successfully cast vote! Please check the application "
134-
+ "logs for more details.");
156+
resp.getWriter()
157+
.write(
158+
"Unable to successfully cast vote! Please check the application "
159+
+ "logs for more details.");
135160
// [END_EXCLUDE]
136161
}
137162
// [END cloud_sql_mysql_servlet_connection]
138163

139164
resp.setStatus(200);
140-
resp.getWriter().printf("Vote successfully cast for '%s' at time %s!\n", team, now);
165+
resp.getWriter().printf("Vote successfully cast for '%s' at time %s!%n", team, now);
141166
}
142-
143167
}

cloud-sql/mysql/servlet/src/main/java/com/example/cloudsql/Vote.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,35 @@
1717
package com.example.cloudsql;
1818

1919
import java.sql.Timestamp;
20+
import java.util.Locale;
2021

2122
public class Vote {
2223

2324
private String candidate;
2425
private Timestamp timeCast;
2526

2627
public Vote(String candidate, Timestamp timeCast) {
27-
this.candidate = candidate.toUpperCase();
28-
this.timeCast = timeCast;
28+
this.candidate = candidate.toUpperCase(Locale.ENGLISH);
29+
this.timeCast = new Timestamp(timeCast.getTime());
2930
}
3031

3132
public String getCandidate() {
3233
return candidate;
3334
}
3435

3536
public void setCandidate(String candidate) {
36-
this.candidate = candidate.toUpperCase();
37+
this.candidate = candidate.toUpperCase(Locale.ENGLISH);
3738
}
3839

3940
public Timestamp getTimeCast() {
40-
return timeCast;
41+
return new Timestamp(timeCast.getTime());
4142
}
4243

4344
public void setTimeCast(Timestamp timeCast) {
44-
this.timeCast = timeCast;
45+
this.timeCast = new Timestamp(timeCast.getTime());
4546
}
4647

48+
public String toString() {
49+
return String.format("Vote(candidate=%s,timeCast=%s)", this.candidate, this.timeCast);
50+
}
4751
}

cloud-sql/postgres/servlet/pom.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<parent>
2828
<groupId>com.google.cloud.samples</groupId>
2929
<artifactId>shared-configuration</artifactId>
30-
<version>1.0.15</version>
30+
<version>1.0.16</version>
3131
</parent>
3232

3333
<properties>
@@ -81,6 +81,10 @@
8181
<groupId>com.google.cloud.tools</groupId>
8282
<artifactId>appengine-maven-plugin</artifactId>
8383
<version>2.2.0</version>
84+
<configuration>
85+
<deploy.projectId>GCLOUD_CONFIG</deploy.projectId>
86+
<deploy.version>GCLOUD_CONFIG</deploy.version>
87+
</configuration>
8488
</plugin>
8589
</plugins>
8690
</build>

0 commit comments

Comments
 (0)