package info.magnolia.support.example.filter; import info.magnolia.cms.beans.config.MIMEMapping; import info.magnolia.cms.beans.config.ServerConfiguration; import info.magnolia.cms.core.AggregationState; import info.magnolia.cms.filters.AbstractMgnlFilter; import info.magnolia.cms.filters.VirtualUriFilter; import info.magnolia.cms.util.RequestDispatchUtil; import info.magnolia.context.MgnlContext; import info.magnolia.context.WebContext; import info.magnolia.objectfactory.Components; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.ArrayUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author cringele * Filter replacing certain types of extensions and redirecting afterwards. * Permanent redirect: SEO optimizations for non duplicate content * Should be placed before cache filter. */ public class RedirectUrlExtensionFilter extends AbstractMgnlFilter { // Bean configurations private String redirectType = RequestDispatchUtil.PERMANENT_PREFIX; private String defaultExtension = "html"; private boolean useServerDefaultExtension = true; private boolean redirectWithoutExtension = false; private boolean redirectUnknownExtension = false; private Collection redirectExtensions; private Collection proibitedLeadPatterns = new ArrayList(Arrays.asList("/.magnolia", "/VAADIN")); // Other variables private final String fallbackRedirectType = redirectType; // "forward" makes no sense, as only the extension is changed public final static String[] ALLOWED_REDIRECT_TYPES = { RequestDispatchUtil.PERMANENT_PREFIX, RequestDispatchUtil.REDIRECT_PREFIX }; private static final Logger log = LoggerFactory.getLogger(VirtualUriFilter.class); @Override public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { final AggregationState aggregationState = MgnlContext.getAggregationState(); String currentURI = aggregationState.getCurrentURI(); String queryString = aggregationState.getQueryString(); String targetUri = getExtensionProcessedTargetUri(currentURI, queryString); if (StringUtils.isEmpty(targetUri)) { chain.doFilter(request, response); return; } if (response.isCommitted()) { log.warn( "Response is already committed, cannot apply virtual URI {} (original URI was {})", targetUri, request.getRequestURI()); chain.doFilter(request, response); return; } if (RequestDispatchUtil.dispatch(targetUri, request, response)) { return; } chain.doFilter(request, response); } protected String getExtensionProcessedTargetUri(String currentURI, String queryString) { // Stop if it matches a prohibited pattern, no need to process further. if (macthesProhibitedPatterns(currentURI)) { return null; } // Stop if requested uri contains only server&contextPath, no need to process further. if (onlyServerAndContextPath()) { return null; } // When using site definitions, the currentURI passed does not contain the SiteDefiniton name. So needing to operate on the originalBrowserURI AggregationState aggregationState = MgnlContext.getAggregationState(); String processedUri = aggregationState.getOriginalBrowserURI(); String beforeProcessed = processedUri; // Processing if (no extension OR "/" OR ".") is at the end of the url if (redirectWithoutExtension) { processedUri = trimMissingExtension(processedUri); } // Iterates trough he configured extensions to eliminate and to redirect for (String redirectExtension : redirectExtensions) { processedUri = removeExtensionIfMatching(processedUri, redirectExtension); } // Should be processed for all not known MIME types regarding the extension -> catching for example %URL%.blah should be changed to %URL%.html if (redirectUnknownExtension) { processedUri = removeExtensionIfNotKnownMimeType(processedUri); } // if processedUri is changed a redirect needs to be done. boolean uriWasProcessed = !processedUri.equals(beforeProcessed); // Checking if empty extension should be redirected, but only if also originally no extension was defined. boolean replaceEmptyExtensions = redirectWithoutExtension && StringUtils.isEmpty(aggregationState.getExtension()); if (uriWasProcessed || replaceEmptyExtensions) { processedUri = addRedirectType(processedUri); processedUri = addDefaultExtension(processedUri); processedUri = addQueryString(queryString, processedUri); return processedUri; } return null; } protected boolean onlyServerAndContextPath() { AggregationState aggregationState = MgnlContext.getAggregationState(); WebContext webContext = MgnlContext.getWebContext(); String contextPath = webContext.getContextPath(); String originalBrowserURL = aggregationState.getOriginalBrowserURL(); String serverName = webContext.getRequest().getServerName(); int serverPort = webContext.getRequest().getServerPort(); String fullServerAndContextPath = serverName; if (serverPort != 80) { fullServerAndContextPath += ":" + serverPort; } fullServerAndContextPath += contextPath; if (originalBrowserURL.endsWith(fullServerAndContextPath) || originalBrowserURL.endsWith(fullServerAndContextPath + "/")) { return true; } return false; } protected String addQueryString(String queryString, String processedUri) { if (queryString != null) { processedUri = processedUri + "?" + queryString; } return processedUri; } protected String removeExtensionIfNotKnownMimeType(String processedUri) { String currentExtension = getUriExtension(processedUri); if (!knownServerMimeType(currentExtension)) { processedUri = StringUtils.removeEnd(processedUri, "." + currentExtension); } return processedUri; } protected String removeExtensionIfMatching(String processedUri, String redirectExtension) { String suffix = "." + redirectExtension; if (StringUtils.endsWith(processedUri, suffix)) { processedUri = StringUtils.removeEnd(processedUri, suffix); } return processedUri; } protected String getUriExtension(String uri) { final String fileName = StringUtils.substringAfterLast(uri, "/"); return StringUtils.substringAfterLast(fileName, "."); } protected String addDefaultExtension(String processedUri) { // Add the default extension to the url if (useServerDefaultExtension) { processedUri = processedUri + "." + Components.getComponent(ServerConfiguration.class).getDefaultExtension(); } else { processedUri = processedUri + "." + defaultExtension; } return processedUri; } protected String addRedirectType(String processedUri) { // Checking of a valid redirect type is set, otherwise using the default. if (ArrayUtils.contains(ALLOWED_REDIRECT_TYPES, redirectType)) { processedUri = redirectType + processedUri; } else { processedUri = fallbackRedirectType + processedUri; } return processedUri; } protected boolean knownServerMimeType(String mimeType) { return StringUtils.isNotEmpty(MIMEMapping.getMIMEType(mimeType)); } protected String trimMissingExtension(String processedUri) { if (processedUri.endsWith("/")) { processedUri = StringUtils.removeEnd(processedUri, "/"); } if (!StringUtils.substringAfterLast(processedUri, "/").contains(".")) { } if (StringUtils.endsWith(processedUri, ".")) { processedUri = StringUtils.removeEnd(processedUri, "."); } return processedUri; } private boolean macthesProhibitedPatterns(String currentURI) { for (String proibitedLeadPattern : proibitedLeadPatterns) { if (StringUtils.startsWith(currentURI, proibitedLeadPattern) || StringUtils.startsWith(currentURI, "/" + proibitedLeadPattern)) { return true; } } return false; } public static String[] getAllowedRedirectTypes() { return ALLOWED_REDIRECT_TYPES; } // Bean setters & getters public String getDefaultExtension() { return defaultExtension; } public void setDefaultExtension(String defaultExtension) { this.defaultExtension = defaultExtension; } public String getRedirectType() { return redirectType; } public void setRedirectType(String redirectType) { this.redirectType = redirectType; } public boolean getUseServerDefaultExtension() { return useServerDefaultExtension; } public void setUseServerDefaultExtension(boolean useServerDefaultExtension) { this.useServerDefaultExtension = useServerDefaultExtension; } public Collection getRedirectExtensions() { return redirectExtensions; } public void setRedirectExtensions(Collection redirectExtensions) { this.redirectExtensions = redirectExtensions; } public void addRedirectExtension(String redirectExtension) { this.redirectExtensions.add(redirectExtension); } public Collection getProibitedLeadPatterns() { return proibitedLeadPatterns; } public void setProibitedLeadPatterns(Collection proibitedLeadPatterns) { this.proibitedLeadPatterns = proibitedLeadPatterns; } public void addProibitedLeadPattern(String proibitedLeadPatterns) { this.proibitedLeadPatterns.add(proibitedLeadPatterns); } public boolean isRedirectWithoutExtension() { return redirectWithoutExtension; } public void setRedirectWithoutExtension(boolean redirectWithoutExtension) { this.redirectWithoutExtension = redirectWithoutExtension; } public boolean isRedirectUnknownExtension() { return redirectUnknownExtension; } public void setRedirectUnknownExtension(boolean redirectUnknownExtension) { this.redirectUnknownExtension = redirectUnknownExtension; } }