Skip to content

Commit bc57147

Browse files
committed
Allow SessionFactory to indicate whether they support particular Capabilities
Also since the default new session pipeline includes all the available and installed there's no need for a fallback to be installed.
1 parent ba07615 commit bc57147

13 files changed

+127
-78
lines changed

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

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@
2929
import static org.openqa.selenium.remote.CapabilityType.BROWSER_NAME;
3030

3131
import com.google.common.annotations.VisibleForTesting;
32+
import com.google.common.collect.ImmutableList;
3233
import com.google.common.collect.ImmutableMap;
3334

3435
import org.openqa.selenium.Capabilities;
3536
import org.openqa.selenium.ImmutableCapabilities;
3637
import org.openqa.selenium.WebDriver;
3738
import org.openqa.selenium.remote.Dialect;
3839

39-
import java.util.LinkedHashMap;
40-
import java.util.Map;
40+
import java.util.List;
4141
import java.util.Objects;
4242
import java.util.Optional;
4343
import java.util.ServiceLoader;
@@ -63,15 +63,15 @@ public class ActiveSessionFactory implements SessionFactory {
6363
}
6464
};
6565

66-
private volatile Map<Predicate<Capabilities>, SessionFactory> factories;
66+
private volatile List<SessionFactory> factories;
6767

