// Copyright 2016 The Nomulus 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. package google.registry.request; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Ordering; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.TreeMap; /** * Path prefix request router for Domain Registry. * *

See the documentation of {@link RequestHandler} for more information. * *

Implementation Details

* *

Request routing is O(logn) because {@link ImmutableSortedMap} performs a binary search over a * contiguous array, which makes it faster than a {@link TreeMap}. However a prefix trie search in * generated code would be the ideal approach. */ final class Router { static Router create(Iterable componentMethods) { return new Router(extractRoutesFromComponent(componentMethods)); } private final ImmutableSortedMap routes; private Router(ImmutableSortedMap routes) { this.routes = routes; } /** Returns the appropriate action route for a request. */ Optional route(String path) { Map.Entry floor = routes.floorEntry(path); if (floor != null) { if (floor.getValue().action().isPrefix() ? path.startsWith(floor.getKey()) : path.equals(floor.getKey())) { return Optional.of(floor.getValue()); } } return Optional.absent(); } private static ImmutableSortedMap extractRoutesFromComponent(Iterable methods) { ImmutableSortedMap.Builder routes = new ImmutableSortedMap.Builder<>(Ordering.natural()); for (Method method : methods) { if (!isDaggerInstantiatorOfType(Runnable.class, method)) { continue; } Action action = method.getReturnType().getAnnotation(Action.class); if (action == null) { continue; } @SuppressWarnings("unchecked") // Safe due to previous checks. Route route = Route.create(action, (Function) newInstantiator(method)); routes.put(action.path(), route); } return routes.build(); } private static boolean isDaggerInstantiatorOfType(Class type, Method method) { return method.getParameterTypes().length == 0 && type.isAssignableFrom(method.getReturnType()); } private static Function newInstantiator(final Method method) { return new Function() { @Override public Object apply(Object component) { try { return method.invoke(component); } catch (IllegalAccessException e) { throw new RuntimeException( "Error reflectively accessing component's @Action factory method", e); } catch (InvocationTargetException e) { // This means an exception was thrown during the injection process while instantiating // the @Action class; we should propagate that underlying exception. Throwables.propagateIfPossible(e.getCause()); throw new AssertionError( "Component's @Action factory method somehow threw checked exception", e); } }}; } }