From 0de81d082a606ef32d4590f6f521f28b2c17804f Mon Sep 17 00:00:00 2001 From: Bart Date: Thu, 19 Jan 2023 20:11:00 +0100 Subject: [PATCH] #2980 Add possibility to evaluate redirect rule on request URI (#2981) * Add possibility to evaluate redirect rule on request URI instead of only resource path Co-authored-by: Bart Thierens Co-authored-by: bart.thierens Co-authored-by: Yegor Kozlov --- CHANGELOG.md | 6 +- .../redirects/filter/RedirectFilter.java | 9 +- .../models/RedirectConfiguration.java | 54 +- .../redirects/models/RedirectRule.java | 13 +- .../servlets/ExportRedirectMapServlet.java | 45 +- .../servlets/ImportRedirectMapServlet.java | 15 +- .../redirects/RedirectResourceBuilder.java | 5 + .../redirects/filter/RedirectFilterTest.java | 114 ++ .../models/RedirectConfigurationTest.java | 17 + .../redirects/models/RedirectRuleTest.java | 6 +- .../ExportRedirectMapServletTest.java | 25 +- .../ImportRedirectMapServletTest.java | 20 +- .../manage-redirects/clientlibs/app.js | 11 +- .../manage-redirects/manage-redirects.html | 1 + .../redirect-row/redirect-row.html | 3 + .../content/redirect-manager/.content.xml | 1034 +++++++++-------- 16 files changed, 812 insertions(+), 566 deletions(-) mode change 100755 => 100644 bundle/src/main/java/com/adobe/acs/commons/redirects/models/RedirectRule.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 559674f116..130e113518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com) ### Changed -- #2982 - Add OSGi configuration option for CSV delimiters in reports -### Added -- #3016 - Added crawl delay +- #2980 - Redirect Manager: Allow evaluating of redirect rules based on request URI ### Added +- #2982 - Add OSGi configuration option for CSV delimiters in reports +- #3016 - Added crawl delay - #3008 - Redirect Manager: Add "State" column - #2977 - Redirect Manager: Add "Effective From" field diff --git a/bundle/src/main/java/com/adobe/acs/commons/redirects/filter/RedirectFilter.java b/bundle/src/main/java/com/adobe/acs/commons/redirects/filter/RedirectFilter.java index de9efd28ad..3f7c34d68c 100755 --- a/bundle/src/main/java/com/adobe/acs/commons/redirects/filter/RedirectFilter.java +++ b/bundle/src/main/java/com/adobe/acs/commons/redirects/filter/RedirectFilter.java @@ -153,13 +153,12 @@ public class RedirectFilter extends AnnotatedStandardMBean @AttributeDefinition(name = "Preserve Query String", description = "Preserve query string in redirects", type = AttributeType.BOOLEAN) boolean preserveQueryString() default true; - @AttributeDefinition(name = "Preserve Extension", description = "Whether to preserve extensions" + @AttributeDefinition(name = "Preserve Extension", description = "Whether to preserve extensions. " + "When this flag is checked (default), redirect filter will preserve the extension from the request, " + "e.g. append .html to the Location header. ", type = AttributeType.BOOLEAN) boolean preserveExtension() default true; - @AttributeDefinition(name = "Evaluate Selectors", description = "Take into account selectors when evaluating redirects. " - + "When this flag is unchecked (default), selectors are ignored and don't participate in rule matching", type = AttributeType.BOOLEAN) + @AttributeDefinition(name = "Evaluate Selectors", description = "(Deprecated) Use the Evaluate URI mode in redirect rule to capture selectors,", type = AttributeType.BOOLEAN) boolean evaluateSelectors() default false; @AttributeDefinition(name = "Additional Response Headers", description = "Optional response headers in the name:value format to apply on delivery," @@ -581,12 +580,12 @@ RedirectMatch match(SlingHttpServletRequest slingRequest) { ValueMap properties = configResource.getValueMap(); String contextPrefix = properties.get(Redirects.CFG_PROP_CONTEXT_PREFIX, ""); - RedirectMatch m = rules.match(resourcePath, contextPrefix); + RedirectMatch m = rules.match(resourcePath, contextPrefix, slingRequest); if (m == null && mapUrls()) { // try mapped url String mappedUrl= mapUrl(resourcePath, slingRequest); // https://www.mysite.com/en/page.html if(!resourcePath.equals(mappedUrl)) { // don't bother if sling mappings are not defined for this path String mappedPath = URI.create(mappedUrl).getPath(); // /en/page.html - m = rules.match(mappedPath); + m = rules.match(mappedPath, "", slingRequest); } } return m; diff --git a/bundle/src/main/java/com/adobe/acs/commons/redirects/models/RedirectConfiguration.java b/bundle/src/main/java/com/adobe/acs/commons/redirects/models/RedirectConfiguration.java index 039a049706..45cc7ef0cb 100755 --- a/bundle/src/main/java/com/adobe/acs/commons/redirects/models/RedirectConfiguration.java +++ b/bundle/src/main/java/com/adobe/acs/commons/redirects/models/RedirectConfiguration.java @@ -20,6 +20,7 @@ package com.adobe.acs.commons.redirects.models; import com.adobe.acs.commons.redirects.filter.RedirectFilter; +import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import java.util.Collection; @@ -32,6 +33,8 @@ * A collection of redirect rules */ public class RedirectConfiguration { + + private boolean nonRegexRequestURIRules = false; /** * path rules keyed by source, e.g. path1 -> path2. * This makes lookup by path a O(1) operation @@ -61,10 +64,15 @@ public RedirectConfiguration(Resource resource, String storageSuffix) { if (rule.getRegex() != null) { patternRules.put(rule.getRegex(), rule); } else { - pathRules.put(normalizePath(rule.getSource()), rule); + // request URI rules are keyed without normalizing + if(rule.getEvaluateURI()){ + nonRegexRequestURIRules = true; + pathRules.put(rule.getSource(), rule); + } else { + pathRules.put(normalizePath(rule.getSource()), rule); + } } } - } /** @@ -80,6 +88,10 @@ public static String normalizePath(String resourcePath) { return resourcePath; } + static String determinePathToEvaluate(String path, boolean evaluateURI, SlingHttpServletRequest request) { + return (evaluateURI && request != null) ? request.getRequestURI() : path; + } + public Map getPathRules() { return pathRules; } @@ -106,10 +118,10 @@ public String getName() { * * @param requestPath the request to match * @return match or null - * @see #match(String, String) + * @see #match(String, String, SlingHttpServletRequest) */ public RedirectMatch match(String requestPath) { - return match(requestPath, ""); + return match(requestPath, "", null); } /** @@ -120,19 +132,27 @@ public RedirectMatch match(String requestPath) { *
  • Match by a regular expression. This is O(N) linear lookup in a list of rules keyed by their regex patterns
  • * * - * @param requestPath the request to match + * @param resourcePath the request to match * @param contextPrefix the optional context prefix to take into account + * @param request the current sling request * @return match or null */ - public RedirectMatch match(String requestPath, String contextPrefix) { - String normalizedPath = normalizePath(requestPath); + public RedirectMatch match(String resourcePath, String contextPrefix, SlingHttpServletRequest request) { + String normalizedPath = normalizePath(resourcePath); RedirectMatch match = null; RedirectRule rule = getPathRule(normalizedPath, contextPrefix); + if(rule == null && hasNonRegexRequestURIRules()){ + // there are request URI rules. Check is any mathes + String pathToEvaluate = determinePathToEvaluate(normalizedPath, true, request); + rule = getPathRule(pathToEvaluate, contextPrefix); + } if (rule != null) { match = new RedirectMatch(rule, null); } else { for (Map.Entry entry : getPatternRules().entrySet()) { - Matcher m = getRuleMatch(entry.getKey(), normalizedPath, contextPrefix); + boolean evaluateURI = entry.getValue().getEvaluateURI(); + String pathToEvaluate = determinePathToEvaluate(normalizedPath, evaluateURI, request); + Matcher m = getRuleMatch(entry.getKey(), pathToEvaluate, contextPrefix); if (m.matches()) { match = new RedirectMatch(entry.getValue(), m); break; @@ -145,25 +165,25 @@ public RedirectMatch match(String requestPath, String contextPrefix) { /** * Utility method that gets the pattern rule taking an optional context prefix into account * @param rulePattern the regex pattern to match the path - * @param normalizedPath the normalized path + * @param pathToEvaluate the path to evaluate for redirects * @param contextPrefix the optional context prefix * @return the matcher associated with the rule */ - private Matcher getRuleMatch(Pattern rulePattern, String normalizedPath, String contextPrefix) { + private Matcher getRuleMatch(Pattern rulePattern, String pathToEvaluate, String contextPrefix) { if("".equals(contextPrefix)) { - return rulePattern.matcher(normalizedPath); + return rulePattern.matcher(pathToEvaluate); } else { //we add the context prefix to the pattern since a pattern might be too broad otherwise, //i.e. "/(.*)" will match anything if(!rulePattern.toString().startsWith(contextPrefix)) { rulePattern = RedirectRule.toRegex(contextPrefix + rulePattern.toString()); } - Matcher matcher = rulePattern.matcher(normalizedPath); + Matcher matcher = rulePattern.matcher(pathToEvaluate); if(!matcher.matches()) { - if (normalizedPath.startsWith(contextPrefix)) { - matcher = rulePattern.matcher(normalizedPath.replace(contextPrefix, "")); + if (pathToEvaluate.startsWith(contextPrefix)) { + matcher = rulePattern.matcher(pathToEvaluate.replace(contextPrefix, "")); } else { - matcher = rulePattern.matcher(contextPrefix + normalizedPath); + matcher = rulePattern.matcher(contextPrefix + pathToEvaluate); } } return matcher; @@ -192,4 +212,8 @@ private RedirectRule getPathRule(String normalizedPath, String contextPrefix) { } } + private boolean hasNonRegexRequestURIRules() { + return this.nonRegexRequestURIRules; + } + } diff --git a/bundle/src/main/java/com/adobe/acs/commons/redirects/models/RedirectRule.java b/bundle/src/main/java/com/adobe/acs/commons/redirects/models/RedirectRule.java old mode 100755 new mode 100644 index 32452d42b0..950f348b6b --- a/bundle/src/main/java/com/adobe/acs/commons/redirects/models/RedirectRule.java +++ b/bundle/src/main/java/com/adobe/acs/commons/redirects/models/RedirectRule.java @@ -54,6 +54,7 @@ public class RedirectRule { public static final String EFFECTIVE_FROM_PROPERTY_NAME = "effectiveFrom"; public static final String NOTE_PROPERTY_NAME = "note"; public static final String CONTEXT_PREFIX_IGNORED_PROPERTY_NAME = "contextPrefixIgnored"; + public static final String EVALUATE_URI_PROPERTY_NAME = "evaluateURI"; public static final String CREATED_PROPERTY_NAME = "jcr:created"; public static final String CREATED_BY_PROPERTY_NAME = "jcr:createdBy"; public static final String MODIFIED_PROPERTY_NAME = "jcr:lastModified"; @@ -69,6 +70,9 @@ public class RedirectRule { @ValueMapValue(injectionStrategy = InjectionStrategy.REQUIRED) private int statusCode; + @ValueMapValue + private boolean evaluateURI; + @ValueMapValue private Calendar untilDate; @@ -134,6 +138,10 @@ public int getStatusCode() { return statusCode; } + public boolean getEvaluateURI() { + return evaluateURI; + } + public String getCreatedBy() { return createdBy; } @@ -190,9 +198,9 @@ public List getTags() { @Override public String toString() { - return String.format("RedirectRule{source='%s', target='%s', statusCode=%s, untilDate=%s, effectiveFrom=%s, note=%s, " + return String.format("RedirectRule{source='%s', target='%s', statusCode=%s, untilDate=%s, effectiveFrom=%s, note=%s, evaluateURI=%s," + "contextPrefixIgnored=%s, tags=%s, created=%s, createdBy=%s, modified=%s, modifiedBy=%s}", - source, target, statusCode, untilDate, effectiveFrom, note, contextPrefixIgnored, + source, target, statusCode, untilDate, effectiveFrom, note, evaluateURI, contextPrefixIgnored, Arrays.toString(tagIds), created, createdBy, modified, modifiedBy); } @@ -238,6 +246,7 @@ static Pattern toRegex(String src) { } /** + * @return whether the rule has expired, i.e. the 'untilDate' property is before the current time * ----[effectiveFrom]---[now]---[untilDate]---> * diff --git a/bundle/src/main/java/com/adobe/acs/commons/redirects/servlets/ExportRedirectMapServlet.java b/bundle/src/main/java/com/adobe/acs/commons/redirects/servlets/ExportRedirectMapServlet.java index 58fcfc344f..08595e789b 100755 --- a/bundle/src/main/java/com/adobe/acs/commons/redirects/servlets/ExportRedirectMapServlet.java +++ b/bundle/src/main/java/com/adobe/acs/commons/redirects/servlets/ExportRedirectMapServlet.java @@ -108,13 +108,14 @@ static XSSFWorkbook export(Collection rules) { headerRow.createCell(2).setCellValue("Status Code"); headerRow.createCell(3).setCellValue("Off Time"); headerRow.createCell(4).setCellValue("Notes"); - headerRow.createCell(5).setCellValue("Ignore Context Prefix"); - headerRow.createCell(6).setCellValue("Tags"); - headerRow.createCell(7).setCellValue("Created"); - headerRow.createCell(8).setCellValue("Created By"); - headerRow.createCell(9).setCellValue("Modified"); - headerRow.createCell(10).setCellValue("Modified By"); - headerRow.createCell(11).setCellValue("On Time"); + headerRow.createCell(5).setCellValue("Evaluate URI"); + headerRow.createCell(6).setCellValue("Ignore Context Prefix"); + headerRow.createCell(7).setCellValue("Tags"); + headerRow.createCell(8).setCellValue("Created"); + headerRow.createCell(9).setCellValue("Created By"); + headerRow.createCell(10).setCellValue("Modified"); + headerRow.createCell(11).setCellValue("Modified By"); + headerRow.createCell(12).setCellValue("On Time"); for (Cell cell : headerRow) { cell.setCellStyle(headerStyle); } @@ -130,38 +131,39 @@ static XSSFWorkbook export(Collection rules) { cell.setCellStyle(dateStyle); } row.createCell(4).setCellValue(rule.getNote()); - row.createCell(5).setCellValue(rule.getContextPrefixIgnored()); + row.createCell(5).setCellValue(rule.getEvaluateURI()); + row.createCell(6).setCellValue(rule.getContextPrefixIgnored()); - Cell cell6 = row.createCell(6); + Cell cell6 = row.createCell(7); String[] tagIds = rule.getTagIds(); if(tagIds != null) { cell6.setCellValue(String.join("\n", tagIds)); } cell6.setCellStyle(cellWrapStyle); - Cell cell7 = row.createCell(7); + Cell cell7 = row.createCell(8); cell7.setCellValue(rule.getCreated()); cell7.setCellStyle(dateStyle); - Cell cell8 = row.createCell(8); + Cell cell8 = row.createCell(9); cell8.setCellValue(rule.getCreatedBy()); cell8.setCellStyle(lockedCellStyle); - Cell cell9 = row.createCell(9); + Cell cell9 = row.createCell(10); cell9.setCellValue(rule.getModified()); cell9.setCellStyle(dateStyle); - Cell cell10 = row.createCell(10); + Cell cell10 = row.createCell(11); cell10.setCellValue(rule.getModifiedBy()); cell10.setCellStyle(lockedCellStyle); Calendar effectiveFrom = rule.getEffectiveFrom(); if (effectiveFrom != null) { - Cell cell = row.createCell(11); + Cell cell = row.createCell(12); cell.setCellValue(effectiveFrom); cell.setCellStyle(dateStyle); } - } + } sheet.setAutoFilter(new CellRangeAddress(0, rownum - 1, 0, 10)); sheet.setColumnWidth(0, 256 * 50); sheet.setColumnWidth(1, 256 * 50); @@ -169,12 +171,13 @@ static XSSFWorkbook export(Collection rules) { sheet.setColumnWidth(3, 256 * 12); sheet.setColumnWidth(4, 256 * 100); sheet.setColumnWidth(5, 256 * 20); - sheet.setColumnWidth(6, 256 * 25); - sheet.setColumnWidth(7, 256 * 12); - sheet.setColumnWidth(8, 256 * 30); - sheet.setColumnWidth(9, 256 * 12); - sheet.setColumnWidth(10, 256 * 30); - sheet.setColumnWidth(11, 256 * 12); + sheet.setColumnWidth(6, 256 * 20); + sheet.setColumnWidth(7, 256 * 25); + sheet.setColumnWidth(8, 256 * 12); + sheet.setColumnWidth(9, 256 * 30); + sheet.setColumnWidth(10, 256 * 12); + sheet.setColumnWidth(11, 256 * 30); + sheet.setColumnWidth(12, 256 * 12); return wb; } diff --git a/bundle/src/main/java/com/adobe/acs/commons/redirects/servlets/ImportRedirectMapServlet.java b/bundle/src/main/java/com/adobe/acs/commons/redirects/servlets/ImportRedirectMapServlet.java index 5bde73acc1..6c6ad353ac 100755 --- a/bundle/src/main/java/com/adobe/acs/commons/redirects/servlets/ImportRedirectMapServlet.java +++ b/bundle/src/main/java/com/adobe/acs/commons/redirects/servlets/ImportRedirectMapServlet.java @@ -190,10 +190,10 @@ private Map readRedirect(Row row){ untilDate.setTime(c3.getDateCellValue()); props.put(RedirectRule.UNTIL_DATE_PROPERTY_NAME, untilDate); } - Cell c11 = row.getCell(11); - if (DateUtil.isCellDateFormatted(c11)) { + Cell c12 = row.getCell(12); + if (DateUtil.isCellDateFormatted(c12)) { Calendar effectiveFrom = Calendar.getInstance(); - effectiveFrom.setTime(c11.getDateCellValue()); + effectiveFrom.setTime(c12.getDateCellValue()); props.put(RedirectRule.EFFECTIVE_FROM_PROPERTY_NAME, effectiveFrom); } Cell c4 = row.getCell(4); @@ -201,10 +201,13 @@ private Map readRedirect(Row row){ props.put(RedirectRule.NOTE_PROPERTY_NAME, c4.getStringCellValue()); } Cell c5 = row.getCell(5); - boolean ignoreContextPrefix = (c5 != null && c5.getBooleanCellValue()); - props.put(RedirectRule.CONTEXT_PREFIX_IGNORED_PROPERTY_NAME, ignoreContextPrefix); + boolean evaluateURI = (c5 != null && c5.getBooleanCellValue()); + props.put(RedirectRule.EVALUATE_URI_PROPERTY_NAME, evaluateURI); Cell c6 = row.getCell(6); - String[] tagIds = c6 == null ? null : c6.getStringCellValue().split("\n"); + boolean ignoreContextPrefix = (c6 != null && c6.getBooleanCellValue()); + props.put(RedirectRule.CONTEXT_PREFIX_IGNORED_PROPERTY_NAME, ignoreContextPrefix); + Cell c7 = row.getCell(7); + String[] tagIds = c7 == null ? null : c7.getStringCellValue().split("\n"); props.put(RedirectRule.TAGS_PROPERTY_NAME, tagIds); return props; } diff --git a/bundle/src/test/java/com/adobe/acs/commons/redirects/RedirectResourceBuilder.java b/bundle/src/test/java/com/adobe/acs/commons/redirects/RedirectResourceBuilder.java index 3ebb269450..77e7a9f4f0 100644 --- a/bundle/src/test/java/com/adobe/acs/commons/redirects/RedirectResourceBuilder.java +++ b/bundle/src/test/java/com/adobe/acs/commons/redirects/RedirectResourceBuilder.java @@ -66,6 +66,11 @@ public RedirectResourceBuilder setStatusCode(int statusCode) { return this; } + public RedirectResourceBuilder setEvaluateURI(boolean evaluateURI) { + props.put(EVALUATE_URI_PROPERTY_NAME, evaluateURI); + return this; + } + public RedirectResourceBuilder setNotes(String note) { props.put(NOTE_PROPERTY_NAME, note); return this; diff --git a/bundle/src/test/java/com/adobe/acs/commons/redirects/filter/RedirectFilterTest.java b/bundle/src/test/java/com/adobe/acs/commons/redirects/filter/RedirectFilterTest.java index 99b3a7eb4e..3dc359d33a 100755 --- a/bundle/src/test/java/com/adobe/acs/commons/redirects/filter/RedirectFilterTest.java +++ b/bundle/src/test/java/com/adobe/acs/commons/redirects/filter/RedirectFilterTest.java @@ -61,7 +61,9 @@ import static com.adobe.acs.commons.redirects.filter.RedirectFilter.getRules; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -138,6 +140,38 @@ private MockSlingHttpServletResponse navigate(String resourcePath) throws IOExce return response; } + private MockSlingHttpServletResponse navigateToURI(String requestPath) throws IOException, ServletException { + MockSlingHttpServletRequest request = spy(context.request()); + doReturn(requestPath).when(request).getRequestURI(); + + String resourcePath = requestPath; + int idx = resourcePath.indexOf('.'); + if (idx > -1) { + resourcePath = resourcePath.substring(0, idx); + } + + int qs = requestPath.lastIndexOf('?'); + if (qs > 0) { + request.setQueryString(requestPath.substring(qs + 1)); + } + context.requestPathInfo().setResourcePath(resourcePath); + request.setResource(getOrCreateResource(resourcePath)); + + + MockSlingHttpServletResponse response = context.response(); + filter.doFilter(request, response, filterChain); + + return response; + } + + private Resource getOrCreateResource(String resourcePath) { + Resource resource = context.resourceResolver().getResource(resourcePath); + if (resource == null) { + resource = context.create().resource(resourcePath); + } + return resource; + } + @Test public void testActivate() { assertTrue(filter.isEnabled()); @@ -165,8 +199,10 @@ public void testReadRules() throws PersistenceException { new RedirectResourceBuilder(context) .setSource("/content/we-retail/en/events/*") .setTarget("/content/we-retail/en/four") + .setEvaluateURI(true) .setStatusCode(301).build() ); + ResourceBuilder rb = context.build().resource(redirectStoragePath).siblingsMode(); rb.resource("redirect-invalid-1","sling:resourceType", "cq:Page"); @@ -178,17 +214,20 @@ public void testReadRules() throws PersistenceException { assertEquals("/content/we-retail/en/one", rule1.getSource()); assertEquals("/content/we-retail/en/two", rule1.getTarget()); assertEquals(302, rule1.getStatusCode()); + assertFalse(rule1.getEvaluateURI()); RedirectRule rule2 = it.next(); assertEquals("/content/we-retail/en/three", rule2.getSource()); assertEquals("/content/we-retail/en/four", rule2.getTarget()); assertEquals(301, rule2.getStatusCode()); + assertFalse(rule2.getEvaluateURI()); RedirectRule rule3 = it.next(); assertEquals("/content/we-retail/en/events/*", rule3.getSource()); assertEquals("/content/we-retail/en/four", rule3.getTarget()); assertEquals(301, rule3.getStatusCode()); assertNotNull(rule3.getRegex()); + assertTrue(rule3.getEvaluateURI()); } @Test @@ -1092,4 +1131,79 @@ public void testSelectorsDisabled() throws Exception { assertEquals("/content/geometrixx/en/page1.html", navigate("/content/geometrixx/en/one.mobile.html").getHeader("Location")); assertEquals("/content/geometrixx/en/page1.html", navigate("/content/geometrixx/en/one.desktop.html").getHeader("Location")); } + + @Test + public void testEvaluateURI() throws Exception { + withRules( + new RedirectResourceBuilder(context) + .setSource("/content/geometrixx/en/one.html/suffix.html") + .setTarget("/content/geometrixx/en/redirected-page") + .setStatusCode(302) + .setEvaluateURI(true) + .build(), + new RedirectResourceBuilder(context) + .setSource("/content/geometrixx/en/two.mobile.html/suffix.html") + .setTarget("/content/geometrixx/en/redirected-page-selector") + .setStatusCode(302) + .setEvaluateURI(true) + .build(), + new RedirectResourceBuilder(context) + .setSource("(.*)/geometrixx/en/three.html/suffix.html") + .setTarget("/content/geometrixx/en/redirected-page-regex") + .setStatusCode(302) + .setEvaluateURI(true) + .build(), + new RedirectResourceBuilder(context) + .setSource("/content/geometrixx/en/(\\w+).(mobile|desktop).html/suffix-(\\d+).html") + .setTarget("/content/geometrixx/en/redirected-page-multi-regex") + .setStatusCode(302) + .setEvaluateURI(true) + .build() + ); + + MockSlingHttpServletResponse responseNoMatch = navigateToURI("/content/geometrixx/en/zero.html"); + assertNull(responseNoMatch.getHeader("Location")); //not redirected + + MockSlingHttpServletResponse responseURI = navigateToURI("/content/geometrixx/en/one.html/suffix.html"); + assertEquals("/content/geometrixx/en/redirected-page", responseURI.getHeader("Location")); + + MockSlingHttpServletResponse responseSelector = navigateToURI("/content/geometrixx/en/two.mobile.html/suffix.html"); + assertEquals("/content/geometrixx/en/redirected-page-selector", responseSelector.getHeader("Location")); + + MockSlingHttpServletResponse responseRegex = navigateToURI("/content/geometrixx/en/three.html/suffix.html"); + assertEquals("/content/geometrixx/en/redirected-page-regex", responseRegex.getHeader("Location")); + + MockSlingHttpServletResponse responseMultiRegex1 = navigateToURI("/content/geometrixx/en/four.mobile.html/suffix-1.html"); + assertEquals("/content/geometrixx/en/redirected-page-multi-regex", responseMultiRegex1.getHeader("Location")); + + MockSlingHttpServletResponse responseMultiRegex2 = navigateToURI("/content/geometrixx/en/four.desktop.html/suffix-2.html"); + assertEquals("/content/geometrixx/en/redirected-page-multi-regex", responseMultiRegex2.getHeader("Location")); + + MockSlingHttpServletResponse responseMultiRegex3 = navigateToURI("/content/geometrixx/en/five.mobile.html/suffix-3.html"); + assertEquals("/content/geometrixx/en/redirected-page-multi-regex", responseMultiRegex3.getHeader("Location")); + } + + @Test + public void testEvaluateURIWithQueryString() throws Exception { + withRules( + new RedirectResourceBuilder(context) + .setSource("/content/geometrixx/en/page.html/suffix.html") + .setTarget("/content/geometrixx/en/redirected") + .setStatusCode(302) + .setEvaluateURI(true) + .build() + ); + + MockSlingHttpServletResponse responseNoQueryString = navigateToURI("/content/geometrixx/en/page.html/suffix.html"); + assertEquals("/content/geometrixx/en/redirected", responseNoQueryString.getHeader("Location")); + + MockSlingHttpServletResponse responseQueryString = navigateToURI("/content/geometrixx/en/page.html/suffix.html?a=1&b=2"); + assertEquals("/content/geometrixx/en/redirected", responseQueryString.getHeader("Location")); + + MockSlingHttpServletResponse responseAnchor = navigateToURI("/content/geometrixx/en/page.html/suffix.html#anchor"); + assertEquals("/content/geometrixx/en/redirected", responseAnchor.getHeader("Location")); + + MockSlingHttpServletResponse responseQueryStringAndAnchor = navigateToURI("/content/geometrixx/en/page.html/suffix.html?a=3&b=4#anchorAndQueryString"); + assertEquals("/content/geometrixx/en/redirected", responseQueryStringAndAnchor.getHeader("Location")); + } } \ No newline at end of file diff --git a/bundle/src/test/java/com/adobe/acs/commons/redirects/models/RedirectConfigurationTest.java b/bundle/src/test/java/com/adobe/acs/commons/redirects/models/RedirectConfigurationTest.java index 39ac582720..0d5a6724bf 100755 --- a/bundle/src/test/java/com/adobe/acs/commons/redirects/models/RedirectConfigurationTest.java +++ b/bundle/src/test/java/com/adobe/acs/commons/redirects/models/RedirectConfigurationTest.java @@ -19,12 +19,17 @@ */ package com.adobe.acs.commons.redirects.models; +import org.apache.sling.api.SlingHttpServletRequest; import org.junit.Test; +import static com.adobe.acs.commons.redirects.models.RedirectConfiguration.determinePathToEvaluate; import static com.adobe.acs.commons.redirects.models.RedirectConfiguration.normalizePath; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; public class RedirectConfigurationTest { + @Test public void testNormalizePath(){ assertEquals("/content/we-retail/en", normalizePath("/content/we-retail/en")); @@ -32,4 +37,16 @@ public void testNormalizePath(){ assertEquals("/content/dam/we-retail/en.html", normalizePath("/content/dam/we-retail/en.html")); assertEquals("/content/dam/we-retail/en.pdf", normalizePath("/content/dam/we-retail/en.pdf")); } + + @Test + public void testPathToEvaluate(){ + final String resourcePath = "/content/we-retail/en"; + final String expectedURI = "/content/we-retail/en.html/suffix.html"; + SlingHttpServletRequest mockRequest = mock(SlingHttpServletRequest.class); + doReturn(expectedURI).when(mockRequest).getRequestURI(); + + assertEquals(resourcePath, determinePathToEvaluate(resourcePath, false, null)); + assertEquals(resourcePath, determinePathToEvaluate(resourcePath, false, mockRequest)); + assertEquals(expectedURI, determinePathToEvaluate(resourcePath, true, mockRequest)); + } } diff --git a/bundle/src/test/java/com/adobe/acs/commons/redirects/models/RedirectRuleTest.java b/bundle/src/test/java/com/adobe/acs/commons/redirects/models/RedirectRuleTest.java index 2c28dc584d..46bb0d316f 100755 --- a/bundle/src/test/java/com/adobe/acs/commons/redirects/models/RedirectRuleTest.java +++ b/bundle/src/test/java/com/adobe/acs/commons/redirects/models/RedirectRuleTest.java @@ -40,6 +40,7 @@ import static org.junit.Assert.assertEquals; public class RedirectRuleTest { + @Rule public SlingContext context = new SlingContext(ResourceResolverType.RESOURCERESOLVER_MOCK); @@ -52,7 +53,8 @@ public void testCreateFromResource() { "statusCode", 302, "untilDate", untilDate, "note", "note-1", - "contextPrefixIgnored", true); + "contextPrefixIgnored", true, + "evaluateURI", true); RedirectRule rule = resource.adaptTo(RedirectRule.class); assertEquals("/content/we-retail/en/one", rule.getSource()); @@ -61,7 +63,7 @@ public void testCreateFromResource() { assertDateEquals("11 January 2021", rule.getUntilDate()); assertEquals("note-1", rule.getNote()); assertTrue(rule.getContextPrefixIgnored()); - + assertTrue(rule.getEvaluateURI()); } /** diff --git a/bundle/src/test/java/com/adobe/acs/commons/redirects/servlets/ExportRedirectMapServletTest.java b/bundle/src/test/java/com/adobe/acs/commons/redirects/servlets/ExportRedirectMapServletTest.java index 504d095476..fad0f36f78 100755 --- a/bundle/src/test/java/com/adobe/acs/commons/redirects/servlets/ExportRedirectMapServletTest.java +++ b/bundle/src/test/java/com/adobe/acs/commons/redirects/servlets/ExportRedirectMapServletTest.java @@ -53,7 +53,7 @@ public class ExportRedirectMapServletTest { public SlingContext context = new SlingContext(ResourceResolverType.RESOURCERESOLVER_MOCK); private ExportRedirectMapServlet servlet; - private String redirectStoragePath = "/conf/acs-commons/redirects"; + private final String redirectStoragePath = "/conf/acs-commons/redirects"; @Before public void setUp() throws PersistenceException { @@ -64,6 +64,7 @@ public void setUp() throws PersistenceException { .setUntilDate(new Calendar.Builder().setDate(2022, 9, 9).build()) .setEffectiveFrom(new Calendar.Builder().setDate(2025, 2, 2).build()) .setNotes("note-1") + .setEvaluateURI(true) .setContextPrefixIgnored(true) .setTagIds(new String[]{"redirects:tag1"}) .setCreatedBy("john.doe") @@ -111,25 +112,27 @@ public void assertSpreadsheet(XSSFWorkbook wb) { assertNotNull(sheet); XSSFRow row1 = sheet.getRow(1); assertEquals("/content/one", row1.getCell(0).getStringCellValue()); - assertEquals("note-1", row1.getCell(4).getStringCellValue()); assertEquals("/content/two", row1.getCell(1).getStringCellValue()); assertEquals(302, (int) row1.getCell(2).getNumericCellValue()); assertDateEquals("09 October 2022", new Calendar.Builder().setInstant(row1.getCell(3).getDateCellValue()).build()); + assertEquals("note-1", row1.getCell(4).getStringCellValue()); assertTrue(row1.getCell(5).getBooleanCellValue()); - assertEquals("redirects:tag1", row1.getCell(6).getStringCellValue()); - assertDateEquals("16 February 1974", new Calendar.Builder().setInstant(row1.getCell(7).getDateCellValue()).build()); - assertEquals("john.doe", row1.getCell(8).getStringCellValue()); - assertDateEquals("22 November 1976", new Calendar.Builder().setInstant(row1.getCell(9).getDateCellValue()).build()); - assertEquals("jane.doe", row1.getCell(10).getStringCellValue()); - assertDateEquals("02 March 2025", new Calendar.Builder().setInstant(row1.getCell(11).getDateCellValue()).build()); + assertTrue(row1.getCell(6).getBooleanCellValue()); + assertEquals("redirects:tag1", row1.getCell(7).getStringCellValue()); + assertDateEquals("16 February 1974", new Calendar.Builder().setInstant(row1.getCell(8).getDateCellValue()).build()); + assertEquals("john.doe", row1.getCell(9).getStringCellValue()); + assertDateEquals("22 November 1976", new Calendar.Builder().setInstant(row1.getCell(10).getDateCellValue()).build()); + assertEquals("jane.doe", row1.getCell(11).getStringCellValue()); + assertDateEquals("02 March 2025", new Calendar.Builder().setInstant(row1.getCell(12).getDateCellValue()).build()); XSSFRow row2 = sheet.getRow(2); assertEquals("/content/three", row2.getCell(0).getStringCellValue()); assertEquals("/content/four", row2.getCell(1).getStringCellValue()); assertEquals(301, (int) row2.getCell(2).getNumericCellValue()); assertFalse(row2.getCell(5).getBooleanCellValue()); - assertEquals("redirects:tag2", row2.getCell(6).getStringCellValue()); - assertEquals("", row2.getCell(8).getStringCellValue()); - assertEquals("john.doe", row2.getCell(10).getStringCellValue()); + assertFalse(row2.getCell(6).getBooleanCellValue()); + assertEquals("redirects:tag2", row2.getCell(7).getStringCellValue()); + assertEquals("", row2.getCell(9).getStringCellValue()); + assertEquals("john.doe", row2.getCell(11).getStringCellValue()); } } \ No newline at end of file diff --git a/bundle/src/test/java/com/adobe/acs/commons/redirects/servlets/ImportRedirectMapServletTest.java b/bundle/src/test/java/com/adobe/acs/commons/redirects/servlets/ImportRedirectMapServletTest.java index 098547bef4..956e18a191 100755 --- a/bundle/src/test/java/com/adobe/acs/commons/redirects/servlets/ImportRedirectMapServletTest.java +++ b/bundle/src/test/java/com/adobe/acs/commons/redirects/servlets/ImportRedirectMapServletTest.java @@ -74,6 +74,7 @@ public void testImport() throws ServletException, IOException { .setStatusCode(302) .setUntilDate(new Calendar.Builder().setDate(2022, 9, 9).build()) .setNotes("note-1") + .setEvaluateURI(true) .setContextPrefixIgnored(true) .setCreatedBy("john.doe") .setTagIds(new String[]{"redirects:tag3"}) @@ -105,9 +106,9 @@ public void testImport() throws ServletException, IOException { row1.createCell(3).setCellValue(new Calendar.Builder().setDate(1974, 01, 16).build()); row1.getCell(3).setCellStyle(dateStyle); row1.createCell(4).setCellValue("note-abc"); - row1.createCell(6).setCellValue("redirects:tag1\nredirects:tag2"); - row1.createCell(11).setCellValue(new Calendar.Builder().setDate(2025, 02, 02).build()); - row1.getCell(11).setCellStyle(dateStyle); + row1.createCell(7).setCellValue("redirects:tag1\nredirects:tag2"); + row1.createCell(12).setCellValue(new Calendar.Builder().setDate(2025, 02, 02).build()); + row1.getCell(12).setCellStyle(dateStyle); Row row2 = sheet.createRow(2); row2.createCell(0).setCellValue("/content/2"); @@ -143,6 +144,8 @@ public void testImport() throws ServletException, IOException { assertEquals("note-1", rule1.getNote()); assertEquals("john.doe", rule1.getCreatedBy()); assertEquals("123", res1.getValueMap().get("custom-1")); + assertTrue(rule1.getEvaluateURI()); + assertTrue(rule1.getContextPrefixIgnored()); assertArrayEquals(new String[]{"redirects:tag3"}, rule1.getTagIds()); Resource res2 = rules.get("/content/three"); @@ -150,6 +153,7 @@ public void testImport() throws ServletException, IOException { RedirectRule rule2 = res2.adaptTo(RedirectRule.class); assertEquals("/en/we-retail", rule2.getTarget()); assertEquals(301, rule2.getStatusCode()); + assertFalse(rule2.getEvaluateURI()); assertFalse(rule2.getContextPrefixIgnored()); assertEquals("xyz", rule2.getModifiedBy()); assertEquals("345", res2.getValueMap().get("custom-2")); @@ -158,13 +162,14 @@ public void testImport() throws ServletException, IOException { assertEquals("/en/we-retail", rule3.getTarget()); assertDateEquals("16 February 1974", rule3.getUntilDate()); assertEquals("note-abc", rule3.getNote()); + assertFalse(rule3.getEvaluateURI()); + assertFalse(rule3.getContextPrefixIgnored()); assertArrayEquals(new String[]{"redirects:tag1", "redirects:tag2"}, rule3.getTagIds()); assertDateEquals("02 March 2025", rule3.getEffectiveFrom()); RedirectRule rule4 = rules.get("/content/2").adaptTo(RedirectRule.class); assertEquals("/en/we-retail", rule4.getTarget()); assertEquals(null, rule4.getUntilDate()); - } @@ -183,6 +188,9 @@ public void testUpdate() throws IOException { rule2.put(RedirectRule.STATUS_CODE_PROPERTY_NAME, 302); rule2.put(RedirectRule.UNTIL_DATE_PROPERTY_NAME, Calendar.getInstance()); rule2.put(RedirectRule.NOTE_PROPERTY_NAME, "note"); + rule2.put(RedirectRule.EVALUATE_URI_PROPERTY_NAME, true); + rule2.put(RedirectRule.CONTEXT_PREFIX_IGNORED_PROPERTY_NAME, true); + Collection> rules = Arrays.asList(rule1, rule2); Resource root = context.resourceResolver().getResource(redirectStoragePath); @@ -195,11 +203,15 @@ public void testUpdate() throws IOException { assertEquals(vm1.get(RedirectRule.TARGET_PROPERTY_NAME), rule1.get(RedirectRule.TARGET_PROPERTY_NAME)); assertFalse(vm1.containsKey(RedirectRule.UNTIL_DATE_PROPERTY_NAME)); assertFalse(vm1.containsKey(RedirectRule.NOTE_PROPERTY_NAME)); + assertFalse(vm1.containsKey(RedirectRule.EVALUATE_URI_PROPERTY_NAME)); + assertFalse(vm1.containsKey(RedirectRule.CONTEXT_PREFIX_IGNORED_PROPERTY_NAME)); ValueMap vm2 = redirects.get(rule2.get(RedirectRule.SOURCE_PROPERTY_NAME)).getValueMap(); assertEquals(vm2.get(RedirectRule.SOURCE_PROPERTY_NAME), rule2.get(RedirectRule.SOURCE_PROPERTY_NAME)); assertEquals(vm2.get(RedirectRule.TARGET_PROPERTY_NAME), rule2.get(RedirectRule.TARGET_PROPERTY_NAME)); assertEquals(vm2.get(RedirectRule.NOTE_PROPERTY_NAME), rule2.get(RedirectRule.NOTE_PROPERTY_NAME)); + assertEquals(vm2.get(RedirectRule.EVALUATE_URI_PROPERTY_NAME), rule2.get(RedirectRule.EVALUATE_URI_PROPERTY_NAME)); + assertEquals(vm2.get(RedirectRule.CONTEXT_PREFIX_IGNORED_PROPERTY_NAME), rule2.get(RedirectRule.CONTEXT_PREFIX_IGNORED_PROPERTY_NAME)); } } diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/manage-redirects/clientlibs/app.js b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/manage-redirects/clientlibs/app.js index 970feef577..5831a1d56e 100755 --- a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/manage-redirects/clientlibs/app.js +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/manage-redirects/clientlibs/app.js @@ -191,6 +191,7 @@ var untilDate = tr.find('.untilDate').data('value'); var effectiveFrom = tr.find('.effectiveFrom').data('value'); var contextPrefixIgnored = tr.find('.contextPrefixIgnored').data('value'); + var evaluateURI = tr.find('.evaluateURI').data('value'); var tags = tr.find('.tags').data('value'); var form = $('#editRuleDialog').find("form"); @@ -206,6 +207,14 @@ var cpi = form.find('input[name="./contextPrefixIgnored"]'); cpi.val(contextPrefixIgnored); cpi.prop("checked", contextPrefixIgnored); + var evalURI = form.find('input[name="./evaluateURI"]'); + evalURI.val(evaluateURI); + evalURI.prop("checked", evaluateURI); + + evalURI.click(function() { + evalURI.val(evalURI.is(":checked")); + }); + cpi.click(function() { cpi.val(cpi.is(":checked")); }); @@ -336,7 +345,7 @@ return false; }); - $(document).on("change", 'input[name="./contextPrefixIgnored"]', function(e) { + $(document).on("change", 'input[name="./contextPrefixIgnored"],input[name="./evaluateURI"]', function(e) { e.preventDefault(); $(this).val($(this).is(":checked")); }); diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/manage-redirects/manage-redirects.html b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/manage-redirects/manage-redirects.html index 611721b935..6925b0544d 100755 --- a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/manage-redirects/manage-redirects.html +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/manage-redirects/manage-redirects.html @@ -120,6 +120,7 @@ Off Time Tags Notes + Evaluate URI Ignore Context Prefix diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/manage-redirects/redirect-row/redirect-row.html b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/manage-redirects/redirect-row/redirect-row.html index aee466f781..c8c8e78916 100755 --- a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/manage-redirects/redirect-row/redirect-row.html +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/manage-redirects/redirect-row/redirect-row.html @@ -28,6 +28,9 @@ ${redirect.note} + + + diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/content/redirect-manager/.content.xml b/ui.apps/src/main/content/jcr_root/apps/acs-commons/content/redirect-manager/.content.xml index 8f600ad9db..abe3d7dd96 100755 --- a/ui.apps/src/main/content/jcr_root/apps/acs-commons/content/redirect-manager/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/content/redirect-manager/.content.xml @@ -1,46 +1,46 @@ + jcr:primaryType="cq:Page"> + jcr:primaryType="cq:PageContent" + jcr:title="Manage Redirects" + sling:resourceType="acs-commons/components/utilities/manage-redirects"> + granite:class="edit-dialog" + granite:id="editPrefixDialog" + jcr:primaryType="nt:unstructured" + jcr:title="Set a Context Prefix for this Redirect Configuration" + sling:resourceType="granite/ui/components/coral/foundation/dialog">
    + jcr:primaryType="nt:unstructured" + sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"> + jcr:primaryType="nt:unstructured" + sling:resourceType="granite/ui/components/coral/foundation/container">
    - + sling:resourceType="granite/ui/components/coral/foundation/form" + async="{Boolean}true" + foundationForm="{Boolean}true" + method="post" + style="vertical"> + + jcr:primaryType="nt:unstructured" + sling:resourceType="granite/ui/components/coral/foundation/form/pathfield" + emptyText="Select/Enter context prefix" + fieldDescription="This context prefix will be added to all Redirect Rules in this configuration." + fieldLabel="Context prefix" + name="./contextPrefix" + required="{Boolean}false" + rootPath="/content"/>
    @@ -50,32 +50,279 @@
    - + sling:resourceType="granite/ui/components/coral/foundation/button" + text="Cancel"> + + jcr:primaryType="nt:unstructured" + sling:resourceType="granite/ui/components/coral/foundation/button" + formId="fn-acsCommons-save_prefix" + icon="save" + text="Save" + type="submit" + variant="primary"/>
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + <_x0033_02 + jcr:primaryType="nt:unstructured" + text="302 (Temporarily Moved)" + value="302"/> + <_x0033_01 + jcr:primaryType="nt:unstructured" + text="301 (Permanently Moved)" + value="301"/> + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + +
    +
    + + + - -
    + + - - - + - - - - - - - - - + - - - - + - - - + - - + - + - - - <_x0033_02 + + <_x0033_02 jcr:primaryType="nt:unstructured" text="302 (Temporarily Moved)" value="302"/> - <_x0033_01 + <_x0033_01 jcr:primaryType="nt:unstructured" text="301 (Permanently Moved)" value="301"/> - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - -
    -