|
| 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