Skip to content

Commit 7b8ce0a

Browse files
committed
Introduce a configurable New Session pipeline
1 parent 421cb16 commit 7b8ce0a

File tree

10 files changed

+139
-51
lines changed

10 files changed

+139
-51
lines changed

java/server/src/com/thoughtworks/selenium/webdriven/WebDriverBackedSeleniumServlet.java

+9-6
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,19 @@
2121

2222
import com.google.common.base.Joiner;
2323
import com.google.common.base.Splitter;
24-
import com.google.common.collect.ImmutableSet;
24+
import com.google.common.collect.ImmutableMap;
2525

2626
import com.thoughtworks.selenium.CommandProcessor;
2727
import com.thoughtworks.selenium.SeleniumException;
2828

2929
import org.openqa.selenium.remote.DesiredCapabilities;
30-
import org.openqa.selenium.remote.Dialect;
3130
import org.openqa.selenium.remote.SessionId;
3231
import org.openqa.selenium.remote.server.ActiveSession;
3332
import org.openqa.selenium.remote.server.ActiveSessionFactory;
3433
import org.openqa.selenium.remote.server.ActiveSessionListener;
3534
import org.openqa.selenium.remote.server.ActiveSessions;
35+
import org.openqa.selenium.remote.server.NewSessionPayload;
36+
import org.openqa.selenium.remote.server.NewSessionPipeline;
3637
import org.openqa.selenium.remote.server.WebDriverServlet;
3738

3839
import java.io.IOException;
@@ -56,6 +57,7 @@ public class WebDriverBackedSeleniumServlet extends HttpServlet {
5657
// Prepare the shared set of thingies
5758
private static final Map<SessionId, CommandProcessor> PROCESSORS = new ConcurrentHashMap<>();
5859

60+
private NewSessionPipeline pipeline;
5961
private ActiveSessions sessions;
6062
private ActiveSessionListener listener;
6163

@@ -74,6 +76,8 @@ public void onStop(ActiveSession session) {
7476
PROCESSORS.remove(session.getId());
7577
}
7678
};
79+
80+
this.pipeline = NewSessionPipeline.builder().add(new ActiveSessionFactory()).create();
7781
}
7882

7983
@Override
@@ -211,10 +215,9 @@ private void startNewSession(
211215
return;
212216
}
213217

