/* * 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.abdera2.common.protocol; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.abdera2.common.templates.CachingContext; import org.apache.abdera2.common.templates.Context; import org.apache.abdera2.common.templates.MapContext; import org.apache.abdera2.common.templates.ObjectContext; import org.apache.abdera2.common.templates.Route; import com.google.common.base.Function; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; /** * 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: * *
 * RouteManager rm =
 *     new RouteManager().addRoute("entry", ":collection/:entry", TargetType.TYPE_ENTRY)
 *         .addRoute("feed", ":collection", TargetType.TYPE_COLLECTION);
 * 
* * The RouteManager can be used by Provider implementations as the target resolver and target builder */ public class RouteManager implements Function, TargetBuilder { public static Generator make() { return new Generator(); } public static class Generator implements Supplier> { protected final ImmutableList.Builder> targets = ImmutableList.builder(); protected final ImmutableMap.Builder> routes = ImmutableMap.builder(); protected final ImmutableMap.Builder, CollectionAdapter> route2CA = ImmutableMap.builder(); public Generator withAll(RouteManager other) { this.targets.addAll(other.targets); this.routes.putAll(other.routes); this.route2CA.putAll(other.route2CA); return this; } public Generator with(Route... routes) { for (Route route : routes) with(route, null); return this; } public Generator with(R key, String pattern) { return with(key, pattern, null); } public Generator with( Route route, TargetType type) { routes.put(route.getKey(), route); if (type != null) targets.add(new RouteTargetType(route, type)); return this; } public Generator with( R key, String pattern, TargetType type) { return with(new Route(key, pattern), type); } public Generator with( R key, String pattern, TargetType type, CollectionAdapter collectionAdapter) { Route route = new Route(key, pattern); route2CA.put(route, collectionAdapter); return with(route, type); } public RouteManager get() { return new RouteManager(this); } } protected static class RouteTargetType { protected Route route; protected TargetType targetType; RouteTargetType(Route route, TargetType targetType) { this.route = route; this.targetType = targetType; } public Route getRoute() { return route; } public TargetType getTargetType() { return targetType; } } protected final List> targets; protected final Map> routes; protected final Map, CollectionAdapter> route2CA;; RouteManager(Generator gen) { this.targets = gen.targets.build(); this.routes = gen.routes.build(); this.route2CA = gen.route2CA.build(); } public Target apply(X request) { String uri = request.getTargetPath(); int idx = uri.indexOf('?'); if (idx != -1) { uri = uri.substring(0, idx); } RouteTargetType target = get(uri); if (target == null) { target = match(uri); } if (target != null) { return getTarget(request, target, uri); } return null; } private RouteTargetType get(String uri) { for (RouteTargetType target : targets) { if (target.route.getPattern().equals(uri)) { return target; } } return null; } private RouteTargetType match(String uri) { for (RouteTargetType target : targets) { if (target.route.match(uri)) { return target; } } return null; } private Target getTarget(RequestContext context, RouteTargetType target, String uri) { CollectionAdapter ca = route2CA.get(target.route); if (ca != null) { context.setAttribute(AbstractWorkspaceManager.COLLECTION_ADAPTER_ATTRIBUTE, ca); } return getTarget(context, target.route, uri, target.targetType); } private Target getTarget(RequestContext context, Route route, String uri, TargetType type) { return new RouteTarget(type, context, route, uri); } public String urlFor(Request context, R key, Object param) { RequestContext rc = (RequestContext) context; Route route = routes.get(key); return route != null ? rc.getContextPath() + route.expand(getContext(param)) : null; } private Context getContext(Object param) { Context context = new EmptyContext(); if (param != null) { if (param instanceof Map) { context = new MapContext(cleanMapCtx(param), true); } else if (param instanceof Context) { context = (Context)param; } else { context = new ObjectContext(param, true); } } return context; } @SuppressWarnings("unchecked") private Map cleanMapCtx(Object param) { Map map = new HashMap(); for (Map.Entry entry : ((Map)param).entrySet()) { map.put(entry.getKey().replaceFirst("^:", ""), entry.getValue()); } ((Map)param).clear(); ((Map)param).putAll(map); return (Map)param; } private static class EmptyContext extends CachingContext { private static final long serialVersionUID = 4681906592987534451L; public EmptyContext() { super(false); } public boolean contains(String var) { return false; } protected T resolveActual(String var) { return null; } public Iterator iterator() { return ImmutableSet.of().iterator(); } } public static class RouteTarget extends SimpleTarget { private final Map 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; } @SuppressWarnings("unchecked") @Override public T getMatcher() { return (T)getRoute(); } public String getParameter(String name) { return params.containsKey(name) ? params.get(name) : super.getParameter(name); } public Iterable getParameterNames() { Iterable ns = super.getParameterNames(); Set names = new HashSet(); for (String name : ns) names.add(name); for (String name : params.keySet()) names.add(name); return names; } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + ((params == null) ? 0 : params.hashCode()); result = prime * result + ((route == null) ? 0 : route.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; RouteTarget other = (RouteTarget) obj; if (params == null) { if (other.params != null) return false; } else if (!params.equals(other.params)) return false; if (route == null) { if (other.route != null) return false; } else if (!route.equals(other.route)) return false; return true; } } }