Created
August 23, 2013 20:32
-
-
Save UnquietCode/6323695 to your computer and use it in GitHub Desktop.
Custom implementation of Spring's RequestMappingHandlerMapping which automatically registers a redirect for the same url with (or without) a trailing slash. This is good in cases where for SEO purposes you want 'url' and 'url/' to resolve to the same page.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.sb.server.web; | |
import org.springframework.core.annotation.AnnotationUtils; | |
import org.springframework.http.HttpStatus; | |
import org.springframework.web.servlet.View; | |
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; | |
import org.springframework.web.servlet.mvc.method.RequestMappingInfo; | |
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; | |
import org.springframework.web.servlet.view.RedirectView; | |
import java.lang.reflect.Method; | |
/** | |
* Custom implementation of annotation-based handler mapping which | |
* looks for the {@link RedirectTrailingSlash} annotation and registers | |
* a handler to cover the case of the url with or without the slash. | |
* The redirect is handled as 301 Redirect | |
* | |
* @author Ben Fagin | |
* @version 2013-08-22 | |
*/ | |
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping { | |
@Override | |
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) { | |
super.registerHandlerMethod(handler, method, mapping); | |
RedirectTrailingSlash redirectAnnotation = AnnotationUtils.findAnnotation(method, RedirectTrailingSlash.class); | |
if (redirectAnnotation == null) { return; } | |
for (String pattern : mapping.getPatternsCondition().getPatterns()) { | |
registerRedirectHandler(pattern, redirectAnnotation.code(), mapping); | |
} | |
} | |
private void registerRedirectHandler(String originalURL, int statusCode, RequestMappingInfo info) { | |
// skip when there's an extension | |
if (originalURL.contains(".")) { return; } | |
final String otherURL; | |
// redirect when not trailing | |
if (originalURL.endsWith("/")) { | |
otherURL = originalURL.substring(0, originalURL.length()-1); | |
} | |
// redirect when trailing | |
else { | |
otherURL = originalURL+"/"; | |
} | |
RedirectHandler handler = new RedirectHandler(originalURL, statusCode); | |
registerHandlerMethod(handler, handlerMethod, synthesizeMapping(info, otherURL)); | |
} | |
private RequestMappingInfo synthesizeMapping(RequestMappingInfo existing, String url) { | |
String[] patterns = resolveEmbeddedValuesInPatterns(new String[]{url}); | |
PatternsRequestCondition patternsMatcher = new PatternsRequestCondition( | |
patterns, getUrlPathHelper(), getPathMatcher(), | |
useSuffixPatternMatch(), false, getFileExtensions() | |
); | |
return new RequestMappingInfo( | |
patternsMatcher, | |
existing.getMethodsCondition(), | |
existing.getParamsCondition(), | |
existing.getHeadersCondition(), | |
existing.getConsumesCondition(), | |
existing.getProducesCondition(), | |
existing.getCustomCondition() | |
); | |
} | |
private static final Method handlerMethod; static { | |
try { | |
handlerMethod = RedirectHandler.class.getMethod("handle"); | |
} catch (Exception ex) { | |
throw new RuntimeException(ex); | |
} | |
} | |
private static class RedirectHandler { | |
private final String redirectURL; | |
private final int statusCode; | |
public RedirectHandler(String redirectURL, int statusCode) { | |
this.redirectURL = redirectURL; | |
this.statusCode = statusCode; | |
} | |
public View handle() { | |
RedirectView view = new RedirectView(redirectURL); | |
view.setStatusCode(HttpStatus.valueOf(statusCode)); | |
return view; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.sb.server.web; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* Marker annotation which says that a given method should | |
* use a redirect when a trailing slash version is encountered. | |
* | |
* The method should already be annotated with | |
* {@link org.springframework.web.bind.annotation.RequestMapping}. | |
* | |
* @author Ben Fagin | |
* @version 2013-08-22 | |
* @see CustomRequestMappingHandlerMapping | |
*/ | |
@Target(ElementType.METHOD) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface RedirectTrailingSlash { | |
/** | |
* The status code to use for the redirect. | |
*/ | |
int code() default 301; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.sb.server.web; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; | |
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; | |
/** | |
* @author Ben Fagin | |
* @version 2013-04-22 | |
*/ | |
@Configuration | |
public class WebConfiguration extends WebMvcConfigurationSupport { | |
@Bean | |
@Override | |
public RequestMappingHandlerMapping requestMappingHandlerMapping() { | |
CustomRequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping(); | |
handlerMapping.setOrder(0); | |
handlerMapping.setInterceptors(getInterceptors()); | |
handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager()); | |
handlerMapping.setUseTrailingSlashMatch(false); | |
handlerMapping.setUseSuffixPatternMatch(false); | |
return handlerMapping; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment