Author: norman.richards(a)jboss.com
Date: 2008-02-26 14:18:37 -0500 (Tue, 26 Feb 2008)
New Revision: 7488
Added:
trunk/src/main/org/jboss/seam/web/IncomingPattern.java
trunk/src/main/org/jboss/seam/web/OutgoingPattern.java
trunk/src/main/org/jboss/seam/web/Pattern.java
trunk/src/main/org/jboss/seam/web/Rewrite.java
trunk/src/main/org/jboss/seam/web/RewriteFilter.java
trunk/src/main/org/jboss/seam/web/RewritingResponse.java
trunk/src/test/unit/org/jboss/seam/test/unit/web/RewriteTest.java
Modified:
trunk/src/test/unit/org/jboss/seam/test/unit/testng.xml
Log:
JBSEAM-274
Added: trunk/src/main/org/jboss/seam/web/IncomingPattern.java
===================================================================
--- trunk/src/main/org/jboss/seam/web/IncomingPattern.java (rev
0)
+++ trunk/src/main/org/jboss/seam/web/IncomingPattern.java 2008-02-26 19:18:37 UTC (rev
7488)
@@ -0,0 +1,149 @@
+package org.jboss.seam.web;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+
+public class IncomingPattern {
+ String view;
+ String pattern;
+
+ java.util.regex.Pattern regexp;
+ List<String> regexpArgs = new ArrayList<String>();
+
+ public IncomingPattern(String view, String pattern) {
+ this.view = view;
+ this.pattern = pattern;
+
+ parsePattern(pattern);
+ }
+
+ public Rewrite rewrite(String path) {
+ return new IncomingRewrite(path);
+ }
+
+ public void parsePattern(String value) {
+ StringBuffer expr = new StringBuffer();
+
+ expr.append("^");
+ while (value.length()>0) {
+ int pos = value.indexOf('{');
+ if (pos == -1) {
+ expr.append(regexpLiteral(value));
+ value = "";
+ } else {
+ int pos2 = value.indexOf('}');
+ if (pos2 == -1) {
+ throw new IllegalArgumentException("invalid pattern");
+ }
+ expr.append(regexpLiteral(value.substring(0,pos)));
+ String arg = value.substring(pos+1,pos2);
+ expr.append(regexpArg(arg));
+ regexpArgs.add(arg);
+ value = value.substring(pos2+1);
+ }
+ }
+ expr.append("$");
+
+ regexp = java.util.regex.Pattern.compile(expr.toString());
+ }
+
+ private String regexpArg(String substring) {
+ return "([^/]+)";
+ }
+
+ private String regexpLiteral(String value) {
+ StringBuffer res = new StringBuffer();
+ for (int i=0; i<value.length(); i++) {
+ char c = value.charAt(i);
+
+ if (Character.isLetterOrDigit(c)) {
+ res.append(c);
+ } else {
+ res.append('\\').append(c);
+ }
+ }
+ return res.toString();
+ }
+
+
+ public class IncomingRewrite
+ implements Rewrite
+ {
+ String incoming;
+ String queryArgs;
+
+ Boolean isMatch = null;
+ private List<String> matchedArgs = new ArrayList<String>();
+
+ public IncomingRewrite(String incoming) {
+ int queryPos = incoming.indexOf('?');
+
+ if (queryPos == -1) {
+ this.incoming = incoming;
+ this.queryArgs = "";
+ } else {
+ this.incoming = incoming.substring(0, queryPos);
+ this.queryArgs = incoming.substring(queryPos+1);
+ }
+
+ this.incoming = stripTrailingSlash(this.incoming);
+ }
+
+ private String stripTrailingSlash(String text) {
+ if (text.endsWith("/")) {
+ return stripTrailingSlash(text.substring(0,text.length()-1));
+ }
+ return text;
+ }
+
+ public boolean isMatch() {
+ if (isMatch == null) {
+ isMatch = match();
+ }
+ return isMatch;
+ }
+
+ protected boolean match() {
+ if (incoming==null) {
+ return false;
+ }
+
+ Matcher matcher = regexp.matcher(incoming);
+ if (matcher.find()) {
+ for (int i=0; i<regexpArgs.size(); i++) {
+ matchedArgs.add(matcher.group(i+1));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public String rewrite() {
+ StringBuffer result = new StringBuffer();
+ result.append(view);
+
+ boolean first = true;
+
+ if (queryArgs.length()>0) {
+ result.append('?').append(queryArgs);
+ first = false;
+ }
+
+ for (int i=0; i<regexpArgs.size(); i++) {
+ String key = regexpArgs.get(i);
+ String value = matchedArgs.get(i);
+
+ if (first) {
+ result.append('?');
+ first = false;
+ } else {
+ result.append('&');
+ }
+ result.append(key).append('=').append(value);
+ }
+
+ return result.toString();
+ }
+ }
+}
Added: trunk/src/main/org/jboss/seam/web/OutgoingPattern.java
===================================================================
--- trunk/src/main/org/jboss/seam/web/OutgoingPattern.java (rev
0)
+++ trunk/src/main/org/jboss/seam/web/OutgoingPattern.java 2008-02-26 19:18:37 UTC (rev
7488)
@@ -0,0 +1,129 @@
+package org.jboss.seam.web;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class OutgoingPattern {
+ String view;
+ String pattern;
+
+ List<String> parts = new ArrayList<String>();
+
+ public OutgoingPattern(String view, String pattern) {
+ this.view = view;
+ this.pattern = pattern;
+
+ parsePattern(pattern);
+ }
+
+ public Rewrite rewrite(String path) {
+ return new OutgoingRewrite(path);
+ }
+
+ private void parsePattern(String value) {
+ while (value.length()>0) {
+ int pos = value.indexOf('{');
+ if (pos == -1) {
+ parts.add(value);
+ value = "";
+ } else {
+ int pos2 = value.indexOf('}');
+ if (pos2 == -1) {
+ throw new IllegalArgumentException("invalid pattern");
+ }
+ parts.add(value.substring(0,pos));
+ parts.add(value.substring(pos,pos2+1));
+ value = value.substring(pos2+1);
+ }
+ }
+ }
+
+ public class OutgoingRewrite
+ implements Rewrite
+ {
+ Boolean isMatch;
+
+ private String base;
+ private List<String> queryArgs = new ArrayList<String>();
+ private List<String> matchedArgs = new ArrayList<String>();
+
+ public OutgoingRewrite(String outgoing) {
+ int queryPos = outgoing.indexOf('?');
+
+ if (queryPos == -1) {
+ this.base = outgoing;
+ } else {
+ this.base = outgoing.substring(0, queryPos);
+ parseArgs(outgoing.substring(queryPos+1));
+ }
+ }
+
+ private void parseArgs(String text) {
+ for (String part: text.split("\\&")) {
+ queryArgs.add(part);
+ }
+ }
+
+ public boolean isMatch() {
+ if (isMatch == null) {
+ isMatch = match();
+ }
+ return isMatch;
+ }
+
+ private boolean match() {
+ if (!base.equals(view)) {
+ return false;
+ }
+
+ for (String part: parts) {
+ if (part.startsWith("{") &&
part.endsWith("}")) {
+ String name = part.substring(1,part.length()-1);
+ String value = matchArg(name);
+
+ if (value == null) {
+ return false;
+ }
+
+ matchedArgs.add(value);
+ }
+ }
+
+ return true;
+ }
+
+ private String matchArg(String argName) {
+ for (int i=0; i<queryArgs.size(); i++) {
+ String query = queryArgs.get(i);
+ int pos = query.indexOf("=");
+
+ if (query.subSequence(0, pos).equals(argName)) {
+ queryArgs.remove(i);
+ return query.substring(pos+1);
+ }
+ }
+ return null;
+ }
+
+ public String rewrite() {
+ StringBuffer res = new StringBuffer();
+
+ int matchedPosition = 0;
+ for (String part: parts) {
+ if (part.startsWith("{")) {
+ res.append(matchedArgs.get(matchedPosition++));
+ } else {
+ res.append(part);
+ }
+ }
+
+ char sep = '?';
+ for (String arg: queryArgs) {
+ res.append(sep).append(arg);
+ sep = '&';
+ }
+
+ return res.toString();
+ }
+ }
+}
Added: trunk/src/main/org/jboss/seam/web/Pattern.java
===================================================================
--- trunk/src/main/org/jboss/seam/web/Pattern.java (rev 0)
+++ trunk/src/main/org/jboss/seam/web/Pattern.java 2008-02-26 19:18:37 UTC (rev 7488)
@@ -0,0 +1,35 @@
+package org.jboss.seam.web;
+
+public class Pattern
+{
+ String view;
+ String pattern;
+
+ IncomingPattern inPattern;
+ OutgoingPattern outPattern;
+
+ public Pattern(String view, String pattern) {
+ this.view = view;
+ this.pattern = pattern;
+
+ inPattern = new IncomingPattern(view, pattern);
+ outPattern = new OutgoingPattern(view, pattern);
+ }
+
+ public Rewrite matchIncoming(String path) {
+ return returnIfMatch(inPattern.rewrite(path));
+ }
+
+ public Rewrite matchOutgoing(String path) {
+ return returnIfMatch(outPattern.rewrite(path));
+ }
+
+ @Override
+ public String toString() {
+ return "Pattern(" + view + ":" + pattern + ")";
+ }
+
+ private Rewrite returnIfMatch(Rewrite rewrite) {
+ return rewrite.isMatch() ? rewrite : null;
+ }
+}
Added: trunk/src/main/org/jboss/seam/web/Rewrite.java
===================================================================
--- trunk/src/main/org/jboss/seam/web/Rewrite.java (rev 0)
+++ trunk/src/main/org/jboss/seam/web/Rewrite.java 2008-02-26 19:18:37 UTC (rev 7488)
@@ -0,0 +1,6 @@
+package org.jboss.seam.web;
+
+public interface Rewrite {
+ public boolean isMatch();
+ public String rewrite();
+}
Added: trunk/src/main/org/jboss/seam/web/RewriteFilter.java
===================================================================
--- trunk/src/main/org/jboss/seam/web/RewriteFilter.java (rev 0)
+++ trunk/src/main/org/jboss/seam/web/RewriteFilter.java 2008-02-26 19:18:37 UTC (rev
7488)
@@ -0,0 +1,120 @@
+package org.jboss.seam.web;
+
+import static org.jboss.seam.ScopeType.APPLICATION;
+import static org.jboss.seam.annotations.Install.BUILT_IN;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.Map.Entry;
+
+import javax.servlet.FilterChain;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.jboss.seam.annotations.Install;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.annotations.intercept.BypassInterceptors;
+import org.jboss.seam.annotations.web.Filter;
+import org.jboss.seam.log.LogProvider;
+import org.jboss.seam.log.Logging;
+
+@Scope(APPLICATION)
+(a)Name("org.jboss.seam.web.rewriteFilter")
+@Install(precedence = BUILT_IN,
classDependencies="javax.faces.context.FacesContext")
+@BypassInterceptors
+(a)Filter(around="org.jboss.seam.web.HotDeployFilter")
+public class RewriteFilter
+ extends AbstractFilter
+{
+ private static LogProvider log = Logging.getLogProvider(RewriteFilter.class);
+
+ Collection<Pattern> patterns = null;
+
+ // need to extract this from Pages!
+ public void setPatterns(Map<String,String> patternMap) {
+ patterns = new TreeSet<Pattern>(new Comparator<Pattern>() {
+ public int compare(Pattern p1, Pattern p2) {
+ return p2.pattern.compareTo(p1.pattern);
+ }
+ });
+
+ for(Entry<String, String> entry: patternMap.entrySet()) {
+ patterns.add(new Pattern(entry.getValue(), entry.getKey()));
+ }
+
+ log.info("Rewrite patterns: " + patterns);
+ }
+
+ public void doFilter(ServletRequest request,
+ ServletResponse response,
+ FilterChain chain)
+ throws IOException,
+ ServletException
+ {
+ if (patterns != null) {
+ if (request instanceof HttpServletRequest && response instanceof
HttpServletResponse) {
+ response = new RewritingResponse((HttpServletRequest) request,
+ (HttpServletResponse)response,
+ patterns);
+ process((HttpServletRequest) request, (HttpServletResponse) response);
+ }
+ }
+
+ if (!response.isCommitted()) {
+ chain.doFilter(request, response);
+ }
+ }
+
+
+ @SuppressWarnings("unchecked")
+ public void process(HttpServletRequest request,
+ HttpServletResponse response)
+ throws IOException,
+ ServletException
+ {
+ String fullPath = request.getRequestURI();
+ log.info("incoming URL is " + fullPath);
+
+ String localPath = strip(fullPath, request.getContextPath());
+
+ Rewrite rewrite = matchPatterns(localPath);
+ if (rewrite!=null) {
+ String newPath = rewrite.rewrite();
+
+ log.info("rewritten incoming path is " + localPath);
+
+ if (!fullPath.equals(request.getContextPath() + newPath)) {
+ RequestDispatcher dispatcher = request.getRequestDispatcher(newPath);
+ dispatcher.forward(request, response);
+ }
+ }
+ }
+
+
+ private Rewrite matchPatterns(String localPath) {
+ for (Pattern pattern: patterns) {
+ Rewrite rewrite = pattern.matchIncoming(localPath);
+ if (rewrite!=null && rewrite.isMatch()) {
+ return rewrite;
+ }
+ }
+ return null;
+ }
+
+ private String strip(String fullPath, String contextPath) {
+ if (fullPath.startsWith(contextPath)) {
+ return fullPath.substring(contextPath.length());
+ } else {
+ return fullPath;
+ }
+ }
+}
+
Added: trunk/src/main/org/jboss/seam/web/RewritingResponse.java
===================================================================
--- trunk/src/main/org/jboss/seam/web/RewritingResponse.java (rev
0)
+++ trunk/src/main/org/jboss/seam/web/RewritingResponse.java 2008-02-26 19:18:37 UTC (rev
7488)
@@ -0,0 +1,76 @@
+package org.jboss.seam.web;
+
+import java.util.Collection;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.jboss.seam.log.LogProvider;
+import org.jboss.seam.log.Logging;
+
+public class RewritingResponse
+ extends HttpServletResponseWrapper
+{
+ private static LogProvider log = Logging.getLogProvider(RewritingResponse.class);
+
+ private HttpServletRequest request;
+ private HttpServletResponse response;
+ private Collection<Pattern> patterns;
+
+
+ public RewritingResponse(HttpServletRequest request,
+ HttpServletResponse response,
+ Collection<Pattern> patterns)
+ {
+ super(response);
+
+ this.request = request;
+ this.response = response;
+ this.patterns = patterns;
+ }
+
+ @Override
+ public String encodeUrl(String url) {
+ return super.encodeURL(url);
+ }
+
+ @Override
+ public String encodeRedirectUrl(String url) {
+ return encodeRedirectURL(url);
+ }
+
+
+ @Override
+ public String encodeURL(String url) {
+ String result = encode(url);
+ log.info("encodeURL " + url + " -> " + result);
+ return result;
+ }
+
+ @Override
+ public String encodeRedirectURL(String url) {
+ log.info("encode redirectURL called with " + url);
+ return super.encodeRedirectURL(url);
+ }
+
+
+ public String encode(String originalUrl) {
+ String url = originalUrl;
+ String contextPath = request.getContextPath();
+
+ if (url.startsWith(contextPath)) {
+ url = url.substring(contextPath.length());
+ }
+
+ for (Pattern pattern: patterns) {
+ Rewrite rewrite = pattern.matchOutgoing(url);
+ if (rewrite != null) {
+ return request.getContextPath() + rewrite.rewrite();
+ }
+ }
+
+ return originalUrl;
+ }
+
+}
Modified: trunk/src/test/unit/org/jboss/seam/test/unit/testng.xml
===================================================================
--- trunk/src/test/unit/org/jboss/seam/test/unit/testng.xml 2008-02-26 18:46:10 UTC (rev
7487)
+++ trunk/src/test/unit/org/jboss/seam/test/unit/testng.xml 2008-02-26 19:18:37 UTC (rev
7488)
@@ -62,4 +62,11 @@
<class name="org.jboss.seam.test.unit.InterpolatorTest"/>
</classes>
</test>
+
+ <test name="Seam Unit Tests: URL Rewrite">
+ <classes>
+ <class name="org.jboss.seam.test.unit.web.RewriteTest" />
+ </classes>
+ </test>
+
</suite>
Added: trunk/src/test/unit/org/jboss/seam/test/unit/web/RewriteTest.java
===================================================================
--- trunk/src/test/unit/org/jboss/seam/test/unit/web/RewriteTest.java
(rev 0)
+++ trunk/src/test/unit/org/jboss/seam/test/unit/web/RewriteTest.java 2008-02-26 19:18:37
UTC (rev 7488)
@@ -0,0 +1,127 @@
+package org.jboss.seam.test.unit.web;
+
+import org.testng.annotations.Test;
+
+import org.jboss.seam.web.Pattern;
+import org.jboss.seam.web.Rewrite;
+
+import static org.testng.Assert.*;
+
+public class RewriteTest
+{
+ @Test
+ public void testBasicInPattern()
+ throws Exception
+ {
+ Pattern pattern = new Pattern("/foo.seam", "/foo");
+
+ testNoMatchIn(pattern, "/bar");
+ testNoMatchIn(pattern, "/fool");
+ testNoMatchIn(pattern, "/foo.seam");
+ testNoMatchIn(pattern, "/foo/bar");
+
+ testMatchIn(pattern, "/foo", "/foo.seam");
+ testMatchIn(pattern, "/foo/", "/foo.seam");
+ testMatchIn(pattern, "/foo?x=y", "/foo.seam?x=y");
+ }
+
+ @Test
+ public void testSingleArgInPattern()
+ throws Exception
+ {
+ Pattern pattern = new Pattern("/foo.seam", "/foo/{id}");
+
+ testNoMatchIn(pattern, "/foo");
+ testNoMatchIn(pattern, "/foo/");
+ testNoMatchIn(pattern, "/foo/bar/baz");
+ testNoMatchIn(pattern, "/foo/bar/baz?x=y");
+
+ testMatchIn(pattern, "/foo/bar", "/foo.seam?id=bar");
+ testMatchIn(pattern, "/foo/bar?x=y",
"/foo.seam?x=y&id=bar");
+ testMatchIn(pattern, "/foo/bar/?x=y",
"/foo.seam?x=y&id=bar");
+ }
+
+ @Test
+ public void testMultiArgInPattern()
+ throws Exception
+ {
+ Pattern pattern = new Pattern("/foo.seam",
"/foo/{id}/{action}");
+
+ testNoMatchIn(pattern, "/foo");
+ testNoMatchIn(pattern, "/foo/bar");
+ testNoMatchIn(pattern, "/foo/bar/baz/qux");
+
+ testMatchIn(pattern, "/foo/bar/baz",
"/foo.seam?id=bar&action=baz");
+ testMatchIn(pattern, "/foo/bar/baz?x=y",
"/foo.seam?x=y&id=bar&action=baz");
+ }
+
+
+ @Test
+ public void testBasicOutPattern()
+ throws Exception
+ {
+ Pattern pattern = new Pattern("/foo.seam", "/foo");
+
+ testNoMatchOut(pattern, "/bar.seam");
+ testNoMatchOut(pattern, "/fool.seam");
+ testNoMatchOut(pattern, "/foo");
+
+ testMatchOut(pattern, "/foo.seam", "/foo");
+ testMatchOut(pattern, "/foo.seam?x=y", "/foo?x=y");
+ }
+
+
+ @Test
+ public void testSingleArgOutPattern()
+ throws Exception
+ {
+ Pattern pattern = new Pattern("/foo.seam", "/foo/{id}");
+
+ testNoMatchOut(pattern, "/foo.seam");
+ testNoMatchOut(pattern, "/foo.seam?x=y");
+ testNoMatchOut(pattern, "/foo.seam/bar");
+ testNoMatchOut(pattern, "/foo.seam/bar?id=test");
+
+ testMatchOut(pattern, "/foo.seam?id=bar", "/foo/bar");
+ testMatchOut(pattern, "/foo.seam?x=y&id=bar",
"/foo/bar?x=y");
+ testMatchOut(pattern, "/foo.seam?id=bar&x=y",
"/foo/bar?x=y");
+ testMatchOut(pattern,
"/foo.seam?a=b&x=y&id=bar&c=d&c=e",
"/foo/bar?a=b&x=y&c=d&c=e");
+ }
+
+ @Test
+ public void testMultiArgOutPattern()
+ throws Exception
+ {
+ Pattern pattern = new Pattern("/foo.seam",
"/foo/{id}/{action}");
+
+ testNoMatchOut(pattern, "/foo.seam");
+ testNoMatchOut(pattern, "/foo.seam?id=bar");
+ testNoMatchOut(pattern, "/foo.seam?action=baz");
+
+ testMatchOut(pattern, "/foo.seam?action=baz&id=bar",
"/foo/bar/baz");
+ testMatchOut(pattern,
"/foo.seam?y=z&action=baz&n=one&n=two&id=bar&x=y",
"/foo/bar/baz?y=z&n=one&n=two&x=y");
+ }
+
+
+ public void testNoMatchIn(Pattern pattern, String incoming) {
+ assertNull(pattern.matchIncoming(incoming), incoming);
+ }
+
+ public void testNoMatchOut(Pattern pattern, String incoming) {
+ assertNull(pattern.matchOutgoing(incoming), incoming);
+ }
+
+ public void testMatchIn(Pattern pattern, String incoming, String expected) {
+ Rewrite rewrite = pattern.matchIncoming(incoming);
+ assertTrue(rewrite.isMatch(), incoming);
+ assertEquals(rewrite.rewrite(), expected);
+ }
+
+ public void testMatchOut(Pattern pattern, String incoming, String expected) {
+ Rewrite rewrite = pattern.matchOutgoing(incoming);
+ assertTrue(rewrite.isMatch(), incoming);
+ assertEquals(rewrite.rewrite(), expected);
+ }
+
+}
+