package net.sourceforge.stripes.controller;
import static java.lang.String.format;
import java.util.List;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.config.DontAutoLoad;
import net.sourceforge.stripes.exception.UrlBindingConflictException;
import net.sourceforge.stripes.util.Log;
import net.sourceforge.stripes.util.bean.ParseException;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
/**
* Tests for {@link UrlBindingFactory}.
*
* @author Ben Gunter
*/
public class UrlBindingFactoryTests {
private static abstract class BaseActionBean implements ActionBean {
public ActionBeanContext getContext() {
return null;
}
public void setContext(ActionBeanContext context) {
}
}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/{")
public static class BadSyntaxActionBean1 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/\\")
public static class BadSyntaxActionBean2 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/{a")
public static class BadSyntaxActionBean3 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/{}")
public static class BadSyntaxActionBean4 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/{=value}")
public static class BadSyntaxActionBean5 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/{}}")
public static class BadSyntaxActionBean6 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("{a}")
public static class BadSyntaxActionBean7 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("")
public static class BadSyntaxActionBean8 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/")
public static class GoodSyntaxActionBean1 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/foo")
public static class GoodSyntaxActionBean2 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/foo/{a}")
public static class GoodSyntaxActionBean3 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/foo/{a}.action")
public static class GoodSyntaxActionBean4 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/\\\\")
public static class GoodSyntaxActionBean5 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/\\{")
public static class GoodSyntaxActionBean6 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/{{}")
public static class GoodSyntaxActionBean7 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/{\\}}")
public static class GoodSyntaxActionBean8 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/{{\\}}")
public static class GoodSyntaxActionBean9 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/{\\\\}")
public static class GoodSyntaxActionBean10 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/syntax/{a\\=b}")
public static class GoodSyntaxActionBean11 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/clash")
public static class ConflictActionBean1 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/clash")
public static class ConflictActionBean2 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/clash")
public static class ConflictActionBean3 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/clash/not")
public static class ConflictActionBean4 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/foo")
public static class FooActionBean extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/foo/{a}")
public static class FooActionBean1 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/foo/{a}/{b}")
public static class FooActionBean2 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/foo/{a}/{b}/{c}")
public static class FooActionBean3 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/foo/{a}/{b}/{c}/{d}")
public static class FooActionBean4 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/foo/{a}/bar")
public static class FooActionBean5 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/foo/{a}/bar/{c}/baz")
public static class FooActionBean6 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/foo/{a}/{b}/{c}/{d}.action")
public static class FooActionBean7 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/foo/goo/{a}")
public static class FooActionBean8 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/suffix/{a}/{b}.action")
public static class SuffixActionBean1 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/suffix/{a}/{b}/{c}/{d}.action")
public static class SuffixActionBean2 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/sts731/{a}/")
public static class STS731ActionBean1 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/sts731/{a}/foo/")
public static class STS731ActionBean2 extends BaseActionBean {}
@DontAutoLoad @net.sourceforge.stripes.action.UrlBinding("/sts731/{a}/bar/")
public static class STS731ActionBean3 extends BaseActionBean {}
private static final Log log = Log.getInstance(UrlBindingFactoryTests.class);
private static UrlBindingFactory urlBindingFactory;
@BeforeClass
@SuppressWarnings("unchecked")
public void setupClass() {
Class<? extends ActionBean>[] classes = new Class[] { ConflictActionBean1.class,
ConflictActionBean2.class, ConflictActionBean3.class, ConflictActionBean4.class,
FooActionBean.class, FooActionBean1.class, FooActionBean2.class,
FooActionBean3.class, FooActionBean4.class, FooActionBean5.class,
FooActionBean6.class, FooActionBean7.class, FooActionBean8.class,
SuffixActionBean1.class, SuffixActionBean2.class,
STS731ActionBean1.class, STS731ActionBean2.class, STS731ActionBean3.class
};
UrlBindingFactory factory = new UrlBindingFactory();
for (Class<? extends ActionBean> clazz : classes) {
log.debug("Parsing and adding @UrlBinding for ", clazz);
factory.addBinding(clazz, UrlBindingFactory.parseUrlBinding(clazz));
}
urlBindingFactory = factory;
}
private List<UrlBindingParameter> checkBinding(String uri, Class<? extends ActionBean> expected) {
log.debug("Checking that ", uri, " maps to ", expected);
UrlBinding binding = urlBindingFactory.getBinding(uri);
Assert.assertNotNull(binding, "The uri \"" + uri + "\" matched nothing");
Assert.assertSame(binding.getBeanType(), expected);
return binding.getParameters();
}
@Test(groups = "fast")
@SuppressWarnings("unchecked")
public void testParser1() {
Class<? extends ActionBean>[] classes = new Class[] { BadSyntaxActionBean1.class,
BadSyntaxActionBean2.class, BadSyntaxActionBean3.class, BadSyntaxActionBean4.class,
BadSyntaxActionBean5.class, BadSyntaxActionBean6.class, BadSyntaxActionBean7.class,
BadSyntaxActionBean8.class };
for (Class<? extends ActionBean> clazz : classes) {
net.sourceforge.stripes.action.UrlBinding annotation = clazz
.getAnnotation(net.sourceforge.stripes.action.UrlBinding.class);
log.debug("Parsing URL binding ", annotation.value(), ", expecting failure");
try {
UrlBindingFactory.parseUrlBinding(clazz);
Assert.assertTrue(false, "Expected parse exception but did not get one");
}
catch (ParseException e) {
log.debug("As expected: ", e.getMessage());
}
}
}
private String removeEscapes(String s) {
return s.replaceAll("\\\\(.)", "$1");
}
@Test(groups = "fast")
@SuppressWarnings("unchecked")
public void testParser2() {
Class<? extends ActionBean>[] classes = new Class[] { GoodSyntaxActionBean1.class,
GoodSyntaxActionBean2.class, GoodSyntaxActionBean3.class,
GoodSyntaxActionBean4.class, GoodSyntaxActionBean5.class,
GoodSyntaxActionBean6.class, GoodSyntaxActionBean7.class,
GoodSyntaxActionBean8.class, GoodSyntaxActionBean9.class,
GoodSyntaxActionBean10.class, GoodSyntaxActionBean11.class };
for (Class<? extends ActionBean> clazz : classes) {
net.sourceforge.stripes.action.UrlBinding annotation = clazz
.getAnnotation(net.sourceforge.stripes.action.UrlBinding.class);
log.debug("Parsing URL binding ", annotation.value());
UrlBinding binding = UrlBindingFactory.parseUrlBinding(clazz);
log.debug("Expression parsed to ", binding);
Assert.assertNotNull(binding);
Assert.assertEquals(binding.toString(), removeEscapes(annotation.value()),
"Parsed expression is not the same as original expression");
}
// Check weird parameter names
classes = new Class[] { GoodSyntaxActionBean7.class, GoodSyntaxActionBean8.class,
GoodSyntaxActionBean9.class, GoodSyntaxActionBean10.class,
GoodSyntaxActionBean11.class };
for (Class<? extends ActionBean> clazz : classes) {
net.sourceforge.stripes.action.UrlBinding annotation = clazz
.getAnnotation(net.sourceforge.stripes.action.UrlBinding.class);
String value = annotation.value();
log.debug("Checking URL binding parameters for ", value);
UrlBinding binding = UrlBindingFactory.parseUrlBinding(clazz);
Assert.assertEquals(binding.getParameters().size(), 1,
"Was expecting exactly one parameter");
String pname = removeEscapes(value.substring(value.indexOf('{') + 1, value
.lastIndexOf('}')));
log.debug("Parameter name is ", pname);
Assert.assertEquals(binding.getParameters().get(0).getName(), pname);
}
}
@Test(groups = "fast", expectedExceptions = UrlBindingConflictException.class)
public void testUrlBindingConflict() {
checkBinding("/clash/not", ConflictActionBean4.class);
checkBinding("/clash/not/", ConflictActionBean4.class);
urlBindingFactory.getBinding("/clash");
}
@Test(groups = "fast")
public void testUrlBindings() {
// No extensions
checkBinding("/foo", FooActionBean.class);
checkBinding("/foo/", FooActionBean.class);
checkBinding("/foo/1", FooActionBean1.class);
checkBinding("/foo/1/", FooActionBean2.class);
checkBinding("/foo/1/2", FooActionBean2.class);
checkBinding("/foo/1/2/", FooActionBean3.class);
checkBinding("/foo/1/2/3", FooActionBean3.class);
checkBinding("/foo/1/2/3/4", FooActionBean4.class);
checkBinding("/foo/1/2/3/4/", FooActionBean4.class);
// With a suffix mixed in
checkBinding("/foo.action", FooActionBean7.class);
checkBinding("/foo/.action", FooActionBean7.class);
checkBinding("/foo/1.action", FooActionBean7.class);
checkBinding("/foo/1/.action", FooActionBean7.class);
checkBinding("/foo/1/2.action", FooActionBean7.class);
checkBinding("/foo/1/2/.action", FooActionBean7.class);
checkBinding("/foo/1/2/3.action", FooActionBean7.class);
checkBinding("/foo/1/2/3/.action", FooActionBean7.class);
checkBinding("/foo/1/2/3/4.action", FooActionBean7.class);
checkBinding("/foo/1/2/3/4/.action", FooActionBean7.class);
// With literals mixed in
checkBinding("/foo/1/bar", FooActionBean5.class);
checkBinding("/foo/1/bar/", FooActionBean3.class); // #3 matches with more components than #6
checkBinding("/foo/1/bar/2", FooActionBean3.class); // #3 matches with more components than #6
checkBinding("/foo/1/bar/2/", FooActionBean4.class); // #4 matches with more components than #6 or #3
checkBinding("/foo/1/bar/2/baz", FooActionBean6.class);
checkBinding("/foo/1/bar/2/baz/", FooActionBean6.class);
// Suffix conflict resolution
checkBinding("/suffix/1.action", SuffixActionBean1.class);
checkBinding("/suffix/1/.action", SuffixActionBean1.class);
checkBinding("/suffix/1/2.action", SuffixActionBean1.class);
checkBinding("/suffix/1/2/.action", SuffixActionBean2.class);
checkBinding("/suffix/1/2/3.action", SuffixActionBean2.class);
checkBinding("/suffix/1/2/3/.action", SuffixActionBean2.class);
checkBinding("/suffix/1/2/3/4.action", SuffixActionBean2.class);
checkBinding("/suffix/1/2/3/4/.action", SuffixActionBean2.class);
// Prefix overrides everything else
checkBinding("/foo/goo", FooActionBean8.class);
checkBinding("/foo/goo/", FooActionBean8.class);
checkBinding("/foo/goo/1", FooActionBean8.class);
checkBinding("/foo/goo/1/", FooActionBean8.class);
checkBinding("/foo/goo/1/2", FooActionBean8.class);
// Suffixes, as reported in STS-731
for (String value : new String[] { "really-long", "long", "XX", "X" }) {
List<UrlBindingParameter> param;
param = checkBinding(format("/sts731/%s/", value), STS731ActionBean1.class);
Assert.assertEquals(param.get(0).getValue(), value);
param = checkBinding(format("/sts731/%s/foo/", value), STS731ActionBean2.class);
Assert.assertEquals(param.get(0).getValue(), value);
param = checkBinding(format("/sts731/%s/bar/", value), STS731ActionBean3.class);
Assert.assertEquals(param.get(0).getValue(), value);
}
}
@Test(groups = "fast")
public void testConflictDetectionIndependentOfClassLoadingOrder() {
UrlBindingFactory factory;
UrlBinding prototype;
// This order works
factory = new UrlBindingFactory();
factory.addBinding(FooActionBean.class, UrlBindingFactory.parseUrlBinding(FooActionBean.class));
factory.addBinding(FooActionBean2.class, UrlBindingFactory.parseUrlBinding(FooActionBean2.class));
factory.addBinding(FooActionBean3.class, UrlBindingFactory.parseUrlBinding(FooActionBean3.class));
factory.addBinding(FooActionBean4.class, UrlBindingFactory.parseUrlBinding(FooActionBean4.class));
factory.addBinding(FooActionBean5.class, UrlBindingFactory.parseUrlBinding(FooActionBean5.class));
factory.addBinding(FooActionBean6.class, UrlBindingFactory.parseUrlBinding(FooActionBean6.class));
factory.addBinding(FooActionBean7.class, UrlBindingFactory.parseUrlBinding(FooActionBean7.class));
factory.addBinding(FooActionBean8.class, UrlBindingFactory.parseUrlBinding(FooActionBean8.class));
prototype = factory.getBindingPrototype("/foo");
Assert.assertNotNull(prototype);
Assert.assertSame(prototype.getBeanType(), FooActionBean.class);
// This order was failing
factory = new UrlBindingFactory();
factory.addBinding(FooActionBean8.class, UrlBindingFactory.parseUrlBinding(FooActionBean8.class));
factory.addBinding(FooActionBean7.class, UrlBindingFactory.parseUrlBinding(FooActionBean7.class));
factory.addBinding(FooActionBean6.class, UrlBindingFactory.parseUrlBinding(FooActionBean6.class));
factory.addBinding(FooActionBean5.class, UrlBindingFactory.parseUrlBinding(FooActionBean5.class));
factory.addBinding(FooActionBean4.class, UrlBindingFactory.parseUrlBinding(FooActionBean4.class));
factory.addBinding(FooActionBean3.class, UrlBindingFactory.parseUrlBinding(FooActionBean3.class));
factory.addBinding(FooActionBean2.class, UrlBindingFactory.parseUrlBinding(FooActionBean2.class));
factory.addBinding(FooActionBean.class, UrlBindingFactory.parseUrlBinding(FooActionBean.class));
factory.getBindingPrototype("/foo");
Assert.assertNotNull(prototype);
Assert.assertSame(prototype.getBeanType(), FooActionBean.class);
// And this should still fail, regardless of order
factory = new UrlBindingFactory();
factory.addBinding(FooActionBean.class, UrlBindingFactory.parseUrlBinding(FooActionBean.class));
factory.addBinding(FooActionBean2.class, UrlBindingFactory.parseUrlBinding(FooActionBean.class));
try {
factory.getBindingPrototype("/foo");
Assert.assertTrue(false, "A URL binding conflict was expected but it didn't happen!");
}
catch (UrlBindingConflictException e) {
log.debug("Got expected URL binding conflict");
}
}
}