Skip to content

Commit 95044a8

Browse files
committed
Intial Cloud SQL Connectivity App.
1 parent 1e3dec3 commit 95044a8

File tree

6 files changed

+430
-0
lines changed

6 files changed

+430
-0
lines changed

cloud-sql/servlet/pom.xml

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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+
<project>
17+
<modelVersion>4.0.0</modelVersion>
18+
<packaging>war</packaging>
19+
<version>1.0-SNAPSHOT</version>
20+
<groupId>com.example.cloudsql</groupId>
21+
<artifactId>tabs-vs-spaces</artifactId>
22+
23+
<!--
24+
The parent pom defines common style checks and testing strategies for our samples.
25+
Removing or replacing it should not affect the execution of the samples in anyway.
26+
-->
27+
<parent>
28+
<groupId>com.google.cloud.samples</groupId>
29+
<artifactId>shared-configuration</artifactId>
30+
<version>1.0.10</version>
31+
</parent>
32+
33+
<properties>
34+
<maven.compiler.target>1.8</maven.compiler.target>
35+
<maven.compiler.source>1.8</maven.compiler.source>
36+
<failOnMissingWebXml>false</failOnMissingWebXml>
37+
</properties>
38+
39+
<dependencies>
40+
<dependency>
41+
<groupId>javax.servlet</groupId>
42+
<artifactId>javax.servlet-api</artifactId>
43+
<version>3.1.0</version>
44+
<type>jar</type>
45+
<scope>provided</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>mysql</groupId>
49+
<artifactId>mysql-connector-java</artifactId>
50+
<version>8.0.11</version>
51+
</dependency>
52+
<dependency>
53+
<groupId>com.google.cloud.sql</groupId>
54+
<artifactId>mysql-socket-factory-connector-j-8</artifactId>
55+
<version>1.0.11</version>
56+
</dependency>
57+
<dependency>
58+
<groupId>com.zaxxer</groupId>
59+
<artifactId>HikariCP</artifactId>
60+
<version>3.1.0</version>
61+
</dependency>
62+
</dependencies>
63+
64+
<build>
65+
<plugins>
66+
<plugin>
67+
<groupId>org.eclipse.jetty</groupId>
68+
<artifactId>jetty-maven-plugin</artifactId>
69+
<version>9.4.10.v20180503</version>
70+
<configuration>
71+
<scanIntervalSeconds>1</scanIntervalSeconds>
72+
</configuration>
73+
</plugin>
74+
<plugin>
75+
<groupId>com.google.cloud.tools</groupId>
76+
<artifactId>appengine-maven-plugin</artifactId>
77+
<version>1.3.2</version>
78+
</plugin>
79+
</plugins>
80+
</build>
81+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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 com.zaxxer.hikari.HikariConfig;
20+
import com.zaxxer.hikari.HikariDataSource;
21+
import java.sql.Connection;
22+
import java.sql.PreparedStatement;
23+
import java.sql.SQLException;
24+
import javax.servlet.ServletContextEvent;
25+
import javax.servlet.ServletContextListener;
26+
import javax.servlet.annotation.WebListener;
27+
import javax.sql.DataSource;
28+
29+
@WebListener
30+
public class ConnectionPoolContextListener implements ServletContextListener {
31+
32+
private static final String DB_USER = System.getenv("DB_USER");
33+
private static final String DB_PASS = System.getenv("DB_PASS");
34+
private static final String DB_NAME = System.getenv("DB_NAME");
35+
36+
private DataSource createConnectionPool(){
37+
// [START cloud_sql_create_connection_pool]
38+
// Create a configuration object, and use it to specify how to connect to our database.
39+
HikariConfig config = new HikariConfig();
40+
41+
// Add information about how to connect to the database
42+
config.setJdbcUrl(String.format("jdbc:mysql:///%s", DB_NAME));
43+
config.setUsername(DB_USER); // e.g. "root", "postgres"
44+
config.setPassword(DB_PASS); // e.g. "my-password"
45+
46+
// Configure the Cloud SQL JDBC Socket Factory to provide authentication
47+
config.addDataSourceProperty("socketFactory", "com.google.cloud.sql.mysql.SocketFactory");
48+
config.addDataSourceProperty("cloudSqlInstance", "kvg-testing:us-central1:test1");
49+
config.addDataSourceProperty("useSSL", "false");
50+
51+
config.setMaximumPoolSize(5); // Limit the maximum number of connections
52+
config.setIdleTimeout(60000); // Terminate unused connections after 10m
53+
54+
// Using the config, create a connection pool to manage connections to your database.
55+
DataSource pool = new HikariDataSource(config);
56+
// [START cloud_sql_create_connection_pool]
57+
return pool;
58+
}
59+
60+
private void createTableSchema(DataSource pool){
61+
// Safely attempt to create the table schema
62+
try (Connection conn = pool.getConnection()){
63+
PreparedStatement createTableStatement = conn.prepareStatement(
64+
"CREATE TABLE IF NOT EXISTS votes ( "
65+
+ "vote_id SERIAL NOT NULL, ts timestamp NOT NULL, team CHAR(6) NOT NULL, "
66+
+ "PRIMARY KEY (vote_id) );"
67+
);
68+
createTableStatement.execute();
69+
} catch (SQLException e) {
70+
throw new Error("Unable to create table 'votes'! \n" + e.toString());
71+
}
72+
}
73+
74+
@Override
75+
public void contextDestroyed(ServletContextEvent event) {
76+
// This function is called when the Servlet is destroyed
77+
DataSource pool = (DataSource) event.getServletContext().getAttribute("pool");
78+
if (pool != null) {
79+
try {
80+
pool.unwrap(HikariDataSource.class).close();
81+
} catch (SQLException e) {
82+
// Handle exception
83+
System.out.println("Any error occurred while the application was shutting down: " + e);
84+
}
85+
}
86+
}
87+
88+
@Override
89+
90+
public void contextInitialized(ServletContextEvent event) {
91+
// This function is called when the application starts and will safely create a connection pool
92+
// that can be used to connect to
93+
DataSource pool = (DataSource) event.getServletContext().getAttribute("pool");
94+
if (pool == null) {
95+
pool = createConnectionPool();
96+
event.getServletContext().setAttribute("pool", pool);
97+
}
98+
createTableSchema(pool);
99+
}
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
* {
18+
box-sizing: border-box;
19+
}
20+
21+
#main {
22+
display: flex;
23+
background: whitesmoke;
24+
text-align: center;
25+
}
26+
27+
#main > article {
28+
margin: auto;
29+
width: 60%;
30+
}
31+
32+
#main > aside {
33+
margin: auto;
34+
width: 20%;
35+
}

0 commit comments

Comments
 (0)