214-
try {
215-
ActiveSession session = new ActiveSessionFactory().createSession(
216-
ImmutableSet.copyOf(Dialect.values()),
217-
caps);
218+
try (NewSessionPayload payload = new NewSessionPayload(
219+
ImmutableMap.of("desiredCapabilities", caps.asMap()))) {
220+
ActiveSession session = pipeline.createNewSession(payload);
218221
sessions.put(session);
219222
sessionId = session.getId();
220223
} catch (Exception e) {

java/server/src/org/openqa/selenium/remote/server/ActiveSessionFactory.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@
3131
import com.google.common.collect.ImmutableMap;
3232

3333
import org.openqa.selenium.Capabilities;
34-
import org.openqa.selenium.SessionNotCreatedException;
3534
import org.openqa.selenium.WebDriver;
3635
import org.openqa.selenium.remote.Dialect;
3736

3837
import java.util.LinkedHashMap;
3938
import java.util.Map;
4039
import java.util.Objects;
40+
import java.util.Optional;
4141
import java.util.ServiceLoader;
4242
import java.util.Set;
4343
import java.util.function.Function;
@@ -49,7 +49,7 @@
4949
/**
5050
* Used to create new {@link ActiveSession} instances as required.
5151
*/
52-
public class ActiveSessionFactory {
52+
public class ActiveSessionFactory implements SessionFactory {
5353

5454
private final static Logger LOG = Logger.getLogger(ActiveSessionFactory.class.getName());
5555

@@ -157,15 +157,16 @@ private static Predicate<Capabilities> containsKey(Pattern pattern) {
157157
return toCompare -> toCompare.asMap().keySet().stream().anyMatch(pattern.asPredicate());
158158
}
159159

160-
public ActiveSession createSession(Set<Dialect> downstreamDialects, Capabilities caps) {
160+
@Override
161+
public Optional<ActiveSession> apply(Set<Dialect> downstreamDialects, Capabilities caps) {
161162
LOG.info("Capabilities are: " + caps);
162163
return factories.entrySet().stream()
163164
.filter(e -> e.getKey().test(caps))
164165
.peek(e -> LOG.info(String.format("%s matched %s", caps, e.getValue())))
165166
.map(Map.Entry::getValue)
166-
.findFirst()
167167
.map(factory -> factory.apply(downstreamDialects, caps))
168-
.orElseThrow(() -> new SessionNotCreatedException(
169-
"Unable to create a new session because of no configuration. " + caps));
168+
.filter(Optional::isPresent)
169+
.map(Optional::get)
170+
.findFirst();
170171
}
171172
}

java/server/src/org/openqa/selenium/remote/server/BUCK

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ java_library(
7676
'AllHandlers.java',
7777
'CommandHandler.java',
7878
'InMemorySession.java',
79+
'NewSessionPipeline.java',
7980
'Passthrough.java',
8081
'ProtocolConverter.java',
8182
'ServicedSession.java',

java/server/src/org/openqa/selenium/remote/server/InMemorySession.java

+5-7
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,17 @@
2525

2626
import org.openqa.selenium.Capabilities;
2727
import org.openqa.selenium.HasCapabilities;
28-
import org.openqa.selenium.SessionNotCreatedException;
2928
import org.openqa.selenium.WebDriver;
3029
import org.openqa.selenium.io.TemporaryFilesystem;
3130
import org.openqa.selenium.remote.Dialect;
32-
import org.openqa.selenium.remote.JsonToBeanConverter;
3331
import org.openqa.selenium.remote.SessionId;
3432
import org.openqa.selenium.remote.http.HttpRequest;
3533
import org.openqa.selenium.remote.http.HttpResponse;
3634

3735
import java.io.File;
3836
import java.io.IOException;
3937
import java.util.Map;
38+
import java.util.Optional;
4039
import java.util.Set;
4140
import java.util.UUID;
4241
import java.util.logging.Logger;
@@ -122,15 +121,14 @@ public void stop() {
122121

123122
public static class Factory implements SessionFactory {
124123

125-
private final JsonToBeanConverter toBean = new JsonToBeanConverter();
126124
private final DriverProvider provider;
127125

128126
public Factory(DriverProvider provider) {
129127
this.provider = provider;
130128
}
131129

132130
@Override
133-
public ActiveSession apply(Set<Dialect> downstreamDialects, Capabilities caps) {
131+
public Optional<ActiveSession> apply(Set<Dialect> downstreamDialects, Capabilities caps) {
134132
// Assume the blob fits in the available memory.
135133
try {
136134
if (!provider.canCreateDriverInstanceFor(caps)) {
@@ -143,9 +141,9 @@ public ActiveSession apply(Set<Dialect> downstreamDialects, Capabilities caps) {
143141
Dialect downstream = downstreamDialects.contains(Dialect.OSS) ?
144142
Dialect.OSS :
145143
downstreamDialects.iterator().next();
146-
return new InMemorySession(driver, caps, downstream);
147-
} catch (IOException|IllegalStateException e) {
148-
throw new SessionNotCreatedException("Cannot establish new session", e);
144+
return Optional.of(new InMemorySession(driver, caps, downstream));
145+
} catch (IOException | IllegalStateException e) {
146+
return Optional.empty();
149147
}
150148
}
151149

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package org.openqa.selenium.remote.server;
2+
3+
import com.google.common.collect.ImmutableList;
4+
5+
import org.openqa.selenium.ImmutableCapabilities;
6+
import org.openqa.selenium.SessionNotCreatedException;
7+
8+
import java.io.IOException;
9+
import java.util.LinkedList;
10+
import java.util.List;
11+
import java.util.Objects;
12+
import java.util.Optional;
13+
14+
public class NewSessionPipeline {
15+
16+
private final List<SessionFactory> factories;
17+
private final SessionFactory fallback;
18+
19+
private NewSessionPipeline(List<SessionFactory> factories, SessionFactory fallback) {
20+
this.factories = factories;
21+
this.fallback = fallback;
22+
}
23+
24+
public static Builder builder() {
25+
return new Builder();
26+
}
27+
28+
public ActiveSession createNewSession(NewSessionPayload payload) throws IOException {
29+
return payload.stream()
30+
.map(caps -> factories.stream()
31+
.map(factory -> factory.apply(payload.getDownstreamDialects(), caps))
32+
.filter(Optional::isPresent)
33+
.map(Optional::get)
34+
.findFirst())
35+
.filter(Optional::isPresent)
36+
.map(Optional::get)
37+
.findFirst()
38+
.orElseGet(() ->
39+
fallback.apply(payload.getDownstreamDialects(), new ImmutableCapabilities())
40+
.orElseThrow(
41+
() -> new SessionNotCreatedException("Unable to create session from " + payload))
42+
);
43+
}
44+
45+
public static class Builder {
46+
private List<SessionFactory> factories = new LinkedList<>();
47+
private SessionFactory fallback = (dialects, caps) -> Optional.empty();
48+
49+
private Builder() {
50+
// Private class
51+
}
52+
53+
public Builder add(SessionFactory factory) {
54+
factories.add(Objects.requireNonNull(factory, "Factory must not be null"));
55+
// for (SessionFactory fac : more) {
56+
// factories.add(Objects.requireNonNull(fac, "Factory must not be null"));
57+
// }
58+
return this;
59+
}
60+
61+
public Builder fallback(SessionFactory factory) {
62+
fallback = factory;
63+
return this;
64+
}
65+
66+
public NewSessionPipeline create() {
67+
return new NewSessionPipeline(ImmutableList.copyOf(factories), fallback);
68+
}
69+
}
70+
}

java/server/src/org/openqa/selenium/remote/server/ServicedSession.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,11 @@
5656
import java.lang.reflect.Method;
5757
import java.net.URL;
5858
import java.util.Map;
59+
import java.util.Optional;
5960
import java.util.Set;
6061
import java.util.function.Function;
6162

62-
class ServicedSession implements ActiveSession {
63+
public class ServicedSession implements ActiveSession {
6364

6465
private final DriverService service;
6566
private final SessionId id;
@@ -149,7 +150,7 @@ public static class Factory implements SessionFactory {
149150
private final Function<Capabilities, ? extends DriverService> createService;
150151
private final String serviceClassName;
151152

152-
Factory(String serviceClassName) {
153+
public Factory(String serviceClassName) {
153154
this.serviceClassName = serviceClassName;
154155
try {
155156
Class<? extends DriverService> driverClazz =
@@ -198,7 +199,7 @@ public static class Factory implements SessionFactory {
198199
}
199200

200201
@Override
201-
public ActiveSession apply(Set<Dialect> downstreamDialects, Capabilities capabilities) {
202+
public Optional<ActiveSession> apply(Set<Dialect> downstreamDialects, Capabilities capabilities) {
202203
DriverService service = createService.apply(capabilities);
203204

204205
try {
@@ -233,14 +234,14 @@ public ActiveSession apply(Set<Dialect> downstreamDialects, Capabilities capabil
233234

234235
Response response = result.createResponse();
235236
//noinspection unchecked
236-
return new ServicedSession(
237+
return Optional.of(new ServicedSession(
237238
service,
238239
downstream,
239240
upstream,
240241
codec,
241242
new SessionId(response.getSessionId()),
242-
(Map<String, Object>) response.getValue());
243-
} catch (IOException|IllegalStateException|NullPointerException e) {
243+
(Map<String, Object>) response.getValue()));
244+
} catch (IOException | IllegalStateException | NullPointerException e) {
244245
throw new SessionNotCreatedException("Cannot establish new session", e);
245246
}
246247
}

java/server/src/org/openqa/selenium/remote/server/SessionFactory.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@
2121
import org.openqa.selenium.Capabilities;
2222
import org.openqa.selenium.remote.Dialect;
2323

24+
import java.util.Optional;
2425
import java.util.Set;
2526

26-
interface SessionFactory {
27-
ActiveSession apply(Set<Dialect> downstreamDialects, Capabilities capabilities);
27+
public interface SessionFactory {
28+
Optional<ActiveSession> apply(Set<Dialect> downstreamDialects, Capabilities capabilities);
2829
}

java/server/src/org/openqa/selenium/remote/server/commandhandler/BeginSession.java

+30-16
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,48 @@
3535
import org.openqa.selenium.remote.server.ActiveSessions;
3636
import org.openqa.selenium.remote.server.CommandHandler;
3737
import org.openqa.selenium.remote.server.NewSessionPayload;
38+
import org.openqa.selenium.remote.server.NewSessionPipeline;
39+
import org.openqa.selenium.remote.server.ServicedSession;
40+
import org.openqa.selenium.remote.server.SessionFactory;
3841
import org.openqa.selenium.remote.server.log.LoggingManager;
42+
import org.openqa.selenium.remote.service.DriverService;
3943

4044
import java.io.IOException;
4145
import java.io.InputStreamReader;
4246
import java.io.Reader;
43-
import java.util.Objects;
47+
import java.util.Optional;
4448
import java.util.logging.Level;
49+
import java.util.stream.Stream;
4550

4651
public class BeginSession implements CommandHandler {
4752

48-
private final ActiveSessionFactory sessionFactory;
53+
private final NewSessionPipeline pipeline;
4954
private final ActiveSessions allSessions;
5055

5156
public BeginSession(ActiveSessions allSessions) {
57+
SessionFactory fallback = Stream.of(
58+
"org.openqa.selenium.chrome.ChromeDriverService",
59+
"org.openqa.selenium.firefox.GeckoDriverService",
60+
"org.openqa.selenium.edge.EdgeDriverService",
61+
"org.openqa.selenium.ie.InternetExplorerDriverService",
62+
"org.openqa.selenium.safari.SafariDriverService")
63+
.filter(name -> {
64+
try {
65+
Class.forName(name).asSubclass(DriverService.class);
66+
return true;
67+
} catch (ReflectiveOperationException e) {
68+
return false;
69+
}
70+
})
71+
.findFirst()
72+
.map(serviceName -> (SessionFactory) new ServicedSession.Factory(serviceName))
73+
.orElse((dialects, caps) -> Optional.empty());
74+
5275
this.allSessions = allSessions;
53-
this.sessionFactory = new ActiveSessionFactory();
76+
this.pipeline = NewSessionPipeline.builder()
77+
.add(new ActiveSessionFactory())
78+
.fallback(fallback)
79+
.create();
5480
}
5581

5682
@Override
@@ -70,19 +96,7 @@ public void execute(HttpRequest req, HttpResponse resp) throws IOException {
7096
req.consumeContentStream(),
7197
req.getContentEncoding());
7298
NewSessionPayload payload = new NewSessionPayload(contentLength, reader)) {
73-
session = payload.stream()
74-
.map(caps -> {
75-
try {
76-
return sessionFactory.createSession(payload.getDownstreamDialects(), caps);
77-
} catch (SessionNotCreatedException e) {
78-
// Do nothing. We'll complain at the end.
79-
return null;
80-
}
81-
})
82-
.filter(Objects::nonNull)
83-
.findFirst()
84-
.orElseThrow(
85-
() -> new SessionNotCreatedException("Unable to create session: " + payload));
99+
session = pipeline.createNewSession(payload);
86100
allSessions.put(session);
87101
}
88102

java/server/test/org/openqa/selenium/remote/server/ActiveSessionFactoryTest.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import static org.junit.Assert.assertEquals;
2121
import static org.junit.Assert.assertSame;
2222

23-
import com.google.common.collect.ImmutableMap;
2423
import com.google.common.collect.ImmutableSet;
2524

2625
import org.junit.Test;
@@ -31,7 +30,7 @@
3130
import org.openqa.selenium.remote.Dialect;
3231

3332
import java.io.IOException;
34-
import java.util.Map;
33+
import java.util.Optional;
3534

3635
public class ActiveSessionFactoryTest {
3736

@@ -48,7 +47,7 @@ protected Iterable<DriverProvider> loadDriverProviders() {
4847
}
4948
};
5049

51-
ActiveSession session = sessionFactory.createSession(ImmutableSet.of(Dialect.W3C), caps);
50+
ActiveSession session = sessionFactory.apply(ImmutableSet.of(Dialect.W3C), caps).get();
5251
assertEquals(driver, session.getWrappedDriver());
5352
}
5453

@@ -57,9 +56,9 @@ public void canBindNewFactoriesAtRunTime() throws IOException {
5756
ActiveSession session = Mockito.mock(ActiveSession.class);
5857

5958
ActiveSessionFactory sessionFactory = new ActiveSessionFactory()
60-
.bind(caps -> "cheese".equals(caps.getBrowserName()), (dialects, caps) -> session);
59+
.bind(caps -> "cheese".equals(caps.getBrowserName()), (dialects, caps) -> Optional.of(session));
6160

62-
ActiveSession created = sessionFactory.createSession(ImmutableSet.copyOf(Dialect.values()), toPayload("cheese"));
61+
ActiveSession created = sessionFactory.apply(ImmutableSet.copyOf(Dialect.values()), toPayload("cheese")).get();
6362

6463
assertSame(session, created);
6564
}

0 commit comments

Comments
 (0)