/*** * 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. */ package br.com.caelum.vraptor.http.route; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Predicates.instanceOf; import static com.google.common.base.Predicates.or; import static com.google.common.base.Strings.isNullOrEmpty; import static java.util.Arrays.asList; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import br.com.caelum.vraptor.Delete; import br.com.caelum.vraptor.Get; import br.com.caelum.vraptor.Options; import br.com.caelum.vraptor.Patch; import br.com.caelum.vraptor.Path; import br.com.caelum.vraptor.Post; import br.com.caelum.vraptor.Put; import br.com.caelum.vraptor.controller.BeanClass; import br.com.caelum.vraptor.controller.HttpMethod; import br.com.caelum.vraptor.core.ReflectionProvider; import br.com.caelum.vraptor.util.StringUtils; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; /** * The default parser routes creator uses the path annotation to create rules. * Note that methods are only registered to be public accessible if the type is * annotated with @Controller. * * If you want to override the convention for default URI, you can create a * class like: * * public class MyRoutesParser extends PathAnnotationRoutesParser { //delegate * constructor protected String extractControllerNameFrom(Class<?> type) { * return //your convention here } * * protected String defaultUriFor(String controllerName, String methodName) { * return //your convention here } } * * @author Guilherme Silveira * @author Lucas Cavalcanti */ @ApplicationScoped public class PathAnnotationRoutesParser implements RoutesParser { private final Router router; private ReflectionProvider reflectionProvider; /** * @deprecated CDI eyes only */ protected PathAnnotationRoutesParser() { this(null, null); } @Inject public PathAnnotationRoutesParser(Router router, ReflectionProvider reflectionProvider) { this.router = router; this.reflectionProvider = reflectionProvider; } @Override public List<Route> rulesFor(BeanClass controller) { Class<?> baseType = controller.getType(); return registerRulesFor(baseType); } protected List<Route> registerRulesFor(Class<?> baseType) { EnumSet<HttpMethod> typeMethods = getHttpMethods(baseType); List<Route> routes = new ArrayList<>(); for (Method javaMethod : baseType.getMethods()) { if (isEligible(javaMethod)) { String[] uris = getURIsFor(javaMethod, baseType); for (String uri : uris) { RouteBuilder rule = router.builderFor(uri); EnumSet<HttpMethod> methods = getHttpMethods(javaMethod); rule.with(methods.isEmpty() ? typeMethods : methods); if(javaMethod.isAnnotationPresent(Path.class)){ rule.withPriority(javaMethod.getAnnotation(Path.class).priority()); } if (getUris(javaMethod).length > 0) { rule.withPriority(Path.DEFAULT); } rule.is(baseType, javaMethod); routes.add(rule.build()); } } } return routes; } private EnumSet<HttpMethod> getHttpMethods(AnnotatedElement annotated) { EnumSet<HttpMethod> methods = EnumSet.noneOf(HttpMethod.class); for (HttpMethod method : HttpMethod.values()) { if (annotated.isAnnotationPresent(method.getAnnotation())) { methods.add(method); } } return methods; } protected boolean isEligible(Method javaMethod) { return Modifier.isPublic(javaMethod.getModifiers()) && !Modifier.isStatic(javaMethod.getModifiers()) && !javaMethod.isBridge() && !javaMethod.getDeclaringClass().equals(Object.class); } protected String[] getURIsFor(Method javaMethod, Class<?> type) { if (javaMethod.isAnnotationPresent(Path.class)) { String[] uris = javaMethod.getAnnotation(Path.class).value(); checkArgument(uris.length > 0, "You must specify at least one path on @Path at %s", javaMethod); checkArgument(getUris(javaMethod).length == 0, "You should specify paths either in @Path(\"/path\") or @Get(\"/path\") (or @Post, @Put, @Delete), not both at %s", javaMethod); fixURIs(type, uris); return uris; } String[] uris = getUris(javaMethod); if(uris.length > 0){ fixURIs(type, uris); return uris; } return new String[] { defaultUriFor(extractControllerNameFrom(type), javaMethod.getName()) }; } protected String[] getUris(Method javaMethod){ Annotation method = FluentIterable.from(asList(javaMethod.getAnnotations())) .filter(instanceOfMethodAnnotation()) .first().orNull(); if (method == null) { return new String[0]; } return (String[]) reflectionProvider.invoke(method, "value"); } protected void fixURIs(Class<?> type, String[] uris) { String prefix = extractPrefix(type); for (int i = 0; i < uris.length; i++) { if (isNullOrEmpty(prefix)) { uris[i] = fixLeadingSlash(uris[i]); } else if (isNullOrEmpty(uris[i])) { uris[i] = prefix; } else { uris[i] = removeTrailingSlash(prefix) + fixLeadingSlash(uris[i]); } } } protected String removeTrailingSlash(String prefix) { return prefix.replaceFirst("/$", ""); } protected String extractPrefix(Class<?> type) { if (type.isAnnotationPresent(Path.class)) { String[] uris = type.getAnnotation(Path.class).value(); checkArgument(uris.length == 1, "You must specify exactly one path on @Path at %s", type); return fixLeadingSlash(uris[0]); } else { return ""; } } private static String fixLeadingSlash(String uri) { if (!uri.startsWith("/")) { return "/" + uri; } return uri; } /** * You can override this method for use a different convention for your * controller name, given a type */ protected String extractControllerNameFrom(Class<?> type) { String prefix = extractPrefix(type); if (isNullOrEmpty(prefix)) { String baseName = StringUtils.lowercaseFirst(type.getSimpleName()); if (baseName.endsWith("Controller")) { return "/" + baseName.substring(0, baseName.lastIndexOf("Controller")); } return "/" + baseName; } else { return prefix; } } /** * You can override this method for use a different convention for your * default URI, given a controller name and a method name */ protected String defaultUriFor(String controllerName, String methodName) { return controllerName + "/" + methodName; } private Predicate<Annotation> instanceOfMethodAnnotation() { return or(instanceOf(Get.class), instanceOf(Post.class), instanceOf(Put.class), instanceOf(Delete.class), instanceOf(Options.class), instanceOf(Patch.class)); } }