6868
public ActiveSessionFactory() {
6969
// Insertion order matters. The first matching predicate is always used for matching.
70-
Map<Predicate<Capabilities>, SessionFactory> builder = new LinkedHashMap<>();
70+
ImmutableList.Builder<SessionFactory> builder = ImmutableList.builder();
7171

7272
// Allow user-defined factories to override default ones.
7373
StreamSupport.stream(loadDriverProviders().spliterator(), false)
74-
.forEach(p -> builder.put(p::canCreateDriverInstanceFor, new InMemorySession.Factory(p)));
74+
.forEach(p -> builder.add(new InMemorySession.Factory(p)));
7575

7676
ImmutableMap.<Predicate<Capabilities>, String>builder()
7777
.put(caps -> {
@@ -96,13 +96,13 @@ public ActiveSessionFactory() {
9696
.build()
9797
.entrySet().stream()
9898
.filter(e -> CLASS_EXISTS.apply(e.getValue()) != null)
99-
.forEach(e -> builder.put(e.getKey(), new ServicedSession.Factory(e.getValue())));
99+
.forEach(e -> builder.add(new ServicedSession.Factory(e.getKey(), e.getValue())));
100100

101101
// Attempt to bind the htmlunitdriver if it's present.
102102
bind(builder, "org.openqa.selenium.htmlunit.HtmlUnitDriver", browserName(HTMLUNIT),
103103
new ImmutableCapabilities(BROWSER_NAME, HTMLUNIT));
104104

105-
this.factories = ImmutableMap.copyOf(builder);
105+
this.factories = builder.build();
106106
}
107107

108108
public synchronized ActiveSessionFactory bind(
@@ -113,11 +113,11 @@ public synchronized ActiveSessionFactory bind(
113113

114114
LOG.info(String.format("Binding %s to respond to %s", useThis, onThis));
115115

116-
LinkedHashMap<Predicate<Capabilities>, SessionFactory> newMap = new LinkedHashMap<>();
117-
newMap.put(onThis, useThis);
118-
newMap.putAll(factories);
116+
ImmutableList.Builder<SessionFactory> builder = ImmutableList.builder();
117+
builder.add(useThis);
118+
builder.addAll(factories);
119119

120-
factories = newMap;
120+
factories = builder.build();
121121

122122
return this;
123123
}
@@ -128,7 +128,7 @@ protected Iterable<DriverProvider> loadDriverProviders() {
128128
}
129129

130130
private void bind(
131-
Map<Predicate<Capabilities>, SessionFactory> builder,
131+
ImmutableList.Builder<SessionFactory> builder,
132132
String className,
133133
Predicate<Capabilities> predicate,
134134
Capabilities capabilities) {
@@ -139,9 +139,7 @@ private void bind(
139139
}
140140

141141
Class<? extends WebDriver> driverClass = clazz.asSubclass(WebDriver.class);
142-
builder.put(
143-
predicate,
144-
new InMemorySession.Factory(new DefaultDriverProvider(capabilities, driverClass)));
142+
builder.add(new InMemorySession.Factory(new DefaultDriverProvider(capabilities, driverClass)));
145143
} catch (ClassCastException ignored) {
146144
// Just carry on. Everything is fine.
147145
}
@@ -161,13 +159,20 @@ private static Predicate<Capabilities> containsKey(Pattern pattern) {
161159
return toCompare -> toCompare.asMap().keySet().stream().anyMatch(pattern.asPredicate());
162160
}
163161

162+
@Override
163+
public boolean isSupporting(Capabilities capabilities) {
164+
return factories.stream()
165+
.map(factory -> factory.isSupporting(capabilities))
166+
.reduce(Boolean::logicalOr)
167+
.orElse(false);
168+
}
169+
164170
@Override
165171
public Optional<ActiveSession> apply(Set<Dialect> downstreamDialects, Capabilities caps) {
166172
LOG.info("Capabilities are: " + caps);
167-
return factories.entrySet().stream()
168-
.filter(e -> e.getKey().test(caps))
169-
.peek(e -> LOG.info(String.format("%s matched %s", caps, e.getValue())))
170-
.map(Map.Entry::getValue)
173+
return factories.stream()
174+
.filter(factory -> factory.isSupporting(caps))
175+
.peek(factory -> LOG.info(String.format("%s matched %s", caps, factory)))
171176
.map(factory -> factory.apply(downstreamDialects, caps))
172177
.filter(Optional::isPresent)
173178
.map(Optional::get)

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

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@
1717

1818
package org.openqa.selenium.remote.server;
1919

20-
import org.openqa.selenium.remote.service.DriverService;
21-
22-
import java.util.Optional;
2320
import java.util.logging.Logger;
24-
import java.util.stream.Stream;
2521

2622
/**
2723
* Used to represent the {@link NewSessionPipeline} that is typically used in the
@@ -35,34 +31,8 @@ private DefaultPipeline() {
3531
// Utility class
3632
}
3733

38-
public static NewSessionPipeline.Builder createPipelineWithDefaultFallbacks() {
39-
// Set up the pipeline to inject
40-
SessionFactory fallback = Stream.of(
41-
"org.openqa.selenium.chrome.ChromeDriverService",
42-
"org.openqa.selenium.firefox.GeckoDriverService",
43-
"org.openqa.selenium.edge.EdgeDriverService",
44-
"org.openqa.selenium.ie.InternetExplorerDriverService",
45-
"org.openqa.selenium.safari.SafariDriverService")
46-
.filter(name -> {
47-
try {
48-
Class.forName(name).asSubclass(DriverService.class);
49-
return true;
50-
} catch (ReflectiveOperationException e) {
51-
return false;
52-
}
53-
})
54-
.findFirst()
55-
.map(serviceName -> {
56-
SessionFactory factory = new ServicedSession.Factory(serviceName);
57-
return (SessionFactory) (dialects, caps) -> {
58-
LOG.info("Using default factory: " + serviceName);
59-
return factory.apply(dialects, caps);
60-
};
61-
})
62-
.orElse((dialects, caps) -> Optional.empty());
63-
34+
public static NewSessionPipeline.Builder createDefaultPipeline() {
6435
return NewSessionPipeline.builder()
65-
.add(new ActiveSessionFactory())
66-
.fallback(fallback);
36+
.add(new ActiveSessionFactory());
6737
}
6838
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ public Factory(DriverProvider provider) {
125125
this.provider = provider;
126126
}
127127

128+
129+
@Override
130+
public boolean isSupporting(Capabilities capabilities) {
131+
return provider.canCreateDriverInstanceFor(capabilities);
132+
}
133+
128134
@Override
129135
public Optional<ActiveSession> apply(Set<Dialect> downstreamDialects, Capabilities caps) {
130136
// Assume the blob fits in the available memory.

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22

33
import com.google.common.collect.ImmutableList;
44

5+
import org.openqa.selenium.Capabilities;
56
import org.openqa.selenium.ImmutableCapabilities;
67
import org.openqa.selenium.SessionNotCreatedException;
8+
import org.openqa.selenium.remote.Dialect;
79
import org.openqa.selenium.remote.NewSessionPayload;
810

911
import java.io.IOException;
1012
import java.util.LinkedList;
1113
import java.util.List;
1214
import java.util.Objects;
1315
import java.util.Optional;
16+
import java.util.Set;
1417
import java.util.function.Function;
1518

1619
public class NewSessionPipeline {
@@ -41,6 +44,7 @@ public ActiveSession createNewSession(NewSessionPayload payload) throws IOExcept
4144
return caps;
4245
})
4346
.map(caps -> factories.stream()
47+
.filter(factory -> factory.isSupporting(caps))
4448
.map(factory -> factory.apply(payload.getDownstreamDialects(), caps))
4549
.filter(Optional::isPresent)
4650
.map(Optional::get)
@@ -57,7 +61,17 @@ public ActiveSession createNewSession(NewSessionPayload payload) throws IOExcept
5761

5862
public static class Builder {
5963
private List<SessionFactory> factories = new LinkedList<>();
60-
private SessionFactory fallback = (dialects, caps) -> Optional.empty();
64+
private SessionFactory fallback = new SessionFactory() {
65+
@Override
66+
public boolean isSupporting(Capabilities capabilities) {
67+
return false;
68+
}
69+
70+
@Override
71+
public Optional<ActiveSession> apply(Set<Dialect> downstreamDialects, Capabilities capabilities) {
72+
return Optional.empty();
73+
}
74+
};
6175
private List<Function<ImmutableCapabilities, ImmutableCapabilities>> mutators = new LinkedList<>();
6276

6377
private Builder() {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ public boolean boot() {
201201
}
202202

203203
private NewSessionPipeline createPipeline(StandaloneConfiguration configuration) {
204-
NewSessionPipeline.Builder builder = DefaultPipeline.createPipelineWithDefaultFallbacks();
204+
NewSessionPipeline.Builder builder = DefaultPipeline.createDefaultPipeline();
205205

206206
if (configuration instanceof GridNodeConfiguration) {
207207
((GridNodeConfiguration) configuration).capabilities.forEach(

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.util.Optional;
4040
import java.util.Set;
4141
import java.util.function.Function;
42+
import java.util.function.Predicate;
4243
import java.util.logging.Level;
4344

4445
import javax.management.MalformedObjectNameException;
@@ -79,10 +80,13 @@ public void stop() {
7980

8081
public static class Factory extends RemoteSession.Factory<DriverService> {
8182

83+
private final Predicate<Capabilities> key;
8284
private final Function<Capabilities, ? extends DriverService> createService;
8385
private final String serviceClassName;
8486

85-
public Factory(String serviceClassName) {
87+
public Factory(Predicate<Capabilities> key, String serviceClassName) {
88+
this.key = key;
89+
8690
this.serviceClassName = serviceClassName;
8791
try {
8892
Class<? extends DriverService> driverClazz =
@@ -129,6 +133,11 @@ public Factory(String serviceClassName) {
129133
}
130134
}
131135

136+
@Override
137+
public boolean isSupporting(Capabilities capabilities) {
138+
return key.test(capabilities);
139+
}
140+
132141
@Override
133142
public Optional<ActiveSession> apply(
134143
Set<Dialect> downstreamDialects,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,7 @@
2525
import java.util.Set;
2626

2727
public interface SessionFactory {
28+
boolean isSupporting(Capabilities capabilities);
29+
2830
Optional<ActiveSession> apply(Set<Dialect> downstreamDialects, Capabilities capabilities);
2931
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public void init() {
8686
NewSessionPipeline pipeline =
8787
(NewSessionPipeline) getServletContext().getAttribute(NEW_SESSION_PIPELINE_KEY);
8888
if (pipeline == null) {
89-
pipeline = DefaultPipeline.createPipelineWithDefaultFallbacks().create();
89+
pipeline = DefaultPipeline.createDefaultPipeline().create();
9090
getServletContext().setAttribute(NEW_SESSION_PIPELINE_KEY, pipeline);
9191
}
9292

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import java.io.IOException;
3333
import java.util.Optional;
34+
import java.util.Set;
3435

3536
public class ActiveSessionFactoryTest {
3637

@@ -56,7 +57,20 @@ public void canBindNewFactoriesAtRunTime() throws IOException {
5657
ActiveSession session = Mockito.mock(ActiveSession.class);
5758

5859
ActiveSessionFactory sessionFactory = new ActiveSessionFactory()
59-
.bind(caps -> "cheese".equals(caps.getBrowserName()), (dialects, caps) -> Optional.of(session));
60+
.bind(caps ->
61+
"cheese".equals(caps.getBrowserName()),
62+
new SessionFactory() {
63+
@Override
64+
public boolean isSupporting(Capabilities capabilities) {
65+
return true;
66+
}
67+
68+
@Override
69+
public Optional<ActiveSession> apply(Set<Dialect> downstreamDialects,
70+
Capabilities capabilities) {
71+
return Optional.of(session);
72+
}
73+
});
6074

6175
ActiveSession created = sessionFactory.apply(ImmutableSet.copyOf(Dialect.values()), toPayload("cheese")).get();
6276

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
package org.openqa.selenium.remote.server;
1919

20+
import static org.junit.Assert.assertEquals;
2021
import static org.mockito.Mockito.any;
2122
import static org.mockito.Mockito.argThat;
23+
import static org.mockito.Mockito.atLeast;
2224
import static org.mockito.Mockito.mock;
2325
import static org.mockito.Mockito.verify;
2426
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -39,7 +41,9 @@ public class NewSessionPipelineTest {
3941
@Test
4042
public void shouldCallSessionFactory() throws IOException {
4143
SessionFactory factory = mock(SessionFactory.class);
44+
when(factory.isSupporting(any())).thenReturn(true);
4245
SessionFactory fallback = mock(SessionFactory.class);
46+
when(fallback.isSupporting(any())).thenReturn(true);
4347
ActiveSession session = mock(ActiveSession.class);
4448
when(factory.apply(any(), any())).thenReturn(Optional.of(session));
4549

@@ -77,6 +81,7 @@ public void shouldBeAbleToFallBack() throws IOException {
7781
@Test
7882
public void shouldUseMutators() throws IOException {
7983
SessionFactory factory = mock(SessionFactory.class);
84+
when(factory.isSupporting(any())).thenReturn(true);
8085
ActiveSession session = mock(ActiveSession.class);
8186
when(factory.apply(any(), any())).thenReturn(Optional.of(session));
8287
FirefoxMutator mutator = new FirefoxMutator(new ImmutableCapabilities(
@@ -89,9 +94,34 @@ public void shouldUseMutators() throws IOException {
8994
"alwaysMatch", ImmutableMap.of("browserName", "firefox")));
9095

9196
NewSessionPipeline pipeline = NewSessionPipeline.builder()
92-
.add(factory).addCapabilitiesMutator(mutator).create();
97+
.add(factory)
98+
.addCapabilitiesMutator(mutator)
99+
.create();
93100

94101
pipeline.createNewSession(NewSessionPayload.create(caps));
95102
verify(factory).apply(any(), argThat(cap -> cap.getCapability("marionette").equals(true)));
96103
}
104+
105+
@Test
106+
public void shouldNotUseFactoriesThatDoNotSupportTheCapabilities() throws IOException {
107+
SessionFactory toBeIgnored = mock(SessionFactory.class);
108+
when(toBeIgnored.isSupporting(any())).thenReturn(false);
109+
when(toBeIgnored.apply(any(), any())).thenThrow(new AssertionError("Must not be called"));
110+
111+
ActiveSession session = mock(ActiveSession.class);
112+
SessionFactory toBeUsed = mock(SessionFactory.class);
113+
when(toBeUsed.isSupporting(any())).thenReturn(true);
114+
when(toBeUsed.apply(any(), any())).thenReturn(Optional.of(session));
115+
116+
NewSessionPipeline pipeline = NewSessionPipeline.builder()
117+
.add(toBeIgnored)
118+
.add(toBeUsed)
119+
.create();
120+
121+
ActiveSession seen =
122+
pipeline.createNewSession(NewSessionPayload.create(new ImmutableCapabilities()));
123+
124+
assertEquals(session, seen);
125+
verify(toBeIgnored, atLeast(1)).isSupporting(any());
126+
}
97127
}

0 commit comments

Comments
 (0)