2424import java .lang .reflect .Modifier ;
2525import java .util .ArrayList ;
2626import java .util .HashMap ;
27+ import java .util .LinkedHashMap ;
2728import java .util .List ;
2829import java .util .Map ;
2930import java .util .Optional ;
4849import org .jooby .mvc .Produces ;
4950import org .jooby .mvc .TRACE ;
5051
52+ import com .google .common .base .CaseFormat ;
5153import com .google .common .collect .ImmutableSet ;
5254
55+ import javaslang .control .Try ;
56+
5357public class MvcRoutes {
5458
5559 private static final String [] EMPTY = new String [0 ];
@@ -59,6 +63,14 @@ public class MvcRoutes {
5963 POST .class , PUT .class , DELETE .class , PATCH .class , HEAD .class , OPTIONS .class , TRACE .class ,
6064 CONNECT .class );
6165
66+ private static final Set <Class <? extends Annotation >> IGNORE = ImmutableSet
67+ .<Class <? extends Annotation >> builder ()
68+ .addAll (VERBS )
69+ .add (Path .class )
70+ .add (Produces .class )
71+ .add (Consumes .class )
72+ .build ();
73+
6274 @ SuppressWarnings ({"unchecked" , "rawtypes" })
6375 public static List <Route .Definition > routes (final Env env , final RouteMetadata classInfo ,
6476 final String rpath , final Class <?> routeClass ) {
@@ -87,7 +99,7 @@ public static List<Route.Definition> routes(final Env env, final RouteMetadata c
8799 }
88100
89101 List <Definition > definitions = new ArrayList <>();
90-
102+ Map < String , String > attrs = attrs ( routeClass . getAnnotations ());
91103 methods
92104 .keySet ()
93105 .stream ()
@@ -109,6 +121,8 @@ public static List<Route.Definition> routes(final Env env, final RouteMetadata c
109121 List <Class <?>> verbs = methods .get (method );
110122 List <MediaType > produces = produces (method );
111123 List <MediaType > consumes = consumes (method );
124+ Map <String , String > localAttrs = new HashMap <>(attrs );
125+ localAttrs .putAll (attrs (method .getAnnotations ()));
112126
113127 for (String path : expandPaths (rootPaths , method )) {
114128 for (Class <?> verb : verbs ) {
@@ -123,6 +137,7 @@ public static List<Route.Definition> routes(final Env env, final RouteMetadata c
123137 .excludes (excludes )
124138 .name (name );
125139
140+ localAttrs .forEach ((n , v ) -> definition .attr (n , v ));
126141 definitions .add (definition );
127142 }
128143 }
@@ -131,6 +146,36 @@ public static List<Route.Definition> routes(final Env env, final RouteMetadata c
131146 return definitions ;
132147 }
133148
149+ private static Map <String , String > attrs (final Annotation [] annotations ) {
150+ Map <String , String > result = new LinkedHashMap <>();
151+ for (Annotation annotation : annotations ) {
152+ result .putAll (attrs (annotation ));
153+ }
154+ return result ;
155+ }
156+
157+ private static Map <String , String > attrs (final Annotation annotation ) {
158+ Map <String , String > result = new LinkedHashMap <>();
159+ Class <? extends Annotation > annotationType = annotation .annotationType ();
160+ if (!IGNORE .contains (annotationType )) {
161+ Method [] attrs = annotation .annotationType ().getDeclaredMethods ();
162+ for (Method attr : attrs ) {
163+ Try .of (() -> attr .invoke (annotation ))
164+ .onSuccess (value -> result .put (attrName (annotation , attr ), value .toString ()));
165+ }
166+ }
167+ return result ;
168+ }
169+
170+ private static String attrName (final Annotation annotation , final Method attr ) {
171+ String name = attr .getName ();
172+ if (name .equals ("value" )) {
173+ return CaseFormat .UPPER_CAMEL .to (CaseFormat .LOWER_CAMEL ,
174+ annotation .annotationType ().getSimpleName ());
175+ }
176+ return name ;
177+ }
178+
134179 private static List <MediaType > produces (final Method method ) {
135180 Function <AnnotatedElement , Optional <List <MediaType >>> fn = (element ) -> {
136181 Produces produces = element .getAnnotation (Produces .class );
0 commit comments