Skip to content

Commit

Permalink
Add support for alternate url bindings for actions
Browse files Browse the repository at this point in the history
  • Loading branch information
TheRittler authored and c24Felix committed Jul 3, 2024
1 parent c87d888 commit fad0315
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
@Documented
public @interface UrlBinding {

/** Additional web-app relative URLs that the ActionBean will respond to. */
String[] alternates() default {};

/** The web-app relative URL that the ActionBean will respond to. */
String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public class UrlBindingFactory {
* @return A {@link UrlBinding} if one is specified, or null if not.
* @throws ParseException If the pattern cannot be parsed
*/
public static UrlBinding parseUrlBinding( Class<? extends ActionBean> beanType ) {
public static UrlBinding parsePrimaryUrlBinding(Class<? extends ActionBean> beanType ) {
// check that class is annotated
org.stripesframework.web.action.UrlBinding annotation = beanType.getAnnotation(org.stripesframework.web.action.UrlBinding.class);
if ( annotation == null ) {
Expand All @@ -79,6 +79,33 @@ public static UrlBinding parseUrlBinding( Class<? extends ActionBean> beanType )
}
}

/**
* Look for a additional binding patterns for the given {@link ActionBean} class, specified by the
* {@link org.stripesframework.web.action.UrlBinding} annotation.
*
* @param beanType The {@link ActionBean} type whose binding is to be parsed
* @return A list of {@link UrlBinding}
* @throws ParseException If the pattern cannot be parsed
*/
public static List<UrlBinding> parseAdditionalUrlBindings(Class<? extends ActionBean> beanType ) {
// check that class is annotated
org.stripesframework.web.action.UrlBinding annotation = beanType.getAnnotation(org.stripesframework.web.action.UrlBinding.class);
List<UrlBinding> bindings = new ArrayList<>();

if ( annotation == null ) {
return bindings;
}

for (String alternate: annotation.alternates()) {
UrlBinding urlBinding = parseUrlBinding(beanType, alternate);
if (urlBinding != null) {
bindings.add(urlBinding);
}
}

return bindings;
}

/**
* Parse the binding pattern and create a {@link UrlBinding} object for the {@link ActionBean}
* class. If pattern is null, then return null.
Expand Down Expand Up @@ -255,24 +282,6 @@ public int compare( String a, String b ) {
* @param binding the URL binding
*/
public void addBinding( Class<? extends ActionBean> beanType, UrlBinding binding ) {
/*
* Search for a class that has already been added with the same name as the class being
* added now. If one is found then remove its information first and then proceed with adding
* it. I know this is not technically correct because two classes from two different class
* loaders can have the same name, but this feature is valuable for extensions that reload
* classes and I consider it highly unlikely to be a problem in practice.
*/
Class<? extends ActionBean> existing = null;
for ( Class<? extends ActionBean> c : _classCache.keySet() ) {
if ( c.getName().equals(beanType.getName()) ) {
existing = c;
break;
}
}
if ( existing != null ) {
removeBinding(existing);
}

// And now we can safely add the class
for ( String path : getCachedPaths(binding) ) {
cachePath(path, binding);
Expand Down Expand Up @@ -393,16 +402,22 @@ public UrlBinding getBinding( HttpServletRequest request ) {
* @return a binding object if one is defined or null if not
*/
public UrlBinding getBindingPrototype( Class<? extends ActionBean> type ) {
UrlBinding binding = _classCache.get(type);
if ( binding != null ) {
return binding;
UrlBinding primaryBinding = _classCache.get(type);
if ( primaryBinding != null ) {
return primaryBinding;
}

binding = parseUrlBinding(type);
if ( binding != null ) {
addBinding(type, binding);
primaryBinding = parsePrimaryUrlBinding(type);
if ( primaryBinding != null ) {
addBinding(type, primaryBinding);
}
return binding;

List<UrlBinding> urlBindings = parseAdditionalUrlBindings(type);
for (UrlBinding urlBinding : urlBindings) {
addBinding(type, urlBinding);
}

return primaryBinding;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
import org.stripesframework.web.action.ActionBean;
import org.stripesframework.web.action.ActionBeanContext;
import org.stripesframework.web.config.DontAutoLoad;
import org.stripesframework.web.controller.UrlBinding;
import org.stripesframework.web.controller.UrlBindingFactory;
import org.stripesframework.web.controller.UrlBindingParameter;

import org.stripesframework.web.exception.UrlBindingConflictException;
import org.stripesframework.web.util.bean.ParseException;
Expand All @@ -40,7 +37,7 @@ public static void setupClass() {

UrlBindingFactory factory = new UrlBindingFactory();
for ( Class<? extends ActionBean> clazz : classes ) {
factory.addBinding(clazz, UrlBindingFactory.parseUrlBinding(clazz));
factory.addBinding(clazz, UrlBindingFactory.parsePrimaryUrlBinding(clazz));
}

urlBindingFactory = factory;
Expand All @@ -49,8 +46,8 @@ public static void setupClass() {
@Test
public void testConflictDetectionIndependentOfClassLoadingOrder_failsRegardlessOfOrder() {
UrlBindingFactory factory = new UrlBindingFactory();
factory.addBinding(FooActionBean.class, UrlBindingFactory.parseUrlBinding(FooActionBean.class));
factory.addBinding(FooActionBean2.class, UrlBindingFactory.parseUrlBinding(FooActionBean.class));
factory.addBinding(FooActionBean.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean.class));
factory.addBinding(FooActionBean2.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean.class));

Throwable throwable = catchThrowable(() -> factory.getBindingPrototype("/foo"));

Expand All @@ -60,14 +57,14 @@ public void testConflictDetectionIndependentOfClassLoadingOrder_failsRegardlessO
@Test
public void testConflictDetectionIndependentOfClassLoadingOrder_works() {
UrlBindingFactory 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));
factory.addBinding(FooActionBean.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean.class));
factory.addBinding(FooActionBean2.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean2.class));
factory.addBinding(FooActionBean3.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean3.class));
factory.addBinding(FooActionBean4.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean4.class));
factory.addBinding(FooActionBean5.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean5.class));
factory.addBinding(FooActionBean6.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean6.class));
factory.addBinding(FooActionBean7.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean7.class));
factory.addBinding(FooActionBean8.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean8.class));

UrlBinding prototype = factory.getBindingPrototype("/foo");

Expand All @@ -78,14 +75,14 @@ public void testConflictDetectionIndependentOfClassLoadingOrder_works() {
@Test
public void testConflictDetectionIndependentOfClassLoadingOrder_worksWithDifferentOrder() {
UrlBindingFactory 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.addBinding(FooActionBean8.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean8.class));
factory.addBinding(FooActionBean7.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean7.class));
factory.addBinding(FooActionBean6.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean6.class));
factory.addBinding(FooActionBean5.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean5.class));
factory.addBinding(FooActionBean4.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean4.class));
factory.addBinding(FooActionBean3.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean3.class));
factory.addBinding(FooActionBean2.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean2.class));
factory.addBinding(FooActionBean.class, UrlBindingFactory.parsePrimaryUrlBinding(FooActionBean.class));

