You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ju...@apache.org on 2014/06/25 16:32:53 UTC
svn commit: r1605423 - in /sling/trunk/bundles/resourceresolver/src/main:
java/org/apache/sling/resourceresolver/impl/console/
java/org/apache/sling/resourceresolver/impl/mapping/ resources/
resources/res/ resources/res/ui/
Author: justin
Date: Wed Jun 25 14:32:53 2014
New Revision: 1605423
URL: http://svn.apache.org/r1605423
Log:
SLING-3505 - minimizing scope of change when a single vanity path changes or when the vanity order of an existing vanity path changes.
Added:
sling/trunk/bundles/resourceresolver/src/main/resources/
sling/trunk/bundles/resourceresolver/src/main/resources/res/
sling/trunk/bundles/resourceresolver/src/main/resources/res/ui/
sling/trunk/bundles/resourceresolver/src/main/resources/res/ui/resourceresolver.css
Modified:
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/console/ResourceResolverWebConsolePlugin.java
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntry.java
Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/console/ResourceResolverWebConsolePlugin.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/console/ResourceResolverWebConsolePlugin.java?rev=1605423&r1=1605422&r2=1605423&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/console/ResourceResolverWebConsolePlugin.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/console/ResourceResolverWebConsolePlugin.java Wed Jun 25 14:32:53 2014
@@ -21,12 +21,15 @@ package org.apache.sling.resourceresolve
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
+import java.net.URL;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
+import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
+import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@@ -71,6 +74,7 @@ public class ResourceResolverWebConsoleP
props.put(Constants.SERVICE_PID, getClass().getName());
props.put("felix.webconsole.label", "jcrresolver");
props.put("felix.webconsole.title", "Resource Resolver");
+ props.put("felix.webconsole.css", "/jcrresolver/res/ui/resourceresolver.css");
props.put("felix.webconsole.category", "Sling");
props.put("felix.webconsole.configprinter.modes", "always");
@@ -275,10 +279,17 @@ public class ResourceResolverWebConsoleP
pw.println("<th class='content'>Redirect</th>");
pw.println("</tr>");
+ Set<String> usedPatterns = new HashSet<String>();
+
for (MapEntry entry : list) {
- pw.println("<tr class='content'>");
+ String pattern = entry.getPattern();
+ pw.print("<tr class='content");
+ if (!usedPatterns.add(pattern)) {
+ pw.print(" duplicate");
+ }
+ pw.println("'>");
pw.println("<td class='content' style='vertical-align: top'>"
- + entry.getPattern() + "</td>");
+ + pattern + "</td>");
pw.print("<td class='content' style='vertical-align: top'>");
String[] repls = entry.getRedirect();
@@ -338,6 +349,17 @@ public class ResourceResolverWebConsoleP
pw.println();
}
+ /**
+ * Method to retrieve static resources from this bundle.
+ */
+ @SuppressWarnings("unused")
+ private URL getResource(final String path) {
+ if (path.startsWith("/jcrresolver/res/ui/")) {
+ return this.getClass().getResource(path.substring(12));
+ }
+ return null;
+ }
+
private static class ResolverRequest extends HttpServletRequestWrapper {
private final URI uri;
Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java?rev=1605423&r1=1605422&r2=1605423&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java Wed Jun 25 14:32:53 2014
@@ -25,18 +25,16 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
-import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
-import java.util.concurrent.Semaphore;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@@ -97,7 +95,7 @@ public class MapEntries implements Event
private Collection<MapEntry> mapMaps;
- private Collection<String> vanityTargets;
+ private Map <String,List <String>> vanityTargets;
private Map<String, Map<String, String>> aliasMap;
@@ -105,8 +103,6 @@ public class MapEntries implements Event
private EventAdmin eventAdmin;
- private final Semaphore initTrigger = new Semaphore(0);
-
private final ReentrantLock initializing = new ReentrantLock();
private final boolean enabledVanityPaths;
@@ -125,7 +121,7 @@ public class MapEntries implements Event
this.resolveMapsMap = Collections.singletonMap(GLOBAL_LIST_KEY, (List<MapEntry>)Collections.EMPTY_LIST);
this.mapMaps = Collections.<MapEntry> emptyList();
- this.vanityTargets = Collections.<String> emptySet();
+ this.vanityTargets = Collections.<String,List <String>>emptyMap();
this.aliasMap = Collections.<String, Map<String, String>>emptyMap();
this.registration = null;
this.eventAdmin = null;
@@ -149,7 +145,7 @@ public class MapEntries implements Event
this.resolveMapsMap = Collections.singletonMap(GLOBAL_LIST_KEY, (List<MapEntry>)Collections.EMPTY_LIST);
this.mapMaps = Collections.<MapEntry> emptyList();
- this.vanityTargets = Collections.<String> emptySet();
+ this.vanityTargets = Collections.<String,List <String>>emptyMap();
this.aliasMap = Collections.<String, Map<String, String>>emptyMap();
doInit();
@@ -160,41 +156,6 @@ public class MapEntries implements Event
props.put(Constants.SERVICE_DESCRIPTION, "Map Entries Observation");
props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
this.registration = bundleContext.registerService(EventHandler.class.getName(), this, props);
-
- final Thread updateThread = new Thread(new Runnable() {
- public void run() {
- MapEntries.this.init();
- }
- }, "MapEntries Update");
- updateThread.setDaemon(true);
- updateThread.start();
- }
-
- /**
- * Signals the init method that a the doInit method should be called.
- */
- private void triggerInit() {
- // only release if there is not one in the queue already
- if (initTrigger.availablePermits() < 1) {
- initTrigger.release();
- }
- }
-
- /**
- * Runs as the method of the update thread. Waits for the triggerInit method
- * to trigger a call to doInit. Terminates when the resolver has been
- * null-ed after having been triggered.
- */
- private void init() {
- while (this.resolver != null) {
- try {
- this.initTrigger.acquire();
- this.doInit();
- } catch (final InterruptedException ie) {
- // just continue acquisition
- }
- }
-
}
/**
@@ -212,36 +173,22 @@ public class MapEntries implements Event
return;
}
- final Map<String, List<MapEntry>> newResolveMapsMap = new HashMap<String, List<MapEntry>>();
- final List<MapEntry> globalResolveMap = new ArrayList<MapEntry>();
- final SortedMap<String, MapEntry> newMapMaps = new TreeMap<String, MapEntry>();
-
- // load the /etc/map entries into the maps
- loadResolverMap(resolver, globalResolveMap, newMapMaps);
-
- // load the configuration into the resolver map
- final Collection<String> vanityTargets = (this.enabledVanityPaths ? this.loadVanityPaths(resolver, newResolveMapsMap) : Collections.<String> emptySet());
- loadConfiguration(factory, globalResolveMap);
-
- // load the configuration into the mapper map
- loadMapConfiguration(factory, newMapMaps);
-
- // sort global list and add to map
- Collections.sort(globalResolveMap);
- newResolveMapsMap.put(GLOBAL_LIST_KEY, globalResolveMap);
-
+ final Map<String, List<MapEntry>> newResolveMapsMap = new ConcurrentHashMap<String, List<MapEntry>>();
+
+ final Map<String,List<String>> vanityTargets = (this.enabledVanityPaths ? this.loadVanityPaths(resolver, newResolveMapsMap) : Collections.<String,List <String>>emptyMap());
+
//optimization made in SLING-2521
if (enableOptimizeAliasResolution){
final Map<String, Map<String, String>> aliasMap = this.loadAliases(resolver);
- this.aliasMap = makeUnmodifiableMap(aliasMap);
+ this.aliasMap = aliasMap;
}
- this.vanityTargets = Collections.unmodifiableCollection(vanityTargets);
- this.resolveMapsMap = Collections.unmodifiableMap(newResolveMapsMap);
- this.mapMaps = Collections.unmodifiableSet(new TreeSet<MapEntry>(newMapMaps.values()));
+ this.vanityTargets = vanityTargets;
+ this.resolveMapsMap = newResolveMapsMap;
- sendChangeEvent();
+ doUpdateConfiguration();
+ sendChangeEvent();
} catch (final Exception e) {
log.warn("doInit: Unexpected problem during initialization", e);
@@ -253,17 +200,174 @@ public class MapEntries implements Event
}
}
- public boolean isOptimizeAliasResolutionEnabled(){
- return this.enableOptimizeAliasResolution;
+ private void doAddAttributes(String path, String[] addedAttributes) {
+ this.initializing.lock();
+ try {
+ boolean configurationUpdate = false;
+ for (String changedAttribute:addedAttributes){
+ if ("sling:vanityPath".equals(changedAttribute)) {
+ doAddVanity(path);
+ } else if ("sling:vanityOrder".equals(changedAttribute)) {
+ doUpdateVanityOrder(path, false);
+ } else if ("sling:alias".equals(changedAttribute)) {
+ doAddAlias(path);
+ } else {
+ configurationUpdate = true;
+ }
+ }
+ if (configurationUpdate) {
+ doUpdateConfiguration();
+ }
+ sendChangeEvent();
+ } finally {
+ this.initializing.unlock();
+ }
+ }
+
+ private void doUpdateAttributes(String path, String[] changedAttributes) {
+ this.initializing.lock();
+ try {
+ boolean configurationUpdate = false;
+ for (String changedAttribute:changedAttributes){
+ if ("sling:vanityPath".equals(changedAttribute)) {
+ doUpdateVanity(path);
+ } else if ("sling:vanityOrder".equals(changedAttribute)) {
+ doUpdateVanityOrder(path, false);
+ } else if ("sling:alias".equals(changedAttribute)) {
+ doUpdateAlias(path);
+ } else {
+ configurationUpdate = true;
+ }
+ }
+ if (configurationUpdate) {
+ doUpdateConfiguration();
+ }
+ sendChangeEvent();
+ } finally {
+ this.initializing.unlock();
+ }
+ }
+
+ private void doRemoveAttributes(String path, String[] removedAttributes, boolean nodeDeletion) {
+ this.initializing.lock();
+ try {
+ boolean configurationUpdate = false;
+ for (String changedAttribute:removedAttributes){
+ if ("sling:vanityPath".equals(changedAttribute)){
+ doRemoveVanity(path);
+ } else if ("sling:vanityOrder".equals(changedAttribute)) {
+ doUpdateVanityOrder(path, true);
+ } else if ("sling:alias".equals(changedAttribute)) {
+ doRemoveAlias(path, nodeDeletion);
+ } else {
+ configurationUpdate = true;
+ }
+ }
+ if (configurationUpdate) {
+ doUpdateConfiguration();
+ }
+ sendChangeEvent();
+ } finally {
+ this.initializing.unlock();
+ }
+ }
+
+ private void doUpdateConfiguration(){
+ final List<MapEntry> globalResolveMap = new ArrayList<MapEntry>();
+ final SortedMap<String, MapEntry> newMapMaps = new TreeMap<String, MapEntry>();
+ // load the /etc/map entries into the maps
+ loadResolverMap(resolver, globalResolveMap, newMapMaps);
+ // load the configuration into the resolver map
+ loadConfiguration(factory, globalResolveMap);
+ // load the configuration into the mapper map
+ loadMapConfiguration(factory, newMapMaps);
+ // sort global list and add to map
+ Collections.sort(globalResolveMap);
+ resolveMapsMap.put(GLOBAL_LIST_KEY, globalResolveMap);
+ this.mapMaps = Collections.unmodifiableSet(new TreeSet<MapEntry>(newMapMaps.values()));
+ }
+
+ private void doAddVanity(String path) {
+ Resource resource = resolver.getResource(path);
+ loadVanityPath(resource, resolveMapsMap, vanityTargets);
+ }
+
+ private void doUpdateVanity(String path) {
+ doRemoveVanity(path);
+ doAddVanity(path);
+ }
+
+ private void doRemoveVanity(String path) {
+ String actualContentPath = getActualContentPath(path);
+ List <String> l = vanityTargets.get(actualContentPath);
+ if (l != null){
+ for (String s : l){
+ List<MapEntry> entries = this.resolveMapsMap.get(s);
+ for (Iterator<MapEntry> iterator =entries.iterator(); iterator.hasNext(); ) {
+ MapEntry entry = iterator.next();
+ String redirect = getMapEntryRedirect(entry);
+ if (redirect != null && redirect.equals(path)) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+ vanityTargets.remove(actualContentPath);
+ }
+
+ private void doUpdateVanityOrder(String path, boolean deletion) {
+ Resource resource = resolver.getResource(path);
+ final ValueMap props = resource.adaptTo(ValueMap.class);
+
+ long vanityOrder;
+ if (deletion) {
+ vanityOrder = 0;
+ } else {
+ vanityOrder = props.get("sling:vanityOrder", Long.class);
+ }
+
+ String actualContentPath = getActualContentPath(path);
+ List<String> vanityPaths = vanityTargets.get(actualContentPath);
+ boolean updatedOrder = false;
+ for (String vanityTarget : vanityPaths) {
+ List<MapEntry> entries = this.resolveMapsMap.get(vanityTarget);
+ for (MapEntry entry : entries) {
+ String redirect = getMapEntryRedirect(entry);
+ if (redirect != null && redirect.equals(actualContentPath)) {
+ entry.setOrder(vanityOrder);
+ updatedOrder = true;
+ }
+ }
+ if (updatedOrder) {
+ Collections.sort(entries);
+ }
+ }
}
+ private void doAddAlias(String path) {
+ Resource resource = resolver.getResource(path);
+ loadAlias(resource, this.aliasMap);
+ }
+
+ private void doUpdateAlias(String path) {
+ doRemoveAlias(path, false);
+ doAddAlias(path);
+ }
- private <K1, K2, V> Map<K1, Map<K2, V>> makeUnmodifiableMap(final Map<K1, Map<K2, V>> map) {
- final Map<K1, Map<K2, V>> newMap = new HashMap<K1, Map<K2, V>>();
- for (final K1 key : map.keySet()) {
- newMap.put(key, Collections.unmodifiableMap(map.get(key)));
+ private void doRemoveAlias(String path, boolean nodeDeletion) {
+ if (nodeDeletion) {
+ if (!"/".equals(path)){
+ path = path.substring(0, path.lastIndexOf("/"));
+ }
+ } else {
+ Resource resource = resolver.getResource(path);
+ path = resource.getParent().getPath();
}
- return Collections.unmodifiableMap(newMap);
+ this.aliasMap.remove(path);
+ }
+
+ public boolean isOptimizeAliasResolutionEnabled() {
+ return this.enableOptimizeAliasResolution;
}
/**
@@ -303,9 +407,6 @@ public class MapEntries implements Event
final ResourceResolver oldResolver = this.resolver;
this.resolver = null;
- // trigger initialization to terminate init thread
- triggerInit();
-
if (oldResolver != null) {
oldResolver.close();
} else {
@@ -331,7 +432,7 @@ public class MapEntries implements Event
entries.addAll(list);
}
Collections.sort(entries);
- return entries;
+ return Collections.unmodifiableList(entries);
}
/**
@@ -382,38 +483,78 @@ public class MapEntries implements Event
return;
}
- // check whether a remove event has an influence on vanity paths
- boolean doInit = true;
- if (SlingConstants.TOPIC_RESOURCE_REMOVED.equals(event.getTopic()) && !path.startsWith(this.mapRoot)) {
- final String checkPath;
- if ( path.endsWith("/jcr:content") ) {
- checkPath = path.substring(0, path.length() - 12);
- } else {
- checkPath = path;
- }
- doInit = false;
- for (final String target : this.vanityTargets) {
- if (target.startsWith(checkPath)) {
- doInit = true;
- break;
+ //removal of a node is handled differently
+ if (SlingConstants.TOPIC_RESOURCE_REMOVED.equals(event.getTopic())) {
+ final String actualContentPath = getActualContentPath(path);
+ for (final String target : this.vanityTargets.keySet()) {
+ if (target.startsWith(actualContentPath)) {
+ doRemoveAttributes(actualContentPath, new String [] {"sling:vanityPath"}, true);
}
}
for (final String target : this.aliasMap.keySet()) {
- if (checkPath.startsWith(target)) {
- doInit = true;
- break;
+ if (actualContentPath.startsWith(target)) {
+ doRemoveAttributes(actualContentPath, new String [] {"sling:alias"}, true);
}
}
- }
+ } else {
+ String [] addedAttributes = (String []) event.getProperty(SlingConstants.PROPERTY_ADDED_ATTRIBUTES);
+ if (addedAttributes != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("found added attributes {}", addedAttributes);
+ }
+ doAddAttributes(path, addedAttributes);
+ }
+
+ String [] changedAttributes = (String []) event.getProperty(SlingConstants.PROPERTY_CHANGED_ATTRIBUTES);
+ if (changedAttributes != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("found changed attributes {}", changedAttributes);
+ }
+ doUpdateAttributes(path, changedAttributes);
+ }
- // trigger an update
- if (doInit) {
- triggerInit();
+ String [] removedAttributes = (String []) event.getProperty(SlingConstants.PROPERTY_REMOVED_ATTRIBUTES);
+ if (removedAttributes != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("found removed attributes {}", removedAttributes);
+ }
+ final String checkPath = getActualContentPath(path);
+ doRemoveAttributes(checkPath, removedAttributes, false);
+ }
}
}
// ---------- internal
+ //TODO add unit test
+ private String getActualContentPath(String path){
+ final String checkPath;
+ if ( path.endsWith("/jcr:content") ) {
+ checkPath = path.substring(0, path.length() - "/jcr:content".length());
+ } else {
+ checkPath = path;
+ }
+ return checkPath;
+ }
+
+ //TODO add unit test
+ private String getMapEntryRedirect(MapEntry mapEntry) {
+ String[] redirect = mapEntry.getRedirect();
+ if (redirect.length > 1) {
+ log.warn("something went wrong, please restart the bundle");
+ return null;
+ }
+
+ String path = redirect[0];
+ if (path.endsWith("$1")) {
+ path = path.substring(0, path.length() - "$1".length());
+ } else if (path.endsWith(".html")) {
+ path = path.substring(0, path.length() - ".html".length());
+ }
+
+ return path;
+ }
+
/**
* Send an OSGi event
*/
@@ -508,7 +649,7 @@ public class MapEntries implements Event
* property
*/
private Map<String, Map<String, String>> loadAliases(final ResourceResolver resolver) {
- final Map<String, Map<String, String>> map = new HashMap<String, Map<String, String>>();
+ final Map<String, Map<String, String>> map = new ConcurrentHashMap<String, Map<String, String>>();
final String queryString = "SELECT sling:alias FROM nt:base WHERE sling:alias IS NOT NULL";
final Iterator<Resource> i = resolver.findResources(queryString, "sql");
while (i.hasNext()) {
@@ -579,23 +720,21 @@ public class MapEntries implements Event
}
}
}
-
+
/**
* Load vanity paths Search for all nodes inheriting the sling:VanityPath
* mixin
*/
- private Collection<String> loadVanityPaths(final ResourceResolver resolver, final Map<String, List<MapEntry>> entryMap) {
+ private Map <String, List<String>> loadVanityPaths(final ResourceResolver resolver, final Map<String, List<MapEntry>> entryMap) {
// sling:VanityPath (uppercase V) is the mixin name
// sling:vanityPath (lowercase) is the property name
- final Set<String> targetPaths = new HashSet<String>();
+ final Map <String, List<String>> targetPaths = new ConcurrentHashMap <String, List<String>>();
final String queryString = "SELECT sling:vanityPath, sling:redirect, sling:redirectStatus FROM sling:VanityPath WHERE sling:vanityPath IS NOT NULL ORDER BY sling:vanityOrder DESC";
final Iterator<Resource> i = resolver.findResources(queryString, "sql");
- final Set<String> processedVanityPaths = new HashSet<String>();
-
while (i.hasNext()) {
final Resource resource = i.next();
- loadVanityPath(resource, entryMap, processedVanityPaths, targetPaths);
+ loadVanityPath(resource, entryMap, targetPaths);
}
return targetPaths;
@@ -604,14 +743,14 @@ public class MapEntries implements Event
/**
* Load vanity path given a resource
*/
- private void loadVanityPath(final Resource resource, final Map<String, List<MapEntry>> entryMap, final Set<String> processedVanityPaths, final Set<String> targetPaths) {
+ private void loadVanityPath(final Resource resource, final Map<String, List<MapEntry>> entryMap, final Map <String, List<String>> targetPaths) {
// ignore system tree
if (resource.getPath().startsWith(JCR_SYSTEM_PREFIX)) {
log.debug("loadVanityPaths: Ignoring {}", resource);
return;
}
- // check whitelist
+ // check white list
if ( this.vanityPathConfig != null ) {
boolean allowed = false;
for(final VanityPathConfig config : this.vanityPathConfig) {
@@ -632,6 +771,11 @@ public class MapEntries implements Event
return;
}
+ long vanityOrder = 0;
+ if (props.containsKey("sling:vanityOrder")) {
+ vanityOrder = props.get("sling:vanityOrder", Long.class);
+ }
+
// url is ignoring scheme and host.port and the path is
// what is stored in the sling:vanityPath property
final String[] pVanityPaths = props.get("sling:vanityPath", new String[0]);
@@ -639,54 +783,62 @@ public class MapEntries implements Event
final String[] result = this.getVanityPathDefinition(pVanityPath);
if (result != null) {
final String url = result[0] + result[1];
+ // redirect target is the node providing the
+ // sling:vanityPath
+ // property (or its parent if the node is called
+ // jcr:content)
+ final Resource redirectTarget;
+ if (resource.getName().equals("jcr:content")) {
+ redirectTarget = resource.getParent();
+ } else {
+ redirectTarget = resource;
+ }
+ final String redirect = redirectTarget.getPath();
+ final String redirectName = redirectTarget.getName();
- if ( !processedVanityPaths.contains(url) ) {
- processedVanityPaths.add(url);
- // redirect target is the node providing the
- // sling:vanityPath
- // property (or its parent if the node is called
- // jcr:content)
- final Resource redirectTarget;
- if (resource.getName().equals("jcr:content")) {
- redirectTarget = resource.getParent();
- } else {
- redirectTarget = resource;
- }
- final String redirect = redirectTarget.getPath();
- final String redirectName = redirectTarget.getName();
+ // whether the target is attained by a external redirect or
+ // by an internal redirect is defined by the sling:redirect
+ // property
+ final int status = props.get("sling:redirect", false) ? props.get(
+ PROP_REDIRECT_EXTERNAL_REDIRECT_STATUS, factory.getDefaultVanityPathRedirectStatus())
+ : -1;
+
+ final String checkPath = result[1];
+
+ if (redirectName.indexOf('.') > -1) {
+ // 1. entry with exact match
+ this.addEntry(entryMap, checkPath, getMapEntry(url + "$", status, false, vanityOrder, redirect));
+
+ final int idx = redirectName.lastIndexOf('.');
+ final String extension = redirectName.substring(idx + 1);
- // whether the target is attained by a external redirect or
- // by an internal redirect is defined by the sling:redirect
- // property
- final int status = props.get("sling:redirect", false) ? props.get(
- PROP_REDIRECT_EXTERNAL_REDIRECT_STATUS, factory.getDefaultVanityPathRedirectStatus())
- : -1;
-
- final String checkPath = result[1];
-
- if (redirectName.indexOf('.') > -1) {
- // 1. entry with exact match
- this.addEntry(entryMap, checkPath, getMapEntry(url + "$", status, false, redirect));
-
- final int idx = redirectName.lastIndexOf('.');
- final String extension = redirectName.substring(idx + 1);
-
- // 2. entry with extension
- this.addEntry(entryMap, checkPath, getMapEntry(url + "\\." + extension, status, false, redirect));
- } else {
- // 1. entry with exact match
- this.addEntry(entryMap, checkPath, getMapEntry(url + "$", status, false, redirect + ".html"));
-
- // 2. entry with match supporting selectors and extension
- this.addEntry(entryMap, checkPath, getMapEntry(url + "(\\..*)", status, false, redirect + "$1"));
- }
- // 3. keep the path to return
- targetPaths.add(redirect);
+ // 2. entry with extension
+ this.addEntry(entryMap, checkPath, getMapEntry(url + "\\." + extension, status, false, vanityOrder, redirect));
+ } else {
+ // 1. entry with exact match
+ this.addEntry(entryMap, checkPath, getMapEntry(url + "$", status, false, vanityOrder, redirect + ".html"));
+
+ // 2. entry with match supporting selectors and extension
+ this.addEntry(entryMap, checkPath, getMapEntry(url + "(\\..*)", status, false, vanityOrder, redirect + "$1"));
}
+ // 3. keep the path to return
+ this.updateTargetPaths(targetPaths, redirect, checkPath);
}
}
}
+ private void updateTargetPaths(final Map<String, List<String>> targetPaths, final String key, final String entry) {
+ if (entry==null){
+ return;
+ }
+ List<String> entries = targetPaths.get(key);
+ if (entries == null) {
+ entries = new ArrayList<String>();
+ targetPaths.put(key, entries);
+ }
+ entries.add(entry);
+ }
+
/**
* Create the vanity path definition. String array containing:
* {protocol}/{host}[.port] {absolute path}
@@ -969,7 +1121,20 @@ public class MapEntries implements Event
MapEntry mapEntry = null;
try{
- mapEntry = new MapEntry(url, status, trailingSlash, redirect);
+ mapEntry = new MapEntry(url, status, trailingSlash, 0, redirect);
+ }catch (IllegalArgumentException iae){
+ //ignore this entry
+ log.debug("ignored entry due exception ",iae);
+ }
+ return mapEntry;
+ }
+
+ private MapEntry getMapEntry(String url, final int status, final boolean trailingSlash, long order,
+ final String... redirect){
+
+ MapEntry mapEntry = null;
+ try{
+ mapEntry = new MapEntry(url, status, trailingSlash, order, redirect);
}catch (IllegalArgumentException iae){
//ignore this entry
log.debug("ignored entry due exception ",iae);
Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntry.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntry.java?rev=1605423&r1=1605422&r2=1605423&view=diff
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntry.java (original)
+++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntry.java Wed Jun 25 14:32:53 2014
@@ -63,6 +63,8 @@ public class MapEntry implements Compara
private final String[] redirect;
private final int status;
+
+ private long order;
public static String appendSlash(String path) {
if (!path.endsWith("/")) {
@@ -143,14 +145,14 @@ public class MapEntry implements Compara
final int status = props
.get(MapEntries.PROP_REDIRECT_EXTERNAL_STATUS,
302);
- return new MapEntry(url, status, trailingSlash, redirect);
+ return new MapEntry(url, status, trailingSlash, 0, redirect);
}
final String[] internalRedirectProps = props.get(ResourceResolverImpl.PROP_REDIRECT_INTERNAL,
String[].class);
final String[] internalRedirect = filterRegExp(internalRedirectProps);
if (internalRedirect != null) {
- return new MapEntry(url, -1, trailingSlash, internalRedirect);
+ return new MapEntry(url, -1, trailingSlash, 0, internalRedirect);
}
}
@@ -213,7 +215,7 @@ public class MapEntry implements Compara
if (!redir.contains("$")) {
MapEntry mapEntry = null;
try{
- mapEntry = new MapEntry(redir.concat(endHook), status, trailingSlash, url);
+ mapEntry = new MapEntry(redir.concat(endHook), status, trailingSlash, 0, url);
}catch (IllegalArgumentException iae){
//ignore this entry
LoggerFactory
@@ -236,7 +238,7 @@ public class MapEntry implements Compara
}
public MapEntry(String url, final int status, final boolean trailingSlash,
- final String... redirect) {
+ final long order, final String... redirect) {
// ensure trailing slashes on redirects if the url
// ends with a trailing slash
@@ -260,6 +262,7 @@ public class MapEntry implements Compara
this.redirect = redirect;
this.status = status;
+ this.order = order;
}
// Returns the replacement or null if the value does not match
@@ -317,7 +320,15 @@ public class MapEntry implements Compara
// lengths are equal, but the entries are not
// so order based on the pattern
- return ownPatternString.toString().compareTo(mPatternString);
+ int stringComparison = ownPatternString.toString().compareTo(mPatternString);
+ if (stringComparison == 0 && order != m.order) {
+ if (m.order > order) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ return stringComparison;
}
// ---------- Object overwrite
@@ -384,4 +395,8 @@ public class MapEntry implements Compara
return list.isEmpty() ? null : (String[]) list.toArray(new String[list.size()]);
}
+
+ void setOrder(long order) {
+ this.order = order;
+ }
}
Added: sling/trunk/bundles/resourceresolver/src/main/resources/res/ui/resourceresolver.css
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/resources/res/ui/resourceresolver.css?rev=1605423&view=auto
==============================================================================
--- sling/trunk/bundles/resourceresolver/src/main/resources/res/ui/resourceresolver.css (added)
+++ sling/trunk/bundles/resourceresolver/src/main/resources/res/ui/resourceresolver.css Wed Jun 25 14:32:53 2014
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+tr.content.duplicate td.content {
+ color: grey;
+ text-decoration: line-through;
+}
\ No newline at end of file