Skip to content

Commit f5fc6cd

Browse files
authored
JDBC session map (#8378)
Provides an implementation of the `SessionMap` that's backed by JDBC.
1 parent 482963e commit f5fc6cd

12 files changed

Lines changed: 545 additions & 2 deletions

File tree

java/maven_deps.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def selenium_java_deps():
7171
"org.eclipse.jetty:jetty-xml:%s" % jetty_version,
7272
"org.eclipse.mylyn.github:org.eclipse.egit.github.core:2.1.5",
7373
"org.hamcrest:hamcrest:2.2",
74+
"org.hsqldb:hsqldb:2.5.0",
7475
"org.mockito:mockito-core:3.3.3",
7576
"org.slf4j:slf4j-jdk14:1.7.30",
7677
"org.testng:testng:7.1.0",

java/maven_install.json

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"dependency_tree": {
3-
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": 189058187,
3+
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": 1361415682,
44
"conflict_resolution": {},
55
"dependencies": [
66
{
@@ -4215,6 +4215,42 @@
42154215
"sha256": "f49e697dbc70591f91a90dd7f741f5780f53f63f34a416d6a9879499d4d666af",
42164216
"url": "https://repo1.maven.org/maven2/org/hamcrest/hamcrest/2.2/hamcrest-2.2-sources.jar"
42174217
},
4218+
{
4219+
"coord": "org.hsqldb:hsqldb:2.5.0",
4220+
"dependencies": [],
4221+
"directDependencies": [],
4222+
"exclusions": [
4223+
"org.hamcrest:hamcrest-all",
4224+
"org.hamcrest:hamcrest-core",
4225+
"io.netty:netty-all"
4226+
],
4227+
"file": "v1/https/repo1.maven.org/maven2/org/hsqldb/hsqldb/2.5.0/hsqldb-2.5.0.jar",
4228+
"mirror_urls": [
4229+
"https://repo1.maven.org/maven2/org/hsqldb/hsqldb/2.5.0/hsqldb-2.5.0.jar",
4230+
"https://jcenter.bintray.com/org/hsqldb/hsqldb/2.5.0/hsqldb-2.5.0.jar",
4231+
"https://maven.google.com/org/hsqldb/hsqldb/2.5.0/hsqldb-2.5.0.jar"
4232+
],
4233+
"sha256": "acda459cc9d6a07b39b284364e93b5f29e11877d687e9544b91778d3554d2b38",
4234+
"url": "https://repo1.maven.org/maven2/org/hsqldb/hsqldb/2.5.0/hsqldb-2.5.0.jar"
4235+
},
4236+
{
4237+
"coord": "org.hsqldb:hsqldb:jar:sources:2.5.0",
4238+
"dependencies": [],
4239+
"directDependencies": [],
4240+
"exclusions": [
4241+
"org.hamcrest:hamcrest-all",
4242+
"org.hamcrest:hamcrest-core",
4243+
"io.netty:netty-all"
4244+
],
4245+
"file": "v1/https/repo1.maven.org/maven2/org/hsqldb/hsqldb/2.5.0/hsqldb-2.5.0-sources.jar",
4246+
"mirror_urls": [
4247+
"https://repo1.maven.org/maven2/org/hsqldb/hsqldb/2.5.0/hsqldb-2.5.0-sources.jar",
4248+
"https://jcenter.bintray.com/org/hsqldb/hsqldb/2.5.0/hsqldb-2.5.0-sources.jar",
4249+
"https://maven.google.com/org/hsqldb/hsqldb/2.5.0/hsqldb-2.5.0-sources.jar"
4250+
],
4251+
"sha256": "a09e15c2ef034480b5b0998d013ca3c98c4ccfd0e1d739acdc73400fd1a21dd3",
4252+
"url": "https://repo1.maven.org/maven2/org/hsqldb/hsqldb/2.5.0/hsqldb-2.5.0-sources.jar"
4253+
},
42184254
{
42194255
"coord": "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.70",
42204256
"dependencies": [],

java/server/src/org/openqa/selenium/grid/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ java_export(
7373
":base-command",
7474
"//java/server/src/org/openqa/selenium/cli",
7575
"//java/server/src/org/openqa/selenium/grid/config",
76+
"//java/server/src/org/openqa/selenium/grid/sessionmap/jdbc",
7677
],
7778
)
7879

java/server/src/org/openqa/selenium/grid/config/ConfigException.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@ public class ConfigException extends RuntimeException {
2222
public ConfigException(String message, Object... args) {
2323
super(String.format(message, args));
2424
}
25+
26+
public ConfigException(Throwable cause) {
27+
super(cause);
28+
}
2529
}

java/server/src/org/openqa/selenium/grid/sessionmap/httpd/SessionMapServer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
4141
import static org.openqa.selenium.grid.config.StandardGridRoles.EVENT_BUS_ROLE;
4242
import static org.openqa.selenium.grid.config.StandardGridRoles.HTTPD_ROLE;
43+
import static org.openqa.selenium.grid.config.StandardGridRoles.SESSION_MAP_ROLE;
4344
import static org.openqa.selenium.json.Json.JSON_UTF_8;
4445
import static org.openqa.selenium.remote.http.Contents.asJson;
4546
import static org.openqa.selenium.remote.http.Route.get;
@@ -61,7 +62,7 @@ public String getDescription() {
6162

6263
@Override
6364
public Set<Role> getConfigurableRoles() {
64-
return ImmutableSet.of(EVENT_BUS_ROLE, HTTPD_ROLE);
65+
return ImmutableSet.of(EVENT_BUS_ROLE, HTTPD_ROLE, SESSION_MAP_ROLE);
6566
}
6667

6768
@Override
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
load("@rules_jvm_external//:defs.bzl", "artifact")
2+
load("//java:version.bzl", "SE_VERSION")
3+
load("//java:defs.bzl", "java_export")
4+
5+
java_export(
6+
name = "jdbc",
7+
srcs = glob(["*.java"]),
8+
maven_coordinates = "org.seleniumhq.selenium:selenium-session-map-jdbc:%s" % SE_VERSION,
9+
pom_template = "//java/client/src/org/openqa/selenium:template-pom",
10+
visibility = [
11+
"//visibility:public",
12+
],
13+
deps = [
14+
"//java:auto-service",
15+
"//java/client/src/org/openqa/selenium/json",
16+
"//java/server/src/org/openqa/selenium/events",
17+
"//java/client/src/org/openqa/selenium/remote",
18+
"//java/server/src/org/openqa/selenium/grid/server",
19+
"//java/server/src/org/openqa/selenium/grid/sessionmap",
20+
"//java/server/src/org/openqa/selenium/grid/config",
21+
"//java/server/src/org/openqa/selenium/grid/log",
22+
"//java/server/src/org/openqa/selenium/grid/data",
23+
artifact("com.beust:jcommander"),
24+
artifact("com.google.guava:guava"),
25+
],
26+
)
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. 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,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.grid.sessionmap.jdbc;
19+
20+
import static org.openqa.selenium.grid.data.SessionClosedEvent.SESSION_CLOSED;
21+
22+
import org.openqa.selenium.Capabilities;
23+
import org.openqa.selenium.ImmutableCapabilities;
24+
import org.openqa.selenium.NoSuchSessionException;
25+
import org.openqa.selenium.events.EventBus;
26+
import org.openqa.selenium.grid.config.Config;
27+
import org.openqa.selenium.grid.config.ConfigException;
28+
import org.openqa.selenium.grid.data.Session;
29+
import org.openqa.selenium.grid.log.LoggingOptions;
30+
import org.openqa.selenium.grid.server.EventBusOptions;
31+
import org.openqa.selenium.grid.sessionmap.SessionMap;
32+
import org.openqa.selenium.internal.Require;
33+
import org.openqa.selenium.json.Json;
34+
import org.openqa.selenium.remote.SessionId;
35+
import org.openqa.selenium.remote.tracing.Tracer;
36+
37+
import java.io.Closeable;
38+
import java.net.URI;
39+
import java.net.URISyntaxException;
40+
import java.sql.Connection;
41+
import java.sql.PreparedStatement;
42+
import java.sql.ResultSet;
43+
import java.sql.SQLException;
44+
import java.util.logging.Logger;
45+
46+
47+
public class JdbcBackedSessionMap extends SessionMap implements Closeable {
48+
49+
private static final Json JSON = new Json();
50+
private static final Logger LOG = Logger.getLogger(JdbcBackedSessionMap.class.getName());
51+
private static final String TABLE_NAME = "sessions_map";
52+
private static final String SESSION_ID_COL = "session_ids";
53+
private static final String SESSION_CAPS_COL = "session_caps";
54+
private static final String SESSION_URI_COL = "session_uri";
55+
private final EventBus bus;
56+
private final Connection connection;
57+
58+
59+
public JdbcBackedSessionMap(Tracer tracer, Connection jdbcConnection, EventBus bus) {
60+
super(tracer);
61+
62+
Require.nonNull("JDBC Connection Object", jdbcConnection);
63+
this.bus = Require.nonNull("Event bus", bus);
64+
65+
this.connection = jdbcConnection;
66+
this.bus.addListener(SESSION_CLOSED, event -> {
67+
SessionId id = event.getData(SessionId.class);
68+
remove(id);
69+
});
70+
}
71+
72+
public static SessionMap create(Config config) {
73+
Tracer tracer = new LoggingOptions(config).getTracer();
74+
EventBus bus = new EventBusOptions(config).getEventBus();
75+
76+
JdbcSessionMapOptions sessionMapOptions = new JdbcSessionMapOptions(config);
77+
78+
Connection connection;
79+
80+
try {
81+
connection = sessionMapOptions.getJdbcConnection();
82+
} catch (SQLException e) {
83+
throw new ConfigException(e);
84+
}
85+
86+
return new JdbcBackedSessionMap(tracer, connection, bus);
87+
}
88+
@Override
89+
public boolean add(Session session) {
90+
Require.nonNull("Session to add", session);
91+
92+
try {
93+
return insertSessionStatement(session).executeUpdate() >= 1;
94+
95+
} catch (SQLException e) {
96+
throw new JdbcException(e);
97+
}
98+
}
99+
100+
@Override
101+
public Session get(SessionId id) throws NoSuchSessionException {
102+
Require.nonNull("Session ID", id);
103+
104+
URI uri = null;
105+
Capabilities caps = null;
106+
String rawUri = null;
107+
108+
try (ResultSet sessions = readSessionStatement(id).executeQuery()){
109+
if (!sessions.next()) {
110+
throw new NoSuchSessionException("Unable to find...");
111+
}
112+
113+
rawUri = sessions.getString(SESSION_URI_COL);
114+
String rawCapabilities = sessions.getString(SESSION_CAPS_COL);
115+
116+
caps = rawCapabilities == null ?
117+
new ImmutableCapabilities() :
118+
JSON.toType(rawCapabilities, Capabilities.class);
119+
try {
120+
uri = new URI(rawUri);
121+
} catch (URISyntaxException e) {
122+
throw new NoSuchSessionException(String.format("Unable to convert session id (%s) to uri: %s", id, rawUri), e);
123+
}
124+
125+
return new Session(id, uri, caps);
126+
} catch (SQLException e) {
127+
throw new JdbcException(e);
128+
}
129+
}
130+
131+
@Override
132+
public void remove(SessionId id) {
133+
Require.nonNull("Session ID", id);
134+
135+
try {
136+
getDeleteSqlForSession(id).executeUpdate();
137+
} catch (SQLException e) {
138+
throw new JdbcException(e.getMessage());
139+
}
140+
}
141+
142+
@Override
143+
public void close() {
144+
try {
145+
connection.close();
146+
} catch (SQLException e) {
147+
LOG.warning("SQL exception while closing JDBC Connection:" + e.getMessage());
148+
}
149+
}
150+
151+
private PreparedStatement insertSessionStatement(Session session) throws SQLException {
152+
PreparedStatement insertStatement = connection.prepareStatement(String.format("insert into %1$s (%2$s, %3$s, %4$s) values (?, ?, ?)",
153+
TABLE_NAME,
154+
SESSION_ID_COL,
155+
SESSION_URI_COL,
156+
SESSION_CAPS_COL));
157+
158+
insertStatement.setString(1, session.getId().toString());
159+
insertStatement.setString(2, session.getUri().toString());
160+
insertStatement.setString(3, JSON.toJson(session.getCapabilities()));
161+
162+
return insertStatement;
163+
}
164+
165+
private PreparedStatement readSessionStatement(SessionId sessionId) throws SQLException {
166+
PreparedStatement getSessionsStatement = connection.prepareStatement(String.format("select * from %1$s where %2$s = ?",
167+
TABLE_NAME,
168+
SESSION_ID_COL));
169+
170+
getSessionsStatement.setMaxRows(1);
171+
getSessionsStatement.setString(1, sessionId.toString());
172+
173+
return getSessionsStatement;
174+
}
175+
176+
private PreparedStatement getDeleteSqlForSession(SessionId sessionId) throws SQLException{
177+
PreparedStatement deleteSessionStatement = connection.prepareStatement(String.format("delete from %1$s where %2$s = ?",
178+
TABLE_NAME,
179+
SESSION_ID_COL));
180+
181+
deleteSessionStatement.setString(1, sessionId.toString());
182+
183+
return deleteSessionStatement;
184+
185+
}
186+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. 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,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.grid.sessionmap.jdbc;
19+
20+
21+
import org.openqa.selenium.WebDriverException;
22+
23+
public class JdbcException extends WebDriverException {
24+
public JdbcException() {
25+
super();
26+
}
27+
28+
public JdbcException(String message) {
29+
super(message);
30+
}
31+
32+
public JdbcException(Throwable cause) {
33+
super(cause);
34+
}
35+
36+
public JdbcException(String message, Throwable cause) {
37+
super(message, cause);
38+
}
39+
}

0 commit comments

Comments
 (0)