Legacy Site Redirects Filter

If your legacy URLs patterns are too complex for a Vanity Redirect, it may be simpler to handle these redirects in a Filter.

The logic to process these legacy URLs redirects will be different for every project, but here’s an example to get you started.

This one handles two different use cases, which is not at all uncommon when migrating from a legacy site.

Note: This code is an example only, and will need customization by a developer before it is useful in a project.

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.psddev.cms.db.Content;
import com.psddev.cms.db.PageFilter;
import com.psddev.dari.db.Predicate;
import com.psddev.dari.db.PredicateParser;
import com.psddev.dari.db.Query;
import com.psddev.dari.util.AbstractFilter;

/**
 * Redirect Legacy URLs to the appropriate permalink.
 */
public class LegacyUrlRedirectFilter extends AbstractFilter implements AbstractFilter.Auto {

    // Legacy URL was /story/normalized-headline/123456
    // "/story" is the prefix
    // "normalized-headline" can be ignored
    // "123456" has been saved on every migrated object using the field "migration.legacyId" with a prefix of "story:"
    private static final Pattern LEGACY_STORY_URI_PATTERN = Pattern.compile("^/story/[^/]+/(.*)$");
    private static final String STORY_ID_PREFIX = "story:";

    // Legacy section path was /section.php?id=789
    // "789" has been saved on every migrated object using the field "migration.legacyId" with a prefix of "section:"
    private static final String LEGACY_SECTION_PATH = "/section.php";
    private static final String LEGACY_SECTION_ID_PARAMETER = "id";
    private static final String SECTION_ID_PREFIX = "section:";

    private static final String LEGACY_ID_FIELD = "migration.legacyId";

    // Helper method to build legacy story ID predicate
    private static Predicate legacyStoryIdPredicate(String storyId) {
        return PredicateParser.Static.parse(LEGACY_ID_FIELD + " = ?", STORY_ID_PREFIX + storyId);
    }

    // Helper method to build legacy section ID predicate
    private static Predicate legacySectionIdPredicate(String sectionId) {
        return PredicateParser.Static.parse(LEGACY_ID_FIELD + " = ?", SECTION_ID_PREFIX + sectionId);
    }

    // Helper method to redirect
    private static void sendRedirect(HttpServletResponse response, String location) {
        response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
        response.setHeader("Location", location);
    }

    // This should run *before* PageFilter.
    @Override
    public void updateDependencies(Class<? extends AbstractFilter> filterClass, List<Class<? extends Filter>> dependencies) {
        if (PageFilter.class == filterClass) {
            dependencies.add(LegacyUrlRedirectFilter.class);
        }
    }

    @Override
    protected void doRequest(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws Exception {

        Object mainObject = PageFilter.Static.getMainObject(request);

        // Only run if a mainObject cannot be found
        if (mainObject == null) {
            String requestPath = request.getRequestURI();

            // Attempt to match the legacy section path
            if (LEGACY_SECTION_PATH.equals(requestPath)) {
                String legacySectionId = request.getParameter(LEGACY_SECTION_ID_PARAMETER);
                if (legacySectionId != null && !legacySectionId.isEmpty()) {
                    Content content = Query.from(Content.class)
                        .where(legacySectionIdPredicate(legacySectionId))
                        .first();
                    if (content != null) {
                        sendRedirect(response, content.getPermalink());
                        return;
                    }
                }
            }

            // Attempt to match the legacy story path and extract the story ID from the path
            Matcher storyPathMatcher = LEGACY_STORY_URI_PATTERN.matcher(requestPath);
            if (storyPathMatcher.matches()) {
                String legacyStoryId = storyPathMatcher.group(1);
                if (legacyStoryId != null && !legacyStoryId.isEmpty()) {
                    Content content = Query.from(Content.class)
                        .where(legacyStoryIdPredicate(legacyStoryId))
                        .first();
                    if (content != null) {
                        sendRedirect(response, content.getPermalink());
                        return;
                    }
                }
            }
        }

        // Otherwise, continue processing the request.
        chain.doFilter(request, response);
    }
}