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