Ok, well no answers, but I'll post what I ended up coming up with. I'm not sure if it's the best solution, but it seems to work okay for me. There's probably a better way to set up the model map, or rewrite path parameters, but servlet request was working okay...
So this is the main MappingHandler:
/**
* The SeoUrlHandlerMapping will map between SEO URL requests and controller method
*/
@Component
public class SeoUrlHandlerMapping extends RequestMappingHandlerMapping {
private static Logger logger = LogManager.getLogger(SeoUrlHandlerMapping.class.getName());
@Autowired
private ProductSeoService productSeoService;
private final Map<String, HandlerMethod> handlerMethods = new LinkedHashMap<String, HandlerMethod>();
@Override
protected void initHandlerMethods() {
logger.debug("initialising the handler methods");
String[] beanNames =
getApplicationContext().getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
Class clazz = getApplicationContext().getType(beanName);
final Class<?> userType = ClassUtils.getUserClass(clazz);
if (isHandler(clazz)){
for (Method method: clazz.getMethods())
{
SeoUrlMapper mapper = AnnotationUtils.getAnnotation(method, SeoUrlMapper.class);
if (mapper != null)
{
RequestMappingInfo mapping = getMappingForMethod(method, userType);
HandlerMethod handlerMethod = createHandlerMethod(beanName, method);
this.handlerMethods.put(mapper.seoType(), handlerMethod);
}
}
}
}
}
/**
* {@inheritDoc}
* Expects a handler to have a type-level @{@link org.springframework.stereotype.Controller} annotation.
*/
@Override
protected boolean isHandler(Class<?> beanType) {
return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
(AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
}
/**
* The lookup handler method, maps the SEOMapper method to the request URL.
* <p>If no mapping is found, or if the URL is disabled, it will simply drop throug
* to the standard 404 handling.</p>
* @param urlPath the path to match.
* @param request the http servlet request.
* @return The HandlerMethod if one was found.
* @throws Exception
*/
@Override
protected HandlerMethod lookupHandlerMethod(String urlPath, HttpServletRequest request) throws Exception {
logger.entry("looking up handler for path: " + urlPath);
// this is just a test.
SeoUrl productUrl = productSeoService.findByURL(urlPath);
if (productUrl instanceof ProductSeoUrl) {
ProductSeoUrl productSeoUrl = (ProductSeoUrl) productUrl;
if (productSeoUrl.getStatus().equals(SeoUrlStatus.OK) || productSeoUrl.getStatus().equals(SeoUrlStatus.DRAFT))
{
request.setAttribute(SeoConstants.ID, productSeoUrl.getProduct().getId());
request.setAttribute(SeoConstants.URL_STATUS, productSeoUrl.getStatus().toString());
return this.handlerMethods.get("PRODUCT");
}else if (productSeoUrl.getStatus().equals(SeoUrlStatus.REDIRECTED))
{
request.setAttribute(SeoConstants.REDIRECT_URL, productSeoUrl.getRedirectURL());
return this.handlerMethods.get("REDIRECT");
}
// otherwise we let it return 404 by dropping through.
}
return null;
}
}
Then I used a custom annotation on Controller methods to isolate the handler methods:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SeoUrlMapper {
/**
* Assigns a type to this mapping.
* <p><b>This should match the SEOEntityType constants</b></p>.
*/
String seoType();
}
And finally in my Controller methods I set the annotations to indicate the methods:
@SeoUrlMapper(seoType = "REDIRECT")
public RedirectView issueRedirect(ModelMap map, HttpServletRequest request)
{
logger.entry();
RedirectView view = new RedirectView();
view.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
view.setUrl((String)request.getAttribute("REDIRECT_URL"));
view.setExposeModelAttributes(false);
logger.exit();
return view;
}
@SeoUrlMapper(seoType = "PRODUCT")
public String viewProductInternal(ModelMap map, HttpServletRequest request)
{
Long id = (Long) request.getAttribute(SeoConstants.ID);
Product product = productService.findForDetailView(id);
return commonViewProduct(product, map);
}