Author: cziegeler Date: Tue Oct 18 07:36:36 2016 New Revision: 1765397 URL: http://svn.apache.org/viewvc?rev=1765397&view=rev Log: SLING-6056 : achieve 1:1 mapping between observation and resource change listener
Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObservationReporter.java sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObserverConfiguration.java sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/ResourceChangeListenerInfo.java Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObservationReporter.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObservationReporter.java?rev=1765397&r1=1765396&r2=1765397&view=diff ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObservationReporter.java (original) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObservationReporter.java Tue Oct 18 07:36:36 2016 @@ -21,18 +21,18 @@ package org.apache.sling.resourceresolve import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import org.apache.sling.api.resource.observation.ResourceChange; -import org.apache.sling.api.resource.observation.ResourceChange.ChangeType; +import org.apache.sling.api.resource.observation.ResourceChangeListener; import org.apache.sling.api.resource.path.Path; import org.apache.sling.api.resource.path.PathSet; import org.apache.sling.spi.resource.provider.ObservationReporter; import org.apache.sling.spi.resource.provider.ObserverConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Implementation of the observation reporter. @@ -40,15 +40,18 @@ import org.apache.sling.spi.resource.pro */ public class BasicObservationReporter implements ObservationReporter { + private Logger logger = LoggerFactory.getLogger(this.getClass()); + /** List of observer configurations for the provider. */ private final List<ObserverConfiguration> configs; - private final Map<ListenerConfig, List<ResourceChangeListenerInfo>> listeners = new HashMap<BasicObservationReporter.ListenerConfig, List<ResourceChangeListenerInfo>>();; - + /** The search path. */ private final String[] searchPath; /** * Create a reporter listening for resource provider changes + * + * @param searchPath The search path * @param infos The listeners map */ public BasicObservationReporter( @@ -56,20 +59,26 @@ public class BasicObservationReporter im final Collection<ResourceChangeListenerInfo> infos) { this.searchPath = searchPath; final Set<String> paths = new HashSet<String>(); + final List<ResourceChangeListenerInfo> result = new ArrayList<>(); for(final ResourceChangeListenerInfo info : infos) { if ( !info.getProviderChangeTypes().isEmpty() ) { for(final Path p : info.getPaths()) { paths.add(p.getPath()); } - fillListeners(info, info.getResourceChangeTypes()); + result.add(info); } } - final ObserverConfiguration cfg = new BasicObserverConfiguration(PathSet.fromStringCollection(paths)); - this.configs = Collections.singletonList(cfg); + final BasicObserverConfiguration cfg = new BasicObserverConfiguration(PathSet.fromStringCollection(paths)); + for(final ResourceChangeListenerInfo i : infos) { + cfg.addListener(i); + } + this.configs = Collections.singletonList((ObserverConfiguration)cfg); } /** * Create a reporter listening for a provider + * + * @param searchPath The search paths * @param infos The listeners map * @param providerPath The mount point of the provider * @param excludePaths Excluded paths for that provider @@ -77,49 +86,75 @@ public class BasicObservationReporter im public BasicObservationReporter( final String[] searchPath, final Collection<ResourceChangeListenerInfo> infos, - final Path providerPath, final PathSet excludePaths) { + final Path providerPath, + final PathSet excludePaths) { this.searchPath = searchPath; - final Map<String, ObserverConfig> configMap = new HashMap<String, ObserverConfig>(); + final List<ObserverConfiguration> observerConfigs = new ArrayList<>(); for(final ResourceChangeListenerInfo info : infos) { if ( !info.getResourceChangeTypes().isEmpty() ) { - boolean add = false; + // find the set of paths that match the provider + final Set<Path> paths = new HashSet<>(); for(final Path p : info.getPaths()) { if ( providerPath.matches(p.getPath()) && excludePaths.matches(p.getPath()) == null ) { - ObserverConfig config = configMap.get(p); - if ( config == null ) { - config = new ObserverConfig(); - configMap.put(p.getPath(), config); + paths.add(p); + } + } + if ( !paths.isEmpty() ) { + final PathSet pathSet = PathSet.fromPathCollection(paths); + // search for an existing configuration with the same paths and hints + BasicObserverConfiguration found = null; + for(final ObserverConfiguration c : observerConfigs) { + if ( c.getPaths().equals(pathSet) + && (( c.getPropertyNamesHint() == null && info.getPropertyNamesHint() == null) + || c.getPropertyNamesHint() != null && c.getPropertyNamesHint().equals(info.getPropertyNamesHint()))) { + found = (BasicObserverConfiguration)c; + break; } - config.types.addAll(info.getResourceChangeTypes()); - if ( info.isExternal() ) { - config.isExternal = true; + } + final BasicObserverConfiguration config; + if ( found != null ) { + // check external and types + boolean createNew = false; + if ( !found.includeExternal() && info.isExternal() ) { + createNew = true; + } + if ( !found.getChangeTypes().equals(info.getResourceChangeTypes()) ) { + createNew = true; } - add = true; + if ( createNew ) { + // create new/updated config + observerConfigs.remove(found); + final Set<ResourceChange.ChangeType> types = new HashSet<>(); + types.addAll(found.getChangeTypes()); + types.addAll(info.getResourceChangeTypes()); + config = new BasicObserverConfiguration(pathSet, + types, + info.isExternal() && found.includeExternal(), + found.getExcludedPaths(), + found.getPropertyNamesHint()); + observerConfigs.add(config); + for(final ResourceChangeListenerInfo i : found.getListeners()) { + config.addListener(i); + } + + } else { + config = found; + } + } else { + // create new config + config = new BasicObserverConfiguration(pathSet, + info.getResourceChangeTypes(), + info.isExternal(), + excludePaths.getSubset(pathSet), + info.getPropertyNamesHint()); + observerConfigs.add(config); } - } - if ( add ) { - fillListeners(info, info.getResourceChangeTypes()); + config.addListener(info); } } } - final List<ObserverConfiguration> result = new ArrayList<ObserverConfiguration>(); - for(final Map.Entry<String, ObserverConfig> entry : configMap.entrySet()) { - final ObserverConfiguration cfg = new BasicObserverConfiguration(entry.getKey(), entry.getValue().types, - entry.getValue().isExternal, excludePaths); - result.add(cfg); - } - this.configs = Collections.unmodifiableList(result); - } - - private void fillListeners(final ResourceChangeListenerInfo info, final Set<ChangeType> types) { - final ListenerConfig cfg = new ListenerConfig(info, types); - List<ResourceChangeListenerInfo> list = this.listeners.get(cfg); - if ( list == null ) { - list = new ArrayList<ResourceChangeListenerInfo>(); - this.listeners.put(cfg, list); - } - list.add(info); + this.configs = Collections.unmodifiableList(observerConfigs); } @Override @@ -129,21 +164,56 @@ public class BasicObservationReporter im @Override public void reportChanges(final Iterable<ResourceChange> changes, final boolean distribute) { - for (final Map.Entry<ListenerConfig, List<ResourceChangeListenerInfo>> entry : this.listeners.entrySet()) { - final List<ResourceChange> filtered = filterChanges(changes, entry.getKey()); - if ( !filtered.isEmpty() ) { - for(final ResourceChangeListenerInfo info : entry.getValue()) { - info.getListener().onChange(filtered); - } + for(final ObserverConfiguration cfg : this.configs) { + final List<ResourceChange> filteredChanges = filterChanges(changes, cfg); + if (!filteredChanges.isEmpty() ) { + this.reportChanges(cfg, filteredChanges, distribute); } } - // TODO implement distribute } @Override - public void reportChanges(ObserverConfiguration config, Iterable<ResourceChange> changes, boolean distribute) { - // TODO Auto-generated method stub - this.reportChanges(changes, distribute); + public void reportChanges(final ObserverConfiguration config, + final Iterable<ResourceChange> changes, + final boolean distribute) { + if ( config != null && config instanceof BasicObserverConfiguration ) { + final BasicObserverConfiguration observerConfig = (BasicObserverConfiguration)config; + + ResourceChangeListenerInfo previousInfo = null; + List<ResourceChange> filteredChanges = null; + for(final ResourceChangeListenerInfo info : observerConfig.getListeners()) { + if ( previousInfo == null || !equals(previousInfo, info) ) { + filteredChanges = filterChanges(changes, info); + previousInfo = info; + } + if ( !filteredChanges.isEmpty() ) { + final ResourceChangeListener listener = info.getListener(); + if ( listener != null ) { + listener.onChange(filteredChanges); + } + } + } + // TODO implement distribute + if ( distribute ) { + logger.error("Distrubte flag is send for observation events, however distribute is currently not implemented!"); + } + } + } + + /** + * Test if two resource change listener infos are equal wrt external and change types + * @param infoA First info + * @param infoB Second info + * @return {@code true} if external and change types are equally configured + */ + private boolean equals(final ResourceChangeListenerInfo infoA, final ResourceChangeListenerInfo infoB) { + if ( infoA.isExternal() && !infoB.isExternal() ) { + return false; + } + if ( !infoA.isExternal() && infoB.isExternal() ) { + return false; + } + return infoA.getResourceChangeTypes().equals(infoB.getResourceChangeTypes()); } /** @@ -152,7 +222,24 @@ public class BasicObservationReporter im * @param config The configuration * @return The filtered list. */ - private List<ResourceChange> filterChanges(final Iterable<ResourceChange> changes, final ListenerConfig config) { + private List<ResourceChange> filterChanges(final Iterable<ResourceChange> changes, final ObserverConfiguration config) { + final ResourceChangeListImpl filtered = new ResourceChangeListImpl(this.searchPath); + for (final ResourceChange c : changes) { + if (matches(c, config)) { + filtered.add(c); + } + } + filtered.lock(); + return filtered; + } + + /** + * Filter the change list based on the resource change listener, only type and external needs to be checkd. + * @param changes The list of changes + * @param config The resource change listener info + * @return The filtered list. + */ + private List<ResourceChange> filterChanges(final Iterable<ResourceChange> changes, final ResourceChangeListenerInfo config) { final ResourceChangeListImpl filtered = new ResourceChangeListImpl(this.searchPath); for (final ResourceChange c : changes) { if (matches(c, config)) { @@ -169,65 +256,35 @@ public class BasicObservationReporter im * @param config The configuration * @return {@code true} whether it matches */ - private boolean matches(final ResourceChange change, final ListenerConfig config) { - if (!config.types.contains(change.getType())) { + private boolean matches(final ResourceChange change, final ObserverConfiguration config) { + if (!config.getChangeTypes().contains(change.getType())) { return false; } - if (!config.isExternal && change.isExternal()) { + if (!config.includeExternal() && change.isExternal()) { return false; } - if (config.paths.matches(change.getPath()) == null ) { + if (config.getPaths().matches(change.getPath()) == null ) { + return false; + } + if ( config.getExcludedPaths().matches(change.getPath()) != null ) { return false; } return true; } - private static final class ObserverConfig { - public final Set<ChangeType> types = new HashSet<ChangeType>(); - public boolean isExternal; - } - - private static final class ListenerConfig { - - public final PathSet paths; - - public final boolean isExternal; - - public final Set<ChangeType> types; - - public ListenerConfig(final ResourceChangeListenerInfo info, Set<ChangeType> types) { - this.paths = info.getPaths(); - this.isExternal = info.isExternal(); - this.types = types; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (isExternal ? 1231 : 1237); - result = prime * result + paths.hashCode(); - result = prime * result + types.hashCode(); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - ListenerConfig other = (ListenerConfig) obj; - if (isExternal != other.isExternal) - return false; - if (!paths.equals(other.paths)) - return false; - if (!types.equals(other.types)) - return false; - return true; + /** + * Match a change against the configuration + * @param change The change + * @param config The configuration + * @return {@code true} whether it matches + */ + private boolean matches(final ResourceChange change, final ResourceChangeListenerInfo config) { + if (!config.getResourceChangeTypes().contains(change.getType())) { + return false; } - + if (!config.isExternal() && change.isExternal()) { + return false; + } + return true; } } Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObserverConfiguration.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObserverConfiguration.java?rev=1765397&r1=1765396&r2=1765397&view=diff ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObserverConfiguration.java (original) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/BasicObserverConfiguration.java Tue Oct 18 07:36:36 2016 @@ -18,14 +18,19 @@ */ package org.apache.sling.resourceresolver.impl.observation; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.apache.sling.api.resource.observation.ResourceChange.ChangeType; import org.apache.sling.api.resource.path.PathSet; import org.apache.sling.spi.resource.provider.ObserverConfiguration; +/** + * Implementation of a {@code ObserverConfiguration} + */ public class BasicObserverConfiguration implements ObserverConfiguration { private final boolean includeExternal; @@ -33,17 +38,23 @@ public class BasicObserverConfiguration private final PathSet paths; private final PathSet excludedPaths; - - private final Set<String> propertyNamesHint = new HashSet<String>(); + + private final Set<String> propertyNamesHint; private final Set<ChangeType> changeTypes; - public BasicObserverConfiguration(final String path, final Set<ChangeType> types, - final boolean isExternal, final PathSet excludePaths) { + private final List<ResourceChangeListenerInfo> listeners = new ArrayList<>(); + + public BasicObserverConfiguration(final PathSet paths, + final Set<ChangeType> types, + final boolean isExternal, + final PathSet excludePaths, + final Set<String> propertyNamesHint) { this.includeExternal = isExternal; - this.paths = PathSet.fromStrings(path); + this.paths = paths; this.changeTypes = Collections.unmodifiableSet(types); - this.excludedPaths = excludePaths.getSubset(path); + this.excludedPaths = excludePaths; + this.propertyNamesHint = propertyNamesHint; } public BasicObserverConfiguration(final PathSet set) { @@ -54,6 +65,23 @@ public class BasicObserverConfiguration types.add(ChangeType.PROVIDER_REMOVED); this.changeTypes = Collections.unmodifiableSet(types); this.excludedPaths = PathSet.EMPTY_SET; + this.propertyNamesHint = null; + } + + /** + * Add a listener + * @param listener The listener + */ + public void addListener(final ResourceChangeListenerInfo listener) { + this.listeners.add(listener); + } + + /** + * All listeners associated with this configuration + * @return List of listeners, might be empty + */ + public List<ResourceChangeListenerInfo> getListeners() { + return this.listeners; } @Override @@ -88,4 +116,57 @@ public class BasicObserverConfiguration public Set<String> getPropertyNamesHint() { return propertyNamesHint; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((changeTypes == null) ? 0 : changeTypes.hashCode()); + result = prime * result + ((excludedPaths == null) ? 0 : excludedPaths.hashCode()); + result = prime * result + (includeExternal ? 1231 : 1237); + result = prime * result + ((paths == null) ? 0 : paths.hashCode()); + result = prime * result + ((propertyNamesHint == null) ? 0 : propertyNamesHint.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BasicObserverConfiguration other = (BasicObserverConfiguration) obj; + if (changeTypes == null) { + if (other.changeTypes != null) + return false; + } else if (!changeTypes.equals(other.changeTypes)) + return false; + if (excludedPaths == null) { + if (other.excludedPaths != null) + return false; + } else if (!excludedPaths.equals(other.excludedPaths)) + return false; + if (includeExternal != other.includeExternal) + return false; + if (paths == null) { + if (other.paths != null) + return false; + } else if (!paths.equals(other.paths)) + return false; + if (propertyNamesHint == null) { + if (other.propertyNamesHint != null) + return false; + } else if (!propertyNamesHint.equals(other.propertyNamesHint)) + return false; + return true; + } + + @Override + public String toString() { + return "BasicObserverConfiguration [includeExternal=" + includeExternal + ", paths=" + paths + + ", excludedPaths=" + excludedPaths + ", propertyNamesHint=" + propertyNamesHint + ", changeTypes=" + + changeTypes + ", listeners=" + listeners + "]"; + } } Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/ResourceChangeListenerInfo.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/ResourceChangeListenerInfo.java?rev=1765397&r1=1765396&r2=1765397&view=diff ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/ResourceChangeListenerInfo.java (original) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/observation/ResourceChangeListenerInfo.java Tue Oct 18 07:36:36 2016 @@ -32,7 +32,9 @@ import org.apache.sling.api.resource.Res import org.apache.sling.api.resource.observation.ExternalResourceChangeListener; import org.apache.sling.api.resource.observation.ResourceChange.ChangeType; import org.apache.sling.api.resource.observation.ResourceChangeListener; +import org.apache.sling.api.resource.path.Path; import org.apache.sling.api.resource.path.PathSet; +import org.apache.sling.commons.osgi.PropertiesUtil; import org.osgi.framework.ServiceReference; /** @@ -50,14 +52,14 @@ public class ResourceChangeListenerInfo private final Set<ChangeType> providerChangeTypes; + private final Set<String> propertyNamesHint; + private final boolean valid; private volatile boolean external = false; private volatile ResourceChangeListener listener; - private static final String GLOB_PREFIX = "glob:"; - public ResourceChangeListenerInfo(final ServiceReference<ResourceChangeListener> ref, final String[] searchPaths) { boolean configValid = true; final Set<String> pathsSet = new HashSet<String>(); @@ -66,23 +68,23 @@ public class ResourceChangeListenerInfo for(final String p : paths) { boolean isGlobPattern = false; String normalisedPath = ResourceUtil.normalize(p); - if (p.startsWith(GLOB_PREFIX)) { + if (p.startsWith(Path.GLOB_PREFIX)) { isGlobPattern = true; - normalisedPath = ResourceUtil.normalize(p.substring(GLOB_PREFIX.length())); + normalisedPath = ResourceUtil.normalize(p.substring(Path.GLOB_PREFIX.length())); } if (!".".equals(p) && normalisedPath.isEmpty()) { configValid = false; } else if ( normalisedPath.startsWith("/") && !isGlobPattern ) { pathsSet.add(normalisedPath); } else if (normalisedPath.startsWith("/") && isGlobPattern) { - pathsSet.add(GLOB_PREFIX + normalisedPath); + pathsSet.add(Path.GLOB_PREFIX + normalisedPath); } else { for(final String sp : searchPaths) { if ( p.equals(".") ) { pathsSet.add(sp); } else { if (isGlobPattern) { - pathsSet.add(GLOB_PREFIX + ResourceUtil.normalize(sp + normalisedPath)); + pathsSet.add(Path.GLOB_PREFIX + ResourceUtil.normalize(sp + normalisedPath)); } else { pathsSet.add(ResourceUtil.normalize(sp + normalisedPath)); } @@ -147,6 +149,14 @@ public class ResourceChangeListenerInfo this.providerChangeTypes = DEFAULT_CHANGE_PROVIDER_TYPES; } + if ( ref.getProperty(ResourceChangeListener.PROPERTY_NAMES_HINT) != null ) { + this.propertyNamesHint = new HashSet<String>(); + for(final String val : PropertiesUtil.toStringArray(ref.getProperty(ResourceChangeListener.PROPERTY_NAMES_HINT)) ) { + this.propertyNamesHint.add(val); + } + } else { + this.propertyNamesHint = null; + } this.valid = configValid; } @@ -166,6 +176,14 @@ public class ResourceChangeListenerInfo return this.paths; } + /** + * Return a set of property name hints + * @return The set of names or {@code null}. + */ + public Set<String> getPropertyNamesHint() { + return this.propertyNamesHint; + } + public boolean isExternal() { return this.external; } @@ -178,4 +196,11 @@ public class ResourceChangeListenerInfo this.listener = listener; this.external = listener instanceof ExternalResourceChangeListener; } + + @Override + public String toString() { + return "ResourceChangeListenerInfo [paths=" + paths + ", resourceChangeTypes=" + resourceChangeTypes + + ", providerChangeTypes=" + providerChangeTypes + ", propertyNamesHint=" + propertyNamesHint + + ", valid=" + valid + ", external=" + external + ", listener=" + listener + "]"; + } }