ObjectPrefixDispatcher.java
/***************************************************************************
Copyright 2014 Emily Estes
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.metanotion.web.concrete;
import java.util.HashMap;
import java.util.Map;
import net.metanotion.functor.Block;
import net.metanotion.util.Dispatcher;
import net.metanotion.util.Message;
import net.metanotion.util.Pair;
import net.metanotion.util.Unknown;
import net.metanotion.web.RequestObject;
/** This dispatcher is designed to extract the first URI segment it encounters into a request variable, and then delegate
to another dispatcher using the rest of the URI. This makes it easy to deal with simple situations that could benefit from
having a parameter as part of the URI rather than a query string.
@param <O> the type of object the delegated dispatcher's messages are evaluated against.
*/
public final class ObjectPrefixDispatcher<O> implements Dispatcher<Unknown,RequestObject> {
private static final class Msg<O> implements Message<Unknown> {
private final String prefix;
private final RequestObject ro;
private final Dispatcher<O, RequestObject> delegate;
private final Block<Map.Entry<Unknown,String>,Boolean> predicate;
private final String objectIdName;
public Msg(final String prefix,
final RequestObject ro,
final Dispatcher<O, RequestObject> delegate,
final Block<Map.Entry<Unknown,String>,Boolean> predicate,
final String objectIdName) {
this.prefix = prefix;
this.ro = ro;
this.delegate = delegate;
this.predicate = predicate;
this.objectIdName = objectIdName;
}
@Override public Class<Unknown> receiverType() { return Unknown.class; }
@Override public Object call(final Unknown o) {
try {
if(predicate.eval(new Pair<Unknown,String>(o, prefix))) {
final Message<O> m = delegate.dispatch(ro);
return m.call(o.lookupInterface(m.receiverType()));
}
} catch (Exception e) { throw new RuntimeException(e); }
throw new SecurityException("Predicate failed on session");
}
}
private static final Block<Map.Entry<Unknown,String>, Boolean> TRUE = new Block<Map.Entry<Unknown,String>,Boolean>() {
@Override public Boolean eval(final Map.Entry<Unknown,String> o) { return true; }
};
private static final String DEFAULT_DELIMITER = "/";
private final Dispatcher<O, RequestObject> delegate;
private final Block<Map.Entry<Unknown,String>,Boolean> predicate;
private final String objectIdName;
private final String delimiter;
private final boolean required;
/** Create a dispatcher which delegates to the provided delegate and puts the first URI segment delimited by "/"
into a variable with the provided name.
@param delegate The dispatcher to delegate to.
@param objectIdName The name of the request variable to bind the URI segment value to.
*/
public ObjectPrefixDispatcher(final Dispatcher<O, RequestObject> delegate, final String objectIdName) {
this(delegate, TRUE, objectIdName, DEFAULT_DELIMITER, true);
}
/** Create a dispatcher which delegates to the provided delegate and puts the first URI segment delimited by "/"
into a variable with the provided name. Also, using the provided predicate evaluate whether the URI segment value
is valid before dispatching the message.
@param delegate The dispatcher to delegate to.
@param predicate A predicate object to test whether the URI segment is valid.
@param objectIdName The name of the request variable to bind the URI segment value to.
*/
public ObjectPrefixDispatcher(final Dispatcher<O, RequestObject> delegate,
final Block<Map.Entry<Unknown,String>,Boolean> predicate,
final String objectIdName) {
this(delegate, predicate, objectIdName, DEFAULT_DELIMITER, true);
}
/** Create a dispatcher which delegates to the provided delegate and puts the first URI segment delimited by the
provided delimiter string into a variable with the provided name. If "required" is false, it is okay to consume the entire
rest of the URI as the value.
@param delegate The dispatcher to delegate to.
@param objectIdName The name of the request variable to bind the URI segment value to.
@param delimiter The string that delimits the URI segments.
@param required true if the delimiter must be found, false otherwise.
*/
public ObjectPrefixDispatcher(final Dispatcher<O, RequestObject> delegate,
final String objectIdName,
final String delimiter,
final boolean required) {
this(delegate, TRUE, objectIdName, delimiter, required);
}
/** Create a dispatcher which delegates to the provided delegate and puts the first URI segment delimited by the
provided delimiter string into a variable with the provided name. If "required" is false, it is okay to consume the entire
rest of the URI as the value. Also, using the provided predicate evaluate whether the URI segment value
is valid before dispatching the message.
@param delegate The dispatcher to delegate to.
@param predicate A predicate object to test whether the URI segment is valid.
@param objectIdName The name of the request variable to bind the URI segment value to.
@param delimiter The string that delimits the URI segments.
@param required true if the delimiter must be found, false otherwise.
*/
public ObjectPrefixDispatcher(final Dispatcher<O, RequestObject> delegate,
final Block<Map.Entry<Unknown,String>,Boolean> predicate,
final String objectIdName,
final String delimiter,
final boolean required) {
this.delegate = delegate;
this.predicate = predicate;
this.objectIdName = objectIdName;
this.delimiter = delimiter;
this.required = required;
}
@Override public Message<Unknown> dispatch(final RequestObject ro) {
final String uri = ro.getResource();
final int segment = uri.indexOf(delimiter);
if((required) && (segment == -1)) { throw new RuntimeException("URI not fully specified."); }
final String prefix = (segment == -1) ? uri : uri.substring(0, segment);
final HashMap<String,Object> vars = new HashMap<>();
vars.put(objectIdName, prefix);
final RequestObject newRo = new DelegatingRequestObject(ro, (segment == -1) ? "" : uri.substring(segment + 1), vars);
return new Msg<O>(prefix, newRo, delegate, predicate, objectIdName);
}
}