Skip to content

Commit 854ac60

Browse files
committed
Introduce a CapabilitiesUtils class for common operations on Capabilities
1 parent d83df53 commit 854ac60

File tree

3 files changed

+227
-177
lines changed

3 files changed

+227
-177
lines changed
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
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.remote;
19+
20+
import com.google.common.collect.ImmutableList;
21+
import com.google.common.collect.ImmutableMap;
22+
import com.google.common.collect.ImmutableSet;
23+
import org.openqa.selenium.Capabilities;
24+
import org.openqa.selenium.ImmutableCapabilities;
25+
import org.openqa.selenium.Proxy;
26+
import org.openqa.selenium.internal.Require;
27+
import org.openqa.selenium.remote.session.CapabilitiesFilter;
28+
import org.openqa.selenium.remote.session.CapabilityTransform;
29+
import org.openqa.selenium.remote.session.ChromeFilter;
30+
import org.openqa.selenium.remote.session.EdgeFilter;
31+
import org.openqa.selenium.remote.session.FirefoxFilter;
32+
import org.openqa.selenium.remote.session.InternetExplorerFilter;
33+
import org.openqa.selenium.remote.session.OperaFilter;
34+
import org.openqa.selenium.remote.session.ProxyTransform;
35+
import org.openqa.selenium.remote.session.SafariFilter;
36+
import org.openqa.selenium.remote.session.StripAnyPlatform;
37+
import org.openqa.selenium.remote.session.W3CPlatformNameNormaliser;
38+
39+
import java.util.Arrays;
40+
import java.util.Collection;
41+
import java.util.HashMap;
42+
import java.util.HashSet;
43+
import java.util.LinkedList;
44+
import java.util.List;
45+
import java.util.Map;
46+
import java.util.Objects;
47+
import java.util.Queue;
48+
import java.util.ServiceLoader;
49+
import java.util.Set;
50+
import java.util.TreeMap;
51+
import java.util.function.Predicate;
52+
import java.util.stream.Collectors;
53+
import java.util.stream.Stream;
54+
55+
import static org.openqa.selenium.remote.CapabilityType.PLATFORM;
56+
import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME;
57+
import static org.openqa.selenium.remote.CapabilityType.PROXY;
58+
59+
public class CapabilitiesUtils {
60+
61+
private static final Predicate<String> ACCEPTED_W3C_PATTERNS = new AcceptedW3CCapabilityKeys();
62+
63+
private CapabilitiesUtils() {
64+
// Helper class
65+
}
66+
67+
public static Stream<Capabilities> makeW3CSafe(Capabilities possiblyInvalidCapabilities) {
68+
Require.nonNull("Capabilities", possiblyInvalidCapabilities);
69+
70+
return makeW3CSafe(possiblyInvalidCapabilities.asMap()).map(ImmutableCapabilities::new);
71+
}
72+
73+
public static Stream<Map<String, Object>> makeW3CSafe(Map<String, Object> possiblyInvalidCapabilities) {
74+
Require.nonNull("Capabilities", possiblyInvalidCapabilities);
75+
76+
Set<CapabilitiesFilter> adapters = getCapabilityFilters();
77+
78+
// If there's an OSS value, generate a stream of capabilities from that using the transforms,
79+
// then add magic to generate each of the w3c capabilities. For the sake of simplicity, we're
80+
// going to make the (probably wrong) assumption we can hold all of the firstMatch values and
81+
// alwaysMatch value in memory at the same time.
82+
Map<String, Object> oss = convertOssToW3C(possiblyInvalidCapabilities);
83+
Stream<Map<String, Object>> fromOss;
84+
Set<String> usedKeys = new HashSet<>();
85+
86+
// Are there any values we care want to pull out into a mapping of their own?
87+
List<Map<String, Object>> firsts = adapters.stream()
88+
.map(adapter -> adapter.apply(oss))
89+
.filter(Objects::nonNull)
90+
.filter(map -> !map.isEmpty())
91+
.map(
92+
map -> map.entrySet().stream()
93+
.filter(entry -> entry.getKey() != null)
94+
.filter(entry -> ACCEPTED_W3C_PATTERNS.test(entry.getKey()))
95+
.filter(entry -> entry.getValue() != null)
96+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)))
97+
.peek(map -> usedKeys.addAll(map.keySet()))
98+
.collect(ImmutableList.toImmutableList());
99+
if (firsts.isEmpty()) {
100+
firsts = ImmutableList.of(ImmutableMap.of());
101+
}
102+
103+
// Are there any remaining unused keys?
104+
Map<String, Object> always = oss.entrySet().stream()
105+
.filter(entry -> !usedKeys.contains(entry.getKey()))
106+
.filter(entry -> entry.getValue() != null)
107+
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
108+
109+
// Firsts contains at least one entry, always contains everything else. Let's combine them
110+
// into the stream to form a unified set of capabilities. Woohoo!
111+
fromOss = firsts.stream()
112+
.map(first -> ImmutableMap.<String, Object>builder().putAll(always).putAll(first).build())
113+
.map(CapabilitiesUtils::applyTransforms)
114+
.map(map -> map.entrySet().stream()
115+
.filter(entry -> ACCEPTED_W3C_PATTERNS.test(entry.getKey()))
116+
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)));
117+
118+
return fromOss;
119+
}
120+
121+
private static Map<String, Object> convertOssToW3C(Map<String, Object> capabilities) {
122+
Map<String, Object> toReturn = new TreeMap<>(capabilities);
123+
124+
// Platform name
125+
if (capabilities.containsKey(PLATFORM) && !capabilities.containsKey(PLATFORM_NAME)) {
126+
toReturn.put(PLATFORM_NAME, String.valueOf(capabilities.get(PLATFORM)));
127+
}
128+
129+
if (capabilities.containsKey(PROXY)) {
130+
Map<String, Object> proxyMap = getProxyFromCapabilities(capabilities);
131+
if (proxyMap.containsKey("noProxy")) {
132+
Map<String, Object> w3cProxyMap = new HashMap<>(proxyMap);
133+
Object rawData = proxyMap.get("noProxy");
134+
if (rawData instanceof String) {
135+
w3cProxyMap.put("noProxy", Arrays.asList(((String) rawData).split(",\\s*")));
136+
}
137+
toReturn.put(CapabilityType.PROXY, w3cProxyMap);
138+
}
139+
}
140+
141+
return toReturn;
142+
}
143+
144+
private static Map<String, Object> getProxyFromCapabilities(Map<String, Object> capabilities) {
145+
Object rawProxy = capabilities.get(CapabilityType.PROXY);
146+
if (rawProxy instanceof Proxy) {
147+
return ((Proxy) rawProxy).toJson();
148+
} else if (rawProxy instanceof Map) {
149+
//noinspection unchecked
150+
return (Map<String, Object>) rawProxy;
151+
} else {
152+
return new HashMap<>();
153+
}
154+
}
155+
156+
private static Map<String, Object> applyTransforms(Map<String, Object> caps) {
157+
Queue<Map.Entry<String, Object>> toExamine = new LinkedList<>(caps.entrySet());
158+
Set<String> seenKeys = new HashSet<>();
159+
Map<String, Object> toReturn = new TreeMap<>();
160+
161+
Set<CapabilityTransform> transforms = getCapabilityTransforms();
162+
163+
// Take each entry and apply the transforms
164+
while (!toExamine.isEmpty()) {
165+
Map.Entry<String, Object> entry = toExamine.remove();
166+
seenKeys.add(entry.getKey());
167+
168+
if (entry.getValue() == null) {
169+
continue;
170+
}
171+
172+
for (CapabilityTransform transform : transforms) {
173+
Collection<Map.Entry<String, Object>> result = transform.apply(entry);
174+
if (result == null) {
175+
toReturn.remove(entry.getKey());
176+
break;
177+
}
178+
179+
for (Map.Entry<String, Object> newEntry : result) {
180+
if (!seenKeys.contains(newEntry.getKey())) {
181+
toExamine.add(newEntry);
182+
} else {
183+
if (newEntry.getKey().equals(entry.getKey())) {
184+
entry = newEntry;
185+
}
186+
toReturn.put(newEntry.getKey(), newEntry.getValue());
187+
}
188+
}
189+
}
190+
}
191+
return toReturn;
192+
}
193+
194+
private static Set<CapabilitiesFilter> getCapabilityFilters() {
195+
ImmutableSet.Builder<CapabilitiesFilter> adapters = ImmutableSet.builder();
196+
ServiceLoader.load(CapabilitiesFilter.class).forEach(adapters::add);
197+
adapters
198+
.add(new ChromeFilter())
199+
.add(new EdgeFilter())
200+
.add(new FirefoxFilter())
201+
.add(new InternetExplorerFilter())
202+
.add(new OperaFilter())
203+
.add(new SafariFilter());
204+
return adapters.build();
205+
}
206+
207+
private static Set<CapabilityTransform> getCapabilityTransforms() {
208+
ImmutableSet.Builder<CapabilityTransform> transforms = ImmutableSet.builder();
209+
ServiceLoader.load(CapabilityTransform.class).forEach(transforms::add);
210+
transforms
211+
.add(new ProxyTransform())
212+
.add(new StripAnyPlatform())
213+
.add(new W3CPlatformNameNormaliser());
214+
return transforms.build();
215+
}
216+
}

0 commit comments

Comments
 (0)