/** * Copyright 2010 Marko Lavikainen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * limitations under the License. */ package net.contextfw.web.application.internal.servlet; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.contextfw.web.application.PropertyProvider; import net.contextfw.web.application.WebApplicationException; import net.contextfw.web.application.component.Component; import net.contextfw.web.application.internal.initializer.InitializerProvider; import net.contextfw.web.application.internal.service.InitHandler; import net.contextfw.web.application.lifecycle.RequestInvocationFilter; import net.contextfw.web.application.lifecycle.View; import org.apache.commons.lang.StringUtils; public class UriMappingFactory { public static class Split { private final String value; private final String variableName; public Split(String value, String variableName) { this.value = value; this.variableName = variableName; } public String getValue() { return value; } public String getVariableName() { return variableName; } } private static final String SPLITTER_STR = "\\<[^<>]+>|[^<>]+"; private static final Pattern PATH_VERIFIER = Pattern.compile("(" + SPLITTER_STR + ")+"); private static final Pattern REGEX_VARIABLE_VERIFIER = Pattern.compile("^<\\w+(:.+)?>$"); private static final Pattern PATH_VARIABLE_VERIFIER = Pattern.compile("^<\\w+>$"); private static final Pattern PATH_SPLITTER = Pattern.compile(SPLITTER_STR); private static final String REGEX = "regex:"; public UriMapping getMapping(Class<? extends Component> viewClass, InitServlet initServlet, String path) { if (path.startsWith(REGEX)) { return getRegexMapping(viewClass, initServlet, path.substring(REGEX.length())); } else { return getPathStyleMapping(viewClass, initServlet, path); } } private String toEscapedRegex(String path) { return path.replaceAll("\\(", "\\\\(") .replaceAll("\\)", "\\\\)") .replaceAll("\\{", "\\\\{") .replaceAll("\\}", "\\\\}") .replaceAll("\\[", "\\\\[") .replaceAll("\\]", "\\\\]"); } private UriMapping getPathStyleMapping( Class<? extends Component> viewClass, InitServlet initServlet, String path) { List<Split> variableSplits = splitByVariables(toEscapedRegex(path), PATH_VARIABLE_VERIFIER); List<Split> pathSplits = splitByVariables(path, PATH_VARIABLE_VERIFIER); StringBuilder constructedPath = new StringBuilder(); for (Split split : pathSplits) { if (split.getVariableName() != null) { constructedPath.append("*"); } else { constructedPath.append(split.getValue()); } } return new PathStyleUriMapping(viewClass, constructedPath.toString(), initServlet, getVariables(variableSplits)); } private Map<String, Pattern> getVariables(List<Split> splits) { Map<String, Pattern> variables = new HashMap<String, Pattern>(); for (int i = 0; i < splits.size(); i++) { if (splits.get(i).getVariableName() != null) { variables.put(splits.get(i).getVariableName(), getVariableMatcher(splits, i)); } } return variables; } private Pattern getVariableMatcher(List<Split> splits, int pos) { StringBuilder before = new StringBuilder(""); StringBuilder after = new StringBuilder(""); for (int i = 0; i < pos; i++) { before.append(splits.get(i).getValue()); } for (int i = pos + 1; i < splits.size(); i++) { after.append(splits.get(i).getValue()); } return Pattern.compile( toNonCapturingMode(before.toString()) + "(" + splits.get(pos).getValue() + ")" + toNonCapturingMode(after.toString())); } private String toNonCapturingMode(String part) { StringBuilder sb = new StringBuilder(); String[] splits = part.split("\\\\\\("); String delim = ""; for (String split : splits) { sb.append(delim).append(split.replaceAll("\\(", "(?:")); delim = "\\("; } return sb.toString(); } private UriMapping getRegexMapping( Class<? extends Component> viewClass, InitServlet initServlet, String path) { List<Split> splits = splitByVariables(path, REGEX_VARIABLE_VERIFIER); StringBuilder constructedPath = new StringBuilder(); for (Split split : splits) { constructedPath.append(split.getValue()); } return new RegexUriMapping(viewClass, constructedPath.toString(), initServlet, getVariables(splits)); } private List<Split> splitByVariables(String path, Pattern variableVerifier) { if (!PATH_VERIFIER.matcher(path).matches()) { throw new WebApplicationException("Path '" + path + "' is not valid"); } List<Split> splits = new ArrayList<Split>(); Matcher matcher = PATH_SPLITTER.matcher(path); while (matcher.find()) { String group = matcher.group(); if (group.startsWith("<")) { splits.add(getVariableSplit(path, group, variableVerifier)); } else { splits.add(new Split(group, null)); } } return splits; } private Split getVariableSplit(String path, String split, Pattern verifier) { if (!verifier.matcher(split).matches()) { throw new WebApplicationException("Variable '" + split + "' in path '" + path + "' is not valid."); } String def = split.substring(1, split.length() - 1); String name = StringUtils.substringBefore(def, ":"); String value = StringUtils.substringAfter(def, ":"); if (StringUtils.isBlank(value)) { value = "[^/]+"; } return new Split(value, name); } @SuppressWarnings("unchecked") public SortedSet<UriMapping> createMappings( Collection<Class<?>> origClasses, ClassLoader classLoader, InitializerProvider initializerProvider, InitHandler initHandler, PropertyProvider properties, RequestInvocationFilter filter) { // Note: This process creates some phantom chains from // views that do not have any url. Those chains are // however ingnored and are not such problem. SortedSet<UriMapping> mappings = new TreeSet<UriMapping>(); try { for (Class<?> origClass : origClasses) { Class<?> cl = classLoader.loadClass(origClass.getCanonicalName()); View annotation = cl.getAnnotation(View.class); if (annotation != null) { if (!Component.class.isAssignableFrom(cl)) { throw new WebApplicationException("Class " + cl.getName() + " annotated with @View does " + "not extend Component"); } List<Class<? extends Component>> chain = initializerProvider.getInitializerChain(cl); InitServlet servlet = new InitServlet(initHandler, chain, filter); for (String url : annotation.url()) { if (!"".equals(url)) { mappings.add(this.getMapping((Class<? extends Component>) cl, servlet, url)); } } for (String property : annotation.property()) { if (!"".equals(property)) { if (!properties.get().containsKey(property)) { throw new WebApplicationException("No url bound to property: " + property); } String url = properties.get().getProperty(property); if (url != null && !"".equals(url)) { mappings.add(this.getMapping((Class<? extends Component>) cl, servlet, url)); } else { throw new WebApplicationException( "No url bound to view component. (class=" + cl.getSimpleName() + ", property=" + property + ")"); } } } } } } catch (ClassNotFoundException e) { throw new WebApplicationException(e); } return mappings; } }