1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.exoplatform.commons.notification.net.router;
18
19 import java.io.UnsupportedEncodingException;
20 import java.net.URLEncoder;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.concurrent.CopyOnWriteArrayList;
26 import java.util.regex.Pattern;
27
28 import org.exoplatform.commons.notification.net.router.regex.ExoMatcher;
29 import org.exoplatform.commons.notification.net.router.regex.ExoPattern;
30 import org.exoplatform.services.log.ExoLogger;
31 import org.exoplatform.services.log.Log;
32 import org.picocontainer.Startable;
33
34 public class ExoRouter implements Startable {
35
36
37
38
39 private static final Log LOG = ExoLogger.getLogger(ExoRouter.class);
40
41 static ExoPattern defaultRoutePattern = ExoPattern.compile("^({path}.*/[^\\s]*)\\s+({action}[^\\s(]+)({params}.+)?(\\s*)$");
42
43
44
45
46 public static List<Route> routes = new CopyOnWriteArrayList<Route>();
47
48 private ExoRouterConfig routerConfig;
49
50 public static void reset() {
51 routes.clear();
52 }
53
54 public ExoRouter() {}
55
56 public void addRoutes(ExoRouterConfig routeConfig) {
57 this.routerConfig = routeConfig;
58 Map<String, String> routeMapping = this.routerConfig.getRouteMapping();
59
60 for(Map.Entry<String, String> entry : routeMapping.entrySet()) {
61 addRoute(entry.getValue(), entry.getKey());
62 }
63 }
64
65
66
67
68
69
70
71
72 public static void addRoute(String path, String action) {
73 addRoute(path, action, null);
74 }
75
76
77
78
79
80
81
82
83
84 public static void addRoute(String path, String action, String params) {
85 appendRoute(path, action, params);
86 }
87
88 public static void appendRoute(String path, String action, String params) {
89 int position = routes.size();
90 routes.add(position, getRoute(path, action, params));
91 }
92
93 public static Route getRoute(String path, String action, String params) {
94 return getRoute(path, action, params, null, 0);
95 }
96
97 public static Route getRoute(String path, String action) {
98 return getRoute(path, action, null, null, 0);
99 }
100
101 public static Route getRoute(String path, String action, String params, String sourceFile, int line) {
102 Route route = new Route();
103 route.path = path.replace("//", "/");
104 route.action = action;
105 route.routesFile = sourceFile;
106 route.routesFileLine = line;
107 route.addParams(params);
108 route.compute();
109 return route;
110 }
111
112
113
114
115 public static void prependRoute(String path, String action, String params) {
116 routes.add(0, getRoute(path, action, params));
117 }
118
119
120
121
122 public static void prependRoute(String path, String action) {
123 routes.add(0, getRoute(path, action));
124 }
125
126 public static Route route(String path) {
127 for (Route route : routes) {
128 Map<String, String> args = route.matches(path);
129 if (args != null) {
130 route.localArgs = args;
131 return route;
132 }
133 }
134
135 return null;
136 }
137
138
139
140
141
142
143
144
145
146 public static ActionBuilder reverse(String action, Map<String, Object> args) {
147 Map<String, Object> argsbackup = new HashMap<String, Object>(args);
148
149 for (Route route : routes) {
150 if (route.actionPattern != null) {
151 ExoMatcher matcher = route.actionPattern.matcher(action);
152 if (matcher.matches()) {
153 for (String group : route.actionArgs) {
154 String v = matcher.group(group);
155 if (v == null) {
156 continue;
157 }
158 args.put(group, v.toLowerCase());
159 }
160 List<String> inPathArgs = new ArrayList<String>(16);
161 boolean allRequiredArgsAreHere = true;
162
163 for (Route.ParamArg arg : route.args) {
164 inPathArgs.add(arg.name);
165 Object value = args.get(arg.name);
166 if (value != null) {
167 if (!value.toString().startsWith(":") && !arg.constraint.matches(value.toString())) {
168 allRequiredArgsAreHere = false;
169 break;
170 }
171 }
172 }
173 if (allRequiredArgsAreHere) {
174 StringBuilder queryString = new StringBuilder();
175 String path = route.path;
176 if (path.endsWith("/?")) {
177 path = path.substring(0, path.length() - 2);
178 }
179 for (Map.Entry<String, Object> entry : args.entrySet()) {
180 String key = entry.getKey();
181 Object value = entry.getValue();
182 if (inPathArgs.contains(key) && value != null) {
183 path = path.replaceAll("\\{(<[^>]+>)?" + key + "\\}", value.toString().replace("$", "\\$").replace("%3A", ":").replace("%40", "@"));
184 } else if (value != null) {
185 try {
186 queryString.append(URLEncoder.encode(key, "UTF-8"));
187 queryString.append("=");
188 if (value.toString().startsWith(":")) {
189 queryString.append(value.toString());
190 } else {
191 queryString.append(URLEncoder.encode(value.toString() + "", "UTF-8"));
192 }
193 queryString.append("&");
194 } catch (UnsupportedEncodingException ex) {
195 LOG.debug("Unsupported encoding error: " + ex);
196 }
197
198 }
199 }
200 String qs = queryString.toString();
201 if (qs.endsWith("&")) {
202 qs = qs.substring(0, qs.length() - 1);
203 }
204 ActionBuilder actionDefinition = new ActionBuilder();
205 actionDefinition.url = qs.length() == 0 ? path : path + "?" + qs;
206 actionDefinition.action = action;
207 actionDefinition.args = argsbackup;
208 return actionDefinition;
209 }
210 }
211 }
212 }
213 return null;
214 }
215
216 public static class ActionBuilder {
217 public String url;
218
219
220 public String action;
221
222
223 public Map<String, Object> args;
224
225 public ActionBuilder add(String key, Object value) {
226 args.put(key, value);
227 return reverse(action, args);
228 }
229
230 public ActionBuilder remove(String key) {
231 args.remove(key);
232 return reverse(action, args);
233 }
234
235 public ActionBuilder addRef(String fragment) {
236 url += "#" + fragment;
237 return this;
238 }
239
240 @Override
241 public String toString() {
242 return url;
243 }
244
245 }
246
247
248
249
250
251
252
253 public static class Route {
254
255 public String path;
256
257 public String action;
258
259 ExoPattern actionPattern;
260
261 List<String> actionArgs = new ArrayList<String>(3);
262
263 ExoPattern pattern;
264
265 public String routesFile;
266
267 List<ParamArg> args = new ArrayList<ParamArg>(3);
268
269 Map<String, String> staticArgs = new HashMap<String, String>(3);
270
271 public Map<String, String> localArgs = null;
272
273 public int routesFileLine;
274
275 static ExoPattern customRegexPattern = ExoPattern.compile("\\{([a-zA-Z_][a-zA-Z_0-9]*)\\}");
276
277 static ExoPattern argsPattern = ExoPattern.compile("\\{<([^>]+)>([a-zA-Z_0-9]+)\\}");
278
279 static ExoPattern paramPattern = ExoPattern.compile("([a-zA-Z_0-9]+):'(.*)'");
280
281 public void compute() {
282 String patternString = this.path;
283 patternString = customRegexPattern.matcher(patternString).replaceAll("\\{<[^/]+>$1\\}");
284 ExoMatcher matcher = argsPattern.matcher(patternString);
285 while (matcher.find()) {
286 ParamArg arg = new ParamArg();
287 arg.name = matcher.group(2);
288 arg.constraint = ExoPattern.compile(matcher.group(1));
289 args.add(arg);
290 }
291
292 patternString = argsPattern.matcher(patternString).replaceAll("({$2}$1)");
293 this.pattern = ExoPattern.compile(patternString);
294
295 patternString = action;
296 patternString = patternString.replace(".", "[.]");
297 for (ParamArg arg : args) {
298 if (patternString.contains("{" + arg.name + "}")) {
299 patternString = patternString.replace("{" + arg.name + "}", "({" + arg.name + "}" + arg.constraint.toString() + ")");
300 actionArgs.add(arg.name);
301 }
302 }
303 actionPattern = ExoPattern.compile(patternString, Pattern.CASE_INSENSITIVE);
304 }
305
306 public void addParams(String params) {
307 if (params == null || params.length() < 1) {
308 return;
309 }
310 params = params.substring(1, params.length() - 1);
311 for (String param : params.split(",")) {
312 ExoMatcher matcher = paramPattern.matcher(param);
313 if (matcher.matches()) {
314 staticArgs.put(matcher.group(1), matcher.group(2));
315 } else {
316 LOG.warn("Ignoring %s (static params must be specified as key:'value',...)");
317 }
318 }
319 }
320
321
322
323
324
325
326
327
328
329
330
331
332
333 public Map<String, String> matches(String path) {
334 ExoMatcher matcher = pattern.matcher(path);
335 if (matcher.matches()) {
336 Map<String, String> localArgs = new HashMap<String, String>();
337 for (ParamArg arg : args) {
338 if (arg.defaultValue == null) {
339 localArgs.put(arg.name, matcher.group(arg.name));
340 }
341 }
342 return localArgs;
343 }
344
345 return null;
346 }
347
348 static class ParamArg {
349 String name;
350
351 ExoPattern constraint;
352
353 String defaultValue;
354 }
355 }
356
357 @Override
358 public void start() {
359
360 }
361
362 @Override
363 public void stop() {
364
365 }
366
367 }