1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package net.anyflow.lannister.http;
18
19 import java.lang.annotation.ElementType;
20 import java.lang.annotation.Retention;
21 import java.lang.annotation.RetentionPolicy;
22 import java.lang.annotation.Target;
23 import java.net.URISyntaxException;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.stream.Stream;
27
28 import org.reflections.Reflections;
29
30 import com.google.common.collect.Maps;
31
32 import net.anyflow.lannister.Settings;
33
34 public abstract class HttpRequestHandler {
35
36 public static final String NO_WEB_RESOURCE_PATH = "none";
37
38 private static final Map<String, Class<? extends HttpRequestHandler>> HANDLER_CLASS_MAP = Maps.newHashMap();
39 private static Set<Class<? extends HttpRequestHandler>> REQUEST_HANDLER_CLASSES;
40 private static String REQUEST_HANDLER_PACKAGE_ROOT;
41
42 private HttpRequest request;
43 private HttpResponse response;
44
45 @Retention(RetentionPolicy.RUNTIME)
46 @Target({ ElementType.TYPE, ElementType.METHOD })
47 public @interface Handles {
48 String[] paths();
49
50 String[] httpMethods();
51
52 String webResourcePath() default NO_WEB_RESOURCE_PATH;
53 }
54
55 public abstract String service();
56
57 protected HttpRequestHandler initialize(HttpRequest request, HttpResponse response) throws URISyntaxException {
58 this.request = request;
59 this.response = response;
60
61 return this;
62 }
63
64 public HttpRequest httpRequest() {
65 return request;
66 }
67
68 public HttpResponse httpResponse() {
69 return response;
70 }
71
72 public static void setRequestHandlerPakcageRoot(String requestHandlerPakcageRoot) {
73 REQUEST_HANDLER_PACKAGE_ROOT = requestHandlerPakcageRoot;
74 }
75
76 protected static MatchedCriterion findRequestHandler(String requestedPath, String httpMethod) {
77 for (Map.Entry<String, Class<? extends HttpRequestHandler>> item : HANDLER_CLASS_MAP.entrySet()) {
78 MatchedCriterion mc = match(requestedPath, httpMethod, item.getKey());
79
80 if (mc.matched) {
81 mc.requestHandlerClass = HANDLER_CLASS_MAP.get(item.getKey());
82 return mc;
83 }
84 }
85
86 if (REQUEST_HANDLER_CLASSES == null) {
87 REQUEST_HANDLER_CLASSES = new Reflections(REQUEST_HANDLER_PACKAGE_ROOT)
88 .getSubTypesOf(HttpRequestHandler.class);
89 }
90
91 final ReturnWrapper wrapper = new ReturnWrapper();
92 REQUEST_HANDLER_CLASSES.stream().anyMatch(c -> {
93 HttpRequestHandler.Handles annotation = c.getAnnotation(HttpRequestHandler.Handles.class);
94 if (annotation == null) { return false; }
95
96 return Stream.of(annotation.httpMethods()).anyMatch(m -> {
97 if (!m.equalsIgnoreCase(httpMethod)) { return false; }
98
99 return Stream.of(annotation.paths()).anyMatch(p -> {
100 return wrapper.setValueIfMatch(requestedPath, c, m, p);
101 });
102 });
103 });
104
105 return wrapper.value == null ? new MatchedCriterion() : wrapper.value;
106 }
107
108 private static class ReturnWrapper {
109 private MatchedCriterion value = null;
110
111 private boolean setValueIfMatch(String requestedPath, Class<? extends HttpRequestHandler> handlerClass,
112 String methodToCheck, String pathToCheck) {
113 String path = (pathToCheck.charAt(0) == '/') ? pathToCheck
114 : Settings.INSTANCE.httpContextRoot() + pathToCheck;
115 String criterion = path + "/" + methodToCheck;
116
117 MatchedCriterion mc = match(requestedPath, methodToCheck, criterion);
118
119 if (mc.matched) {
120 HANDLER_CLASS_MAP.put(criterion, handlerClass);
121 mc.requestHandlerClass = handlerClass;
122 this.value = mc;
123
124 return true;
125 }
126 else {
127 return false;
128 }
129 };
130 }
131
132 protected static class MatchedCriterion {
133 private boolean matched = false;
134 private Class<? extends HttpRequestHandler> requestHandlerClass;
135 private String criterionPath;
136 private String criterionHttpMethod;
137 private final Map<String, String> pathParameters = Maps.newHashMap();
138
139 public Class<? extends HttpRequestHandler> requestHandlerClass() {
140 return requestHandlerClass;
141 }
142
143 public String criterionPath() {
144 return criterionPath;
145 }
146
147 public String criterionHttpMethod() {
148 return criterionHttpMethod;
149 }
150
151 public Map<String, String> pathParameters() {
152 return pathParameters;
153 }
154 }
155
156 private static MatchedCriterion match(String requestedPath, String httpMethod, String criterion) {
157 MatchedCriterion ret = new MatchedCriterion();
158
159 String testTarget = requestedPath + "/" + httpMethod;
160
161 String[] testTokens = testTarget.split("/");
162 String[] criterionTokens = criterion.split("/");
163
164 if (criterionTokens.length != testTokens.length) { return ret; }
165
166
167 for (int i = 1; i < criterionTokens.length; ++i) {
168 if (criterionTokens[i].startsWith("{") && criterionTokens[i].endsWith("}")) {
169 ret.pathParameters.put(criterionTokens[i].substring(1, criterionTokens[i].length() - 1), testTokens[i]);
170 }
171 else if (!criterionTokens[i].equalsIgnoreCase(testTokens[i])) { return ret; }
172 }
173
174 ret.matched = true;
175 ret.criterionHttpMethod = httpMethod;
176 ret.criterionPath = criterion.replace("/" + httpMethod, "");
177
178 return ret;
179 }
180 }