You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@abdera.apache.org by jm...@apache.org on 2008/01/26 22:06:33 UTC

svn commit: r615492 - in /incubator/abdera/java/branches/server_refactor_all: dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/ server/src/main/java/org/apache/abdera/protocol/server/impl/

Author: jmsnell
Date: Sat Jan 26 13:06:32 2008
New Revision: 615492

URL: http://svn.apache.org/viewvc?rev=615492&view=rev
Log:
Add preliminary support for a "routes" based TargetResolver and TargetBuilder.  A route is loosely based on the Ruby-on-Rails notion of a route...

RouteManager rm = 
  new RouteManager()
    .addRoute("entry",":feed/:entry",TargetType.TYPE_ENTRY)
    .addRoute("feed",":feed",TargetType.TYPE_COLLECTION);
    
The RouteManager implements Resolver<Target> and TargetBuilder so it can be used by Provider implementations to fill both of those roles.  

This is a preliminary implementation that needs additional work to flesh out all the details. 

Added:
    incubator/abdera/java/branches/server_refactor_all/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Route.java
    incubator/abdera/java/branches/server_refactor_all/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/URIRoute.java
    incubator/abdera/java/branches/server_refactor_all/server/src/main/java/org/apache/abdera/protocol/server/impl/RouteManager.java

