Skip to content

Commit 29cdc06

Browse files
committed
Session Handling with Firestore tutorial.
1 parent 27ef838 commit 29cdc06

File tree

4 files changed

+443
-0
lines changed

4 files changed

+443
-0
lines changed

run/session-handling/pom.xml

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

0 commit comments

Comments
 (0)