/*** * Copyright (c) 2009 Caelum - www.caelum.com.br/opensource * 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. */ /*** * * Copyright (c) 2009 Caelum - www.caelum.com.br/opensource All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. 2. Redistributions in * binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. 3. Neither the name of the * copyright holders nor the names of its contributors may be used to endorse or * promote products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package br.com.caelum.vraptor.http.route; import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import br.com.caelum.vraptor.Path; import br.com.caelum.vraptor.core.Converters; import br.com.caelum.vraptor.http.ParameterNameProvider; import br.com.caelum.vraptor.proxy.MethodInvocation; import br.com.caelum.vraptor.proxy.Proxifier; import br.com.caelum.vraptor.proxy.SuperMethod; import br.com.caelum.vraptor.resource.DefaultResourceMethod; import br.com.caelum.vraptor.resource.HttpMethod; import br.com.caelum.vraptor.resource.ResourceMethod; import br.com.caelum.vraptor.util.StringUtils; import br.com.caelum.vraptor.util.Stringnifier; import com.google.common.base.Joiner; /** * Should be used in one of two ways, either configure the type and invoke the * method or pass the method (java reflection) object. * * If not specified, the built route will have the lowest priority (higher value * of priority), so will be the last to be used. * * @author Guilherme Silveira */ public class DefaultRouteBuilder implements RouteBuilder { private final Set<HttpMethod> supportedMethods = EnumSet.noneOf(HttpMethod.class); private final Proxifier proxifier; private static final Logger logger = LoggerFactory.getLogger(DefaultRouteBuilder.class); private final String originalUri; private Route strategy = new NoStrategy(); private int priority = Path.LOWEST; private final DefaultParameterControlBuilder builder; private final TypeFinder finder; private final Converters converters; private final ParameterNameProvider nameProvider; private final Evaluator evaluator; public DefaultRouteBuilder(Proxifier proxifier, TypeFinder finder, Converters converters, ParameterNameProvider nameProvider, Evaluator evaluator, String uri) { this.proxifier = proxifier; this.finder = finder; this.converters = converters; this.nameProvider = nameProvider; this.evaluator = evaluator; this.originalUri = uri; builder = new DefaultParameterControlBuilder(); } public class DefaultParameterControlBuilder implements ParameterControlBuilder { private final Map<String, String> parameters = new HashMap<String, String>(); private String name; private DefaultParameterControlBuilder withParameter(String name) { this.name = name; return this; } public DefaultRouteBuilder ofType(Class<?> type) { parameters.put(name, regexFor(type)); return DefaultRouteBuilder.this; } @SuppressWarnings("unchecked") private String regexFor(Class<?> type) { if (Arrays.asList(Integer.class, Long.class, int.class, long.class, BigInteger.class, Short.class, short.class).contains(type)) { return "-?\\d+"; } else if (Arrays.asList(char.class, Character.class).contains(type)){ return "."; } else if (Arrays.asList(Double.class, BigDecimal.class, double.class, Float.class, float.class).contains( type)) { return "-?\\d*\\.?\\d+"; } else if (Arrays.asList(Boolean.class, boolean.class).contains(type)) { return "true|false"; } else if (Enum.class.isAssignableFrom(type)) { return Joiner.on("|").join(type.getEnumConstants()); } return "[^/]+"; } public DefaultRouteBuilder matching(String regex) { parameters.put(name, regex); return DefaultRouteBuilder.this; } private ParametersControl build() { return new DefaultParametersControl(originalUri, parameters, converters, evaluator); } } public DefaultParameterControlBuilder withParameter(String name) { return builder.withParameter(name); } public <T> T is(final Class<T> type) { MethodInvocation<T> handler = new MethodInvocation<T>() { public Object intercept(Object proxy, Method method, Object[] args, SuperMethod superMethod) { boolean alreadySetTheStrategy = !strategy.getClass().equals(NoStrategy.class); if (alreadySetTheStrategy) { // the virtual machine might be invoking the finalize return null; } is(type, method); return null; } }; return proxifier.proxify(type, handler); } public void is(Class<?> type, Method method) { addParametersInfo(method); ResourceMethod resourceMethod = DefaultResourceMethod.instanceFor(type, method); String[] parameterNames = nameProvider.parameterNamesFor(method); this.strategy = new FixedMethodStrategy(originalUri, resourceMethod, this.supportedMethods, builder.build(), priority, parameterNames); logger.info(String.format("%-50s%s -> %10s", originalUri, this.supportedMethods.isEmpty() ? "[ALL]" : this.supportedMethods, Stringnifier.simpleNameFor(method))); } private void addParametersInfo(Method method) { String[] parameters = StringUtils.extractParameters(originalUri); Map<String, Class<?>> types = finder.getParameterTypes(method, sanitize(parameters)); for (Entry<String, Class<?>> entry : types.entrySet()) { if (!builder.parameters.containsKey(entry.getKey())) { builder.withParameter(entry.getKey()).ofType(entry.getValue()); } } for (String parameter : parameters) { String[] split = parameter.split(":"); if (split.length >= 2 && !builder.parameters.containsKey(parameter)) { builder.withParameter(parameter).matching(split[1]); } } } private static String[] sanitize(String[] parameters) { String[] sanitized = new String[parameters.length]; for (int i = 0; i < parameters.length; i++) { sanitized[i] = parameters[i].replaceAll("(\\:.*|\\*)$", ""); } return sanitized; } /** * Accepts also this http method request. If this method is not invoked, any * http method is supported, otherwise all parameters passed are supported. * * @param method * @return */ public DefaultRouteBuilder with(HttpMethod method) { this.supportedMethods.add(method); return this; } public DefaultRouteBuilder with(Set<HttpMethod> methods) { this.supportedMethods.addAll(methods); return this; } /** * Changes Route priority * * @param priority * @return */ public DefaultRouteBuilder withPriority(int priority) { this.priority = priority; return this; } public Route build() { if (strategy instanceof NoStrategy) { throw new IllegalRouteException("You have created a route, but did not specify any method to be invoked: " + originalUri); } return strategy; } @Override public String toString() { if (supportedMethods.isEmpty()) { return String.format("<< Route: %s => %s >>", originalUri, this.strategy.toString()); } return String.format("<< Route: %s %s=> %s >>", originalUri, supportedMethods, this.strategy.toString()); } }