blob: 816a396cee82913400d4cfaba6607c60ec1d553d [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// 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.
#ifndef COBALT_WEBDRIVER_UTIL_DISPATCH_COMMAND_FACTORY_H_
#define COBALT_WEBDRIVER_UTIL_DISPATCH_COMMAND_FACTORY_H_
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/ptr_util.h"
namespace cobalt {
namespace webdriver {
namespace util {
namespace internal {
namespace {
template <typename T>
struct ForwardType {
typedef const T& value;
};
template <typename T>
struct ForwardType<T&> {
typedef T& value;
};
template <typename T, size_t n>
struct ForwardType<T[n]> {
typedef const T* value;
};
// See comment for CallbackParamTraits<T[n]>.
template <typename T>
struct ForwardType<T[]> {
typedef const T* value;
};
// unique_ptr-like move-only types.
template <typename T>
struct ForwardType<std::unique_ptr<T>> {
typedef std::unique_ptr<T> value;
};
} // namespace
// Convert a value to a base::Value to return it as a part of the WebDriver
// response object.
template <typename T>
std::unique_ptr<base::Value> ToValue(const T& value) {
return T::ToValue(value);
}
template <typename T>
std::unique_ptr<base::Value> ToValue(const std::vector<T>& value) {
std::unique_ptr<base::ListValue> list_value(new base::ListValue());
for (int i = 0; i < value.size(); ++i) {
list_value->Append(std::move(ToValue<T>(value[i])));
}
return std::unique_ptr<base::Value>(list_value.release());
}
// Partial template specialization for base::optional.
template <typename T>
std::unique_ptr<base::Value> ToValue(const base::Optional<T>& value) {
if (value) {
return ToValue<T>(*value);
} else {
return std::make_unique<base::Value>();
}
}
// Template specialization for std::string.
template <>
std::unique_ptr<base::Value> ToValue(const std::string& value) {
return std::unique_ptr<base::Value>(new base::Value(value));
}
// Template specialization for bool.
template <>
std::unique_ptr<base::Value> ToValue(const bool& value) {
return std::unique_ptr<base::Value>(new base::Value(value));
}
template <typename R>
std::unique_ptr<base::Value> ToValue(const CommandResult<R>& command_result) {
return ToValue(command_result.result());
}
template <>
std::unique_ptr<base::Value> ToValue(
const CommandResult<void>& command_result) {
return std::make_unique<base::Value>();
}
// Convert a base::Value to a base::Optional<T>. If value could not be converted
// base::nullopt will be returned.
template <typename T>
base::Optional<T> FromValue(const base::Value* value) {
return T::FromValue(value);
}
template <>
base::Optional<GURL> FromValue(const base::Value* value) {
const char kUrlKey[] = "url";
std::string url;
const base::DictionaryValue* dictionary_value;
if (!value->GetAsDictionary(&dictionary_value) ||
!dictionary_value->GetString(kUrlKey, &url)) {
return base::nullopt;
}
return GURL(url.c_str());
}
// Returns an appropriate response through the CommandResultHandler.
// On failure, the error response value is created and on success the
// result in the CommandResult instance is converted to a base::Value and
// returned.
template <typename R>
void ReturnResponse(const base::Optional<protocol::SessionId>& session_id,
const util::CommandResult<R>& command_result,
WebDriverDispatcher::CommandResultHandler* result_handler) {
std::unique_ptr<base::Value> result;
if (command_result.is_success()) {
result = internal::ToValue(command_result);
} else {
result =
protocol::Response::CreateErrorResponse(command_result.error_message());
}
result_handler->SendResult(session_id, command_result.status_code(),
std::move(result));
}
} // namespace internal
// Helper class to create WebDriverDispatcher::DispatchCommandCallbacks that
// can be used with the WebDriverDispatcher::RegisterCommand function.
// Specify the XXXDriver class name as a template parameter.
template <class DriverClassT>
class DispatchCommandFactory
: public base::RefCounted<DispatchCommandFactory<DriverClassT> > {
// Max retries for the "can_retry" CommandResult case.
static const int kMaxRetries = 5;
public:
// Typedef'd for less verbose code.
typedef WebDriverDispatcher::CommandResultHandler CommandResultHandler;
typedef WebDriverDispatcher::DispatchCommandCallback DispatchCommandCallback;
typedef WebDriverDispatcher::PathVariableMap PathVariableMap;
// Callback that takes PathVariableMap* and CommandResultHandler* as arguments
// and returns a SessionDriver*.
// Attempts to extract the sessionID from the PathVariableMap and finds the
// session that this ID maps to. If no such session occurrs, an error is sent
// through the CommandResultHandler and NULL is returned.
typedef base::Callback<SessionDriver*(const PathVariableMap* path_variables,
CommandResultHandler*)>
GetSessionCommand;
// Callback that takes PathVariableMap* and CommandResultHandler* as arguments
// and returns a DriverClass*.
// If necessary extracts the id from the PathVariableMap and finds the driver
// that this ID maps to. If no such driver occurs, an error is sent through
// the CommandResultHandler and NULL is returned.
typedef base::Callback<DriverClassT*(SessionDriver*,
const PathVariableMap* path_variables,
CommandResultHandler*)> GetDriverCommand;
// Takes GetSessionCommand and GetDriverCommand callbacks. These will be
// called when the DispatchCommandCallback is called to try to find the
// correct XXXDriver based on the path variables.
DispatchCommandFactory(const GetSessionCommand& get_session,
const GetDriverCommand& get_driver)
: get_session_(get_session), get_driver_(get_driver) {}
// Returns a DispatchCommandCallback that will call the specified
// command_callback.
// If the path variables successfull map to a DriverClass instance, it will
// be passed as the argument to the command_callback.
// If no such DriverClass instance can be found, the command_callback will not
// be run. The results of the command will be sent through the
// CommandResultHandler.
template <typename R>
DispatchCommandCallback GetCommandHandler(
const base::Callback<util::CommandResult<R>(DriverClassT*)>&
command_callback) {
typedef CommandHandler<R> CommandHandler;
return CommandHandler::GetCommandHandler(
get_session_, get_driver_,
base::Bind(&CommandHandler::RunCommand, command_callback));
}
// Returns a DispatchCommandCallback that will call the specified
// command_callback with an argument.
// If the path variables successfull map to a DriverClass instance, it will
// be passed as the argument to the command_callback.
// If the parameters passed to the command cannot be converted to an instance
// of type A1, the command_callback will not be called an an appropriate
// error will be returned through the CommandRequestHandler.
// If no such DriverClass instance can be found, the command_callback will not
// be run.
template <typename R, typename A1>
DispatchCommandCallback GetCommandHandler(
const base::Callback<util::CommandResult<R>(DriverClassT*, const A1&)>&
command_callback) {
typedef CommandHandler<R> CommandHandler;
return CommandHandler::GetCommandHandler(
get_session_, get_driver_,
base::Bind(&CommandHandler::template ExtractParameterAndRunCommand<A1>,
command_callback));
}
private:
// Works with the DispatchCommandFactory to create a DispatchCommandCallback
// for a particular return value R. Putting this into a nested template class
// helps alleviate some of the headaches of working with C++ templates.
template <typename R>
class CommandHandler : public base::RefCounted<CommandHandler<R> > {
public:
// Wrapper around an actual command to run. It will extract arguments from
// |parameters| if necessary and pass them to the command.
// If parameters cannot be extracted, an appropriate error will be sent to
// the CommandResultHandler.
// Upon successful completion of the command, the result will be sent to
// CommandResultHandler.
typedef base::Callback<void(
const base::Optional<protocol::SessionId>&, DriverClassT* driver,
const base::Value* parameters, CommandResultHandler* result_handler)>
RunCommandCallback;
typedef base::Callback<util::CommandResult<R>(DriverClassT*)>
DriverCommandFunction;
// After binding the first argument, this function signature matches that of
// RunCommandCallback.
// The first argument is a base::Callback that runs a DriverClassT member
// function with no parameters.
static void RunCommand(
const DriverCommandFunction& driver_command,
const base::Optional<protocol::SessionId>& session_id,
DriverClassT* driver, const base::Value* parameters,
CommandResultHandler* result_handler) {
// Ignore parameters.
int retries = 0;
util::CommandResult<R> command_result;
do {
command_result = driver_command.Run(driver);
} while (command_result.can_retry() && (retries++ < kMaxRetries));
internal::ReturnResponse(session_id, command_result, result_handler);
}
// After binding the first argument, this function signature matches that of
// RunCommandCallback.
// The first argument is a base::Callback that runs a DriverClassT member
// function with one parameter.
template <typename A1>
static void ExtractParameterAndRunCommand(
const base::Callback<util::CommandResult<R>(
DriverClassT*, typename internal::ForwardType<A1>::value)>&
driver_command,
const base::Optional<protocol::SessionId>& session_id,
DriverClassT* driver, const base::Value* parameters,
CommandResultHandler* result_handler) {
// Extract the parameter from |parameters|. If unsuccessful, return a
// kInvalidParameters error. Otherwise, pass the extracted parameter to
// the |driver_command|.
using A1_NO_REF = typename std::remove_reference<A1>::type;
base::Optional<A1_NO_REF> param =
internal::FromValue<A1_NO_REF>(parameters);
if (!param) {
result_handler->SendInvalidRequestResponse(
WebDriverDispatcher::CommandResultHandler::kInvalidParameters, "");
} else {
int retries = 0;
util::CommandResult<R> command_result;
do {
command_result = driver_command.Run(driver, param.value());
} while (command_result.can_retry() && (retries++ < kMaxRetries));
internal::ReturnResponse(session_id, command_result, result_handler);
}
}
// Create a new CommandHandler instance, and base::Bind to its HandleCommand
// function.
static DispatchCommandCallback GetCommandHandler(
const GetSessionCommand& get_session_command,
const GetDriverCommand& get_driver_command,
const RunCommandCallback& run_command_callback) {
scoped_refptr<CommandHandler> command_handler(new CommandHandler(
get_session_command, get_driver_command, run_command_callback));
return base::Bind(&CommandHandler::HandleCommand, command_handler);
}
private:
// Simple constructor that sets each of the necessary callbacks.
CommandHandler(const GetSessionCommand& get_session_callback,
const GetDriverCommand& get_driver_callback,
const RunCommandCallback& run_command_callback)
: get_session_(get_session_callback),
get_driver_(get_driver_callback),
run_command_(run_command_callback) {}
// This function matches the WebDriverDispatcher::DispatchCommandCallback
// signature, and as such is appropriate to be passed to the
// WebDriverDispatcher::RegisterCommand function.
// Using the the callbacks stored in this CommandHandler, it will extract
// the appropriate XXXDriver, and execute |run_command_| on it.
void HandleCommand(const base::Value* value,
const PathVariableMap* path_variables,
std::unique_ptr<CommandResultHandler> result_handler) {
SessionDriver* session =
get_session_.Run(path_variables, result_handler.get());
if (session) {
DriverClassT* driver =
get_driver_.Run(session, path_variables, result_handler.get());
if (driver) {
run_command_.Run(session->session_id(), driver, value,
result_handler.get());
}
}
}
GetSessionCommand get_session_;
GetDriverCommand get_driver_;
RunCommandCallback run_command_;
};
GetSessionCommand get_session_;
GetDriverCommand get_driver_;
};
} // namespace util
} // namespace webdriver
} // namespace cobalt
#endif // COBALT_WEBDRIVER_UTIL_DISPATCH_COMMAND_FACTORY_H_