package org.caudexorigo.jpt.web.netty.routing; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.caudexorigo.http.netty.HttpAction; import org.caudexorigo.http.netty.HttpRequestWrapper; import org.caudexorigo.http.netty.WebException; import org.caudexorigo.jpt.web.netty.NettyWebJptAction; import org.caudexorigo.jpt.web.netty.routing.namedregexp.NamedMatcher; import org.caudexorigo.jpt.web.netty.routing.namedregexp.NamedPattern; import org.jboss.netty.handler.codec.http.HttpMethod; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * RoutingManager * For more information read routing.readme * */ public class RoutingManager { private static final Logger log = LoggerFactory.getLogger(RoutingManager.class); private static Map<HttpMethod, Map<NamedPattern, String>> routes = new HashMap<HttpMethod, Map<NamedPattern, String>>(); private static Map<NamedPattern, String> reverseRoutes = new LinkedHashMap<NamedPattern, String>(); private List<HttpMethod> supportedMethods = new ArrayList<HttpMethod>(); private ArrayList<Pattern> inaccessiblePatterns = new ArrayList<Pattern>(); private final String basePath; private final String baseUrl; /** * * @param routesFile Refers to the configuration filepath * @param basePath Is the file system base path (eg. /servers/alerts/frontend/wwwroot/template) * @param baseUrl */ public RoutingManager(String routesFile, String basePath, String baseUrl) { this(SafeFileReader.getFileReader(routesFile), basePath, baseUrl); } /** * * @param reader Refers to the configuration 'file' * @param basePath The file system base path (eg. /servers/example_app/frontend/wwwroot/template) * @param baseUrl The web application base url (eg. http://www.example.com) */ public RoutingManager(Reader reader, String basePath, String baseUrl) { this.basePath = basePath; this.baseUrl = baseUrl; supportedMethods = new ArrayList<HttpMethod>(5); supportedMethods.add(HttpMethod.GET); supportedMethods.add(HttpMethod.POST); supportedMethods.add(HttpMethod.DELETE); supportedMethods.add(HttpMethod.PUT); supportedMethods.add(HttpMethod.HEAD); try { BufferedReader in = new BufferedReader(reader); String line; while ((line = in.readLine()) != null) { if (!StringUtils.isBlank(line)) { processLine(line); } } in.close(); } catch (IOException ioe) { log.error("Error processing routing file.", ioe); } } private void processLine(String line) { // Is comment? if (line.startsWith("#")) { return; } // Split String[] parts = line.split("\\s+"); // 3parts or denny if (parts.length != 3) { if ((parts.length == 2) && (parts[0].equals("DENY"))) { // DENY entry inaccessiblePatterns.add(Pattern.compile(parts[1])); return; } log.error("Invalid route '{}'", line); return; } // Create uri pattern String uriPattern = parts[1]; uriPattern = makePattern(uriPattern); if (uriPattern == null) { log.error("Invalid uri pattern '{}'", parts[1]); return; } // Create template relative path pattern String templatePattern = parts[2]; templatePattern = makePattern(templatePattern); if (uriPattern == null) { log.error("Invalid template pattern '{}'", parts[2]); return; } // Methods List<HttpMethod> methods = null; if (parts[0].equals("*")) { methods = supportedMethods; } else { methods = new ArrayList<HttpMethod>(0); methods.add(HttpMethod.valueOf(parts[0])); } // Add to routes and reverseRoutes for (HttpMethod method : methods) { Map<NamedPattern, String> route = routes.get(method); if (route == null) { route = new LinkedHashMap<NamedPattern, String>(); routes.put(method, route); } route.put(NamedPattern.compile(uriPattern), parts[2]); } reverseRoutes.put(NamedPattern.compile(templatePattern), parts[1]); } private String makePattern(String expression) { boolean searching = true; int strIndex = 0; StringBuilder sb = new StringBuilder(); while (searching) { int beginIndex = expression.indexOf('<', strIndex); if (beginIndex == -1) { break; } int endIndex = expression.indexOf('>', strIndex); if (endIndex == -1) { // Incorrect format return null; } sb.append(expression.substring(strIndex, beginIndex)); // text prior // to '<' String partId = expression.substring(beginIndex + 1, endIndex); // sb.append(String.format("(?<%s>(\\d|\\w)+)", partId)); sb.append(String.format("(?<%s>\\w+?)", partId)); strIndex = endIndex + 1; if (strIndex >= expression.length()) { searching = false; } } sb.append(expression.substring(strIndex, expression.length())); return sb.toString(); } /** * Given a file path template, returns a URL. * @param template File path template (eg. file.jpt) * @return an URL (eg. http://www.example.com/file) */ public String reverse(String template) { return reverse(template, Collections.EMPTY_MAP); } /** * Given a file path template, returns a URL. * @param template File path template (eg. file.jpt) * @param params Extra parameters (eg. action->editar) * @return An URL (eg. http://www.example.com/subscricao/editar/tempo). Note: if params was not defined the returned value whould be http://www.example.com/subscricao/<action>/tempo */ public String reverse(String template, Map<String, String> params) { HashMap<String, String> localDefaultValues = new HashMap<String, String>(params); String uriPath = null; for (NamedPattern uriPattern : reverseRoutes.keySet()) { NamedMatcher uriMatcher = uriPattern.matcher(template); if (uriMatcher.matches()) { // Start by replacing template path uriPath = reverseRoutes.get(uriPattern); for (String groupName : uriPattern.groupNames()) { String replacement = uriMatcher.group(groupName); uriPath = StringUtils.replace(uriPath, String.format("<%s>", groupName), replacement); template = StringUtils.replace(template, String.format("<%s>", groupName), replacement); localDefaultValues.remove(groupName); } break; } } for(String key : localDefaultValues.keySet()) { uriPath = StringUtils.replace(uriPath, String.format("<%s>", key), localDefaultValues.get(key)); } return (uriPath != null) ? baseUrl + uriPath : null; } /** * Maps a resource request to a HttpAction (NettyWebJptAction) or null. Parameters extracted from the the uri are added to the HttpRequestWrapper. * @param method HTTP Verb * @param uri Requested (eg. resource detalhes/astrologia) * @param request HttpRequestWrapper * @return HttpAction (NettyWebJptAction) or null */ public HttpAction map(HttpMethod method, String uri, HttpRequestWrapper request) { for (Pattern inaccessibleUri : inaccessiblePatterns) { Matcher matcher = inaccessibleUri.matcher(uri); if (matcher.matches()) { log.info("Forbidding access to '{}'", uri); throw new WebException(new RuntimeException("Access denied"), 403); } } Map<NamedPattern, String> patterns = routes.get(method); if (patterns == null) { return null; } String templatePath = null; for (NamedPattern uriPattern : patterns.keySet()) { NamedMatcher uriMatcher = uriPattern.matcher(uri); if (uriMatcher.matches()) { // Start by replacing template path templatePath = patterns.get(uriPattern); for (String groupName : uriPattern.groupNames()) { String replacement = uriMatcher.group(groupName); templatePath = StringUtils.replace(templatePath, String.format("<%s>", groupName), replacement); uri = StringUtils.replace(uri, String.format("<%s>", groupName), replacement); } // Use group names to added them to request parameters for (String gn : uriPattern.groupNames()) { String value = uriMatcher.group(gn); if (request != null) { request.addParameter(gn, value); } } break; } } if (templatePath != null) { URI p_uri = URI.create(basePath + templatePath); NettyWebJptAction action = new NettyWebJptAction(p_uri); action.setRequest(request); return action; } return null; } } class SafeFileReader { static FileReader getFileReader(String fileName) { try { return new FileReader(fileName); } catch (Exception e) { throw new RuntimeException(e); } } }