|
| 1 | +package io.appium.java_client.remote; |
| 2 | + |
| 3 | +import static com.google.common.base.Charsets.UTF_8; |
| 4 | +import static com.google.common.net.HttpHeaders.CONTENT_LENGTH; |
| 5 | +import static com.google.common.net.HttpHeaders.CONTENT_TYPE; |
| 6 | +import static com.google.common.net.MediaType.JSON_UTF_8; |
| 7 | +import static org.openqa.selenium.remote.ErrorCodes.SESSION_NOT_CREATED; |
| 8 | +import static org.openqa.selenium.remote.ErrorCodes.SUCCESS; |
| 9 | + |
| 10 | +import com.google.common.base.Preconditions; |
| 11 | + |
| 12 | +import org.openqa.selenium.Capabilities; |
| 13 | +import org.openqa.selenium.SessionNotCreatedException; |
| 14 | +import org.openqa.selenium.remote.BeanToJsonConverter; |
| 15 | +import org.openqa.selenium.remote.Command; |
| 16 | +import org.openqa.selenium.remote.DesiredCapabilities; |
| 17 | +import org.openqa.selenium.remote.Dialect; |
| 18 | +import org.openqa.selenium.remote.ErrorHandler; |
| 19 | +import org.openqa.selenium.remote.JsonException; |
| 20 | +import org.openqa.selenium.remote.JsonToBeanConverter; |
| 21 | +import org.openqa.selenium.remote.Response; |
| 22 | +import org.openqa.selenium.remote.SessionId; |
| 23 | +import org.openqa.selenium.remote.http.HttpClient; |
| 24 | +import org.openqa.selenium.remote.http.HttpMethod; |
| 25 | +import org.openqa.selenium.remote.http.HttpRequest; |
| 26 | +import org.openqa.selenium.remote.http.HttpResponse; |
| 27 | + |
| 28 | +import java.io.IOException; |
| 29 | +import java.net.HttpURLConnection; |
| 30 | +import java.util.HashMap; |
| 31 | +import java.util.Map; |
| 32 | +import java.util.Optional; |
| 33 | + |
| 34 | +class AppiumProtocolHandShake { |
| 35 | + |
| 36 | + public Result createSession(HttpClient client, Command command) |
| 37 | + throws IOException { |
| 38 | + |
| 39 | + Capabilities desired = (Capabilities) command.getParameters().get("desiredCapabilities"); |
| 40 | + desired = desired == null ? new DesiredCapabilities() : desired; |
| 41 | + Capabilities required = (Capabilities) command.getParameters().get("requiredCapabilities"); |
| 42 | + required = required == null ? new DesiredCapabilities() : required; |
| 43 | + |
| 44 | + String des = new BeanToJsonConverter().convert(desired); |
| 45 | + String req = new BeanToJsonConverter().convert(required); |
| 46 | + |
| 47 | + // Assume the remote end obeys the robustness principle. |
| 48 | + StringBuilder parameters = new StringBuilder("{"); |
| 49 | + amendW3CParameters(parameters, des, req); |
| 50 | + parameters.append(","); |
| 51 | + amendOssParamters(parameters, des, req); |
| 52 | + parameters.append("}"); |
| 53 | + Optional<Result> result = createSession(client, parameters); |
| 54 | + |
| 55 | + // Assume a fragile OSS webdriver implementation |
| 56 | + if (!result.isPresent()) { |
| 57 | + parameters = new StringBuilder("{"); |
| 58 | + amendOssParamters(parameters, des, req); |
| 59 | + parameters.append("}"); |
| 60 | + result = createSession(client, parameters); |
| 61 | + } |
| 62 | + |
| 63 | + // Assume a fragile w3c implementation |
| 64 | + if (!result.isPresent()) { |
| 65 | + parameters = new StringBuilder("{"); |
| 66 | + amendW3CParameters(parameters, des, req); |
| 67 | + parameters.append("}"); |
| 68 | + result = createSession(client, parameters); |
| 69 | + } |
| 70 | + |
| 71 | + if (result.isPresent()) { |
| 72 | + Result toReturn = result.get(); |
| 73 | + return toReturn; |
| 74 | + } |
| 75 | + |
| 76 | + throw new SessionNotCreatedException( |
| 77 | + String.format( |
| 78 | + "Unable to create new remote session. " |
| 79 | + + "desired capabilities = %s, required capabilities = %s", |
| 80 | + desired, |
| 81 | + required)); |
| 82 | + } |
| 83 | + |
| 84 | + private Optional<Result> createSession(HttpClient client, StringBuilder params) |
| 85 | + throws IOException { |
| 86 | + // Create the http request and send it |
| 87 | + HttpRequest request = new HttpRequest(HttpMethod.POST, "/session"); |
| 88 | + String content = params.toString(); |
| 89 | + byte[] data = content.getBytes(UTF_8); |
| 90 | + |
| 91 | + request.setHeader(CONTENT_LENGTH, String.valueOf(data.length)); |
| 92 | + request.setHeader(CONTENT_TYPE, JSON_UTF_8.toString()); |
| 93 | + request.setContent(data); |
| 94 | + HttpResponse response = client.execute(request, true); |
| 95 | + |
| 96 | + Map<?, ?> jsonBlob = null; |
| 97 | + String resultString = response.getContentString(); |
| 98 | + try { |
| 99 | + jsonBlob = new JsonToBeanConverter().convert(Map.class, resultString); |
| 100 | + } catch (ClassCastException e) { |
| 101 | + return Optional.empty(); |
| 102 | + } catch (JsonException e) { |
| 103 | + // Fine. Handle that below |
| 104 | + } |
| 105 | + |
| 106 | + if (jsonBlob == null) { |
| 107 | + jsonBlob = new HashMap<>(); |
| 108 | + } |
| 109 | + |
| 110 | + // If the result looks positive, return the result. |
| 111 | + Object sessionId = jsonBlob.get("sessionId"); |
| 112 | + Object value = jsonBlob.get("value"); |
| 113 | + Object w3cError = jsonBlob.get("error"); |
| 114 | + Object ossStatus = jsonBlob.get("status"); |
| 115 | + Map<String, ?> capabilities = null; |
| 116 | + if (value != null && value instanceof Map) { |
| 117 | + capabilities = (Map<String, ?>) value; |
| 118 | + } else if (value != null && value instanceof Capabilities) { |
| 119 | + capabilities = ((Capabilities) capabilities).asMap(); |
| 120 | + } |
| 121 | + |
| 122 | + if (response.getStatus() == HttpURLConnection.HTTP_OK) { |
| 123 | + if (sessionId != null && capabilities != null) { |
| 124 | + Dialect dialect = ossStatus == null ? Dialect.W3C : Dialect.OSS; |
| 125 | + return Optional.of( |
| 126 | + new Result(dialect, String.valueOf(sessionId), capabilities)); |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + // If the result was an error that we believe has to do with the remote end failing to start the |
| 131 | + // session, create an exception and throw it. |
| 132 | + Response tempResponse = null; |
| 133 | + if ("session not created".equals(w3cError)) { |
| 134 | + tempResponse = new Response(null); |
| 135 | + tempResponse.setStatus(SESSION_NOT_CREATED); |
| 136 | + tempResponse.setValue(jsonBlob); |
| 137 | + } else if ( |
| 138 | + ossStatus instanceof Number |
| 139 | + && ((Number) ossStatus).intValue() == SESSION_NOT_CREATED) { |
| 140 | + tempResponse = new Response(null); |
| 141 | + tempResponse.setStatus(SESSION_NOT_CREATED); |
| 142 | + tempResponse.setValue(jsonBlob); |
| 143 | + } |
| 144 | + |
| 145 | + if (tempResponse != null) { |
| 146 | + new ErrorHandler().throwIfResponseFailed(tempResponse, 0); |
| 147 | + } |
| 148 | + |
| 149 | + // Otherwise, just return empty. |
| 150 | + return Optional.empty(); |
| 151 | + } |
| 152 | + |
| 153 | + private void amendW3CParameters( |
| 154 | + StringBuilder params, |
| 155 | + String desired, |
| 156 | + String required) { |
| 157 | + params.append("\"capabilities\": {"); |
| 158 | + params.append("\"desiredCapabilities\": ").append(desired); |
| 159 | + params.append(","); |
| 160 | + params.append("\"requiredCapabilities\": ").append(required); |
| 161 | + params.append("}"); |
| 162 | + } |
| 163 | + |
| 164 | + private void amendOssParamters( |
| 165 | + StringBuilder params, |
| 166 | + String desired, |
| 167 | + String required) { |
| 168 | + params.append("\"desiredCapabilities\": ").append(desired); |
| 169 | + params.append(","); |
| 170 | + params.append("\"requiredCapabilities\": ").append(required); |
| 171 | + } |
| 172 | + |
| 173 | + |
| 174 | + public class Result { |
| 175 | + private final Dialect dialect; |
| 176 | + private final Map<String, ?> capabilities; |
| 177 | + private final SessionId sessionId; |
| 178 | + |
| 179 | + private Result(Dialect dialect, String sessionId, Map<String, ?> capabilities) { |
| 180 | + this.dialect = dialect; |
| 181 | + this.sessionId = new SessionId(Preconditions.checkNotNull(sessionId)); |
| 182 | + this.capabilities = capabilities; |
| 183 | + } |
| 184 | + |
| 185 | + public Dialect getDialect() { |
| 186 | + return dialect; |
| 187 | + } |
| 188 | + |
| 189 | + public Response createResponse() { |
| 190 | + Response response = new Response(sessionId); |
| 191 | + response.setValue(capabilities); |
| 192 | + response.setStatus(SUCCESS); |
| 193 | + return response; |
| 194 | + } |
| 195 | + |
| 196 | + @Override |
| 197 | + public String toString() { |
| 198 | + return String.format("%s: %s", dialect, capabilities); |
| 199 | + } |
| 200 | + } |
| 201 | +} |
0 commit comments