Added: incubator/abdera/java/branches/server_refactor_all/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Route.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/branches/server_refactor_all/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Route.java?rev=615492&view=auto
==============================================================================
--- incubator/abdera/java/branches/server_refactor_all/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Route.java (added)
+++ incubator/abdera/java/branches/server_refactor_all/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Route.java Sat Jan 26 13:06:32 2008
@@ -0,0 +1,281 @@
+package org.apache.abdera.i18n.templates;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.abdera.i18n.text.CharUtils;
+
+/**
+ * A type of URI Template loosely based on Ruby on Rails style Routes.
+ * 
+ * Example: 
+ *   Route feed_route = new Route("feed",":feed/:entry");
+ *   
+ */
+@SuppressWarnings("unchecked") 
+public class Route
+  implements Iterable<String>,
+             Cloneable,
+             Serializable {
+
+  private static final long serialVersionUID = -8979172281494208841L;
+  
+  private static final Evaluator EVALUATOR = new Evaluator();
+  private static final Pattern VARIABLE = Pattern.compile("[\\*\\:](?:\\()?[^\\/,;\\.#\\)]+(?:\\))?");
+  
+  private final String name;
+  private final String pattern;
+  private final String[] tokens;
+  private final String[] variables;
+  private final Pattern parser;
+  private final String[] index;
+  
+  public Route(
+    String name,
+    String pattern) {
+      this.name = name;
+      this.pattern = CharUtils.stripBidiInternal(pattern);
+      this.tokens = initTokens();
+      this.variables = initVariables();
+      this.parser = compile();
+      this.index = index();
+  }
+  
+  private String[] initTokens() {
+    Matcher matcher = VARIABLE.matcher(pattern);
+    List<String> tokens = new ArrayList<String>();
+    while (matcher.find()) {
+      String token = matcher.group();
+      if (!tokens.contains(token))
+        tokens.add(token);
+    }
+    return tokens.toArray(new String[tokens.size()]);
+  }
+  
+  private String[] initVariables() {
+    List<String> list = new ArrayList<String>();
+    for (String token : this) {
+      String var = var(token);
+      if (!list.contains(var)) list.add(var);
+    }
+    String[] vars = list.toArray(new String[list.size()]);
+    Arrays.sort(vars);
+    return vars;
+  }
+  
+  private Pattern compile() {
+    Matcher m = VARIABLE.matcher(pattern);
+    StringBuilder buf = new StringBuilder();
+    int e = -1, s = 0;
+    while(m.find(s)) {
+      e = m.start();
+      if (s != 0) {
+        String q = "(?:" + Pattern.quote(pattern.substring(s,e)) + ")";
+        buf.append(q);
+        buf.append("]+))?");
+        buf.append(q);
+      }
+      buf.append("(?:([^\\/,;\\.#\\)\\?");
+      s = m.end();
+    }
+    if (s > 0) {
+      if (s < pattern.length()) {
+        String q = "(?:" + Pattern.quote(pattern.substring(s)) + ")";
+        buf.append(q);
+      }
+      buf.append("]+))?");
+    }
+    return Pattern.compile(buf.toString());
+  }
+  
+  private String[] index() {
+    List<String> index = new ArrayList<String>();
+    Matcher m = VARIABLE.matcher(pattern);
+    int s = 0;
+    while(m.find(s)) {
+      String var = var(m.group(0));
+      if (!index.contains(var)) index.add(var);
+      s = m.end();
+    }
+    return index.toArray(new String[index.size()]);
+  }
+  
+  /**
+   * Returns true if the given uri matches the route pattern
+   */
+  public boolean match(String pattern) {
+    Matcher m = parser.matcher(pattern);
+    return m.find();
+  }
+  
+  /**
+   * Parses the given uri using the route pattern
+   */
+  public Map<String,String> parse(String pattern) {
+    Matcher m = parser.matcher(pattern);
+    Map<String,String> results = new HashMap<String,String>();
+    if (m.find()) {
+      for (int n = 1; n <= m.groupCount(); n++) {
+        String label = index[n-1];
+        results.put(label, m.group(n));
+      }
+    }
+    return results;
+  }
+
+  /**
+   * Expand the route pattern given the specified context
+   */
+  public String expand(
+    Context context) {
+      String pattern = this.pattern;
+      for(String token : this) {
+        pattern = replace(
+          pattern, 
+          token, 
+          EVALUATOR.evaluate(
+            var(token), 
+            context));
+      }
+      StringBuffer buf = new StringBuffer(pattern);
+      boolean qs = false;
+      for (String var : context) {
+        if (Arrays.binarySearch(variables, var) < 0) {
+          if (!qs) {
+            buf.append("?");
+            qs = true;
+          } else {
+            buf.append("&");
+          }
+          buf.append(var)
+             .append("=")
+             .append(EVALUATOR.evaluate(var, context));
+        }
+      }
+      
+      return buf.toString();
+  }
+  
+  private String var(String token) {
+    token = token.substring(1);
+    if (token.startsWith("("))
+      token = token.substring(1);
+    if (token.endsWith(")"))
+      token = token.substring(0,token.length()-1);
+    return token;
+  }
+
+  /**
+   * Expand the route pattern given the specified context object
+   **/
+  public String expand(Object object) {
+    return expand(object,false);
+  }
+
+  /**
+   * Expand the route pattern using IRI escaping rules
+   */
+  public String expand(Object object, boolean isiri) {
+    return expand(
+      object instanceof Context ? 
+        (Context)object :
+        object instanceof Map ? 
+          new HashMapContext((Map)object,isiri) :
+          new ObjectContext(object,isiri));
+  }
+  
+  private String replace(
+    String pattern, 
+    String token, 
+    String value) {
+      return pattern.replaceAll(
+        Pattern.quote(token),
+        value);
+  }
+
+  
+  public String getName() {
+    return name;
+  }
+  
+  public String getPattern() {
+    return pattern;
+  }
+
+  public Iterator<String> iterator() {
+    return Arrays.asList(tokens).iterator();
+  }
+  
+  public String[] getVariables() {
+    return variables;
+  }
+  
+  public Route clone() {
+    try {
+      return (Route)super.clone();
+    } catch (Throwable e) {
+      return new Route(name,pattern);  // not going to happen, but just in case
+    }
+  }
+
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((name == null) ? 0 : name.hashCode());
+    result = prime * result + ((pattern == null) ? 0 : pattern.hashCode());
+    return result;
+  }
+
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (obj == null) return false;
+    if (getClass() != obj.getClass()) return false;
+    final Route other = (Route) obj;
+    if (name == null) {
+      if (other.name != null) return false;
+    } else if (!name.equals(other.name)) return false;
+    if (pattern == null) {
+      if (other.pattern != null) return false;
+    } else if (!pattern.equals(other.pattern)) return false;
+    return true;
+  }
+
+  public String toString() {
+    return pattern;
+  }
+
+  public static String expand(String pattern, Context context) {
+    if (context == null || pattern == null) throw new IllegalArgumentException();
+    Route route = new Route(null, pattern);
+    return route.expand(context);
+  }
+  
+  public static String expand(String pattern, Object object) {
+    return expand(pattern,object,false);
+  }
+  
+  public static String expand(String pattern, Object object, boolean isiri) {
+    if (object == null || pattern == null) throw new IllegalArgumentException();
+    Route route = new Route(null,pattern);
+    return route.expand(object,isiri);
+  }
+  
+  public static String expandAnnotated(Object object) {
+    if (object == null) throw new IllegalArgumentException();
+    Class _class = object.getClass();
+    URIRoute uriroute = (URIRoute) _class.getAnnotation(URIRoute.class);
+    if (uriroute != null) {
+      return expand(uriroute.value(),object,uriroute.isiri());
+    } else {
+      throw new IllegalArgumentException("No Route provided");
+    }
+  }
+
+}

