Skip to content

Commit 1a310e4

Browse files
authored
Session Handling with Firestore tutorial. (#1746)
1 parent b309411 commit 1a310e4

File tree

4 files changed

+445
-0
lines changed

4 files changed

+445
-0
lines changed

session-handling/pom.xml

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Copyright 2019 Google LLC
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<project>
18+
<modelVersion>4.0.0</modelVersion>
19+
<packaging>war</packaging>
20+
21+
<groupId>com.example.getstarted</groupId>
22+
<artifactId>session-handling</artifactId>
23+
<version>1.0-SNAPSHOT</version>
24+
25+
<parent>
26+
<groupId>com.google.cloud.samples</groupId>
27+
<artifactId>shared-configuration</artifactId>
28+
<version>1.0.11</version>
29+
</parent>
30+
31+
<properties>
32+
<gcloud.appId>MY_PROJECT</gcloud.appId>
33+
34+
<failOnMissingWebXml>false</failOnMissingWebXml>
35+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
36+
<maven.compiler.source>11</maven.compiler.source>
37+
<maven.compiler.target>11</maven.compiler.target>
38+
<maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
39+
<maven.compiler.showWarnings>true</maven.compiler.showWarnings>
40+
<maven.compiler.failOnWarning>false</maven.compiler.failOnWarning>
41+
<maven.war.filteringDeploymentDescriptors>false</maven.war.filteringDeploymentDescriptors>
42+
<jetty.version>9.4.21.v20190926</jetty.version>
43+
</properties>
44+
45+
<dependencies>
46+
<!-- https://mvnrepository.com/artifact/com.google.cloud/google-cloud-firestore -->
47+
<dependency>
48+
<groupId>com.google.cloud</groupId>
49+
<artifactId>google-cloud-firestore</artifactId>
50+
<version>0.52.0-beta</version>
51+
</dependency>
52+
53+
<dependency>
54+
<groupId>javax.servlet</groupId>
55+
<artifactId>javax.servlet-api</artifactId>
56+
<version>4.0.0</version>
57+
</dependency>
58+
59+
<dependency>
60+
<groupId>com.google.guava</groupId>
61+
<artifactId>guava</artifactId>
62+
<version>23.0</version>
63+
<scope>compile</scope>
64+
</dependency>
65+
66+
<dependency>
67+
<groupId>io.opencensus</groupId>
68+
<artifactId>opencensus-contrib-http-util</artifactId>
69+
<version>0.11.1</version>
70+
</dependency>
71+
72+
<!-- Test dependencies -->
73+
<dependency>
74+
<groupId>junit</groupId>
75+
<artifactId>junit</artifactId>
76+
<version>4.12</version>
77+
<scope>test</scope>
78+
</dependency>
79+
<dependency>
80+
<groupId>org.seleniumhq.selenium</groupId>
81+
<artifactId>selenium-server</artifactId>
82+
<version>3.3.1</version>
83+
<scope>test</scope>
84+
</dependency>
85+
<dependency>
86+
<groupId>org.seleniumhq.selenium</groupId>
87+
<artifactId>selenium-chrome-driver</artifactId>
88+
<version>3.3.1</version>
89+
<scope>test</scope>
90+
</dependency>
91+
</dependencies>
92+
93+
<build>
94+
<finalName>bookshelf-session-handling</finalName>
95+
<!-- Optional - for hot reload of the web application when using an IDE Eclipse / IDEA -->
96+
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes
97+
</outputDirectory>
98+
<plugins>
99+
<plugin>
100+
<groupId>org.eclipse.jetty</groupId>
101+
<artifactId>jetty-maven-plugin</artifactId>
102+
<version>${jetty.version}</version>
103+
</plugin>
104+
<plugin>
105+
<groupId>com.google.cloud.tools</groupId>
106+
<artifactId>jib-maven-plugin</artifactId>
107+
<version>1.7.0</version>
108+
<configuration>
109+
<to>
110+
<image>gcr.io/${gcloud.appId}/session-handling</image>
111+
</to>
112+
</configuration>
113+
</plugin>
114+
</plugins>
115+
</build>
116+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/* Copyright 2019 Google LLC
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package com.example.gettingstarted.actions;
17+
18+
// [START session_handling_servlet]
19+
20+
import java.io.IOException;
21+
import java.util.Random;
22+
import java.util.logging.Logger;
23+
24+
import javax.servlet.annotation.WebServlet;
25+
import javax.servlet.http.HttpServlet;
26+
import javax.servlet.http.HttpServletRequest;
27+
import javax.servlet.http.HttpServletResponse;
28+
29+
@WebServlet(
30+
name = "helloworld",
31+
urlPatterns = {"/"})
32+
public class HelloWorldServlet extends HttpServlet {
33+
private static String[] greetings = {
34+
"Hello World", "Hallo Welt", "Ciao Mondo", "Salut le Monde", "Hola Mundo",
35+
};
36+
private static final Logger logger = Logger.getLogger(HelloWorldServlet.class.getName());
37+
38+
@Override
39+
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
40+
if (!req.getServletPath().equals("/")) {
41+
return;
42+
}
43+
// Get current values for the session.
44+
// If any attribute doesn't exist, add it to the session.
45+
Integer views = (Integer) req.getSession().getAttribute("views");
46+
if (views == null) {
47+
views = 0;
48+
}
49+
views++;
50+
req.getSession().setAttribute("views", views);
51+
52+
String greeting = (String) req.getSession().getAttribute("greeting");
53+
if (greeting == null) {
54+
greeting = greetings[new Random().nextInt(greetings.length)];
55+
req.getSession().setAttribute("greeting", greeting);
56+
}
57+
58+
logger.info("Writing response " + req.toString());
59+
resp.getWriter().write(String.format("%d views for %s", views, greeting));
60+
}
61+
}
62+
// [END session_handling_servlet]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/* Copyright 2019 Google LLC
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package com.example.gettingstarted.util;
17+
18+
import com.example.gettingstarted.actions.HelloWorldServlet;
19+
import com.google.cloud.firestore.CollectionReference;
20+
import com.google.cloud.firestore.DocumentSnapshot;
21+
import com.google.cloud.firestore.Firestore;
22+
import com.google.cloud.firestore.FirestoreOptions;
23+
import com.google.cloud.firestore.QueryDocumentSnapshot;
24+
import com.google.cloud.firestore.QuerySnapshot;
25+
import com.google.common.collect.Maps;
26+
import java.io.IOException;
27+
import java.math.BigInteger;
28+
import java.security.SecureRandom;
29+
import java.text.SimpleDateFormat;
30+
import java.util.Calendar;
31+
import java.util.Date;
32+
import java.util.Enumeration;
33+
import java.util.HashMap;
34+
import java.util.Map;
35+
import java.util.concurrent.ExecutionException;
36+
import java.util.logging.Logger;
37+
38+
import javax.servlet.Filter;
39+
import javax.servlet.FilterChain;
40+
import javax.servlet.FilterConfig;
41+
import javax.servlet.ServletException;
42+
import javax.servlet.ServletRequest;
43+
import javax.servlet.ServletResponse;
44+
import javax.servlet.annotation.WebFilter;
45+
import javax.servlet.http.Cookie;
46+
import javax.servlet.http.HttpServletRequest;
47+
import javax.servlet.http.HttpServletResponse;
48+
import javax.servlet.http.HttpSession;
49+
50+
@WebFilter(
51+
filterName = "FirestoreSessionFilter ",
52+
urlPatterns = {""})
53+
public class FirestoreSessionFilter implements Filter {
54+
private static final SimpleDateFormat dtf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
55+
private static final Logger logger = Logger.getLogger(HelloWorldServlet.class.getName());
56+
private static Firestore firestore;
57+
private static CollectionReference sessions;
58+
59+
// [START sessions_handling_init]
60+
@Override
61+
public void init(FilterConfig config) throws ServletException {
62+
// Initialize local copy of datastore session variables.
63+
firestore = FirestoreOptions.getDefaultInstance().getService();
64+
sessions = firestore.collection("sessions");
65+
66+
try {
67+
// Delete all sessions unmodified for over two days.
68+
Calendar cal = Calendar.getInstance();
69+
cal.setTime(new Date());
70+
cal.add(Calendar.HOUR, -48);
71+
Date twoDaysAgo = Calendar.getInstance().getTime();
72+
QuerySnapshot sessionDocs =
73+
sessions.whereLessThan("lastModified", dtf.format(twoDaysAgo)).get().get();
74+
for (QueryDocumentSnapshot snapshot : sessionDocs.getDocuments()) {
75+
snapshot.getReference().delete();
76+
}
77+
} catch (InterruptedException | ExecutionException e) {
78+
throw new ServletException("Exception initializing FirestoreSessionFilter.", e);
79+
}
80+
}
81+
// [END sessions_handling_init]
82+
83+
// [START sessions_handling_filter]
84+
@Override
85+
public void doFilter(ServletRequest servletReq, ServletResponse servletResp, FilterChain chain)
86+
throws IOException, ServletException {
87+
HttpServletRequest req = (HttpServletRequest) servletReq;
88+
HttpServletResponse resp = (HttpServletResponse) servletResp;
89+
90+
// For this app only call Firestore for requests to base path `/`.
91+
if (!req.getServletPath().equals("/")) {
92+
chain.doFilter(servletReq, servletResp);
93+
return;
94+
}
95+
96+
// Check if the session cookie is there, if not there, make a session cookie using a unique
97+
// identifier.
98+
String sessionId = getCookieValue(req, "bookshelfSessionId");
99+
if (sessionId.equals("")) {
100+
String sessionNum = new BigInteger(130, new SecureRandom()).toString(32);
101+
Cookie session = new Cookie("bookshelfSessionId", sessionNum);
102+
session.setPath("/");
103+
resp.addCookie(session);
104+
}
105+
106+
// session variables for request
107+
Map<String, Object> firestoreMap = null;
108+
try {
109+
firestoreMap = loadSessionVariables(req);
110+
} catch (ExecutionException | InterruptedException e) {
111+
throw new ServletException("Exception loading session variables.", e);
112+
}
113+
114+
for (Map.Entry<String, Object> entry : firestoreMap.entrySet()) {
115+
servletReq.setAttribute(entry.getKey(), entry.getValue());
116+
}
117+
118+
// Allow the servlet to process request and response
119+
chain.doFilter(servletReq, servletResp);
120+
121+
// Create session map
122+
HttpSession session = req.getSession();
123+
Map<String, Object> sessionMap = new HashMap<>();
124+
Enumeration<String> attrNames = session.getAttributeNames();
125+
while (attrNames.hasMoreElements()) {
126+
String attrName = attrNames.nextElement();
127+
sessionMap.put(attrName, session.getAttribute(attrName));
128+
}
129+
130+
131+
logger.info(
132+
"Saving data to " + sessionId + " with views: " + session.getAttribute("views"));
133+
firestore.runTransaction((ob) -> sessions.document(sessionId).set(sessionMap));
134+
}
135+
// [END sessions_handling_filter]
136+
137+
private String getCookieValue(HttpServletRequest req, String cookieName) {
138+
Cookie[] cookies = req.getCookies();
139+
if (cookies != null) {
140+
for (Cookie cookie : cookies) {
141+
if (cookie.getName().equals(cookieName)) {
142+
return cookie.getValue();
143+
}
144+
}
145+
}
146+
return "";
147+
}
148+
149+
// [START sessions_load_session_variables]
150+
151+
/**
152+
* Take an HttpServletRequest, and copy all of the current session variables over to it
153+
*
154+
* @param req Request from which to extract session.
155+
* @return a map of strings containing all the session variables loaded or an empty map.
156+
*/
157+
private Map<String, Object> loadSessionVariables(HttpServletRequest req)
158+
throws ExecutionException, InterruptedException {
159+
Map<String, Object> datastoreMap = new HashMap<>();
160+
String sessionId = getCookieValue(req, "bookshelfSessionId");
161+
if (sessionId.equals("")) {
162+
return datastoreMap;
163+
}
164+
165+
return firestore
166+
.runTransaction(
167+
(ob) -> {
168+
DocumentSnapshot session = sessions.document(sessionId).get().get();
169+
Map<String, Object> data = session.getData();
170+
if (data == null) {
171+
data = Maps.newHashMap();
172+
}
173+
return data;
174+
})
175+
.get();
176+
}
177+
// [END sessions_load_session_variables]
178+
}

0 commit comments

Comments
 (0)