UrlBinding prototype = factory.getBindingPrototype("/foo");

Expand All @@ -103,7 +100,7 @@ public void testParser1() {
for ( Class<? extends ActionBean> clazz : classes ) {
org.stripesframework.web.action.UrlBinding annotation = clazz.getAnnotation(org.stripesframework.web.action.UrlBinding.class);

Throwable throwable = catchThrowable(() -> UrlBindingFactory.parseUrlBinding(clazz));
Throwable throwable = catchThrowable(() -> UrlBindingFactory.parsePrimaryUrlBinding(clazz));

assertThat(throwable).describedAs("Expected failure for parsing " + annotation.value()).isInstanceOf(ParseException.class);
}
Expand All @@ -120,7 +117,7 @@ public void testParser2() {
for ( Class<? extends ActionBean> clazz : classes ) {
org.stripesframework.web.action.UrlBinding annotation = clazz.getAnnotation(org.stripesframework.web.action.UrlBinding.class);

UrlBinding binding = UrlBindingFactory.parseUrlBinding(clazz);
UrlBinding binding = UrlBindingFactory.parsePrimaryUrlBinding(clazz);

assertThat(binding).isNotNull();
assertThat(binding.toString()).isEqualTo(removeEscapes(annotation.value()), "Parsed expression is not the same as original expression");
Expand Down Expand Up @@ -209,7 +206,7 @@ void testParser3() {
org.stripesframework.web.action.UrlBinding annotation = clazz.getAnnotation(org.stripesframework.web.action.UrlBinding.class);
String value = annotation.value();

UrlBinding binding = UrlBindingFactory.parseUrlBinding(clazz);
UrlBinding binding = UrlBindingFactory.parsePrimaryUrlBinding(clazz);
assertThat(binding).isNotNull();
assertThat(binding.getParameters()).hasSize(1);

Expand Down

0 comments on commit fad0315

Please sign in to comment.