Added: incubator/abdera/java/branches/server_refactor_all/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/URIRoute.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/branches/server_refactor_all/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/URIRoute.java?rev=615492&view=auto
==============================================================================
--- incubator/abdera/java/branches/server_refactor_all/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/URIRoute.java (added)
+++ incubator/abdera/java/branches/server_refactor_all/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/URIRoute.java Sat Jan 26 13:06:32 2008
@@ -0,0 +1,38 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  The ASF licenses this file to You
+* 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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.i18n.templates;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * The URITemplate annotation can be used to associate a URI Template with a
+ * particular Java class.  Instances of that class can then be expanded using
+ * the associated URI Template.  The public fields and getter methods on the 
+ * class will be automatically mapped to URI Template variable names. Alternative
+ * varnames can be specified for each field/getter using the VarName annotation
+ */
+@Retention(RUNTIME)
+@Target(TYPE)
+public @interface URIRoute {
+  String value();
+  boolean isiri() default false;
+}

Added: incubator/abdera/java/branches/server_refactor_all/server/src/main/java/org/apache/abdera/protocol/server/impl/RouteManager.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/branches/server_refactor_all/server/src/main/java/org/apache/abdera/protocol/server/impl/RouteManager.java?rev=615492&view=auto
==============================================================================
--- incubator/abdera/java/branches/server_refactor_all/server/src/main/java/org/apache/abdera/protocol/server/impl/RouteManager.java (added)
+++ incubator/abdera/java/branches/server_refactor_all/server/src/main/java/org/apache/abdera/protocol/server/impl/RouteManager.java Sat Jan 26 13:06:32 2008
@@ -0,0 +1,169 @@
+package org.apache.abdera.protocol.server.impl;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.abdera.i18n.templates.CachingContext;
+import org.apache.abdera.i18n.templates.Context;
+import org.apache.abdera.i18n.templates.HashMapContext;
+import org.apache.abdera.i18n.templates.ObjectContext;
+import org.apache.abdera.i18n.templates.Route;
+import org.apache.abdera.protocol.Request;
+import org.apache.abdera.protocol.Resolver;
+import org.apache.abdera.protocol.server.RequestContext;
+import org.apache.abdera.protocol.server.Target;
+import org.apache.abdera.protocol.server.TargetBuilder;
+import org.apache.abdera.protocol.server.TargetType;
+
+/**
+ * This is a largely experimental implementation of a Target Resolver and
+ * Target Builder based on URL patterns similar (but not identical) to 
+ * Ruby on Rails style routes.  
+ * 
+ * For instance:
+ * <pre>
+ *   RouteManager rm = 
+ *     new RouteManager()
+ *       .addRoute("entry",":collection/:entry", TargetType.TYPE_ENTRY)
+ *       .addRoute("feed", ":collection", TargetType.TYPE_COLLECTION);
+ * </pre>
+ *
+ * The RouteManager can be used by Provider implementations as the target
+ * resolver and target builder
+ *
+ */
+public class RouteManager
+  implements Resolver<Target>,
+             TargetBuilder {
+
+  protected Map<Route,TargetType> targets = 
+    new HashMap<Route,TargetType>();
+  
+  protected Map<String,Route> routes = 
+    new HashMap<String,Route>();
+
+  public RouteManager addRoute(
+    Route route) {
+      return addRoute(route,null);
+  }
+  
+  public RouteManager addRoute(
+    String name, 
+    String pattern) {
+      return addRoute(
+        name,
+        pattern,
+        null);
+  }
+  
+  public RouteManager addRoute(
+    Route route, 
+    TargetType type) {
+      routes.put(route.getName(), route);
+      if (type != null) 
+        targets.put(route, type);
+      return this;
+  }
+  
+  public RouteManager addRoute(
+    String name, 
+    String pattern, 
+    TargetType type) {
+      return addRoute(
+        new Route(
+          name, 
+          pattern), 
+        type);
+  }
+  
+  public Target resolve(Request request) {
+    RequestContext context = (RequestContext) request;
+    String uri = context.getTargetPath();
+    for(Map.Entry<Route, TargetType> entry : targets.entrySet()) {
+      if (entry.getKey().match(uri)) {
+        return getTarget(context, entry.getKey(), uri, entry.getValue());
+      }
+    }
+    return null;
+  }
+
+  private Target getTarget(
+    RequestContext context,
+    Route route, 
+    String uri, 
+    TargetType type) {
+      return new RouteTarget(type, context, route, uri);
+  }
+  
+  public String urlFor(
+    RequestContext context, 
+    Object key, 
+    Object param) {
+      Route route = routes.get(key);
+//      return route != null ?
+//        route.expand(TemplateTargetBuilder.getContext(context,param)) :
+//        null;
+      return route != null ?
+        route.expand(getContext(param)) :
+        null;
+  }
+  
+  private Context getContext(Object param) {
+    Context context = null;
+    if (param != null) {
+      if (param instanceof Map) {
+        context = new HashMapContext((Map<String,Object>)param, true);
+      } else if (param instanceof Context) {
+        context = (Context)param;
+      } else {
+        context = new ObjectContext(param,true);
+      }
+    } else context = new EmptyContext();
+    return context;
+  }
+
+  private static class EmptyContext 
+    extends CachingContext {
+    protected <T> T resolveActual(String var) {
+      return null;
+    }
+    public Iterator<String> iterator() {
+      List<String> list = Arrays.asList(new String[0]);
+      return list.iterator();
+    }
+  }
+  
+  public static class RouteTarget 
+    extends AbstractTarget {
+      private final Map<String,String> params;
+      private final Route route;      
+      public RouteTarget(
+        TargetType type, 
+        RequestContext context,
+        Route route,
+        String uri) {
+          super(type, context);
+          this.route = route;
+          this.params = route.parse(uri);
+      }
+      public Route getRoute() {
+        return route;
+      }
+      public String getParameter(String name) {
+        return params.containsKey(name) ?
+          params.get(name) :
+          super.getParameter(name);
+      }
+      public String[] getParameterNames() {
+        List<String> names = new ArrayList(Arrays.asList(super.getParameterNames()));
+        for (String name : params.keySet()) {
+          if (!names.contains(name))
+            names.add(name);
+        }
+        return names.toArray(new String[names.size()]);
+      }
+  }
+}