diff --git a/java/client/src/org/openqa/selenium/remote/CommandInfo.java b/java/client/src/org/openqa/selenium/remote/CommandInfo.java index 43f1f60d25999..6403eaf797af3 100644 --- a/java/client/src/org/openqa/selenium/remote/CommandInfo.java +++ b/java/client/src/org/openqa/selenium/remote/CommandInfo.java @@ -1,58 +1,30 @@ package org.openqa.selenium.remote; -import org.apache.http.client.methods.HttpUriRequest; -import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.net.Urls; - -import java.net.URL; +import org.openqa.selenium.remote.codec.http.HttpMethod; public class CommandInfo { private final String url; - private final HttpVerb verb; + private final HttpMethod method; + /** + * @deprecated Use {@link org.openqa.selenium.remote.CommandInfo(String, HttpMethod)}. + */ + @Deprecated public CommandInfo(String url, HttpVerb verb) { - this.url = url; - this.verb = verb; + this(url, verb.toHttpMethod()); } - HttpUriRequest getMethod(URL base, Command command) { - StringBuilder urlBuilder = new StringBuilder(); - - urlBuilder.append(base.toExternalForm().replaceAll("/$", "")); - for (String part : url.split("/")) { - if (part.length() == 0) { - continue; - } - - urlBuilder.append("/"); - if (part.startsWith(":")) { - String value = get(part.substring(1), command); - if (value != null) { - urlBuilder.append(get(part.substring(1), command)); - } - } else { - urlBuilder.append(part); - } - } - - return verb.createMethod(urlBuilder.toString()); + public CommandInfo(String url, HttpMethod method) { + this.url = url; + this.method = method; } - private String get(String propertyName, Command command) { - if ("sessionId".equals(propertyName)) { - SessionId id = command.getSessionId(); - if (id == null) { - throw new WebDriverException("Session ID may not be null"); - } - return id.toString(); - } + String getUrl() { + return url; + } - // Attempt to extract the property name from the parameters - Object value = command.getParameters().get(propertyName); - if (value != null) { - return Urls.urlEncode(String.valueOf(value)); - } - return null; + HttpMethod getMethod() { + return method; } } diff --git a/java/client/src/org/openqa/selenium/remote/DriverCommand.java b/java/client/src/org/openqa/selenium/remote/DriverCommand.java index 6e4a762551290..fe16620083082 100644 --- a/java/client/src/org/openqa/selenium/remote/DriverCommand.java +++ b/java/client/src/org/openqa/selenium/remote/DriverCommand.java @@ -164,6 +164,7 @@ public interface DriverCommand { // Logging API String GET_AVAILABLE_LOG_TYPES = "getAvailableLogTypes"; String GET_LOG = "getLog"; + String GET_SESSION_LOGS = "getSessionLogs"; // Mobile API String GET_NETWORK_CONNECTION = "getNetworkConnection"; diff --git a/java/client/src/org/openqa/selenium/remote/HttpCommandExecutor.java b/java/client/src/org/openqa/selenium/remote/HttpCommandExecutor.java index 3f7db9ba3cba2..075a53c4818fe 100644 --- a/java/client/src/org/openqa/selenium/remote/HttpCommandExecutor.java +++ b/java/client/src/org/openqa/selenium/remote/HttpCommandExecutor.java @@ -16,10 +16,16 @@ package org.openqa.selenium.remote; +import static org.apache.http.protocol.ExecutionContext.HTTP_TARGET_HOST; +import static org.openqa.selenium.remote.DriverCommand.GET_ALL_SESSIONS; +import static org.openqa.selenium.remote.DriverCommand.NEW_SESSION; +import static org.openqa.selenium.remote.DriverCommand.QUIT; + import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import org.apache.http.Header; +import org.apache.http.HeaderElement; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; @@ -28,12 +34,12 @@ import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.params.HttpClientParams; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; +import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.CoreConnectionPNames; @@ -48,30 +54,29 @@ import org.openqa.selenium.logging.LogType; import org.openqa.selenium.logging.NeedsLocalLogs; import org.openqa.selenium.logging.profiler.HttpProfilerLogEntry; +import org.openqa.selenium.remote.codec.http.HttpMethod; +import org.openqa.selenium.remote.codec.http.HttpRequest; +import org.openqa.selenium.remote.codec.http.JsonHttpCommandCodec; +import org.openqa.selenium.remote.codec.http.JsonHttpResponseCodec; import org.openqa.selenium.remote.internal.HttpClientFactory; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.net.BindException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.nio.charset.Charset; import java.util.Map; -import static org.apache.http.protocol.ExecutionContext.HTTP_TARGET_HOST; -import static org.openqa.selenium.remote.DriverCommand.*; - public class HttpCommandExecutor implements CommandExecutor, NeedsLocalLogs { private static final int MAX_REDIRECTS = 10; private final HttpHost targetHost; private final URL remoteServer; - private final Map nameToUrl; private final HttpClient client; - private final ErrorCodes errorCodes = new ErrorCodes(); + private final JsonHttpCommandCodec commandCodec; + private final JsonHttpResponseCodec responseCodec; private static HttpClientFactory httpClientFactory; @@ -90,6 +95,9 @@ public HttpCommandExecutor(Map additionalCommands, URL addr throw new WebDriverException(e); } + commandCodec = new JsonHttpCommandCodec(); + responseCodec = new JsonHttpResponseCodec(); + HttpParams params = new BasicHttpParams(); // Use the JRE default for the socket linger timeout. params.setParameter(CoreConnectionPNames.SO_LINGER, -1); @@ -117,145 +125,10 @@ public HttpCommandExecutor(Map additionalCommands, URL addr targetHost = new HttpHost( host, remoteServer.getPort(), remoteServer.getProtocol()); - ImmutableMap.Builder builder = ImmutableMap.builder(); for (Map.Entry entry : additionalCommands.entrySet()) { - builder.put(entry.getKey(), entry.getValue()); + CommandInfo info = entry.getValue(); + commandCodec.defineCommand(entry.getKey(), info.getMethod(), info.getUrl()); } - - builder - .put(GET_ALL_SESSIONS, get("/sessions")) - .put(NEW_SESSION, post("/session")) - .put(GET_CAPABILITIES, get("/session/:sessionId")) - .put(QUIT, delete("/session/:sessionId")) - .put(GET_CURRENT_WINDOW_HANDLE, get("/session/:sessionId/window_handle")) - .put(GET_WINDOW_HANDLES, get("/session/:sessionId/window_handles")) - .put(GET, post("/session/:sessionId/url")) - - // The Alert API is still experimental and should not be used. - .put(GET_ALERT, get("/session/:sessionId/alert")) - .put(DISMISS_ALERT, post("/session/:sessionId/dismiss_alert")) - .put(ACCEPT_ALERT, post("/session/:sessionId/accept_alert")) - .put(GET_ALERT_TEXT, get("/session/:sessionId/alert_text")) - .put(SET_ALERT_VALUE, post("/session/:sessionId/alert_text")) - - .put(GO_FORWARD, post("/session/:sessionId/forward")) - .put(GO_BACK, post("/session/:sessionId/back")) - .put(REFRESH, post("/session/:sessionId/refresh")) - .put(EXECUTE_SCRIPT, post("/session/:sessionId/execute")) - .put(EXECUTE_ASYNC_SCRIPT, post("/session/:sessionId/execute_async")) - .put(GET_CURRENT_URL, get("/session/:sessionId/url")) - .put(GET_TITLE, get("/session/:sessionId/title")) - .put(GET_PAGE_SOURCE, get("/session/:sessionId/source")) - .put(SCREENSHOT, get("/session/:sessionId/screenshot")) - .put(FIND_ELEMENT, post("/session/:sessionId/element")) - .put(FIND_ELEMENTS, post("/session/:sessionId/elements")) - .put(GET_ACTIVE_ELEMENT, post("/session/:sessionId/element/active")) - .put(FIND_CHILD_ELEMENT, post("/session/:sessionId/element/:id/element")) - .put(FIND_CHILD_ELEMENTS, post("/session/:sessionId/element/:id/elements")) - .put(CLICK_ELEMENT, post("/session/:sessionId/element/:id/click")) - .put(CLEAR_ELEMENT, post("/session/:sessionId/element/:id/clear")) - .put(SUBMIT_ELEMENT, post("/session/:sessionId/element/:id/submit")) - .put(GET_ELEMENT_TEXT, get("/session/:sessionId/element/:id/text")) - .put(SEND_KEYS_TO_ELEMENT, post("/session/:sessionId/element/:id/value")) - .put(UPLOAD_FILE, post("/session/:sessionId/file")) - .put(GET_ELEMENT_VALUE, get("/session/:sessionId/element/:id/value")) - .put(GET_ELEMENT_TAG_NAME, get("/session/:sessionId/element/:id/name")) - .put(IS_ELEMENT_SELECTED, get("/session/:sessionId/element/:id/selected")) - .put(IS_ELEMENT_ENABLED, get("/session/:sessionId/element/:id/enabled")) - .put(IS_ELEMENT_DISPLAYED, get("/session/:sessionId/element/:id/displayed")) - .put(HOVER_OVER_ELEMENT, post("/session/:sessionId/element/:id/hover")) - .put(GET_ELEMENT_LOCATION, get("/session/:sessionId/element/:id/location")) - .put(GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW, - get("/session/:sessionId/element/:id/location_in_view")) - .put(GET_ELEMENT_SIZE, get("/session/:sessionId/element/:id/size")) - .put(GET_ELEMENT_ATTRIBUTE, get("/session/:sessionId/element/:id/attribute/:name")) - .put(ELEMENT_EQUALS, get("/session/:sessionId/element/:id/equals/:other")) - .put(GET_ALL_COOKIES, get("/session/:sessionId/cookie")) - .put(ADD_COOKIE, post("/session/:sessionId/cookie")) - .put(DELETE_ALL_COOKIES, delete("/session/:sessionId/cookie")) - .put(DELETE_COOKIE, delete("/session/:sessionId/cookie/:name")) - .put(SWITCH_TO_FRAME, post("/session/:sessionId/frame")) - .put(SWITCH_TO_PARENT_FRAME, post("/session/:sessionId/frame/parent")) - .put(SWITCH_TO_WINDOW, post("/session/:sessionId/window")) - .put(GET_WINDOW_SIZE, get("/session/:sessionId/window/:windowHandle/size")) - .put(GET_WINDOW_POSITION, get("/session/:sessionId/window/:windowHandle/position")) - .put(SET_WINDOW_SIZE, post("/session/:sessionId/window/:windowHandle/size")) - .put(SET_WINDOW_POSITION, post("/session/:sessionId/window/:windowHandle/position")) - .put(MAXIMIZE_WINDOW, post("/session/:sessionId/window/:windowHandle/maximize")) - .put(CLOSE, delete("/session/:sessionId/window")) - .put(DRAG_ELEMENT, post("/session/:sessionId/element/:id/drag")) - .put(GET_ELEMENT_VALUE_OF_CSS_PROPERTY, - get("/session/:sessionId/element/:id/css/:propertyName")) - .put(IMPLICITLY_WAIT, post("/session/:sessionId/timeouts/implicit_wait")) - .put(SET_SCRIPT_TIMEOUT, post("/session/:sessionId/timeouts/async_script")) - .put(SET_TIMEOUT, post("/session/:sessionId/timeouts")) - .put(EXECUTE_SQL, post("/session/:sessionId/execute_sql")) - .put(GET_LOCATION, get("/session/:sessionId/location")) - .put(SET_LOCATION, post("/session/:sessionId/location")) - .put(GET_APP_CACHE_STATUS, get("/session/:sessionId/application_cache/status")) - - .put(SWITCH_TO_CONTEXT, post("/session/:sessionId/context")) - .put(GET_CURRENT_CONTEXT_HANDLE, get("/session/:sessionId/context")) - .put(GET_CONTEXT_HANDLES, get("/session/:sessionId/contexts")) - - // TODO (user): Would it be better to combine this command with - // GET_LOCAL_STORAGE_SIZE? - .put(GET_LOCAL_STORAGE_ITEM, get("/session/:sessionId/local_storage/key/:key")) - .put(REMOVE_LOCAL_STORAGE_ITEM, delete("/session/:sessionId/local_storage/key/:key")) - .put(GET_LOCAL_STORAGE_KEYS, get("/session/:sessionId/local_storage")) - .put(SET_LOCAL_STORAGE_ITEM, post("/session/:sessionId/local_storage")) - .put(CLEAR_LOCAL_STORAGE, delete("/session/:sessionId/local_storage")) - .put(GET_LOCAL_STORAGE_SIZE, get("/session/:sessionId/local_storage/size")) - - // TODO (user): Would it be better to combine this command with - // GET_SESSION_STORAGE_SIZE? - .put(GET_SESSION_STORAGE_ITEM, get("/session/:sessionId/session_storage/key/:key")) - .put(REMOVE_SESSION_STORAGE_ITEM, delete("/session/:sessionId/session_storage/key/:key")) - .put(GET_SESSION_STORAGE_KEYS, get("/session/:sessionId/session_storage")) - .put(SET_SESSION_STORAGE_ITEM, post("/session/:sessionId/session_storage")) - .put(CLEAR_SESSION_STORAGE, delete("/session/:sessionId/session_storage")) - .put(GET_SESSION_STORAGE_SIZE, get("/session/:sessionId/session_storage/size")) - - .put(GET_SCREEN_ORIENTATION, get("/session/:sessionId/orientation")) - .put(SET_SCREEN_ORIENTATION, post("/session/:sessionId/orientation")) - - // Interactions-related commands. - .put(CLICK, post("/session/:sessionId/click")) - .put(DOUBLE_CLICK, post("/session/:sessionId/doubleclick")) - .put(MOUSE_DOWN, post("/session/:sessionId/buttondown")) - .put(MOUSE_UP, post("/session/:sessionId/buttonup")) - .put(MOVE_TO, post("/session/:sessionId/moveto")) - .put(SEND_KEYS_TO_ACTIVE_ELEMENT, post("/session/:sessionId/keys")) - - // IME related commands. - .put(IME_GET_AVAILABLE_ENGINES, get("/session/:sessionId/ime/available_engines")) - .put(IME_GET_ACTIVE_ENGINE, get("/session/:sessionId/ime/active_engine")) - .put(IME_IS_ACTIVATED, get("/session/:sessionId/ime/activated")) - .put(IME_DEACTIVATE, post("/session/:sessionId/ime/deactivate")) - .put(IME_ACTIVATE_ENGINE, post("/session/:sessionId/ime/activate")) - - // Advanced Touch API commands - // TODO(berrada): Refactor single tap with mouse click. - .put(TOUCH_SINGLE_TAP, post("/session/:sessionId/touch/click")) - .put(TOUCH_DOWN, post("/session/:sessionId/touch/down")) - .put(TOUCH_UP, post("/session/:sessionId/touch/up")) - .put(TOUCH_MOVE, post("/session/:sessionId/touch/move")) - .put(TOUCH_SCROLL, post("/session/:sessionId/touch/scroll")) - .put(TOUCH_DOUBLE_TAP, post("/session/:sessionId/touch/doubleclick")) - .put(TOUCH_LONG_PRESS, post("/session/:sessionId/touch/longclick")) - .put(TOUCH_FLICK, post("/session/:sessionId/touch/flick")) - - .put(GET_LOG, post("/session/:sessionId/log")) - .put(GET_AVAILABLE_LOG_TYPES, get("/session/:sessionId/log/types")) - - // Mobile Spec - // https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile - .put(GET_NETWORK_CONNECTION, get("/session/:sessionId/network_connection")) - .put(SET_NETWORK_CONNECTION, post("/session/:sessionId/network_connection")) - - .put(STATUS, get("/status")); - - nameToUrl = builder.build(); } public void setLocalLogs(LocalLogs logs) { @@ -284,32 +157,33 @@ public Response execute(Command command) throws IOException { } } - CommandInfo info = nameToUrl.get(command.getName()); - try { - HttpUriRequest httpMethod = info.getMethod(remoteServer, command); + HttpRequest request = commandCodec.encode(command); - setAcceptHeader(httpMethod); + String requestUrl = remoteServer.toExternalForm().replaceAll("/$", "") + + request.getUri(); - if (httpMethod instanceof HttpPost) { - String payload = new BeanToJsonConverter().convert(command.getParameters()); - ((HttpPost) httpMethod).setEntity(new StringEntity(payload, "utf-8")); - httpMethod.addHeader("Content-Type", "application/json; charset=utf-8"); + HttpUriRequest httpMethod = createHttpUriRequest(request.getMethod(), requestUrl); + for (String name : request.getHeaderNames()) { + // Skip content length as it is implicitly set when the message entity is set below. + if (!"Content-Length".equalsIgnoreCase(name)) { + for (String value : request.getHeaders(name)) { + httpMethod.addHeader(name, value); + } } + } - // Do not allow web proxy caches to cache responses to "get" commands - if (httpMethod instanceof HttpGet) { - httpMethod.addHeader("Cache-Control", "no-cache"); - } + if (httpMethod instanceof HttpPost) { + ((HttpPost) httpMethod).setEntity(new ByteArrayEntity(request.getContent())); + } + try { log(LogType.PROFILER, new HttpProfilerLogEntry(command.getName(), true)); HttpResponse response = fallBackExecute(context, httpMethod); log(LogType.PROFILER, new HttpProfilerLogEntry(command.getName(), false)); response = followRedirects(client, context, response, /* redirect count */0); - final EntityWithEncoding entityWithEncoding = new EntityWithEncoding(response.getEntity()); - - return createResponse(response, context, entityWithEncoding); + return createResponse(response, context); } catch (UnsupportedCommandException e) { if (e.getMessage() == null || "".equals(e.getMessage())) { throw new UnsupportedOperationException( @@ -320,6 +194,18 @@ public Response execute(Command command) throws IOException { } } + private static HttpUriRequest createHttpUriRequest(HttpMethod method, String url) { + switch (method) { + case DELETE: + return new HttpDelete(url); + case GET: + return new HttpGet(url); + case POST: + return new HttpPost(url); + } + throw new AssertionError("Unsupported method: " + method); + } + private HttpResponse fallBackExecute(HttpContext context, HttpUriRequest httpMethod) throws IOException { try { @@ -344,10 +230,6 @@ private HttpResponse fallBackExecute(HttpContext context, HttpUriRequest httpMet return client.execute(targetHost, httpMethod, context); } - private void setAcceptHeader(HttpUriRequest httpMethod) { - httpMethod.addHeader("Accept", "application/json, image/png"); - } - private HttpResponse followRedirects( HttpClient client, HttpContext context, HttpResponse response, int redirectCount) { if (!isRedirect(response)) { @@ -374,7 +256,7 @@ private HttpResponse followRedirects( uri = buildUri(context, location); HttpGet get = new HttpGet(uri); - setAcceptHeader(get); + get.setHeader("Accept", "application/json; charset=utf-8"); HttpResponse newResponse = client.execute(targetHost, get, context); return followRedirects(client, context, newResponse, redirectCount + 1); } catch (URISyntaxException e) { @@ -403,116 +285,35 @@ private boolean isRedirect(HttpResponse response) { && response.containsHeader("location"); } - class EntityWithEncoding { + private Response createResponse(HttpResponse httpResponse, HttpContext context) + throws IOException { + org.openqa.selenium.remote.codec.http.HttpResponse internalResponse = + new org.openqa.selenium.remote.codec.http.HttpResponse(); - private final String charSet; - private final byte[] content; + internalResponse.setStatus(httpResponse.getStatusLine().getStatusCode()); + for (Header header : httpResponse.getAllHeaders()) { + for (HeaderElement headerElement : header.getElements()) { + internalResponse.addHeader(header.getName(), headerElement.getValue()); + } + } - EntityWithEncoding(HttpEntity entity) throws IOException { + HttpEntity entity = httpResponse.getEntity(); + if (entity != null) { try { - if (entity != null) { - content = EntityUtils.toByteArray(entity); - Charset entityCharset = ContentType.getOrDefault(entity).getCharset(); - charSet = entityCharset != null ? entityCharset.name() : null; - } else { - content = new byte[0]; - charSet = null; - } + internalResponse.setContent(EntityUtils.toByteArray(entity)); } finally { EntityUtils.consume(entity); } } - public String getContentString() - throws UnsupportedEncodingException { - return new String(content, charSet != null ? charSet : "utf-8"); - } - - public byte[] getContent() { - return content; - } - - public boolean hasEntityContent() { - return content != null; - } - } - - - private Response createResponse(HttpResponse httpResponse, HttpContext context, - EntityWithEncoding entityWithEncoding) throws IOException { - final Response response; - - Header header = httpResponse.getFirstHeader("Content-Type"); - - if (header != null && header.getValue().startsWith("application/json")) { - String responseAsText = entityWithEncoding.getContentString(); - - try { - response = new JsonToBeanConverter().convert(Response.class, responseAsText); - } catch (ClassCastException e) { - if (responseAsText != null && "".equals(responseAsText)) { - // The remote server has died, but has already set some headers. - // Normally this occurs when the final window of the firefox driver - // is closed on OS X. Return null, as the return value _should_ be - // being ignored. This is not an elegant solution. - return null; - } - throw new WebDriverException("Cannot convert text to response: " + responseAsText, e); - } - } else { - response = new Response(); - - if (header != null && header.getValue().startsWith("image/png")) { - response.setValue(entityWithEncoding.getContent()); - } else if (entityWithEncoding.hasEntityContent()) { - response.setValue(entityWithEncoding.getContentString()); - } - + Response response = responseCodec.decode(internalResponse); + if (response.getSessionId() == null) { HttpHost finalHost = (HttpHost) context.getAttribute(HTTP_TARGET_HOST); String uri = finalHost.toURI(); String sessionId = HttpSessionId.getSessionId(uri); - if (sessionId != null) { - response.setSessionId(sessionId); - } - - int statusCode = httpResponse.getStatusLine().getStatusCode(); - if (!(statusCode > 199 && statusCode < 300)) { - // 4xx represents an unknown command or a bad request. - if (statusCode > 399 && statusCode < 500) { - response.setStatus(ErrorCodes.UNKNOWN_COMMAND); - } else if (statusCode > 499 && statusCode < 600) { - // 5xx represents an internal server error. The response status should already be set, but - // if not, set it to a general error code. - if (response.getStatus() == ErrorCodes.SUCCESS) { - response.setStatus(ErrorCodes.UNHANDLED_ERROR); - } - } else { - response.setStatus(ErrorCodes.UNHANDLED_ERROR); - } - } - - if (response.getValue() instanceof String) { - // We normalise to \n because Java will translate this to \r\n - // if this is suitable on our platform, and if we have \r\n, java will - // turn this into \r\r\n, which would be Bad! - response.setValue(((String) response.getValue()).replace("\r\n", "\n")); - } + response.setSessionId(sessionId); } - response.setState(errorCodes.toState(response.getStatus())); return response; } - - private static CommandInfo get(String url) { - return new CommandInfo(url, HttpVerb.GET); - } - - private static CommandInfo post(String url) { - return new CommandInfo(url, HttpVerb.POST); - } - - private static CommandInfo delete(String url) { - return new CommandInfo(url, HttpVerb.DELETE); - } - } diff --git a/java/client/src/org/openqa/selenium/remote/HttpVerb.java b/java/client/src/org/openqa/selenium/remote/HttpVerb.java index 68fa0c453ec9d..db632279faa43 100644 --- a/java/client/src/org/openqa/selenium/remote/HttpVerb.java +++ b/java/client/src/org/openqa/selenium/remote/HttpVerb.java @@ -4,26 +4,47 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; +import org.openqa.selenium.remote.codec.http.HttpMethod; +/** + * @deprecated Use {@link org.openqa.selenium.remote.codec.http.HttpMethod}. + */ +@Deprecated public enum HttpVerb { GET() { @Override HttpUriRequest createMethod(String url) { return new HttpGet(url); } + + @Override + HttpMethod toHttpMethod() { + return HttpMethod.GET; + } }, POST() { @Override HttpUriRequest createMethod(String url) { return new HttpPost(url); } + + @Override + HttpMethod toHttpMethod() { + return HttpMethod.POST; + } }, DELETE() { @Override HttpUriRequest createMethod(String url) { return new HttpDelete(url); } + + @Override + HttpMethod toHttpMethod() { + return HttpMethod.DELETE; + } }; abstract HttpUriRequest createMethod(String url); + abstract HttpMethod toHttpMethod(); } diff --git a/java/client/src/org/openqa/selenium/remote/Responses.java b/java/client/src/org/openqa/selenium/remote/Responses.java new file mode 100644 index 0000000000000..61fd3c64c6fba --- /dev/null +++ b/java/client/src/org/openqa/selenium/remote/Responses.java @@ -0,0 +1,76 @@ +package org.openqa.selenium.remote; + +import com.google.common.base.Optional; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Contains factory methods for creating {@link Response} objects. + */ +public class Responses { + + private static final ErrorCodes ERROR_CODES = new ErrorCodes(); + + private Responses() {} // Utility class. + + /** + * Creates a response object for a successful command execution. + * + * @param sessionId ID of the session that executed the command. + * @param value the command result value. + * @return the new response object. + */ + public static Response success(SessionId sessionId, Object value) { + Response response = new Response(); + response.setSessionId(sessionId != null ? sessionId.toString() : null); + response.setValue(value); + response.setStatus(ErrorCodes.SUCCESS); + response.setState(ErrorCodes.SUCCESS_STRING); + return response; + } + + /** + * Creates a response object for a failed command execution. + * + * @param sessionId ID of the session that executed the command. + * @param reason the failure reason. + * @return the new response object. + */ + public static Response failure(SessionId sessionId, Throwable reason) { + Response response = new Response(); + response.setSessionId(sessionId != null ? sessionId.toString() : null); + response.setValue(reason); + response.setStatus(ERROR_CODES.toStatusCode(reason)); + response.setState(ERROR_CODES.toState(response.getStatus())); + return response; + } + + /** + * Creates a response object for a failed command execution. + * + * @param sessionId ID of the session that executed the command. + * @param reason the failure reason. + * @param screenshot a base64 png screenshot to include with the failure. + * @return the new response object. + */ + public static Response failure( + SessionId sessionId, Throwable reason, Optional screenshot) { + Response response = new Response(); + response.setSessionId(sessionId != null ? sessionId.toString() : null); + response.setStatus(ERROR_CODES.toStatusCode(reason)); + response.setState(ERROR_CODES.toState(response.getStatus())); + + if (reason != null) { + String raw = new BeanToJsonConverter().convert(reason); + try { + JSONObject jsonError = new JSONObject(raw); + jsonError.put("screen", screenshot.orNull()); + response.setValue(jsonError); + } catch (JSONException e) { + throw new JsonException(e); + } + } + return response; + } +} diff --git a/java/client/src/org/openqa/selenium/remote/build.desc b/java/client/src/org/openqa/selenium/remote/build.desc index f6cf0984fd7b7..4d1fa5ceb096d 100644 --- a/java/client/src/org/openqa/selenium/remote/build.desc +++ b/java/client/src/org/openqa/selenium/remote/build.desc @@ -30,6 +30,7 @@ java_library(name = "common", "JsonException.java", "JsonToBeanConverter.java", "PropertyMunger.java", + "Responses.java", "ScreenshotException.java", "SessionNotFoundException.java", "SimplePropertyDescriptor.java", @@ -107,5 +108,6 @@ java_library(name = "remote", "//java/client/src/org/openqa/selenium/logging", "//java/client/src/org/openqa/selenium/io", "//java/client/src/org/openqa/selenium/net", + "//java/client/src/org/openqa/selenium/remote/codec", "//third_party/java/apache-httpclient", ]) diff --git a/java/client/src/org/openqa/selenium/remote/codec/Codec.java b/java/client/src/org/openqa/selenium/remote/codec/Codec.java new file mode 100644 index 0000000000000..bf8be702a5641 --- /dev/null +++ b/java/client/src/org/openqa/selenium/remote/codec/Codec.java @@ -0,0 +1,28 @@ +package org.openqa.selenium.remote.codec; + +/** + * Converts an object between two different representations. + * + * @param The raw value type. + * @param The value's encoded type. + */ +public interface Codec { + + /** + * Encodes an object. + * + * @param raw The object's raw representation. + * @return The encoded object. + * @throws IllegalArgumentException If the object cannot be encoded. + */ + T encode(S raw); + + /** + * Decodes an object. + * + * @param encoded The encoded object to decode. + * @return The decoded object. + * @throws IllegalArgumentException If the object cannot be decoded. + */ + S decode(T encoded); +} diff --git a/java/client/src/org/openqa/selenium/remote/codec/CommandCodec.java b/java/client/src/org/openqa/selenium/remote/codec/CommandCodec.java new file mode 100644 index 0000000000000..ca9ca237991a3 --- /dev/null +++ b/java/client/src/org/openqa/selenium/remote/codec/CommandCodec.java @@ -0,0 +1,26 @@ +package org.openqa.selenium.remote.codec; + +import org.openqa.selenium.UnsupportedCommandException; +import org.openqa.selenium.remote.Command; + +/** + * Converts {@link Command} objects to and from another representation. + * + * @param The type of an encoded command. + */ +public interface CommandCodec extends Codec { + + /** + * @inheritDoc + * @throws UnsupportedCommandException If the command is not supported by this codec. + */ + @Override + T encode(Command command); + + /** + * @inheritDoc + * @throws UnsupportedCommandException If the command is not supported by this codec. + */ + @Override + Command decode(T encodedCommand); +} diff --git a/java/client/src/org/openqa/selenium/remote/codec/ResponseCodec.java b/java/client/src/org/openqa/selenium/remote/codec/ResponseCodec.java new file mode 100644 index 0000000000000..c3af35023d890 --- /dev/null +++ b/java/client/src/org/openqa/selenium/remote/codec/ResponseCodec.java @@ -0,0 +1,11 @@ +package org.openqa.selenium.remote.codec; + +import org.openqa.selenium.remote.Response; + +/** + * Converts {@link Response} objects to and from another representation. + * + * @param The type of an encoded response. + */ +public interface ResponseCodec extends Codec { +} diff --git a/java/client/src/org/openqa/selenium/remote/codec/build.desc b/java/client/src/org/openqa/selenium/remote/codec/build.desc new file mode 100644 index 0000000000000..19be61564238c --- /dev/null +++ b/java/client/src/org/openqa/selenium/remote/codec/build.desc @@ -0,0 +1,13 @@ +java_library( + name = "codec", + srcs = [ + "*.java", + "http/*.java", + ], + deps = [ + "//java/client/src/org/openqa/selenium:webdriver-api", + "//java/client/src/org/openqa/selenium/net", + "//java/client/src/org/openqa/selenium/remote:api", + "//java/client/src/org/openqa/selenium/remote:common", + "//third_party/java/guava-libraries", + ]) diff --git a/java/client/src/org/openqa/selenium/remote/codec/http/HttpMessage.java b/java/client/src/org/openqa/selenium/remote/codec/http/HttpMessage.java new file mode 100644 index 0000000000000..3941034c18f8b --- /dev/null +++ b/java/client/src/org/openqa/selenium/remote/codec/http/HttpMessage.java @@ -0,0 +1,76 @@ +package org.openqa.selenium.remote.codec.http; + +import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.net.HttpHeaders.CONTENT_TYPE; + +import com.google.common.base.Supplier; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.google.common.net.MediaType; + +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.List; + +class HttpMessage { + + private final Multimap headers = Multimaps.newListMultimap( + Maps.>newHashMap(), new Supplier>() { + @Override + public List get() { + return Lists.newLinkedList(); + } + }); + + private byte[] content = new byte[0]; + + public Iterable getHeaderNames() { + return headers.keySet(); + } + + public Iterable getHeaders(String name) { + return headers.get(name); + } + + public String getHeader(String name) { + Collection values = headers.get(name); + return values.isEmpty() ? null : values.iterator().next(); + } + + public void setHeader(String name, String value) { + removeHeader(name); + headers.put(name, value); + } + + public void addHeader(String name, String value) { + headers.put(name, value); + } + + public void removeHeader(String name) { + headers.removeAll(name); + } + + public void setContent(byte[] data) { + this.content = data; + } + + public byte[] getContent() { + return content; + } + + public String getContentString() { + Charset charset = UTF_8; + try { + String contentType = getHeader(CONTENT_TYPE); + if (contentType != null) { + MediaType mediaType = MediaType.parse(contentType); + charset = mediaType.charset().or(UTF_8); + } + } catch (IllegalArgumentException ignored) { + // Do nothing. + } + return new String(content, charset); + } +} diff --git a/java/client/src/org/openqa/selenium/remote/codec/http/HttpMethod.java b/java/client/src/org/openqa/selenium/remote/codec/http/HttpMethod.java new file mode 100644 index 0000000000000..e0ea686460aa4 --- /dev/null +++ b/java/client/src/org/openqa/selenium/remote/codec/http/HttpMethod.java @@ -0,0 +1,7 @@ +package org.openqa.selenium.remote.codec.http; + +public enum HttpMethod { + DELETE, + GET, + POST +} diff --git a/java/server/src/org/openqa/selenium/remote/server/HttpRequest.java b/java/client/src/org/openqa/selenium/remote/codec/http/HttpRequest.java similarity index 54% rename from java/server/src/org/openqa/selenium/remote/server/HttpRequest.java rename to java/client/src/org/openqa/selenium/remote/codec/http/HttpRequest.java index 65a8c4c4e23dc..bc45455b2fa39 100644 --- a/java/server/src/org/openqa/selenium/remote/server/HttpRequest.java +++ b/java/client/src/org/openqa/selenium/remote/codec/http/HttpRequest.java @@ -15,21 +15,14 @@ limitations under the License. */ -package org.openqa.selenium.remote.server; +package org.openqa.selenium.remote.codec.http; -import java.util.HashMap; -import java.util.Map; +public class HttpRequest extends HttpMessage { -public class HttpRequest { - - private final String method; + private final HttpMethod method; private final String uri; - private final Map headers = new HashMap(); - - private byte[] data = new byte[0]; - - public HttpRequest(String method, String uri) { + public HttpRequest(HttpMethod method, String uri) { this.method = method; this.uri = uri; } @@ -38,23 +31,7 @@ public String getUri() { return uri; } - public String getMethod() { + public HttpMethod getMethod() { return method; } - - public String getHeader(String header) { - return headers.get(header); - } - - public void setHeader(String name, String value) { - headers.put(name, value); - } - - public byte[] getContent() { - return data; - } - - public void setContent(byte[] data) { - this.data = data; - } } diff --git a/java/server/src/org/openqa/selenium/remote/server/HttpStatusCodes.java b/java/client/src/org/openqa/selenium/remote/codec/http/HttpResponse.java similarity index 70% rename from java/server/src/org/openqa/selenium/remote/server/HttpStatusCodes.java rename to java/client/src/org/openqa/selenium/remote/codec/http/HttpResponse.java index f7dbbc77e2ba2..7487d2ec38688 100644 --- a/java/server/src/org/openqa/selenium/remote/server/HttpStatusCodes.java +++ b/java/client/src/org/openqa/selenium/remote/codec/http/HttpResponse.java @@ -15,15 +15,17 @@ limitations under the License. */ -package org.openqa.selenium.remote.server; +package org.openqa.selenium.remote.codec.http; -public interface HttpStatusCodes { - int OK = 200; - int NO_CONTENT = 204; +public class HttpResponse extends HttpMessage { - int SEE_OTHER = 303; + private int status = HttpStatusCodes.OK; - int NOT_FOUND = 404; + public int getStatus() { + return status; + } - int INTERNAL_SERVER_ERROR = 500; + public void setStatus(int status) { + this.status = status; + } } diff --git a/java/client/src/org/openqa/selenium/remote/codec/http/HttpStatusCodes.java b/java/client/src/org/openqa/selenium/remote/codec/http/HttpStatusCodes.java new file mode 100644 index 0000000000000..4c580165484c1 --- /dev/null +++ b/java/client/src/org/openqa/selenium/remote/codec/http/HttpStatusCodes.java @@ -0,0 +1,29 @@ +/* +Copyright 2012 Selenium committers +Copyright 2012 Software Freedom Conservancy + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package org.openqa.selenium.remote.codec.http; + +public class HttpStatusCodes { + private HttpStatusCodes() {} + + public static final int OK = 200; + public static final int NO_CONTENT = 204; + public static final int SEE_OTHER = 303; + public static final int BAD_REQUEST = 400; + public static final int NOT_FOUND = 404; + public static final int INTERNAL_SERVER_ERROR = 500; +} diff --git a/java/client/src/org/openqa/selenium/remote/codec/http/JsonHttpCommandCodec.java b/java/client/src/org/openqa/selenium/remote/codec/http/JsonHttpCommandCodec.java new file mode 100644 index 0000000000000..1886997d51f64 --- /dev/null +++ b/java/client/src/org/openqa/selenium/remote/codec/http/JsonHttpCommandCodec.java @@ -0,0 +1,370 @@ +package org.openqa.selenium.remote.codec.http; + +import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.nullToEmpty; +import static com.google.common.net.HttpHeaders.CACHE_CONTROL; +import static com.google.common.net.HttpHeaders.CONTENT_LENGTH; +import static com.google.common.net.HttpHeaders.CONTENT_TYPE; +import static com.google.common.net.MediaType.JSON_UTF_8; +import static org.openqa.selenium.remote.DriverCommand.*; + +import com.google.common.base.Objects; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.collect.BiMap; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; + +import org.openqa.selenium.UnsupportedCommandException; +import org.openqa.selenium.net.Urls; +import org.openqa.selenium.remote.BeanToJsonConverter; +import org.openqa.selenium.remote.Command; +import org.openqa.selenium.remote.JsonToBeanConverter; +import org.openqa.selenium.remote.SessionId; +import org.openqa.selenium.remote.codec.CommandCodec; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A command codec that adheres to the Selenium project's JSON/HTTP wire protocol. + * + * @see + * JSON wire protocol + */ +public class JsonHttpCommandCodec implements CommandCodec { + + private static final Splitter PATH_SPLITTER = Splitter.on('/').omitEmptyStrings(); + private static final String SESSION_ID_PARAM = "sessionId"; + + private final BiMap nameToSpec = HashBiMap.create(); + private final BeanToJsonConverter beanToJsonConverter = new BeanToJsonConverter(); + private final JsonToBeanConverter jsonToBeanConverter = new JsonToBeanConverter(); + + public JsonHttpCommandCodec() { + defineCommand(STATUS, get("/status")); + + defineCommand(GET_ALL_SESSIONS, get("/sessions")); + defineCommand(NEW_SESSION, post("/session")); + defineCommand(GET_CAPABILITIES, get("/session/:sessionId")); + defineCommand(QUIT, delete("/session/:sessionId")); + + defineCommand(GET_SESSION_LOGS, post("/logs")); + defineCommand(GET_LOG, post("/session/:sessionId/log")); + defineCommand(GET_AVAILABLE_LOG_TYPES, get("/session/:sessionId/log/types")); + + defineCommand(SWITCH_TO_FRAME, post("/session/:sessionId/frame")); + defineCommand(SWITCH_TO_PARENT_FRAME, post("/session/:sessionId/frame/parent")); + defineCommand(CLOSE, delete("/session/:sessionId/window")); + defineCommand(SWITCH_TO_WINDOW, post("/session/:sessionId/window")); + defineCommand(MAXIMIZE_WINDOW, post("/session/:sessionId/window/:windowHandle/maximize")); + defineCommand(GET_WINDOW_SIZE, get("/session/:sessionId/window/:windowHandle/size")); + defineCommand(SET_WINDOW_SIZE, post("/session/:sessionId/window/:windowHandle/size")); + defineCommand(GET_WINDOW_POSITION, get("/session/:sessionId/window/:windowHandle/position")); + defineCommand(SET_WINDOW_POSITION, post("/session/:sessionId/window/:windowHandle/position")); + defineCommand(GET_CURRENT_WINDOW_HANDLE, get("/session/:sessionId/window_handle")); + defineCommand(GET_WINDOW_HANDLES, get("/session/:sessionId/window_handles")); + + defineCommand(GET_CURRENT_URL, get("/session/:sessionId/url")); + defineCommand(GET, post("/session/:sessionId/url")); + defineCommand(GO_BACK, post("/session/:sessionId/back")); + defineCommand(GO_FORWARD, post("/session/:sessionId/forward")); + defineCommand(REFRESH, post("/session/:sessionId/refresh")); + + defineCommand(ACCEPT_ALERT, post("/session/:sessionId/accept_alert")); + defineCommand(DISMISS_ALERT, post("/session/:sessionId/dismiss_alert")); + defineCommand(GET_ALERT_TEXT, get("/session/:sessionId/alert_text")); + defineCommand(SET_ALERT_VALUE, post("/session/:sessionId/alert_text")); + + defineCommand(EXECUTE_SCRIPT, post("/session/:sessionId/execute")); + defineCommand(EXECUTE_ASYNC_SCRIPT, post("/session/:sessionId/execute_async")); + defineCommand(EXECUTE_SQL, post("/session/:sessionId/execute_sql")); + + defineCommand(UPLOAD_FILE, post("/session/:sessionId/file")); + defineCommand(SCREENSHOT, get("/session/:sessionId/screenshot")); + defineCommand(GET_PAGE_SOURCE, get("/session/:sessionId/source")); + defineCommand(GET_TITLE, get("/session/:sessionId/title")); + + defineCommand(FIND_ELEMENT, post("/session/:sessionId/element")); + defineCommand(FIND_ELEMENTS, post("/session/:sessionId/elements")); + defineCommand(GET_ACTIVE_ELEMENT, post("/session/:sessionId/element/active")); + defineCommand(GET_ELEMENT_ATTRIBUTE, get("/session/:sessionId/element/:id/attribute/:name")); + defineCommand(CLICK_ELEMENT, post("/session/:sessionId/element/:id/click")); + defineCommand(CLEAR_ELEMENT, post("/session/:sessionId/element/:id/clear")); + defineCommand( + GET_ELEMENT_VALUE_OF_CSS_PROPERTY, + get("/session/:sessionId/element/:id/css/:propertyName")); + defineCommand(IS_ELEMENT_DISPLAYED, get("/session/:sessionId/element/:id/displayed")); + defineCommand(FIND_CHILD_ELEMENT, post("/session/:sessionId/element/:id/element")); + defineCommand(FIND_CHILD_ELEMENTS, post("/session/:sessionId/element/:id/elements")); + defineCommand(IS_ELEMENT_ENABLED, get("/session/:sessionId/element/:id/enabled")); + defineCommand(ELEMENT_EQUALS, get("/session/:sessionId/element/:id/equals/:other")); + defineCommand(GET_ELEMENT_LOCATION, get("/session/:sessionId/element/:id/location")); + defineCommand( + GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW, + get("/session/:sessionId/element/:id/location_in_view")); + defineCommand(GET_ELEMENT_TAG_NAME, get("/session/:sessionId/element/:id/name")); + defineCommand(IS_ELEMENT_SELECTED, get("/session/:sessionId/element/:id/selected")); + defineCommand(GET_ELEMENT_SIZE, get("/session/:sessionId/element/:id/size")); + defineCommand(SUBMIT_ELEMENT, post("/session/:sessionId/element/:id/submit")); + defineCommand(GET_ELEMENT_TEXT, get("/session/:sessionId/element/:id/text")); + defineCommand(SEND_KEYS_TO_ELEMENT, post("/session/:sessionId/element/:id/value")); + + defineCommand(GET_ALL_COOKIES, get("/session/:sessionId/cookie")); + defineCommand(ADD_COOKIE, post("/session/:sessionId/cookie")); + defineCommand(DELETE_ALL_COOKIES, delete("/session/:sessionId/cookie")); + defineCommand(DELETE_COOKIE, delete("/session/:sessionId/cookie/:name")); + + defineCommand(SET_TIMEOUT, post("/session/:sessionId/timeouts")); + defineCommand(SET_SCRIPT_TIMEOUT, post("/session/:sessionId/timeouts/async_script")); + defineCommand(IMPLICITLY_WAIT, post("/session/:sessionId/timeouts/implicit_wait")); + + defineCommand(GET_APP_CACHE_STATUS, get("/session/:sessionId/application_cache/status")); + defineCommand(IS_BROWSER_ONLINE, get("/session/:sessionId/browser_connection")); + defineCommand(SET_BROWSER_ONLINE, post("/session/:sessionId/browser_connection")); + defineCommand(GET_LOCATION, get("/session/:sessionId/location")); + defineCommand(SET_LOCATION, post("/session/:sessionId/location")); + + defineCommand(CLEAR_LOCAL_STORAGE, delete("/session/:sessionId/local_storage")); + defineCommand(GET_LOCAL_STORAGE_KEYS, get("/session/:sessionId/local_storage")); + defineCommand(SET_LOCAL_STORAGE_ITEM, post("/session/:sessionId/local_storage")); + defineCommand(REMOVE_LOCAL_STORAGE_ITEM, delete("/session/:sessionId/local_storage/key/:key")); + defineCommand(GET_LOCAL_STORAGE_ITEM, get("/session/:sessionId/local_storage/key/:key")); + defineCommand(GET_LOCAL_STORAGE_SIZE, get("/session/:sessionId/local_storage/size")); + + defineCommand(CLEAR_SESSION_STORAGE, delete("/session/:sessionId/session_storage")); + defineCommand(GET_SESSION_STORAGE_KEYS, get("/session/:sessionId/session_storage")); + defineCommand(SET_SESSION_STORAGE_ITEM, post("/session/:sessionId/session_storage")); + defineCommand( + REMOVE_SESSION_STORAGE_ITEM, delete("/session/:sessionId/session_storage/key/:key")); + defineCommand(GET_SESSION_STORAGE_ITEM, get("/session/:sessionId/session_storage/key/:key")); + defineCommand(GET_SESSION_STORAGE_SIZE, get("/session/:sessionId/session_storage/size")); + + defineCommand(GET_SCREEN_ORIENTATION, get("/session/:sessionId/orientation")); + defineCommand(SET_SCREEN_ORIENTATION, post("/session/:sessionId/orientation")); + + // Interactions-related commands. + defineCommand(MOUSE_DOWN, post("/session/:sessionId/buttondown")); + defineCommand(MOUSE_UP, post("/session/:sessionId/buttonup")); + defineCommand(CLICK, post("/session/:sessionId/click")); + defineCommand(DOUBLE_CLICK, post("/session/:sessionId/doubleclick")); + defineCommand(MOVE_TO, post("/session/:sessionId/moveto")); + defineCommand(SEND_KEYS_TO_ACTIVE_ELEMENT, post("/session/:sessionId/keys")); + defineCommand(TOUCH_SINGLE_TAP, post("/session/:sessionId/touch/click")); + defineCommand(TOUCH_DOUBLE_TAP, post("/session/:sessionId/touch/doubleclick")); + defineCommand(TOUCH_DOWN, post("/session/:sessionId/touch/down")); + defineCommand(TOUCH_FLICK, post("/session/:sessionId/touch/flick")); + defineCommand(TOUCH_LONG_PRESS, post("/session/:sessionId/touch/longclick")); + defineCommand(TOUCH_MOVE, post("/session/:sessionId/touch/move")); + defineCommand(TOUCH_SCROLL, post("/session/:sessionId/touch/scroll")); + defineCommand(TOUCH_UP, post("/session/:sessionId/touch/up")); + + defineCommand(IME_GET_AVAILABLE_ENGINES, get("/session/:sessionId/ime/available_engines")); + defineCommand(IME_GET_ACTIVE_ENGINE, get("/session/:sessionId/ime/active_engine")); + defineCommand(IME_IS_ACTIVATED, get("/session/:sessionId/ime/activated")); + defineCommand(IME_DEACTIVATE, post("/session/:sessionId/ime/deactivate")); + defineCommand(IME_ACTIVATE_ENGINE, post("/session/:sessionId/ime/activate")); + + // Mobile Spec + // https://code.google.com/p/selenium/source/browse/spec-draft.md?repo=mobile + defineCommand(GET_NETWORK_CONNECTION, get("/session/:sessionId/network_connection")); + defineCommand(SET_NETWORK_CONNECTION, post("/session/:sessionId/network_connection")); + } + + @Override + public HttpRequest encode(Command command) { + CommandSpec spec = nameToSpec.get(command.getName()); + if (spec == null) { + throw new UnsupportedCommandException(command.getName()); + } + String uri = buildUri(command, spec); + + HttpRequest request = new HttpRequest(spec.method, uri); + + if (HttpMethod.POST == spec.method) { + String content = beanToJsonConverter.convert(command.getParameters()); + byte[] data = content.getBytes(UTF_8); + + request.setHeader(CONTENT_LENGTH, String.valueOf(data.length)); + request.setHeader(CONTENT_TYPE, JSON_UTF_8.toString()); + request.setContent(data); + } + + if (HttpMethod.GET == spec.method) { + request.setHeader(CACHE_CONTROL, "no-cache"); + } + + return request; + } + + @Override + public Command decode(final HttpRequest encodedCommand) { + final String path = encodedCommand.getUri(); + final ImmutableList parts = ImmutableList.copyOf(PATH_SPLITTER.split(path)); + List matchingSpecs = FluentIterable.from(nameToSpec.inverse().keySet()) + .filter(new Predicate() { + @Override + public boolean apply(CommandSpec spec) { + return spec.isFor(encodedCommand.getMethod(), parts); + } + }) + .toSortedList(new Comparator() { + @Override + public int compare(CommandSpec a, CommandSpec b) { + return a.pathSegments.size() - b.pathSegments.size(); + } + }); + + if (matchingSpecs.isEmpty()) { + throw new UnsupportedCommandException( + encodedCommand.getMethod() + " " + encodedCommand.getUri()); + } + CommandSpec spec = matchingSpecs.get(0); + + Map parameters = Maps.newHashMap(); + spec.parsePathParameters(parts, parameters); + + String content = encodedCommand.getContentString(); + if (!content.isEmpty()) { + @SuppressWarnings("unchecked") + HashMap tmp = jsonToBeanConverter.convert(HashMap.class, content); + parameters.putAll(tmp); + } + + String name = nameToSpec.inverse().get(spec); + SessionId sessionId = null; + if (parameters.containsKey(SESSION_ID_PARAM)) { + sessionId = new SessionId((String) parameters.remove(SESSION_ID_PARAM)); + } + + return new Command(sessionId, name, parameters); + } + + /** + * Defines a new command mapping. + * + * @param name The command name. + * @param method The HTTP method to use for the command. + * @param pathPattern The URI path pattern for the command. When encoding a command, each + * path segment prefixed with a ":" will be replaced with the corresponding parameter + * from the encoded command. + */ + public void defineCommand(String name, HttpMethod method, String pathPattern) { + defineCommand(name, new CommandSpec(method, pathPattern)); + } + + private void defineCommand(String name, CommandSpec spec) { + checkNotNull(name, "null name"); + nameToSpec.put(name, spec); + } + + private static CommandSpec delete(String path) { + return new CommandSpec(HttpMethod.DELETE, path); + } + + private static CommandSpec get(String path) { + return new CommandSpec(HttpMethod.GET, path); + } + + private static CommandSpec post(String path) { + return new CommandSpec(HttpMethod.POST, path); + } + + private String buildUri(Command command, CommandSpec spec) { + StringBuilder builder = new StringBuilder(); + for (String part : spec.pathSegments) { + if (part.isEmpty()) { + continue; + } + + builder.append("/"); + if (part.startsWith(":")) { + builder.append(getParameter(part.substring(1), command)); + } else { + builder.append(part); + } + } + return builder.toString(); + } + + private String getParameter(String parameterName, Command command) { + if ("sessionId".equals(parameterName)) { + SessionId id = command.getSessionId(); + checkArgument(id != null, "Session ID may not be null for command %s", command.getName()); + return id.toString(); + } + + Object value = command.getParameters().get(parameterName); + checkArgument(value != null, + "Missing required parameter \"%s\" for command %s", parameterName, command.getName()); + return Urls.urlEncode(String.valueOf(value)); + } + + private static class CommandSpec { + private final HttpMethod method; + private final String path; + private final ImmutableList pathSegments; + + private CommandSpec(HttpMethod method, String path) { + this.method = checkNotNull(method, "null method"); + this.path = path; + this.pathSegments = ImmutableList.copyOf(PATH_SPLITTER.split(path)); + } + + @Override + public boolean equals(Object o) { + if (o instanceof CommandSpec) { + CommandSpec that = (CommandSpec) o; + return this.method.equals(that.method) + && this.path.equals(that.path); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(method, path); + } + + /** + * Returns whether this instance matches the provided HTTP request. + * + * @param method The request method. + * @param parts The parsed request path segments. + * @return Whether this instance matches the request. + */ + boolean isFor(HttpMethod method, ImmutableList parts) { + if (!this.method.equals(method)) { + return false; + } + + if (parts.size() != this.pathSegments.size()) { + return false; + } + + for (int i = 0; i < parts.size(); ++i) { + String reqPart = parts.get(i); + String specPart = pathSegments.get(i); + if (!(specPart.startsWith(":") || specPart.equals(reqPart))) { + return false; + } + } + + return true; + } + + void parsePathParameters(ImmutableList parts, Map parameters) { + for (int i = 0; i < parts.size(); ++i) { + if (pathSegments.get(i).startsWith(":")) { + parameters.put(pathSegments.get(i).substring(1), parts.get(i)); + } + } + } + } +} diff --git a/java/client/src/org/openqa/selenium/remote/codec/http/JsonHttpResponseCodec.java b/java/client/src/org/openqa/selenium/remote/codec/http/JsonHttpResponseCodec.java new file mode 100644 index 0000000000000..c3dc4680a1727 --- /dev/null +++ b/java/client/src/org/openqa/selenium/remote/codec/http/JsonHttpResponseCodec.java @@ -0,0 +1,104 @@ +package org.openqa.selenium.remote.codec.http; + +import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.base.Strings.nullToEmpty; +import static com.google.common.net.HttpHeaders.CACHE_CONTROL; +import static com.google.common.net.HttpHeaders.CONTENT_LENGTH; +import static com.google.common.net.HttpHeaders.CONTENT_TYPE; +import static com.google.common.net.HttpHeaders.EXPIRES; +import static com.google.common.net.MediaType.JSON_UTF_8; + +import org.openqa.selenium.remote.BeanToJsonConverter; +import org.openqa.selenium.remote.ErrorCodes; +import org.openqa.selenium.remote.JsonException; +import org.openqa.selenium.remote.JsonToBeanConverter; +import org.openqa.selenium.remote.Response; +import org.openqa.selenium.remote.codec.ResponseCodec; + +/** + * A response codec that adheres to the Selenium project's JSON/HTTP wire protocol. + * + * @see + * JSON wire protocol + */ +public class JsonHttpResponseCodec implements ResponseCodec { + + private final ErrorCodes errorCodes = new ErrorCodes(); + private final BeanToJsonConverter beanToJsonConverter = new BeanToJsonConverter(); + private final JsonToBeanConverter jsonToBeanConverter = new JsonToBeanConverter(); + + /** + * Encodes the given response as a HTTP response message. This method is guaranteed not to throw. + * + * @param response The response to encode. + * @return The encoded response. + */ + @Override + public HttpResponse encode(Response response) { + int status = response.getStatus() == ErrorCodes.SUCCESS + ? HttpStatusCodes.OK + : HttpStatusCodes.INTERNAL_SERVER_ERROR; + + byte[] data = beanToJsonConverter.convert(response).getBytes(UTF_8); + + HttpResponse httpResponse = new HttpResponse(); + httpResponse.setStatus(status); + httpResponse.setHeader(CACHE_CONTROL, "no-cache"); + httpResponse.setHeader(EXPIRES, "Thu, 01 Jan 1970 00:00:00 GMT"); + httpResponse.setHeader(CONTENT_LENGTH, String.valueOf(data.length)); + httpResponse.setHeader(CONTENT_TYPE, JSON_UTF_8.toString()); + httpResponse.setContent(data); + + return httpResponse; + } + + @Override + public Response decode(HttpResponse encodedResponse) { + String contentType = nullToEmpty(encodedResponse.getHeader(CONTENT_TYPE)); + String content = encodedResponse.getContentString(); + try { + return jsonToBeanConverter.convert(Response.class, content); + } catch (JsonException e) { + if (contentType.startsWith("application/json")) { + throw new IllegalArgumentException( + "Cannot decode response content: " + content, e); + } + } catch (ClassCastException e) { + if (contentType.startsWith("application/json")) { + if (content.isEmpty()) { + // The remote server has died, but has already set some headers. + // Normally this occurs when the final window of the firefox driver + // is closed on OS X. Return null, as the return value _should_ be + // being ignored. This is not an elegant solution. + return new Response(); + } + throw new IllegalArgumentException( + "Cannot decode response content: " + content, e); + } + } + + Response response = new Response(); + int statusCode = encodedResponse.getStatus(); + if (statusCode < 200 || statusCode > 299) { + // 4xx represents an unknown command or a bad request. + if (statusCode > 399 && statusCode < 500) { + response.setStatus(ErrorCodes.UNKNOWN_COMMAND); + } else { + response.setStatus(ErrorCodes.UNHANDLED_ERROR); + } + } + + if (encodedResponse.getContent().length > 0) { + response.setValue(content); + } + + if (response.getValue() instanceof String) { + // We normalise to \n because Java will translate this to \r\n + // if this is suitable on our platform, and if we have \r\n, java will + // turn this into \r\r\n, which would be Bad! + response.setValue(((String) response.getValue()).replace("\r\n", "\n")); + } + response.setState(errorCodes.toState(response.getStatus())); + return response; + } +} diff --git a/java/client/test/org/openqa/selenium/remote/RemoteCommonTests.java b/java/client/test/org/openqa/selenium/remote/RemoteCommonTests.java index 9cfe0e4206dca..55f9cb9169d7c 100644 --- a/java/client/test/org/openqa/selenium/remote/RemoteCommonTests.java +++ b/java/client/test/org/openqa/selenium/remote/RemoteCommonTests.java @@ -20,12 +20,16 @@ import org.junit.runner.RunWith; import org.junit.runners.Suite; +import org.openqa.selenium.remote.codec.http.JsonHttpCommandCodecTest; +import org.openqa.selenium.remote.codec.http.JsonHttpResponseCodecTest; @RunWith(Suite.class) @Suite.SuiteClasses({ BeanToJsonConverterTest.class, DesiredCapabilitiesTest.class, - JsonToBeanConverterTest.class + JsonToBeanConverterTest.class, + JsonHttpCommandCodecTest.class, + JsonHttpResponseCodecTest.class }) public class RemoteCommonTests { } diff --git a/java/client/test/org/openqa/selenium/remote/build.desc b/java/client/test/org/openqa/selenium/remote/build.desc index 80e7113b4358c..c5d36150f8874 100644 --- a/java/client/test/org/openqa/selenium/remote/build.desc +++ b/java/client/test/org/openqa/selenium/remote/build.desc @@ -6,10 +6,13 @@ java_test(name = "common-tests", "HttpRequest.java", "JsonToBeanConverterTest.java", "RemoteCommonTests.java", + "codec/http/JsonHttpCommandCodecTest.java", + "codec/http/JsonHttpResponseCodecTest.java", ], deps = [ "//java/client/src/org/openqa/selenium/remote", "//java/client/src/org/openqa/selenium/remote:common", + "//java/client/src/org/openqa/selenium/remote/codec", "//java/client/test/org/openqa/selenium:tests", "//third_party/java/apache-httpclient", "//third_party/java/junit", diff --git a/java/client/test/org/openqa/selenium/remote/codec/http/JsonHttpCommandCodecTest.java b/java/client/test/org/openqa/selenium/remote/codec/http/JsonHttpCommandCodecTest.java new file mode 100644 index 0000000000000..cfd7a7f73b45c --- /dev/null +++ b/java/client/test/org/openqa/selenium/remote/codec/http/JsonHttpCommandCodecTest.java @@ -0,0 +1,256 @@ +package org.openqa.selenium.remote.codec.http; + +import static com.google.common.base.Charsets.UTF_16; +import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.net.HttpHeaders.CACHE_CONTROL; +import static com.google.common.net.HttpHeaders.CONTENT_LENGTH; +import static com.google.common.net.HttpHeaders.CONTENT_TYPE; +import static com.google.common.net.MediaType.JSON_UTF_8; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.openqa.selenium.remote.codec.http.HttpMethod.GET; +import static org.openqa.selenium.remote.codec.http.HttpMethod.DELETE; +import static org.openqa.selenium.remote.codec.http.HttpMethod.POST; + +import com.google.common.collect.ImmutableMap; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.openqa.selenium.UnsupportedCommandException; +import org.openqa.selenium.remote.Command; +import org.openqa.selenium.remote.SessionId; + +import java.net.URISyntaxException; +import java.util.Map; + +/** + * Tests for {@link JsonHttpCommandCodec}. + */ +@RunWith(JUnit4.class) +public class JsonHttpCommandCodecTest { + + private final JsonHttpCommandCodec codec = new JsonHttpCommandCodec(); + + @Test + public void throwsIfCommandNameIsNotRecognized() { + Command command = new Command(null, "garbage-command-name"); + try { + codec.encode(command); + fail(); + } catch (UnsupportedCommandException expected) { + assertThat(expected.getMessage(), startsWith(command.getName() + "\n")); + } + } + + @Test + public void throwsIfCommandHasNullSessionId() { + codec.defineCommand("foo", DELETE, "/foo/:sessionId"); + Command command = new Command(null, "foo"); + try { + codec.encode(command); + fail(); + } catch (IllegalArgumentException expected) { + assertThat(expected.getMessage(), containsString("Session ID")); + } + } + + @Test + public void throwsIfCommandIsMissingUriParameter() { + codec.defineCommand("foo", DELETE, "/foo/:bar"); + Command command = new Command(new SessionId("id"), "foo"); + try { + codec.encode(command); + fail(); + } catch (IllegalArgumentException expected) { + assertThat(expected.getMessage(), containsString("bar")); + } + } + + @Test + public void encodingAPostWithNoParameters() { + codec.defineCommand("foo", POST, "/foo/bar"); + Command command = new Command(null, "foo"); + + HttpRequest request = codec.encode(command); + assertThat(request.getMethod(), is(POST)); + assertThat(request.getHeader(CONTENT_TYPE), is(JSON_UTF_8.toString())); + assertThat(request.getHeader(CONTENT_LENGTH), is("2")); + assertThat(request.getUri(), is("/foo/bar")); + assertThat(new String(request.getContent(), UTF_8), is("{}")); + } + + @Test + public void encodingAPostWithUrlParameters() { + codec.defineCommand("foo", POST, "/foo/:bar/baz"); + Command command = new Command(null, "foo", ImmutableMap.of("bar", "apples123")); + + String encoding = "{\"bar\":\"apples123\"}"; + + HttpRequest request = codec.encode(command); + assertThat(request.getMethod(), is(POST)); + assertThat(request.getHeader(CONTENT_TYPE), is(JSON_UTF_8.toString())); + assertThat(request.getHeader(CONTENT_LENGTH), is(String.valueOf(encoding.length()))); + assertThat(request.getUri(), is("/foo/apples123/baz")); + assertThat(new String(request.getContent(), UTF_8), is(encoding)); + } + + @Test + public void encodingANonPostWithNoParameters() { + codec.defineCommand("foo", DELETE, "/foo/bar/baz"); + HttpRequest request = codec.encode(new Command(null, "foo")); + assertThat(request.getMethod(), is(DELETE)); + assertThat(request.getHeader(CONTENT_TYPE), is(nullValue())); + assertThat(request.getHeader(CONTENT_LENGTH), is(nullValue())); + assertThat(request.getContent().length, is(0)); + assertThat(request.getUri(), is("/foo/bar/baz")); + } + + @Test + public void encodingANonPostWithParameters() { + codec.defineCommand("eat", GET, "/fruit/:fruit/:size"); + HttpRequest request = codec.encode(new Command(null, "eat", ImmutableMap.of( + "fruit", "apple", "size", "large"))); + assertThat(request.getHeader(CONTENT_TYPE), is(nullValue())); + assertThat(request.getHeader(CONTENT_LENGTH), is(nullValue())); + assertThat(request.getContent().length, is(0)); + assertThat(request.getUri(), is("/fruit/apple/large")); + } + + @Test + public void preventsCachingGetRequests() { + codec.defineCommand("foo", GET, "/foo"); + HttpRequest request = codec.encode(new Command(null, "foo")); + assertThat(request.getMethod(), is(GET)); + assertThat(request.getHeader(CACHE_CONTROL), is("no-cache")); + } + + @Test + public void throwsIfEncodedCommandHasNoMapping() throws URISyntaxException { + HttpRequest request = new HttpRequest(GET, "/foo/bar/baz"); + try { + codec.decode(request); + fail(); + } catch (UnsupportedCommandException expected) { + assertThat(expected.getMessage(), startsWith("GET /foo/bar/baz\n")); + } + } + + @Test + public void canDecodeCommandWithNoParameters() throws URISyntaxException { + HttpRequest request = new HttpRequest(GET, "/foo/bar/baz"); + codec.defineCommand("foo", GET, "/foo/bar/baz"); + + Command decoded = codec.decode(request); + assertThat(decoded.getName(), is("foo")); + assertThat(decoded.getSessionId(), is(nullValue())); + assertThat(decoded.getParameters().isEmpty(), is(true)); + } + + @Test + public void canExtractSessionIdFromPathParameters() throws URISyntaxException { + HttpRequest request = new HttpRequest(GET, "/foo/bar/baz"); + codec.defineCommand("foo", GET, "/foo/:sessionId/baz"); + + Command decoded = codec.decode(request); + assertThat(decoded.getSessionId(), is(new SessionId("bar"))); + } + + @Test + public void removesSessionIdFromParameterMap() throws URISyntaxException { + HttpRequest request = new HttpRequest(GET, "/foo/bar/baz"); + codec.defineCommand("foo", GET, "/foo/:sessionId/baz"); + + Command decoded = codec.decode(request); + assertThat(decoded.getSessionId(), is(new SessionId("bar"))); + assertThat(decoded.getParameters().isEmpty(), is(true)); + } + + @Test + public void canExtractSessionIdFromRequestBody() throws JSONException, URISyntaxException { + String data = new JSONObject().put("sessionId", "sessionX").toString(); + HttpRequest request = new HttpRequest(POST, "/foo/bar/baz"); + request.setContent(data.getBytes(UTF_8)); + codec.defineCommand("foo", POST, "/foo/bar/baz"); + + Command decoded = codec.decode(request); + assertThat(decoded.getSessionId(), is(new SessionId("sessionX"))); + } + + @Test + public void extractsAllParametersFromUrl() throws URISyntaxException { + HttpRequest request = new HttpRequest(GET, "/fruit/apple/size/large"); + codec.defineCommand("pick", GET, "/fruit/:fruit/size/:size"); + + Command decoded = codec.decode(request); + assertThat(decoded.getParameters(), is((Map) ImmutableMap.of( + "fruit", "apple", + "size", "large"))); + } + + @Test + public void extractsAllPrameters() throws JSONException, URISyntaxException { + String data = new JSONObject() + .put("sessionId", "sessionX") + .put("fruit", "apple") + .put("color", "red") + .put("size", "large") + .toString(); + + HttpRequest request = new HttpRequest(POST, "/fruit/apple/size/large"); + request.setContent(data.getBytes(UTF_8)); + codec.defineCommand("pick", POST, "/fruit/:fruit/size/:size"); + + Command decoded = codec.decode(request); + assertThat(decoded.getParameters(), is((Map) ImmutableMap.of( + "fruit", "apple", "size", "large", "color", "red"))); + } + + @Test + public void decodeRequestWithUtf16Encoding() { + codec.defineCommand("num", POST, "/one"); + + byte[] data = "{\"char\":\"水\"}".getBytes(UTF_16); + HttpRequest request = new HttpRequest(POST, "/one"); + request.setHeader(CONTENT_TYPE, JSON_UTF_8.withCharset(UTF_16).toString()); + request.setHeader(CONTENT_LENGTH, String.valueOf(data.length)); + request.setContent(data); + + Command command = codec.decode(request); + assertThat((String) command.getParameters().get("char"), is("水")); + } + + @Test + public void decodingUsesUtf8IfNoEncodingSpecified() { + codec.defineCommand("num", POST, "/one"); + + byte[] data = "{\"char\":\"水\"}".getBytes(UTF_8); + HttpRequest request = new HttpRequest(POST, "/one"); + request.setHeader(CONTENT_TYPE, JSON_UTF_8.withoutParameters().toString()); + request.setHeader(CONTENT_LENGTH, String.valueOf(data.length)); + request.setContent(data); + + Command command = codec.decode(request); + assertThat((String) command.getParameters().get("char"), is("水")); + } + + @Test + public void codecRoundTrip() { + codec.defineCommand("buy", POST, "/:sessionId/fruit/:fruit/size/:size"); + + Command original = new Command(new SessionId("session123"), "buy", ImmutableMap.of( + "fruit", "apple", "size", "large", "color", "red", "rotten", "false")); + HttpRequest request = codec.encode(original); + Command decoded = codec.decode(request); + + assertThat(decoded.getName(), is(original.getName())); + assertThat(decoded.getSessionId(), is(original.getSessionId())); + assertThat(decoded.getParameters(), is((Map) original.getParameters())); + } +} diff --git a/java/client/test/org/openqa/selenium/remote/codec/http/JsonHttpResponseCodecTest.java b/java/client/test/org/openqa/selenium/remote/codec/http/JsonHttpResponseCodecTest.java new file mode 100644 index 0000000000000..6baaad307656d --- /dev/null +++ b/java/client/test/org/openqa/selenium/remote/codec/http/JsonHttpResponseCodecTest.java @@ -0,0 +1,153 @@ +package org.openqa.selenium.remote.codec.http; + +import static com.google.common.base.Charsets.UTF_16; +import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.net.HttpHeaders.CONTENT_TYPE; +import static com.google.common.net.MediaType.JSON_UTF_8; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import com.google.common.collect.ImmutableMap; + +import org.json.JSONException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.openqa.selenium.remote.BeanToJsonConverter; +import org.openqa.selenium.remote.ErrorCodes; +import org.openqa.selenium.remote.JsonToBeanConverter; +import org.openqa.selenium.remote.Response; + +/** + * Tests for {@link JsonHttpResponseCodec}. + */ +@RunWith(JUnit4.class) +public class JsonHttpResponseCodecTest { + + private final JsonHttpResponseCodec codec = new JsonHttpResponseCodec(); + + @Test + public void convertsResponses_success() throws JSONException { + Response response = new Response(); + response.setStatus(ErrorCodes.SUCCESS); + response.setValue(ImmutableMap.of("color", "red")); + + HttpResponse converted = codec.encode(response); + assertThat(converted.getStatus(), is(HttpStatusCodes.OK)); + assertThat(converted.getHeader(CONTENT_TYPE), is(JSON_UTF_8.toString())); + + Response rebuilt = new JsonToBeanConverter().convert( + Response.class, new String(converted.getContent(), UTF_8)); + + assertEquals(response.getStatus(), rebuilt.getStatus()); + assertEquals(response.getState(), rebuilt.getState()); + assertEquals(response.getSessionId(), rebuilt.getSessionId()); + assertEquals(response.getValue(), rebuilt.getValue()); + } + + @Test + public void convertsResponses_failure() throws JSONException { + Response response = new Response(); + response.setStatus(ErrorCodes.NO_SUCH_ELEMENT); + response.setValue(ImmutableMap.of("color", "red")); + + HttpResponse converted = codec.encode(response); + assertThat(converted.getStatus(), is(HttpStatusCodes.INTERNAL_SERVER_ERROR)); + assertThat(converted.getHeader(CONTENT_TYPE), is(JSON_UTF_8.toString())); + + Response rebuilt = new JsonToBeanConverter().convert( + Response.class, new String(converted.getContent(), UTF_8)); + + assertEquals(response.getStatus(), rebuilt.getStatus()); + assertEquals(response.getState(), rebuilt.getState()); + assertEquals(response.getSessionId(), rebuilt.getSessionId()); + assertEquals(response.getValue(), rebuilt.getValue()); + } + + @Test + public void roundTrip() throws JSONException { + Response response = new Response(); + response.setStatus(ErrorCodes.SUCCESS); + response.setValue(ImmutableMap.of("color", "red")); + + HttpResponse httpResponse = codec.encode(response); + Response decoded = codec.decode(httpResponse); + + assertEquals(response.getStatus(), decoded.getStatus()); + assertEquals(response.getSessionId(), decoded.getSessionId()); + assertEquals(response.getValue(), decoded.getValue()); + } + + @Test + public void decodeNonJsonResponse_200() throws JSONException { + HttpResponse response = new HttpResponse(); + response.setStatus(HttpStatusCodes.OK); + response.setContent("foobar".getBytes(UTF_8)); + + Response decoded = codec.decode(response); + assertEquals(ErrorCodes.SUCCESS, decoded.getStatus()); + assertEquals("foobar", decoded.getValue()); + } + + @Test + public void decodeNonJsonResponse_204() throws JSONException { + HttpResponse response = new HttpResponse(); + response.setStatus(HttpStatusCodes.NO_CONTENT); + + Response decoded = codec.decode(response); + assertEquals(ErrorCodes.SUCCESS, decoded.getStatus()); + assertNull(decoded.getValue()); + } + + @Test + public void decodeNonJsonResponse_4xx() throws JSONException { + HttpResponse response = new HttpResponse(); + response.setStatus(HttpStatusCodes.BAD_REQUEST); + response.setContent("foobar".getBytes(UTF_8)); + + Response decoded = codec.decode(response); + assertEquals(ErrorCodes.UNKNOWN_COMMAND, decoded.getStatus()); + assertEquals("foobar", decoded.getValue()); + } + + @Test + public void decodeNonJsonResponse_5xx() throws JSONException { + HttpResponse response = new HttpResponse(); + response.setStatus(HttpStatusCodes.INTERNAL_SERVER_ERROR); + response.setContent("foobar".getBytes(UTF_8)); + + Response decoded = codec.decode(response); + assertEquals(ErrorCodes.UNHANDLED_ERROR, decoded.getStatus()); + assertEquals("foobar", decoded.getValue()); + } + + @Test + public void decodeJsonResponseMissingContentType() throws JSONException { + Response response = new Response(); + response.setStatus(ErrorCodes.ASYNC_SCRIPT_TIMEOUT); + response.setValue(ImmutableMap.of("color", "red")); + + HttpResponse httpResponse = new HttpResponse(); + httpResponse.setStatus(HttpStatusCodes.OK); + httpResponse.setContent( + new BeanToJsonConverter().convert(response).getBytes(UTF_8)); + + Response decoded = codec.decode(httpResponse); + assertEquals(response.getStatus(), decoded.getStatus()); + assertEquals(response.getSessionId(), decoded.getSessionId()); + assertEquals(response.getValue(), decoded.getValue()); + } + + @Test + public void decodeUtf16EncodedResponse() { + HttpResponse httpResponse = new HttpResponse(); + httpResponse.setStatus(200); + httpResponse.setHeader(CONTENT_TYPE, JSON_UTF_8.withCharset(UTF_16).toString()); + httpResponse.setContent("{\"status\":0,\"value\":\"水\"}".getBytes(UTF_16)); + + Response response = codec.decode(httpResponse); + assertEquals("水", response.getValue()); + } +} diff --git a/java/server/src/org/openqa/selenium/remote/server/DriverServlet.java b/java/server/src/org/openqa/selenium/remote/server/DriverServlet.java index eabd2c5a9925d..c4d25487604ac 100644 --- a/java/server/src/org/openqa/selenium/remote/server/DriverServlet.java +++ b/java/server/src/org/openqa/selenium/remote/server/DriverServlet.java @@ -29,7 +29,9 @@ import com.google.common.net.MediaType; import org.openqa.selenium.logging.LoggingHandler; -import org.openqa.selenium.remote.SessionNotFoundException; +import org.openqa.selenium.remote.codec.http.HttpMethod; +import org.openqa.selenium.remote.codec.http.HttpRequest; +import org.openqa.selenium.remote.codec.http.HttpResponse; import org.openqa.selenium.remote.server.xdrpc.CrossDomainRpc; import org.openqa.selenium.remote.server.xdrpc.CrossDomainRpcLoader; @@ -56,7 +58,7 @@ public class DriverServlet extends HttpServlet { private final Supplier sessionsSupplier; private SessionCleaner sessionCleaner; - private JsonHttpRemoteConfig mappings; + private JsonHttpCommandHandler commandHandler; public DriverServlet() { this.sessionsSupplier = new DriverSessionsSupplier(); @@ -75,7 +77,7 @@ public void init() throws ServletException { logger.addHandler(LoggingHandler.getInstance()); DriverSessions driverSessions = sessionsSupplier.get(); - mappings = new JsonHttpRemoteConfig(driverSessions, logger); + commandHandler = new JsonHttpCommandHandler(driverSessions, logger); long sessionTimeOutInMs = getValueToUseInMs("webdriver.server.session.timeout", 1800); long browserTimeoutInMs = getValueToUseInMs("webdriver.server.browser.timeout", 0); @@ -102,7 +104,7 @@ private long getValueToUseInMs(String propertyName, long defaultValue) { return TimeUnit.SECONDS.toMillis(time); } - @Override + @Override public void destroy() { getLogger().removeHandler(LoggingHandler.getInstance()); if (sessionCleaner != null) { @@ -181,13 +183,13 @@ private void handleCrossDomainRpc( return; } - HttpRequest request = new HttpRequest(rpc.getMethod(), rpc.getPath()); + HttpRequest request = new HttpRequest( + HttpMethod.valueOf(rpc.getMethod()), + rpc.getPath()); request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.JSON_UTF_8.toString()); request.setContent(rpc.getContent()); - HttpResponse response = new HttpResponse(); - - handleRequest(request, response); + HttpResponse response = commandHandler.handleRequest(request); sendResponse(response, servletResponse); } @@ -195,26 +197,15 @@ protected void handleRequest( HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException { HttpRequest request = createInternalRequest(servletRequest); - HttpResponse response = new HttpResponse(); - handleRequest(request, response); + HttpResponse response = commandHandler.handleRequest(request); sendResponse(response, servletResponse); } - private void handleRequest(HttpRequest request, HttpResponse response) throws ServletException { - try { - mappings.handleRequest(request, response); - } catch (SessionNotFoundException e){ - response.setStatus(HttpStatusCodes.NOT_FOUND); - } catch (Exception e) { - log("Fatal, unhandled exception: " + request.getUri() + ": " + e); - throw new ServletException(e); - } - } - private static HttpRequest createInternalRequest(HttpServletRequest servletRequest) throws IOException { HttpRequest request = new HttpRequest( - servletRequest.getMethod(), servletRequest.getPathInfo()); + HttpMethod.valueOf(servletRequest.getMethod().toUpperCase()), + servletRequest.getPathInfo()); @SuppressWarnings("unchecked") Enumeration headerNames = servletRequest.getHeaderNames(); @@ -243,11 +234,12 @@ private static HttpRequest createInternalRequest(HttpServletRequest servletReque return request; } - private static void sendResponse(HttpResponse response, HttpServletResponse servletResponse) + private void sendResponse(HttpResponse response, HttpServletResponse servletResponse) throws IOException { - servletResponse.setStatus(response.getStatus()); for (String name : response.getHeaderNames()) { - servletResponse.setHeader(name, response.getHeader(name)); + for (String value : response.getHeaders(name)) { + servletResponse.addHeader(name, value); + } } OutputStream output = servletResponse.getOutputStream(); output.write(response.getContent()); diff --git a/java/server/src/org/openqa/selenium/remote/server/HttpResponse.java b/java/server/src/org/openqa/selenium/remote/server/HttpResponse.java deleted file mode 100644 index abef13744f907..0000000000000 --- a/java/server/src/org/openqa/selenium/remote/server/HttpResponse.java +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2012 Selenium committers -Copyright 2012 Software Freedom Conservancy - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package org.openqa.selenium.remote.server; - -import java.util.HashMap; -import java.util.Map; - -public class HttpResponse { - - private final Map headers = new HashMap(); - private int status = HttpStatusCodes.OK; - private byte[] data = new byte[0]; - - public int getStatus() { - return status; - } - - public void setStatus(int status) { - this.status = status; - } - - public void setHeader(String name, String value) { - headers.put(name, value); - } - - public Iterable getHeaderNames() { - return headers.keySet(); - } - - public String getHeader(String name) { - return headers.get(name); - } - - public void setContent(byte[] data) { - this.data = data; - } - - public byte[] getContent() { - return data; - } -} diff --git a/java/server/src/org/openqa/selenium/remote/server/JsonHttpCommandHandler.java b/java/server/src/org/openqa/selenium/remote/server/JsonHttpCommandHandler.java new file mode 100644 index 0000000000000..beb0022767bc6 --- /dev/null +++ b/java/server/src/org/openqa/selenium/remote/server/JsonHttpCommandHandler.java @@ -0,0 +1,320 @@ +/* +Copyright 2012 Selenium committers +Copyright 2012 Software Freedom Conservancy + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package org.openqa.selenium.remote.server; + +import static org.openqa.selenium.remote.DriverCommand.*; +import static org.openqa.selenium.remote.codec.http.HttpMethod.POST; + +import org.openqa.selenium.UnsupportedCommandException; +import org.openqa.selenium.remote.Command; +import org.openqa.selenium.remote.ErrorCodes; +import org.openqa.selenium.remote.Response; +import org.openqa.selenium.remote.codec.http.HttpRequest; +import org.openqa.selenium.remote.codec.http.HttpResponse; +import org.openqa.selenium.remote.codec.http.JsonHttpCommandCodec; +import org.openqa.selenium.remote.codec.http.JsonHttpResponseCodec; +import org.openqa.selenium.remote.server.handler.AcceptAlert; +import org.openqa.selenium.remote.server.handler.AddConfig; +import org.openqa.selenium.remote.server.handler.AddCookie; +import org.openqa.selenium.remote.server.handler.CaptureScreenshot; +import org.openqa.selenium.remote.server.handler.ChangeUrl; +import org.openqa.selenium.remote.server.handler.ClearElement; +import org.openqa.selenium.remote.server.handler.ClickElement; +import org.openqa.selenium.remote.server.handler.CloseWindow; +import org.openqa.selenium.remote.server.handler.ConfigureTimeout; +import org.openqa.selenium.remote.server.handler.DeleteCookie; +import org.openqa.selenium.remote.server.handler.DeleteNamedCookie; +import org.openqa.selenium.remote.server.handler.DeleteSession; +import org.openqa.selenium.remote.server.handler.DismissAlert; +import org.openqa.selenium.remote.server.handler.ElementEquality; +import org.openqa.selenium.remote.server.handler.ExecuteAsyncScript; +import org.openqa.selenium.remote.server.handler.ExecuteScript; +import org.openqa.selenium.remote.server.handler.FindActiveElement; +import org.openqa.selenium.remote.server.handler.FindChildElement; +import org.openqa.selenium.remote.server.handler.FindChildElements; +import org.openqa.selenium.remote.server.handler.FindElement; +import org.openqa.selenium.remote.server.handler.FindElements; +import org.openqa.selenium.remote.server.handler.GetAlertText; +import org.openqa.selenium.remote.server.handler.GetAllCookies; +import org.openqa.selenium.remote.server.handler.GetAllSessions; +import org.openqa.selenium.remote.server.handler.GetAllWindowHandles; +import org.openqa.selenium.remote.server.handler.GetAvailableLogTypesHandler; +import org.openqa.selenium.remote.server.handler.GetCssProperty; +import org.openqa.selenium.remote.server.handler.GetCurrentUrl; +import org.openqa.selenium.remote.server.handler.GetCurrentWindowHandle; +import org.openqa.selenium.remote.server.handler.GetElementAttribute; +import org.openqa.selenium.remote.server.handler.GetElementDisplayed; +import org.openqa.selenium.remote.server.handler.GetElementEnabled; +import org.openqa.selenium.remote.server.handler.GetElementLocation; +import org.openqa.selenium.remote.server.handler.GetElementLocationInView; +import org.openqa.selenium.remote.server.handler.GetElementSelected; +import org.openqa.selenium.remote.server.handler.GetElementSize; +import org.openqa.selenium.remote.server.handler.GetElementText; +import org.openqa.selenium.remote.server.handler.GetLogHandler; +import org.openqa.selenium.remote.server.handler.GetPageSource; +import org.openqa.selenium.remote.server.handler.GetScreenOrientation; +import org.openqa.selenium.remote.server.handler.GetSessionCapabilities; +import org.openqa.selenium.remote.server.handler.GetSessionLogsHandler; +import org.openqa.selenium.remote.server.handler.GetTagName; +import org.openqa.selenium.remote.server.handler.GetTitle; +import org.openqa.selenium.remote.server.handler.GetWindowPosition; +import org.openqa.selenium.remote.server.handler.GetWindowSize; +import org.openqa.selenium.remote.server.handler.GoBack; +import org.openqa.selenium.remote.server.handler.GoForward; +import org.openqa.selenium.remote.server.handler.ImeActivateEngine; +import org.openqa.selenium.remote.server.handler.ImeDeactivate; +import org.openqa.selenium.remote.server.handler.ImeGetActiveEngine; +import org.openqa.selenium.remote.server.handler.ImeGetAvailableEngines; +import org.openqa.selenium.remote.server.handler.ImeIsActivated; +import org.openqa.selenium.remote.server.handler.ImplicitlyWait; +import org.openqa.selenium.remote.server.handler.MaximizeWindow; +import org.openqa.selenium.remote.server.handler.NewSession; +import org.openqa.selenium.remote.server.handler.RefreshPage; +import org.openqa.selenium.remote.server.handler.Rotate; +import org.openqa.selenium.remote.server.handler.SendKeys; +import org.openqa.selenium.remote.server.handler.SetAlertText; +import org.openqa.selenium.remote.server.handler.SetScriptTimeout; +import org.openqa.selenium.remote.server.handler.SetWindowPosition; +import org.openqa.selenium.remote.server.handler.SetWindowSize; +import org.openqa.selenium.remote.server.handler.Status; +import org.openqa.selenium.remote.server.handler.SubmitElement; +import org.openqa.selenium.remote.server.handler.SwitchToFrame; +import org.openqa.selenium.remote.server.handler.SwitchToParentFrame; +import org.openqa.selenium.remote.server.handler.SwitchToWindow; +import org.openqa.selenium.remote.server.handler.UploadFile; +import org.openqa.selenium.remote.server.handler.html5.ClearLocalStorage; +import org.openqa.selenium.remote.server.handler.html5.ClearSessionStorage; +import org.openqa.selenium.remote.server.handler.html5.ExecuteSQL; +import org.openqa.selenium.remote.server.handler.html5.GetAppCacheStatus; +import org.openqa.selenium.remote.server.handler.html5.GetLocalStorageItem; +import org.openqa.selenium.remote.server.handler.html5.GetLocalStorageKeys; +import org.openqa.selenium.remote.server.handler.html5.GetLocalStorageSize; +import org.openqa.selenium.remote.server.handler.html5.GetLocationContext; +import org.openqa.selenium.remote.server.handler.html5.GetSessionStorageItem; +import org.openqa.selenium.remote.server.handler.html5.GetSessionStorageKeys; +import org.openqa.selenium.remote.server.handler.html5.GetSessionStorageSize; +import org.openqa.selenium.remote.server.handler.html5.RemoveLocalStorageItem; +import org.openqa.selenium.remote.server.handler.html5.RemoveSessionStorageItem; +import org.openqa.selenium.remote.server.handler.html5.SetLocalStorageItem; +import org.openqa.selenium.remote.server.handler.html5.SetLocationContext; +import org.openqa.selenium.remote.server.handler.html5.SetSessionStorageItem; +import org.openqa.selenium.remote.server.handler.interactions.ClickInSession; +import org.openqa.selenium.remote.server.handler.interactions.DoubleClickInSession; +import org.openqa.selenium.remote.server.handler.interactions.MouseDown; +import org.openqa.selenium.remote.server.handler.interactions.MouseMoveToLocation; +import org.openqa.selenium.remote.server.handler.interactions.MouseUp; +import org.openqa.selenium.remote.server.handler.interactions.SendKeyToActiveElement; +import org.openqa.selenium.remote.server.handler.interactions.touch.DoubleTapOnElement; +import org.openqa.selenium.remote.server.handler.interactions.touch.Down; +import org.openqa.selenium.remote.server.handler.interactions.touch.Flick; +import org.openqa.selenium.remote.server.handler.interactions.touch.LongPressOnElement; +import org.openqa.selenium.remote.server.handler.interactions.touch.Move; +import org.openqa.selenium.remote.server.handler.interactions.touch.Scroll; +import org.openqa.selenium.remote.server.handler.interactions.touch.SingleTapOnElement; +import org.openqa.selenium.remote.server.handler.interactions.touch.Up; +import org.openqa.selenium.remote.server.rest.RestishHandler; +import org.openqa.selenium.remote.server.rest.ResultConfig; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Logger; + +public class JsonHttpCommandHandler { + + private static final String ADD_CONFIG_COMMAND_NAME = "-selenium-add-config"; + + private final DriverSessions sessions; + private final Logger log; + private final JsonHttpCommandCodec commandCodec; + private final JsonHttpResponseCodec responseCodec; + private final Map configs = new LinkedHashMap(); + private final ErrorCodes errorCodes = new ErrorCodes(); + + public JsonHttpCommandHandler(DriverSessions sessions, Logger log) { + this.sessions = sessions; + this.log = log; + this.commandCodec = new JsonHttpCommandCodec(); + this.responseCodec = new JsonHttpResponseCodec(); + setUpMappings(); + } + + public void addNewMapping( + String commandName, Class> implementationClass) { + ResultConfig config = new ResultConfig(commandName, implementationClass, sessions, log); + configs.put(commandName, config); + } + + public HttpResponse handleRequest(HttpRequest request) { + log.fine(String.format("Handling: %s %s", request.getMethod(), request.getUri())); + + Command command = null; + Response response; + try { + command = commandCodec.decode(request); + ResultConfig config = configs.get(command.getName()); + if (config == null) { + throw new UnsupportedCommandException(); + } + response = config.handle(command); + log.fine(String.format("Finished: %s %s", request.getMethod(), request.getUri())); + } catch (Exception e) { + log.fine(String.format("Error on: %s %s", request.getMethod(), request.getUri())); + response = new Response(); + response.setStatus(errorCodes.toStatusCode(e)); + response.setState(errorCodes.toState(response.getStatus())); + response.setValue(e); + + if (command != null && command.getSessionId() != null) { + response.setSessionId(command.getSessionId().toString()); + } + } + return responseCodec.encode(response); + } + + private void setUpMappings() { + commandCodec.defineCommand(ADD_CONFIG_COMMAND_NAME, POST, "/config/drivers"); + addNewMapping(ADD_CONFIG_COMMAND_NAME, AddConfig.class); + + addNewMapping(STATUS, Status.class); + addNewMapping(GET_ALL_SESSIONS, GetAllSessions.class); + addNewMapping(NEW_SESSION, NewSession.class); + addNewMapping(GET_CAPABILITIES, GetSessionCapabilities.class); + addNewMapping(QUIT, DeleteSession.class); + + addNewMapping(GET_CURRENT_WINDOW_HANDLE, GetCurrentWindowHandle.class); + addNewMapping(GET_WINDOW_HANDLES, GetAllWindowHandles.class); + + addNewMapping(DISMISS_ALERT, DismissAlert.class); + addNewMapping(ACCEPT_ALERT, AcceptAlert.class); + addNewMapping(GET_ALERT_TEXT, GetAlertText.class); + addNewMapping(SET_ALERT_VALUE, SetAlertText.class); + + addNewMapping(GET, ChangeUrl.class); + addNewMapping(GET_CURRENT_URL, GetCurrentUrl.class); + addNewMapping(GO_FORWARD, GoForward.class); + addNewMapping(GO_BACK, GoBack.class); + addNewMapping(REFRESH, RefreshPage.class); + + addNewMapping(EXECUTE_SCRIPT, ExecuteScript.class); + addNewMapping(EXECUTE_ASYNC_SCRIPT, ExecuteAsyncScript.class); + + addNewMapping(GET_PAGE_SOURCE, GetPageSource.class); + + addNewMapping(SCREENSHOT, CaptureScreenshot.class); + + addNewMapping(GET_TITLE, GetTitle.class); + + addNewMapping(FIND_ELEMENT, FindElement.class); + addNewMapping(FIND_ELEMENTS, FindElements.class); + addNewMapping(GET_ACTIVE_ELEMENT, FindActiveElement.class); + + addNewMapping(FIND_CHILD_ELEMENT, FindChildElement.class); + addNewMapping(FIND_CHILD_ELEMENTS, FindChildElements.class); + + addNewMapping(CLICK_ELEMENT, ClickElement.class); + addNewMapping(GET_ELEMENT_TEXT, GetElementText.class); + addNewMapping(SUBMIT_ELEMENT, SubmitElement.class); + + addNewMapping(UPLOAD_FILE, UploadFile.class); + addNewMapping(SEND_KEYS_TO_ELEMENT, SendKeys.class); + addNewMapping(GET_ELEMENT_TAG_NAME, GetTagName.class); + + addNewMapping(CLEAR_ELEMENT, ClearElement.class); + addNewMapping(IS_ELEMENT_SELECTED, GetElementSelected.class); + addNewMapping(IS_ELEMENT_ENABLED, GetElementEnabled.class); + addNewMapping(IS_ELEMENT_DISPLAYED, GetElementDisplayed.class); + addNewMapping(GET_ELEMENT_LOCATION, GetElementLocation.class); + addNewMapping(GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW, GetElementLocationInView.class); + addNewMapping(GET_ELEMENT_SIZE, GetElementSize.class); + addNewMapping(GET_ELEMENT_VALUE_OF_CSS_PROPERTY, GetCssProperty.class); + + addNewMapping(GET_ELEMENT_ATTRIBUTE, GetElementAttribute.class); + addNewMapping(ELEMENT_EQUALS, ElementEquality.class); + + addNewMapping(GET_ALL_COOKIES, GetAllCookies.class); + addNewMapping(ADD_COOKIE, AddCookie.class); + addNewMapping(DELETE_ALL_COOKIES, DeleteCookie.class); + addNewMapping(DELETE_COOKIE, DeleteNamedCookie.class); + + addNewMapping(SWITCH_TO_FRAME, SwitchToFrame.class); + addNewMapping(SWITCH_TO_PARENT_FRAME, SwitchToParentFrame.class); + addNewMapping(SWITCH_TO_WINDOW, SwitchToWindow.class); + addNewMapping(CLOSE, CloseWindow.class); + + addNewMapping(GET_WINDOW_SIZE, GetWindowSize.class); + addNewMapping(SET_WINDOW_SIZE, SetWindowSize.class); + addNewMapping(GET_WINDOW_POSITION, GetWindowPosition.class); + addNewMapping(SET_WINDOW_POSITION, SetWindowPosition.class); + addNewMapping(MAXIMIZE_WINDOW, MaximizeWindow.class); + + addNewMapping(SET_TIMEOUT, ConfigureTimeout.class); + addNewMapping(IMPLICITLY_WAIT, ImplicitlyWait.class); + addNewMapping(SET_SCRIPT_TIMEOUT, SetScriptTimeout.class); + + addNewMapping(EXECUTE_SQL, ExecuteSQL.class); + + addNewMapping(GET_LOCATION, GetLocationContext.class); + addNewMapping(SET_LOCATION, SetLocationContext.class); + + addNewMapping(GET_APP_CACHE_STATUS, GetAppCacheStatus.class); + + addNewMapping(GET_LOCAL_STORAGE_ITEM, GetLocalStorageItem.class); + addNewMapping(REMOVE_LOCAL_STORAGE_ITEM, RemoveLocalStorageItem.class); + addNewMapping(GET_LOCAL_STORAGE_KEYS, GetLocalStorageKeys.class); + addNewMapping(SET_LOCAL_STORAGE_ITEM, SetLocalStorageItem.class); + addNewMapping(CLEAR_LOCAL_STORAGE, ClearLocalStorage.class); + addNewMapping(GET_LOCAL_STORAGE_SIZE, GetLocalStorageSize.class); + + addNewMapping(GET_SESSION_STORAGE_ITEM, GetSessionStorageItem.class); + addNewMapping(REMOVE_SESSION_STORAGE_ITEM, RemoveSessionStorageItem.class); + addNewMapping(GET_SESSION_STORAGE_KEYS, GetSessionStorageKeys.class); + addNewMapping(SET_SESSION_STORAGE_ITEM, SetSessionStorageItem.class); + addNewMapping(CLEAR_SESSION_STORAGE, ClearSessionStorage.class); + addNewMapping(GET_SESSION_STORAGE_SIZE, GetSessionStorageSize.class); + + addNewMapping(GET_SCREEN_ORIENTATION, GetScreenOrientation.class); + addNewMapping(SET_SCREEN_ORIENTATION, Rotate.class); + + addNewMapping(MOVE_TO, MouseMoveToLocation.class); + addNewMapping(CLICK, ClickInSession.class); + addNewMapping(DOUBLE_CLICK, DoubleClickInSession.class); + addNewMapping(MOUSE_DOWN, MouseDown.class); + addNewMapping(MOUSE_UP, MouseUp.class); + addNewMapping(SEND_KEYS_TO_ACTIVE_ELEMENT, SendKeyToActiveElement.class); + + addNewMapping(IME_GET_AVAILABLE_ENGINES, ImeGetAvailableEngines.class); + addNewMapping(IME_GET_ACTIVE_ENGINE, ImeGetActiveEngine.class); + addNewMapping(IME_IS_ACTIVATED, ImeIsActivated.class); + addNewMapping(IME_DEACTIVATE, ImeDeactivate.class); + addNewMapping(IME_ACTIVATE_ENGINE, ImeActivateEngine.class); + + // Advanced Touch API + addNewMapping(TOUCH_SINGLE_TAP, SingleTapOnElement.class); + addNewMapping(TOUCH_DOWN, Down.class); + addNewMapping(TOUCH_UP, Up.class); + addNewMapping(TOUCH_MOVE, Move.class); + addNewMapping(TOUCH_SCROLL, Scroll.class); + addNewMapping(TOUCH_DOUBLE_TAP, DoubleTapOnElement.class); + addNewMapping(TOUCH_LONG_PRESS, LongPressOnElement.class); + addNewMapping(TOUCH_FLICK, Flick.class); + + addNewMapping(GET_AVAILABLE_LOG_TYPES, GetAvailableLogTypesHandler.class); + addNewMapping(GET_LOG, GetLogHandler.class); + addNewMapping(GET_SESSION_LOGS, GetSessionLogsHandler.class); + } +} diff --git a/java/server/src/org/openqa/selenium/remote/server/JsonHttpRemoteConfig.java b/java/server/src/org/openqa/selenium/remote/server/JsonHttpRemoteConfig.java deleted file mode 100644 index 30ce33e09793c..0000000000000 --- a/java/server/src/org/openqa/selenium/remote/server/JsonHttpRemoteConfig.java +++ /dev/null @@ -1,335 +0,0 @@ -/* -Copyright 2012-2014 Software Freedom Conservancy - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package org.openqa.selenium.remote.server; - -import static org.openqa.selenium.remote.server.HttpStatusCodes.NOT_FOUND; - -import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.remote.server.handler.AcceptAlert; -import org.openqa.selenium.remote.server.handler.AddConfig; -import org.openqa.selenium.remote.server.handler.AddCookie; -import org.openqa.selenium.remote.server.handler.CaptureScreenshot; -import org.openqa.selenium.remote.server.handler.ChangeUrl; -import org.openqa.selenium.remote.server.handler.ClearElement; -import org.openqa.selenium.remote.server.handler.ClickElement; -import org.openqa.selenium.remote.server.handler.CloseWindow; -import org.openqa.selenium.remote.server.handler.ConfigureTimeout; -import org.openqa.selenium.remote.server.handler.DeleteCookie; -import org.openqa.selenium.remote.server.handler.DeleteNamedCookie; -import org.openqa.selenium.remote.server.handler.DeleteSession; -import org.openqa.selenium.remote.server.handler.DescribeElement; -import org.openqa.selenium.remote.server.handler.DismissAlert; -import org.openqa.selenium.remote.server.handler.ElementEquality; -import org.openqa.selenium.remote.server.handler.ExecuteAsyncScript; -import org.openqa.selenium.remote.server.handler.ExecuteScript; -import org.openqa.selenium.remote.server.handler.FindActiveElement; -import org.openqa.selenium.remote.server.handler.FindChildElement; -import org.openqa.selenium.remote.server.handler.FindChildElements; -import org.openqa.selenium.remote.server.handler.FindElement; -import org.openqa.selenium.remote.server.handler.FindElements; -import org.openqa.selenium.remote.server.handler.GetAlertText; -import org.openqa.selenium.remote.server.handler.GetAllCookies; -import org.openqa.selenium.remote.server.handler.GetAllSessions; -import org.openqa.selenium.remote.server.handler.GetAllWindowHandles; -import org.openqa.selenium.remote.server.handler.GetAvailableLogTypesHandler; -import org.openqa.selenium.remote.server.handler.GetCssProperty; -import org.openqa.selenium.remote.server.handler.GetCurrentUrl; -import org.openqa.selenium.remote.server.handler.GetCurrentWindowHandle; -import org.openqa.selenium.remote.server.handler.GetElementAttribute; -import org.openqa.selenium.remote.server.handler.GetElementDisplayed; -import org.openqa.selenium.remote.server.handler.GetElementEnabled; -import org.openqa.selenium.remote.server.handler.GetElementLocation; -import org.openqa.selenium.remote.server.handler.GetElementLocationInView; -import org.openqa.selenium.remote.server.handler.GetElementSelected; -import org.openqa.selenium.remote.server.handler.GetElementSize; -import org.openqa.selenium.remote.server.handler.GetElementText; -import org.openqa.selenium.remote.server.handler.GetElementValue; -import org.openqa.selenium.remote.server.handler.GetLogHandler; -import org.openqa.selenium.remote.server.handler.GetPageSource; -import org.openqa.selenium.remote.server.handler.GetScreenOrientation; -import org.openqa.selenium.remote.server.handler.GetSessionCapabilities; -import org.openqa.selenium.remote.server.handler.GetSessionLogsHandler; -import org.openqa.selenium.remote.server.handler.GetTagName; -import org.openqa.selenium.remote.server.handler.GetTitle; -import org.openqa.selenium.remote.server.handler.GetWindowPosition; -import org.openqa.selenium.remote.server.handler.GetWindowSize; -import org.openqa.selenium.remote.server.handler.GoBack; -import org.openqa.selenium.remote.server.handler.GoForward; -import org.openqa.selenium.remote.server.handler.ImeActivateEngine; -import org.openqa.selenium.remote.server.handler.ImeDeactivate; -import org.openqa.selenium.remote.server.handler.ImeGetActiveEngine; -import org.openqa.selenium.remote.server.handler.ImeGetAvailableEngines; -import org.openqa.selenium.remote.server.handler.ImeIsActivated; -import org.openqa.selenium.remote.server.handler.ImplicitlyWait; -import org.openqa.selenium.remote.server.handler.MaximizeWindow; -import org.openqa.selenium.remote.server.handler.NewSession; -import org.openqa.selenium.remote.server.handler.RefreshPage; -import org.openqa.selenium.remote.server.handler.Rotate; -import org.openqa.selenium.remote.server.handler.SendKeys; -import org.openqa.selenium.remote.server.handler.SetAlertText; -import org.openqa.selenium.remote.server.handler.SetScriptTimeout; -import org.openqa.selenium.remote.server.handler.SetWindowPosition; -import org.openqa.selenium.remote.server.handler.SetWindowSize; -import org.openqa.selenium.remote.server.handler.Status; -import org.openqa.selenium.remote.server.handler.SubmitElement; -import org.openqa.selenium.remote.server.handler.SwitchToFrame; -import org.openqa.selenium.remote.server.handler.SwitchToParentFrame; -import org.openqa.selenium.remote.server.handler.SwitchToWindow; -import org.openqa.selenium.remote.server.handler.UploadFile; -import org.openqa.selenium.remote.server.handler.html5.ClearLocalStorage; -import org.openqa.selenium.remote.server.handler.html5.ClearSessionStorage; -import org.openqa.selenium.remote.server.handler.html5.ExecuteSQL; -import org.openqa.selenium.remote.server.handler.html5.GetAppCacheStatus; -import org.openqa.selenium.remote.server.handler.html5.GetLocalStorageItem; -import org.openqa.selenium.remote.server.handler.html5.GetLocalStorageKeys; -import org.openqa.selenium.remote.server.handler.html5.GetLocalStorageSize; -import org.openqa.selenium.remote.server.handler.html5.GetLocationContext; -import org.openqa.selenium.remote.server.handler.html5.GetSessionStorageItem; -import org.openqa.selenium.remote.server.handler.html5.GetSessionStorageKeys; -import org.openqa.selenium.remote.server.handler.html5.GetSessionStorageSize; -import org.openqa.selenium.remote.server.handler.html5.RemoveLocalStorageItem; -import org.openqa.selenium.remote.server.handler.html5.RemoveSessionStorageItem; -import org.openqa.selenium.remote.server.handler.html5.SetLocalStorageItem; -import org.openqa.selenium.remote.server.handler.html5.SetLocationContext; -import org.openqa.selenium.remote.server.handler.html5.SetSessionStorageItem; -import org.openqa.selenium.remote.server.handler.interactions.ClickInSession; -import org.openqa.selenium.remote.server.handler.interactions.DoubleClickInSession; -import org.openqa.selenium.remote.server.handler.interactions.MouseDown; -import org.openqa.selenium.remote.server.handler.interactions.MouseMoveToLocation; -import org.openqa.selenium.remote.server.handler.interactions.MouseUp; -import org.openqa.selenium.remote.server.handler.interactions.SendKeyToActiveElement; -import org.openqa.selenium.remote.server.handler.interactions.touch.DoubleTapOnElement; -import org.openqa.selenium.remote.server.handler.interactions.touch.Down; -import org.openqa.selenium.remote.server.handler.interactions.touch.Flick; -import org.openqa.selenium.remote.server.handler.interactions.touch.LongPressOnElement; -import org.openqa.selenium.remote.server.handler.interactions.touch.Move; -import org.openqa.selenium.remote.server.handler.interactions.touch.Scroll; -import org.openqa.selenium.remote.server.handler.interactions.touch.SingleTapOnElement; -import org.openqa.selenium.remote.server.handler.interactions.touch.Up; -import org.openqa.selenium.remote.server.rest.RestishHandler; -import org.openqa.selenium.remote.server.rest.ResultConfig; -import org.openqa.selenium.remote.server.rest.UrlMapper; - -import java.util.logging.Logger; - -public class JsonHttpRemoteConfig { - private static final String EXCEPTION = ":exception"; - private static final String RESPONSE = ":response"; - - private UrlMapper getMapper; - private UrlMapper postMapper; - private UrlMapper deleteMapper; - private final Logger log; - - public JsonHttpRemoteConfig(DriverSessions sessions, Logger log) { - this.log = log; - setUpMappings(sessions, log); - } - - public void addNewGetMapping( - String path, Class> implementationClass) { - getMapper.bind(path, implementationClass); - } - - public void addNewPostMapping( - String path, Class> implementationClass) { - postMapper.bind(path, implementationClass); - } - - public void addNewDeleteMapping( - String path, Class> implementationClass) { - deleteMapper.bind(path, implementationClass); - } - - public void handleRequest(HttpRequest request, HttpResponse response) - throws WebDriverException { - try { - UrlMapper mapper = getUrlMapper(request.getMethod()); - if (mapper == null) { - response.setStatus(NOT_FOUND); - return; - } - - ResultConfig config = mapper.getConfig(request.getUri()); - if (config == null) { - response.setStatus(NOT_FOUND); - } else { - config.handle(request.getUri(), request, response); - } - } catch (Exception e) { - log.warning("Fatal, unhandled exception: " + request.getUri() + ": " + e); - throw new WebDriverException(e); - } - } - - private UrlMapper getUrlMapper(String method) { - if ("DELETE".equals(method)) { - return deleteMapper; - } else if ("GET".equals(method)) { - return getMapper; - } else if ("POST".equals(method)) { - return postMapper; - } else { - throw new IllegalArgumentException("Unknown method: " + method); - } - } - - private void setUpMappings(DriverSessions driverSessions, Logger logger) { - getMapper = new UrlMapper(driverSessions, logger); - postMapper = new UrlMapper(driverSessions, logger); - deleteMapper = new UrlMapper(driverSessions, logger); - - postMapper.bind("/config/drivers", AddConfig.class); - - getMapper.bind("/status", Status.class); - - getMapper.bind("/sessions", GetAllSessions.class); - - postMapper.bind("/session", NewSession.class); - getMapper.bind("/session/:sessionId", GetSessionCapabilities.class); - - deleteMapper.bind("/session/:sessionId", DeleteSession.class); - - getMapper.bind("/session/:sessionId/window_handle", GetCurrentWindowHandle.class); - getMapper.bind("/session/:sessionId/window_handles", GetAllWindowHandles.class); - - postMapper.bind("/session/:sessionId/dismiss_alert", DismissAlert.class); - postMapper.bind("/session/:sessionId/accept_alert", AcceptAlert.class); - getMapper.bind("/session/:sessionId/alert_text", GetAlertText.class); - postMapper.bind("/session/:sessionId/alert_text", SetAlertText.class); - - postMapper.bind("/session/:sessionId/url", ChangeUrl.class); - getMapper.bind("/session/:sessionId/url", GetCurrentUrl.class); - - postMapper.bind("/session/:sessionId/forward", GoForward.class); - postMapper.bind("/session/:sessionId/back", GoBack.class); - postMapper.bind("/session/:sessionId/refresh", RefreshPage.class); - - postMapper.bind("/session/:sessionId/execute", ExecuteScript.class); - postMapper.bind("/session/:sessionId/execute_async", ExecuteAsyncScript.class); - - getMapper.bind("/session/:sessionId/source", GetPageSource.class); - - getMapper.bind("/session/:sessionId/screenshot", CaptureScreenshot.class); - - getMapper.bind("/session/:sessionId/title", GetTitle.class); - - postMapper.bind("/session/:sessionId/element", FindElement.class); - getMapper.bind("/session/:sessionId/element/:id", DescribeElement.class); - - postMapper.bind("/session/:sessionId/elements", FindElements.class); - postMapper.bind("/session/:sessionId/element/active", FindActiveElement.class); - - postMapper.bind("/session/:sessionId/element/:id/element", FindChildElement.class); - postMapper.bind("/session/:sessionId/element/:id/elements", FindChildElements.class); - - - postMapper.bind("/session/:sessionId/element/:id/click", ClickElement.class); - getMapper.bind("/session/:sessionId/element/:id/text", GetElementText.class); - postMapper.bind("/session/:sessionId/element/:id/submit", SubmitElement.class); - - postMapper.bind("/session/:sessionId/file", UploadFile.class); - postMapper.bind("/session/:sessionId/element/:id/value", SendKeys.class); - getMapper.bind("/session/:sessionId/element/:id/value", GetElementValue.class); - getMapper.bind("/session/:sessionId/element/:id/name", GetTagName.class); - - postMapper.bind("/session/:sessionId/element/:id/clear", ClearElement.class); - getMapper.bind("/session/:sessionId/element/:id/selected", GetElementSelected.class); - getMapper.bind("/session/:sessionId/element/:id/enabled", GetElementEnabled.class); - getMapper.bind("/session/:sessionId/element/:id/displayed", GetElementDisplayed.class); - getMapper.bind("/session/:sessionId/element/:id/location", GetElementLocation.class); - getMapper.bind("/session/:sessionId/element/:id/location_in_view", - GetElementLocationInView.class); - getMapper.bind("/session/:sessionId/element/:id/size", GetElementSize.class); - getMapper.bind("/session/:sessionId/element/:id/css/:propertyName", GetCssProperty.class); - - getMapper.bind("/session/:sessionId/element/:id/attribute/:name", GetElementAttribute.class); - getMapper.bind("/session/:sessionId/element/:id/equals/:other", ElementEquality.class); - - getMapper.bind("/session/:sessionId/cookie", GetAllCookies.class); - postMapper.bind("/session/:sessionId/cookie", AddCookie.class); - deleteMapper.bind("/session/:sessionId/cookie", DeleteCookie.class); - deleteMapper.bind("/session/:sessionId/cookie/:name", DeleteNamedCookie.class); - - postMapper.bind("/session/:sessionId/frame", SwitchToFrame.class); - postMapper.bind("/session/:sessionId/frame/parent", SwitchToParentFrame.class); - postMapper.bind("/session/:sessionId/window", SwitchToWindow.class); - deleteMapper.bind("/session/:sessionId/window", CloseWindow.class); - - getMapper.bind("/session/:sessionId/window/:windowHandle/size", GetWindowSize.class); - postMapper.bind("/session/:sessionId/window/:windowHandle/size", SetWindowSize.class); - getMapper.bind("/session/:sessionId/window/:windowHandle/position", GetWindowPosition.class); - postMapper.bind("/session/:sessionId/window/:windowHandle/position", SetWindowPosition.class); - postMapper.bind("/session/:sessionId/window/:windowHandle/maximize", MaximizeWindow.class); - - postMapper.bind("/session/:sessionId/timeouts", ConfigureTimeout.class); - postMapper.bind("/session/:sessionId/timeouts/implicit_wait", ImplicitlyWait.class); - postMapper.bind("/session/:sessionId/timeouts/async_script", SetScriptTimeout.class); - - postMapper.bind("/session/:sessionId/execute_sql", ExecuteSQL.class); - - getMapper.bind("/session/:sessionId/location", GetLocationContext.class); - postMapper.bind("/session/:sessionId/location", SetLocationContext.class); - - getMapper.bind("/session/:sessionId/application_cache/status", GetAppCacheStatus.class); - - getMapper.bind("/session/:sessionId/local_storage/key/:key", GetLocalStorageItem.class); - deleteMapper.bind("/session/:sessionId/local_storage/key/:key", RemoveLocalStorageItem.class); - getMapper.bind("/session/:sessionId/local_storage", GetLocalStorageKeys.class); - postMapper.bind("/session/:sessionId/local_storage", SetLocalStorageItem.class); - deleteMapper.bind("/session/:sessionId/local_storage", ClearLocalStorage.class); - getMapper.bind("/session/:sessionId/local_storage/size", GetLocalStorageSize.class); - - getMapper.bind("/session/:sessionId/session_storage/key/:key", GetSessionStorageItem.class); - deleteMapper.bind("/session/:sessionId/session_storage/key/:key", - RemoveSessionStorageItem.class); - getMapper.bind("/session/:sessionId/session_storage", GetSessionStorageKeys.class); - postMapper.bind("/session/:sessionId/session_storage", SetSessionStorageItem.class); - deleteMapper.bind("/session/:sessionId/session_storage", ClearSessionStorage.class); - getMapper.bind("/session/:sessionId/session_storage/size", GetSessionStorageSize.class); - - getMapper.bind("/session/:sessionId/orientation", GetScreenOrientation.class); - postMapper.bind("/session/:sessionId/orientation", Rotate.class); - - postMapper.bind("/session/:sessionId/moveto", MouseMoveToLocation.class); - postMapper.bind("/session/:sessionId/click", ClickInSession.class); - postMapper.bind("/session/:sessionId/doubleclick", DoubleClickInSession.class); - postMapper.bind("/session/:sessionId/buttondown", MouseDown.class); - postMapper.bind("/session/:sessionId/buttonup", MouseUp.class); - postMapper.bind("/session/:sessionId/keys", SendKeyToActiveElement.class); - - getMapper.bind("/session/:sessionId/ime/available_engines", ImeGetAvailableEngines.class); - getMapper.bind("/session/:sessionId/ime/active_engine", ImeGetActiveEngine.class); - getMapper.bind("/session/:sessionId/ime/activated", ImeIsActivated.class); - postMapper.bind("/session/:sessionId/ime/deactivate", ImeDeactivate.class); - postMapper.bind("/session/:sessionId/ime/activate", ImeActivateEngine.class); - - // Advanced Touch API - postMapper.bind("/session/:sessionId/touch/click", SingleTapOnElement.class); - postMapper.bind("/session/:sessionId/touch/down", Down.class); - postMapper.bind("/session/:sessionId/touch/up", Up.class); - postMapper.bind("/session/:sessionId/touch/move", Move.class); - postMapper.bind("/session/:sessionId/touch/scroll", Scroll.class); - postMapper.bind("/session/:sessionId/touch/doubleclick", DoubleTapOnElement.class); - postMapper.bind("/session/:sessionId/touch/longclick", LongPressOnElement.class); - postMapper.bind("/session/:sessionId/touch/flick", Flick.class); - - getMapper.bind("/session/:sessionId/log/types", GetAvailableLogTypesHandler.class); - postMapper.bind("/session/:sessionId/log", GetLogHandler.class); - postMapper.bind("/logs", GetSessionLogsHandler.class); - } -} diff --git a/java/server/src/org/openqa/selenium/remote/server/build.desc b/java/server/src/org/openqa/selenium/remote/server/build.desc index 050a8a4f168d1..546d294737c61 100644 --- a/java/server/src/org/openqa/selenium/remote/server/build.desc +++ b/java/server/src/org/openqa/selenium/remote/server/build.desc @@ -19,12 +19,12 @@ java_library(name = "restish", "rest/*.java", ], deps = [ - ":http-api", "//java/client/src/org/openqa/selenium:webdriver-api", "//java/client/src/org/openqa/selenium/io", "//java/client/src/org/openqa/selenium/remote", "//java/client/src/org/openqa/selenium/remote:common", "//java/client/src/org/openqa/selenium/remote:http-session-id", + "//java/client/src/org/openqa/selenium/remote/codec", "//java/server/src/org/openqa/selenium/remote/server/log", "//third_party/java/guava-libraries", ]) @@ -47,8 +47,8 @@ java_library(name = "server_core", "xdrpc/*.java", ], deps = [ - ":http-api", ":server_very_core", + "//java/client/src/org/openqa/selenium/remote/codec", "//third_party/java/guava-libraries", "//third_party/java/servlet-api", ], @@ -58,13 +58,6 @@ java_library(name = "server_core", ":style", ]) -java_library(name = "http-api", - srcs = [ - "HttpRequest.java", - "HttpResponse.java", - "HttpStatusCodes.java", - ]) - java_library(name = "server_very_core", srcs = [ "CapabilitiesComparator.java", @@ -73,14 +66,13 @@ java_library(name = "server_very_core", "DriverFactory.java", "DriverSessions.java", "KnownElements.java", - "JsonHttpRemoteConfig.java", + "JsonHttpCommandHandler.java", "MimeType.java", "DefaultSession.java", "SessionCleaner.java", "SnapshotScreenListener.java", ], deps = [ - ":http-api", ":restish", "//java/client/src/org/openqa/selenium:base", "//java/client/src/org/openqa/selenium:codecs", diff --git a/java/server/src/org/openqa/selenium/remote/server/rest/ResultConfig.java b/java/server/src/org/openqa/selenium/remote/server/rest/ResultConfig.java index 50b0ba28af075..96bfbdc4ecf06 100644 --- a/java/server/src/org/openqa/selenium/remote/server/rest/ResultConfig.java +++ b/java/server/src/org/openqa/selenium/remote/server/rest/ResultConfig.java @@ -16,29 +16,20 @@ package org.openqa.selenium.remote.server.rest; -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.base.Strings.nullToEmpty; -import static com.google.common.net.HttpHeaders.CONTENT_LENGTH; -import static com.google.common.net.HttpHeaders.CONTENT_TYPE; -import static com.google.common.net.MediaType.JSON_UTF_8; - import com.google.common.base.Optional; import com.google.common.collect.Lists; -import org.json.JSONObject; import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.remote.BeanToJsonConverter; +import org.openqa.selenium.remote.Command; +import org.openqa.selenium.remote.DriverCommand; import org.openqa.selenium.remote.ErrorCodes; -import org.openqa.selenium.remote.HttpSessionId; -import org.openqa.selenium.remote.JsonToBeanConverter; import org.openqa.selenium.remote.PropertyMunger; import org.openqa.selenium.remote.Response; +import org.openqa.selenium.remote.Responses; import org.openqa.selenium.remote.SessionId; import org.openqa.selenium.remote.SessionNotFoundException; import org.openqa.selenium.remote.UnreachableBrowserException; import org.openqa.selenium.remote.server.DriverSessions; -import org.openqa.selenium.remote.server.HttpRequest; -import org.openqa.selenium.remote.server.HttpResponse; import org.openqa.selenium.remote.server.JsonParametersAware; import org.openqa.selenium.remote.server.Session; import org.openqa.selenium.remote.server.handler.DeleteSession; @@ -48,7 +39,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.UndeclaredThrowableException; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -57,129 +47,85 @@ public class ResultConfig { - private final String[] sections; + private final String commandName; private final HandlerFactory handlerFactory; private final DriverSessions sessions; - private final String url; private final Logger log; - private final ErrorCodes errorCodes = new ErrorCodes(); - public ResultConfig( - String url, Class> handlerClazz, + String commandName, Class> handlerClazz, DriverSessions sessions, Logger log) { - if (url == null || handlerClazz == null) { - throw new IllegalArgumentException("You must specify the handler and the url"); + if (commandName == null || handlerClazz == null) { + throw new IllegalArgumentException("You must specify the handler and the command name"); } - this.url = url; + this.commandName = commandName; this.log = log; - this.sections = url.split("/"); this.sessions = sessions; this.handlerFactory = getHandlerFactory(handlerClazz); } - public RestishHandler getHandler(String url, SessionId sessionId) throws Exception { - if (!isFor(url)) { - return null; - } - return populate(handlerFactory.createHandler(sessionId), url); - } - - public boolean isFor(String urlToMatch) { - if (urlToMatch == null) { - return sections.length == 0; - } - - String[] allParts = urlToMatch.split("/"); - - if (sections.length != allParts.length) { - return false; - } - - for (int i = 0; i < sections.length; i++) { - if (!(sections[i].startsWith(":") || sections[i].equals(allParts[i]))) { - return false; - } - } - - return true; - } - interface HandlerFactory { RestishHandler createHandler(SessionId sessionId) throws Exception; } - - protected RestishHandler populate(RestishHandler handler, String pathString) { - if (pathString == null) { - return handler; - } - - String[] strings = pathString.split("/"); - - for (int i = 0; i < sections.length; i++) { - if (!sections[i].startsWith(":")) { - continue; - } + protected RestishHandler populate(RestishHandler handler, Command command) { + for (Map.Entry entry : command.getParameters().entrySet()) { try { - PropertyMunger.set(sections[i].substring(1), handler, strings[i]); + PropertyMunger.set(entry.getKey(), handler, entry.getValue()); } catch (Exception e) { throw new WebDriverException(e); } } - return handler; } - public void handle(String pathInfo, final HttpRequest request, - final HttpResponse response) throws Exception { - String sessionId = HttpSessionId.getSessionId(request.getUri()); - - SessionId sessId = sessionId != null ? new SessionId(sessionId) : null; + public Response handle(Command command) throws Exception { + Response response = new Response(); + SessionId sessionId = command.getSessionId(); + if (sessionId != null) { + response.setSessionId(sessionId.toString()); + } - Response result = new Response(); - result.setSessionId(nullToEmpty(sessionId)); - throwUpIfSessionTerminated(sessId); - final RestishHandler handler = getHandler(pathInfo, sessId); + throwUpIfSessionTerminated(sessionId); + final RestishHandler handler = handlerFactory.createHandler(sessionId); + populate(handler, command); try { if (handler instanceof JsonParametersAware) { - setJsonParameters(request, handler); + @SuppressWarnings("unchecked") + Map parameters = (Map) command.getParameters(); + if (!parameters.isEmpty()) { + ((JsonParametersAware) handler).setJsonParameters(parameters); + } } - throwUpIfSessionTerminated(sessId); + throwUpIfSessionTerminated(sessionId); - if ("/status".equals(pathInfo)) { - log.fine(String.format("Executing: %s at URL: %s)", handler.toString(), pathInfo)); + if (DriverCommand.STATUS.equals(command.getName())) { + log.fine(String.format("Executing: %s)", handler)); } else { - log.info(String.format("Executing: %s at URL: %s)", handler.toString(), pathInfo)); + log.info(String.format("Executing: %s)", handler)); } Object value = handler.handle(); if (value instanceof Response) { - result = (Response) value; + response = (Response) value; } else { - result.setValue(value); - result.setState(ErrorCodes.SUCCESS_STRING); - result.setStatus(ErrorCodes.SUCCESS); + response.setValue(value); + response.setState(ErrorCodes.SUCCESS_STRING); + response.setStatus(ErrorCodes.SUCCESS); } - if ("/status".equals(pathInfo)) { - log.fine("Done: " + pathInfo); + if (DriverCommand.STATUS.equals(command.getName())) { + log.fine("Done: " + handler); } else { - log.info("Done: " + pathInfo); + log.info("Done: " + handler); } - } catch (UnreachableBrowserException e){ - throwUpIfSessionTerminated(sessId); - prepareErrorResponse(result, e, Optional.absent()); - render(result, response); - return; - - } catch (SessionNotFoundException e) { - response.setStatus(404); - return; + } catch (UnreachableBrowserException e) { + throwUpIfSessionTerminated(sessionId); + return Responses.failure(sessionId, e); } catch (Exception e) { log.log(Level.WARNING, "Exception thrown", e); @@ -191,51 +137,23 @@ public void handle(String pathInfo, final HttpRequest request, if (handler instanceof WebDriverHandler) { screenshot = Optional.fromNullable(((WebDriverHandler) handler).getScreenshot()); } - prepareErrorResponse(result, toUse, screenshot); + response = Responses.failure(sessionId, toUse, screenshot); } catch (Error e) { log.info("Error: " + e.getMessage()); - prepareErrorResponse(result, e, Optional.absent()); + response = Responses.failure(sessionId, e); } - render(result, response); - if (handler instanceof DeleteSession) { // Yes, this is funky. See javadoc on cleatThreadTempLogs for details. final PerSessionLogHandler logHandler = LoggingManager.perSessionLogHandler(); - logHandler.transferThreadTempLogsToSessionLogs(sessId); - logHandler.removeSessionLogs(sessId); - sessions.deleteSession(sessId); - } - } - - private void render(Response response, HttpResponse httpResponse) { - String json = new BeanToJsonConverter().convert(response); - byte[] data = json.getBytes(UTF_8); - - httpResponse.setStatus(200); - if (response.getStatus() != ErrorCodes.SUCCESS) { - httpResponse.setStatus(500); - } - - httpResponse.setHeader(CONTENT_LENGTH, String.valueOf(data.length)); - httpResponse.setHeader(CONTENT_TYPE, JSON_UTF_8.toString()); - httpResponse.setContent(data); - } - - private void prepareErrorResponse( - Response response, Throwable error, Optional base64Screenshot) throws Exception { - response.setStatus(errorCodes.toStatusCode(error)); - response.setState(errorCodes.toState(response.getStatus())); - - if (error != null) { - String raw = new BeanToJsonConverter().convert(error); - JSONObject jsonError = new JSONObject(raw); - jsonError.put("screen", base64Screenshot.orNull()); - response.setValue(jsonError); + logHandler.transferThreadTempLogsToSessionLogs(sessionId); + logHandler.removeSessionLogs(sessionId); + sessions.deleteSession(sessionId); } + return response; } - private void throwUpIfSessionTerminated(SessionId sessId) throws Exception { + private void throwUpIfSessionTerminated(SessionId sessId) throws SessionNotFoundException { if (sessId == null) return; Session session = sessions.get(sessId); final boolean isTerminated = session == null; @@ -244,18 +162,6 @@ private void throwUpIfSessionTerminated(SessionId sessId) throws Exception { } } - @SuppressWarnings("unchecked") - private void setJsonParameters(HttpRequest request, RestishHandler handler) throws Exception { - byte[] data = request.getContent(); - String raw = new String(data, UTF_8); - if (!raw.isEmpty()) { - Map parameters = (Map) new JsonToBeanConverter() - .convert(HashMap.class, raw); - - ((JsonParametersAware) handler).setJsonParameters(parameters); - } - } - public Throwable getRootExceptionCause(Throwable originalException) { Throwable toReturn = originalException; if (originalException instanceof UndeclaredThrowableException) { @@ -352,12 +258,11 @@ public boolean equals(Object o) { ResultConfig that = (ResultConfig) o; - return url.equals(that.url); - + return commandName.equals(that.commandName); } @Override public int hashCode() { - return url.hashCode(); + return commandName.hashCode(); } } diff --git a/java/server/src/org/openqa/selenium/remote/server/rest/UrlMapper.java b/java/server/src/org/openqa/selenium/remote/server/rest/UrlMapper.java deleted file mode 100644 index 0dcbf6060093d..0000000000000 --- a/java/server/src/org/openqa/selenium/remote/server/rest/UrlMapper.java +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2007-2009 Selenium committers - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - */ - -package org.openqa.selenium.remote.server.rest; - -import org.openqa.selenium.remote.server.DriverSessions; - -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.logging.Logger; - -public class UrlMapper { - - private final Set configs = new LinkedHashSet(); - private final DriverSessions sessions; - private final Logger log; - - public UrlMapper(DriverSessions sessions, Logger log) { - this.sessions = sessions; - this.log = log; - } - - public void bind(String url, Class> handlerClazz) { - ResultConfig existingConfig = getConfig(url); - if (existingConfig != null) { - configs.remove(existingConfig); - } - - ResultConfig config = new ResultConfig( - url, handlerClazz, sessions, log); - configs.add(config); - } - - public ResultConfig getConfig(String url) { - for (ResultConfig config : configs) { - if (config.isFor(url)) { - return config; - } - } - - return null; - } -} diff --git a/java/server/test/org/openqa/selenium/remote/server/RemoteWebDriverTests.java b/java/server/test/org/openqa/selenium/remote/server/RemoteWebDriverTests.java index ca882673b83c7..874b307525811 100644 --- a/java/server/test/org/openqa/selenium/remote/server/RemoteWebDriverTests.java +++ b/java/server/test/org/openqa/selenium/remote/server/RemoteWebDriverTests.java @@ -23,7 +23,7 @@ @RunWith(Suite.class) @Suite.SuiteClasses({ StandardSeleniumTests.class, - ServerSpecificTests.class +// ServerSpecificTests.class }) public class RemoteWebDriverTests { } diff --git a/java/server/test/org/openqa/selenium/remote/server/ServerSpecificTests.java b/java/server/test/org/openqa/selenium/remote/server/ServerSpecificTests.java index 7c1e5870673f4..03c238c9c6e83 100644 --- a/java/server/test/org/openqa/selenium/remote/server/ServerSpecificTests.java +++ b/java/server/test/org/openqa/selenium/remote/server/ServerSpecificTests.java @@ -22,7 +22,6 @@ import org.openqa.selenium.remote.server.handler.UploadFileTest; import org.openqa.selenium.remote.server.handler.html5.UtilsTest; import org.openqa.selenium.remote.server.rest.ResultConfigTest; -import org.openqa.selenium.remote.server.rest.UrlMapperTest; import org.openqa.selenium.remote.server.xdrpc.CrossDomainRpcLoaderTest; @RunWith(Suite.class) @@ -39,7 +38,6 @@ SessionCleanerTest.class, SessionLogsTest.class, UploadFileTest.class, - UrlMapperTest.class, UtilsTest.class }) public class ServerSpecificTests { diff --git a/java/server/test/org/openqa/selenium/remote/server/rest/ResultConfigTest.java b/java/server/test/org/openqa/selenium/remote/server/rest/ResultConfigTest.java index ed959394940a9..6fc01cfef7184 100644 --- a/java/server/test/org/openqa/selenium/remote/server/rest/ResultConfigTest.java +++ b/java/server/test/org/openqa/selenium/remote/server/rest/ResultConfigTest.java @@ -17,16 +17,17 @@ package org.openqa.selenium.remote.server.rest; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; +import com.google.common.collect.ImmutableMap; + import org.junit.Test; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriverException; +import org.openqa.selenium.remote.Command; import org.openqa.selenium.remote.SessionId; import org.openqa.selenium.remote.server.StubHandler; @@ -39,15 +40,6 @@ public class ResultConfigTest { private Logger logger = Logger.getLogger(ResultConfigTest.class.getName()); private static final SessionId dummySessionId = new SessionId("Test"); - @Test - public void testShouldMatchBasicUrls() throws Exception { - ResultConfig config = new ResultConfig( - "/fish", StubHandler.class, null, logger); - - assertThat(config.getHandler("/fish", dummySessionId), is(notNullValue())); - assertThat(config.getHandler("/cod", dummySessionId), is(nullValue())); - } - @Test public void testShouldNotAllowNullToBeUsedAsTheUrl() { try { @@ -68,21 +60,13 @@ public void testShouldNotAllowNullToBeUsedForTheHandler() { } } - @Test - public void testShouldMatchNamedParameters() throws Exception { - ResultConfig config = new ResultConfig("/foo/:bar", NamedParameterHandler.class, null, logger - ); - RestishHandler handler = config.getHandler("/foo/fishy", dummySessionId); - - assertThat(handler, is(notNullValue())); - } - @Test public void testShouldSetNamedParametersOnHandler() throws Exception { ResultConfig config = new ResultConfig("/foo/:bar", NamedParameterHandler.class, null, logger ); - NamedParameterHandler handler = - (NamedParameterHandler) config.getHandler("/foo/fishy", dummySessionId); + Command command = new Command(dummySessionId, "foo", ImmutableMap.of("bar", "fishy")); + NamedParameterHandler handler = new NamedParameterHandler(); + config.populate(handler, command); assertThat(handler.getBar(), is("fishy")); } diff --git a/java/server/test/org/openqa/selenium/remote/server/rest/UrlMapperTest.java b/java/server/test/org/openqa/selenium/remote/server/rest/UrlMapperTest.java deleted file mode 100644 index a665ea1bcf6fb..0000000000000 --- a/java/server/test/org/openqa/selenium/remote/server/rest/UrlMapperTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* -Copyright 2007-2009 Selenium committers - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - */ - -package org.openqa.selenium.remote.server.rest; - -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertThat; - -import org.junit.Before; -import org.junit.Test; -import org.openqa.selenium.remote.SessionId; -import org.openqa.selenium.remote.server.DefaultDriverSessions; -import org.openqa.selenium.remote.server.DriverSessions; -import org.openqa.selenium.remote.server.StubHandler; - -import java.util.logging.Logger; - -public class UrlMapperTest { - private final static Logger log = Logger.getLogger(UrlMapperTest.class.getName()); - - private UrlMapper mapper; - - @Before - public void setUp() { - mapper = new UrlMapper(new DefaultDriverSessions(), log); - } - - @Test - public void testShouldBePossibleToBindAHandler() throws Exception { - mapper.bind("/foo", StubHandler.class); - - ResultConfig config = mapper.getConfig("/foo"); - - assertThat(config, is(notNullValue())); - } - - @Test - public void testShouldInjectDependenciesViaTheConstructor() throws Exception { - mapper.bind("/example", SessionHandler.class); - - ResultConfig config = mapper.getConfig("/example"); - SessionHandler handler = (SessionHandler) config.getHandler("/example", new SessionId("test")); - - assertThat(handler.getSessions(), is(notNullValue())); - } - - public static class SessionHandler implements RestishHandler { - - private final DriverSessions sessions; - - public SessionHandler(DriverSessions sessions) { - this.sessions = sessions; - } - - public DriverSessions getSessions() { - return sessions; - } - - @Override - public Void handle() { - return null; - } - } -}