Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for alternate url bindings for actions #37

Merged
merged 5 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -386,10 +386,11 @@ protected void addActionBean( Class<? extends ActionBean> clazz ) {
return;
}

getUrlBindingFactory().removeBinding(clazz);
// make sure mapping exists in cache
UrlBinding proto = getUrlBindingFactory().getBindingPrototype(clazz);
if ( proto == null ) {
getUrlBindingFactory().addBinding(clazz, new UrlBinding(clazz, binding));
getUrlBindingFactory().addBinding(clazz, new UrlBinding(clazz, binding), true);
}

// Construct the mapping of event->method for the class
Expand Down Expand Up @@ -471,7 +472,7 @@ protected Set<Class<? extends ActionBean>> findClasses() {
}

/** Provides subclasses with access to the configuration object. */
protected Configuration getConfiguration() { return _configuration; }
protected Configuration getConfiguration() {return _configuration;}

/**
* Looks to see if there is a single non-empty parameter value for the parameter name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,25 +60,6 @@ public class UrlBindingFactory {

private static final Log log = Log.getInstance(UrlBindingFactory.class);

/**
* Look for a binding pattern for the given {@link ActionBean} class, specified by the
* {@link org.stripesframework.web.action.UrlBinding} annotation. If the annotation is found,
* create and return a {@link UrlBinding} object for the class. Otherwise, return null.
*
* @param beanType The {@link ActionBean} type whose binding is to be parsed
* @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 ) {
// check that class is annotated
org.stripesframework.web.action.UrlBinding annotation = beanType.getAnnotation(org.stripesframework.web.action.UrlBinding.class);
if ( annotation == null ) {
return null;
} else {
return parseUrlBinding(beanType, annotation.value());
}
}

/**
* 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 @@ -233,13 +214,15 @@ public String getValue() {
}

/** Maps {@link ActionBean} classes to {@link UrlBinding}s */
private final Map<Class<? extends ActionBean>, UrlBinding> _classCache = new HashMap<>();
private final Map<Class<? extends ActionBean>, UrlBinding> _classPrimaryBindingCache = new HashMap<>();
/** Maps {@link ActionBean} classes to {@link UrlBinding}s */
private final Map<Class<? extends ActionBean>, List<UrlBinding>> _classBindingCache = new HashMap<>();
/** Maps simple paths to {@link UrlBinding}s */
private final Map<String, UrlBinding> _pathCache = new HashMap<>();
private final Map<String, UrlBinding> _pathCache = new HashMap<>();
/** Keeps a list of all the paths that could not be cached due to conflicts between URL bindings */
private final Map<String, List<UrlBinding>> _pathConflicts = new HashMap<>();
private final Map<String, List<UrlBinding>> _pathConflicts = new HashMap<>();
/** Holds the set of paths that are cached, sorted from longest to shortest */
private final Map<String, Set<UrlBinding>> _prefixCache = new TreeMap<>(new Comparator<>() {
private final Map<String, Set<UrlBinding>> _prefixCache = new TreeMap<>(new Comparator<>() {

@Override
public int compare( String a, String b ) {
Expand All @@ -252,42 +235,29 @@ public int compare( String a, String b ) {
* Map an {@link ActionBean} to a URL.
*
* @param beanType the {@link ActionBean} class
* @param binding the URL binding
* @param binding the URL binding
* @param isPrimary
*/
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);
}
TheRittler marked this conversation as resolved.
Show resolved Hide resolved

public void addBinding( Class<? extends ActionBean> beanType, UrlBinding binding, boolean isPrimary ) {
// And now we can safely add the class
for ( String path : getCachedPaths(binding) ) {
cachePath(path, binding);
}
for ( String prefix : getCachedPrefixes(binding) ) {
cachePrefix(prefix, binding);
}
_classCache.put(beanType, binding);

_classBindingCache.computeIfAbsent(beanType, k -> new ArrayList<>()).add(binding);
if ( isPrimary ) {
_classPrimaryBindingCache.put(beanType, binding);
}
}

/**
* Get all the classes implementing {@link ActionBean}
*/
public Collection<Class<? extends ActionBean>> getActionBeanClasses() {
return Collections.unmodifiableSet(_classCache.keySet());
return Collections.unmodifiableSet(_classPrimaryBindingCache.keySet());
}

/**
Expand Down Expand Up @@ -393,16 +363,12 @@ 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 = _classPrimaryBindingCache.get(type);
if ( primaryBinding != null ) {
return primaryBinding;
}

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

/**
Expand Down Expand Up @@ -539,62 +505,97 @@ public HashMap<String, Class<? extends ActionBean>> getPathMap() {
* @param beanType the {@link ActionBean} class
*/
public synchronized void removeBinding( Class<? extends ActionBean> beanType ) {
UrlBinding binding = _classCache.get(beanType);
if ( binding == null ) {
List<UrlBinding> urlBindings = _classBindingCache.get(beanType);
if ( urlBindings == null ) {
return;
}

Set<UrlBinding> resolvedConflicts = null;
for ( String path : getCachedPaths(binding) ) {
log.debug("Clearing cached path ", path, " for ", binding);
_pathCache.remove(path);
for ( UrlBinding binding : urlBindings ) {
Set<UrlBinding> resolvedConflicts = null;
for ( String path : getCachedPaths(binding) ) {
log.debug("Clearing cached path ", path, " for ", binding);
_pathCache.remove(path);

List<UrlBinding> conflicts = _pathConflicts.get(path);
if ( conflicts != null ) {
log.debug("Removing ", binding, " from conflicts list ", conflicts);
conflicts.remove(binding);
List<UrlBinding> conflicts = _pathConflicts.get(path);
if ( conflicts != null ) {
log.debug("Removing ", binding, " from conflicts list ", conflicts);
conflicts.remove(binding);

if ( conflicts.size() == 1 ) {
if ( resolvedConflicts == null ) {
resolvedConflicts = new LinkedHashSet<>();
}

if ( conflicts.size() == 1 ) {
if ( resolvedConflicts == null ) {
resolvedConflicts = new LinkedHashSet<>();
// this cannot work since conflicts.get(0) returns an UrlBinding but PathCache is a Map<String, UrlBinding>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get that comment, could you explain it a bit more?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_pathCache.get() takes a String as an Argument while conflicts.get(0) returns an UrlBinding
--> ergo _patchCache.get(UrlBinding) will return null and will not work.

resolvedConflicts.add(_pathCache.get(conflicts.get(0)));
conflicts.clear();
}

resolvedConflicts.add(_pathCache.get(conflicts.get(0)));
conflicts.clear();
if ( conflicts.isEmpty() ) {
_pathConflicts.remove(path);
}
}
}

if ( conflicts.isEmpty() ) {
_pathConflicts.remove(path);
for ( String prefix : getCachedPrefixes(binding) ) {
Set<UrlBinding> bindings = _prefixCache.get(prefix);
if ( bindings != null ) {
log.debug("Clearing cached prefix ", prefix, " for ", binding);
bindings.remove(binding);
if ( bindings.isEmpty() ) {
_prefixCache.remove(prefix);
}
}
}
}

for ( String prefix : getCachedPrefixes(binding) ) {
Set<UrlBinding> bindings = _prefixCache.get(prefix);
if ( bindings != null ) {
log.debug("Clearing cached prefix ", prefix, " for ", binding);
bindings.remove(binding);
if ( bindings.isEmpty() ) {
_prefixCache.remove(prefix);
if ( resolvedConflicts != null ) {
log.debug("Resolved conflicts with ", resolvedConflicts);

for ( UrlBinding conflict : resolvedConflicts ) {
removeBinding(conflict.getBeanType());
addBinding(conflict.getBeanType(), conflict, true);
}
}
}

_classCache.remove(beanType);
_classPrimaryBindingCache.remove(beanType);
_classBindingCache.remove(beanType);
}

if ( resolvedConflicts != null ) {
log.debug("Resolved conflicts with ", resolvedConflicts);
@Override
public String toString() {
return String.valueOf(_classPrimaryBindingCache);
}

for ( UrlBinding conflict : resolvedConflicts ) {
removeBinding(conflict.getBeanType());
addBinding(conflict.getBeanType(), conflict);
/**
* Look for a binding pattern for the given {@link ActionBean} class, specified by the
* {@link org.stripesframework.web.action.UrlBinding} annotation. If the annotation is found,
* create and return a {@link UrlBinding} object for the class. Otherwise, return null.
*
* @param beanType The {@link ActionBean} type whose binding is to be parsed
* @return A {@link UrlBinding} if one is specified, or null if not.
* @throws ParseException If the pattern cannot be parsed
*/
protected UrlBinding addUrlBindings( 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 ) {
return null;
}

UrlBinding primaryBinding = parseUrlBinding(beanType, annotation.value());
if ( primaryBinding != null ) {
addBinding(beanType, primaryBinding, true);
}

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

@Override
public String toString() {
return String.valueOf(_classCache);
return primaryBinding;
}

/**
Expand Down
Loading
Loading