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()]);
+ }
+ }
+}