Skip to content

Commit

Permalink
Rework how we obtain command handlers
Browse files Browse the repository at this point in the history
Ideally, each time we request a handler it is
created anew. The `CommandHandlerFactory` makes
this a simpler task.
  • Loading branch information
shs96c committed Nov 7, 2018
1 parent c9b57f0 commit cacc020
Show file tree
Hide file tree
Showing 14 changed files with 714 additions and 4 deletions.
4 changes: 3 additions & 1 deletion java/server/src/org/openqa/selenium/grid/router/Router.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.openqa.selenium.grid.sessionmap.SessionMap;
import org.openqa.selenium.grid.web.CommandHandler;
import org.openqa.selenium.grid.web.CompoundHandler;
import org.openqa.selenium.grid.web.Routes;
import org.openqa.selenium.injector.Injector;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
Expand All @@ -35,7 +36,8 @@
*/
public class Router implements Predicate<HttpRequest>, CommandHandler {

private final CompoundHandler handler;
private final Injector injector;
private final Routes handlerFactory;

public Router(SessionMap sessions, Distributor distributor) {
HandleSession activeSession = new HandleSession(sessions);
Expand Down
57 changes: 57 additions & 0 deletions java/server/src/org/openqa/selenium/grid/web/CombinedRoute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you 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.grid.web;

import com.google.common.collect.ImmutableList;

import org.openqa.selenium.injector.Injector;
import org.openqa.selenium.injector.UnableToInstaniateInstanceException;
import org.openqa.selenium.remote.http.HttpRequest;

import java.util.List;
import java.util.Optional;

public class CombinedRoute extends Route<CombinedRoute> {

private final List<Routes> factories;

CombinedRoute(List<Routes> factories) {
this.factories = ImmutableList.copyOf(factories);
}

@Override
protected void validate() {
// No-op
}

@Override
protected CommandHandler newHandler(Injector injector, HttpRequest request) {
for (Routes factory : factories) {
try {
Optional<CommandHandler> handler = factory.match(injector, request);
if (handler.isPresent()) {
return handler.get();
}
} catch (IllegalArgumentException | UnableToInstaniateInstanceException e) {
// ignore and carry on
}
}
return getFallback(injector);
}

}
63 changes: 63 additions & 0 deletions java/server/src/org/openqa/selenium/grid/web/PredicatedRoute.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you 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.grid.web;

import org.openqa.selenium.injector.Injector;
import org.openqa.selenium.remote.http.HttpRequest;

import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;

public class PredicatedRoute extends Route<PredicatedRoute> {

private Function<Injector, CommandHandler> handlerFunc;
private final Predicate<HttpRequest> predicate;

PredicatedRoute(Predicate<HttpRequest> predicate) {
this.predicate = Objects.requireNonNull(predicate);
}

public PredicatedRoute using(Class<? extends CommandHandler> handlerClass) {
Objects.requireNonNull(handlerClass);
handlerFunc = (inj) -> inj.newInstance(handlerClass);
return this;
}

public PredicatedRoute using(CommandHandler handlerInstance) {
Objects.requireNonNull(handlerInstance);
handlerFunc = (inj) -> handlerInstance;
return this;
}

@Override
protected void validate() {
if (handlerFunc == null) {
throw new IllegalStateException("Handler for route is required");
}
}

@Override
protected CommandHandler newHandler(Injector injector, HttpRequest request) {
if (!predicate.test(request)) {
return getFallback(injector);
}

return handlerFunc.apply(injector);
}
}
106 changes: 106 additions & 0 deletions java/server/src/org/openqa/selenium/grid/web/Route.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you 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.grid.web;

import org.openqa.selenium.injector.Injector;
import org.openqa.selenium.remote.http.HttpRequest;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;

public abstract class Route<T extends Route> {

private final List<Class<? extends CommandHandler>> decorators = new ArrayList<>();
private Class<? extends CommandHandler> fallback;

protected Route() {
// no-op
}

protected abstract void validate();

protected abstract CommandHandler newHandler(Injector injector, HttpRequest request);

/**
* The given {@code decorator} must take a {@link CommandHandler} in its longest constructor.
*/
public T decorateWith(Class<? extends CommandHandler> decorator) {
Objects.requireNonNull(decorator);

// Find the longest constructor, which is what the injector uses
Constructor<?> constructor = Arrays.stream(decorator.getDeclaredConstructors())
.max(Comparator.comparing(Constructor::getParameterCount))
.orElse(null);

if (constructor == null) {
throw new IllegalArgumentException("Unable to find a constructor for " + decorator);
}

Boolean hasHandlerArg = Arrays.stream(constructor.getParameterTypes())
.map(CommandHandler.class::isAssignableFrom)
.reduce(Boolean::logicalOr)
.orElse(false);

if (!hasHandlerArg) {
throw new IllegalArgumentException(
"Decorator must take a CommandHandler as a constructor arg in its longest " +
"constructor. " + decorator);
}

decorators.add(decorator);

//noinspection unchecked
return (T) this;
}

public T fallbackTo(Class<? extends CommandHandler> fallback) {
this.fallback = Objects.requireNonNull(fallback);
//noinspection unchecked
return (T) this;
}

public Routes build() {
validate();

BiFunction<Injector, HttpRequest, CommandHandler> func = (inj, req) -> {
CommandHandler handler = newHandler(inj, req);
if (handler == null) {
return getFallback(inj);
}

Injector injector = inj;
for (Class<? extends CommandHandler> decorator : decorators) {
injector = Injector.builder().parent(injector).register(handler).build();
handler = injector.newInstance(decorator);
}

return handler;
};

return new Routes(func);
}

protected CommandHandler getFallback(Injector injector) {
return fallback == null ? null : injector.newInstance(fallback);
}
}
80 changes: 80 additions & 0 deletions java/server/src/org/openqa/selenium/grid/web/Routes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you 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.grid.web;

import static org.openqa.selenium.remote.http.HttpMethod.DELETE;
import static org.openqa.selenium.remote.http.HttpMethod.GET;
import static org.openqa.selenium.remote.http.HttpMethod.POST;

import com.google.common.collect.ImmutableList;

import org.openqa.selenium.injector.Injector;
import org.openqa.selenium.remote.http.HttpRequest;

import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class Routes {

private final BiFunction<Injector, HttpRequest, CommandHandler> handlerFunc;

Routes(BiFunction<Injector, HttpRequest, CommandHandler> handlerFunc) {
this.handlerFunc = Objects.requireNonNull(handlerFunc);
}

public static PredicatedRoute matching(Predicate<HttpRequest> predicate) {
return new PredicatedRoute(predicate);
}

public static SpecificRoute delete(String template) {
return new SpecificRoute(DELETE, template);
}

public static SpecificRoute get(String template) {
return new SpecificRoute(GET, template);
}

public static SpecificRoute post(String template) {
return new SpecificRoute(POST, template);
}

public static CombinedRoute combine(Route atLeastOne, Route... optionalOthers) {
return combine(
atLeastOne.build(),
Arrays.stream(optionalOthers).map(Route::build).toArray(Routes[]::new));
}

public static CombinedRoute combine(
Routes atLeastOne,
Routes... optionalOthers) {
ImmutableList<Routes> queue =
Stream.concat(Stream.of(atLeastOne), Arrays.stream(optionalOthers))
.collect(ImmutableList.toImmutableList());

return new CombinedRoute(queue.reverse());
}

public Optional<CommandHandler> match(Injector injector, HttpRequest request) {
return Optional.of(handlerFunc.apply(injector, request));
}

}
Loading

0 comments on commit cacc020

Please sign in to comment.