WIP: Jdbc session map - #8378
Conversation
shs96c
left a comment
There was a problem hiding this comment.
Welcome aboard :) This is a great start. I've added some comments, but we're also chatting on Slack, so you can ask things there too.
| // specific language governing permissions and limitations | ||
| // under the License. | ||
|
|
||
| package org.openqa.selenium.grid.sessionmap.config; |
There was a problem hiding this comment.
Please move this to the jdbc package you've just created.
| this.config = config; | ||
| } | ||
|
|
||
| public Connection getJdbcConnection() throws SQLException { |
There was a problem hiding this comment.
Given that this is only used in the JdbcSessionMap, it would be better to either move this into a helper method in the JdbcSessionMap, or create a jdbc.JdbcOptions class.
| public class JdbcBackedSessionMap extends SessionMap implements Closeable { | ||
|
|
||
| private static final Json JSON = new Json(); | ||
| private String tableName; |
| try { | ||
| connection = sessionMapOptions.getJdbcConnection(); | ||
| } catch (SQLException e) { | ||
| // TODO: Handle SQLException |
There was a problem hiding this comment.
You can throw a ConfigException here.
| Statement insertStatement = connection.createStatement(); | ||
| // TODO: Insert Call | ||
| } catch (SQLException e) { | ||
| // TODO: Handle SQLException |
There was a problem hiding this comment.
You can create a custom JdbcException which extends RuntimeException or WebDriverException if you want to.
| try { | ||
| connection.close(); | ||
| } catch (SQLException e) { | ||
| e.printStackTrace(); |
There was a problem hiding this comment.
Log using a java.util.logging.Logger at the warning level and swallow. There's nothing sensible to be done on the way out.
30f1300 to
00854be
Compare
shs96c
left a comment
There was a problem hiding this comment.
This is a great start. It's shaping up well.
| Capabilities caps = null; | ||
|
|
||
| try { | ||
| ResultSet sessions = readSessionStatement(id).executeQuery(); |
There was a problem hiding this comment.
Use try-with-resources so that the ResultSet will be closed properly.
| try { | ||
| ResultSet sessions = readSessionStatement(id).executeQuery(); | ||
|
|
||
| while(sessions.next()) { |
There was a problem hiding this comment.
How many results are you expecting? Perhaps use a LIMIT 1 to ensure only one row at most is sent back?
There was a problem hiding this comment.
With the limit in place, you either have 0 or 1 columns. You should be able to replace the while with an if, which may simplify this a little, as you can fast-path out by throwing the NoSuchSessionException if there's no session found.
| try { | ||
| uri = new URI(rawUri); | ||
| } | ||
| catch (URISyntaxException e) { |
There was a problem hiding this comment.
Java style is to put the closing brace and the catch statement on the same line } catch (URISyntaxException e) {
|
|
||
| sessions.close(); | ||
|
|
||
| if (uri == null) { |
There was a problem hiding this comment.
Those can't happen. If rawUri was null, then new URI would have thrown.
| } | ||
|
|
||
| private PreparedStatement insertSessionStatement(Session session) throws SQLException { | ||
| PreparedStatement insertStatement = connection.prepareStatement("insert into " + tableName + " values(?,?)"); |
There was a problem hiding this comment.
How do you know that tableName is not, itself, an SQL injection attack?
| private String sessionUri(SessionId sessionId) { | ||
| Require.nonNull("Session ID", sessionId); | ||
|
|
||
| return "session:" + sessionId.toString() + ":uri"; |
There was a problem hiding this comment.
The session id should be a UUID (it will be unique, even if it's not formatted correctly)
|
|
||
| private PreparedStatement getDeleteSqlForSession(SessionId sessionId) throws SQLException{ | ||
| PreparedStatement deleteSessionStatement = connection.prepareStatement("delete from " + tableName + " where " + sessionIdCol + " like ?"); | ||
| deleteSessionStatement.setString(1, sessionUri(sessionId).concat("%")); |
There was a problem hiding this comment.
A session id is unique and complete. You don't need to wildcard it.
| @Parameter( | ||
| names = "--jdbc-sessionid-column", | ||
| description = "Column name where session id will be stored") | ||
| @ConfigValue(section = "sessions", name = "jdbc-sessionid-column", example = "session_id") |
There was a problem hiding this comment.
Make life easy for yourself. Don't let this be configurable.
| @Parameter( | ||
| names = "--jdbc-table", | ||
| description = "Name of the table in database to store sessions in.") | ||
| @ConfigValue(section = "sessions", name = "jdbc-table", example = "myP@ssw%d") |
There was a problem hiding this comment.
I'd be very tempted to not make this configurable. If someone wants to use this, they'll just need to create the table needed.
| names = "--jdbc-capabilities-column", | ||
| description = "Column name where session id will be stored") | ||
| @ConfigValue(section = "sessions", name = "jdbc-capabilities-column", example = "capabilities") | ||
| private String sessionCapsColumn; |
There was a problem hiding this comment.
Again, don't let this be configurable.
shs96c
left a comment
There was a problem hiding this comment.
Just a couple of nits, and we're there. This looks great :) Thank you!
| try { | ||
| ResultSet sessions = readSessionStatement(id).executeQuery(); | ||
|
|
||
| while(sessions.next()) { |
There was a problem hiding this comment.
With the limit in place, you either have 0 or 1 columns. You should be able to replace the while with an if, which may simplify this a little, as you can fast-path out by throwing the NoSuchSessionException if there's no session found.
| Tracer tracer = new LoggingOptions(config).getTracer(); | ||
| JdbcSessionMapOptions sessionMapOptions = new JdbcSessionMapOptions(config); | ||
| String tableName = sessionMapOptions.getJdbcTableName(); | ||
| String sessionIdColName = sessionMapOptions.getJdbcSessionIdColName(); |
There was a problem hiding this comment.
All of these should be hard-coded now, so there's no need to look them up in the options :)
| private static final Logger LOG = Logger.getLogger(JdbcBackedSessionMap.class.getName()); | ||
| private final Connection connection; | ||
| private final String tableName; | ||
| private final String sessionIdCol; |
There was a problem hiding this comment.
Since we're hard-coding the table names, make these constants or just use magic strings....
|
|
||
| private static final String SESSIONS_SECTION = "sessions"; | ||
| private static final Logger LOG = Logger.getLogger(JdbcSessionMapOptions.class.getName()); | ||
| private static final String tableName = "sessions_map"; |
There was a problem hiding this comment.
It's safe to move these constants into the JdbcSessionMap itself.
shs96c
left a comment
There was a problem hiding this comment.
One more round, then let's land this and iterate :)
| ":base-command", | ||
| "//java/server/src/org/openqa/selenium/cli", | ||
| "//java/server/src/org/openqa/selenium/grid/config", | ||
| "//java/server/src/org/openqa/selenium/grid/sessionmap/jdbc", |
There was a problem hiding this comment.
Is this deliberate? Or a debugging aid?
There was a problem hiding this comment.
After speaking on Slack, it seems that this is here to help bootstrap things so that new flags appear on the classpath.
|
|
||
| private static final Json JSON = new Json(); | ||
| private static final Logger LOG = Logger.getLogger(JdbcBackedSessionMap.class.getName()); | ||
| private final EventBus bus; |
There was a problem hiding this comment.
Nit: place the instance fields below the constants.
| try { | ||
| connection = sessionMapOptions.getJdbcConnection(); | ||
| } catch (SQLException e) { | ||
| throw new ConfigException(e.toString()); |
There was a problem hiding this comment.
Maybe add the underlying exception too? new ConfigException(e)
| String rawUri = null; | ||
|
|
||
| try (ResultSet sessions = readSessionStatement(id).executeQuery()){ | ||
| if (sessions.next()) { |
There was a problem hiding this comment.
Prefer multiple return points and less indenting:
if (!session.next()) {
throw new NoSuchSessionException("Unable to find...");
}
| } | ||
|
|
||
| private PreparedStatement insertSessionStatement(Session session) throws SQLException { | ||
| PreparedStatement insertStatement = connection.prepareStatement("insert into " + TABLE_NAME |
There was a problem hiding this comment.
Nit: Newline before the arguments begin. That avoids excessive line length and makes the code a bit easier to read.
| } | ||
|
|
||
| private PreparedStatement insertSessionStatement(Session session) throws SQLException { | ||
| PreparedStatement insertStatement = connection.prepareStatement("insert into " + TABLE_NAME |
There was a problem hiding this comment.
Consider using String.format instead of concatenating strings. When there's just one, it's fine, but it starts to get hard to read after a while.
| } | ||
|
|
||
| private PreparedStatement readSessionStatement(SessionId sessionId) throws SQLException { | ||
| PreparedStatement getSessionsStatement = connection.prepareStatement("select * from " + TABLE_NAME |
There was a problem hiding this comment.
Nit: prefer String.format for readability
| @Parameter( | ||
| names = "--jdbc-password", | ||
| description = "Password for the user to make a JDBC connection") | ||
| @ConfigValue(section = "sessions", name = "jdbc-password", example = "myP@ssw%d") |
There was a problem hiding this comment.
Humorous aside: We tend to use hunter2 as the password of choice: https://knowyourmeme.com/memes/hunter2 https://w3c.github.io/webdriver/#example-7
| private final Config config; | ||
|
|
||
| public JdbcSessionMapOptions(Config config) { | ||
| this.config = config; |
shs96c
left a comment
There was a problem hiding this comment.
LGTM. Congratulations on landing this. It's a lovely feature!
| ":base-command", | ||
| "//java/server/src/org/openqa/selenium/cli", | ||
| "//java/server/src/org/openqa/selenium/grid/config", | ||
| "//java/server/src/org/openqa/selenium/grid/sessionmap/jdbc", |
There was a problem hiding this comment.
After speaking on Slack, it seems that this is here to help bootstrap things so that new flags appear on the classpath.
Provides an implementation of the `SessionMap` that's backed by JDBC.
Thanks for contributing to Selenium!
A PR well described will help maintainers to quickly review and merge it
Before submitting your PR, please check our contributing guidelines.
Avoid large PRs, help reviewers by making them as simple and short as possible.
Description
Fixes #8172
Motivation and Context
Types of changes
Checklist