blob: 70c7238a000349c5bc1d3a8a646381cebcfdf735 [file] [log] [blame]
/*
* 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.
*/
package org.apache.sling.resourceresolver.impl.observation;
import static org.apache.sling.api.resource.observation.ResourceChangeListener.CHANGES;
import static org.apache.sling.api.resource.observation.ResourceChangeListener.PATHS;
import static org.apache.sling.commons.osgi.PropertiesUtil.toStringArray;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.apache.sling.api.resource.ResourceUtil;
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;
/**
* Information about a resource change listener.
*/
public class ResourceChangeListenerInfo implements Comparable<ResourceChangeListenerInfo> {
private static final Set<ChangeType> DEFAULT_CHANGE_RESOURCE_TYPES = EnumSet.of(ChangeType.ADDED, ChangeType.REMOVED, ChangeType.CHANGED);
private static final Set<ChangeType> DEFAULT_CHANGE_PROVIDER_TYPES = EnumSet.of(ChangeType.PROVIDER_ADDED, ChangeType.PROVIDER_REMOVED);
private final PathSet paths;
private final Set<ChangeType> resourceChangeTypes;
private final Set<ChangeType> providerChangeTypes;
private final Set<String> propertyNamesHint;
private final boolean valid;
private volatile boolean external = false;
private volatile ResourceChangeListener listener;
public ResourceChangeListenerInfo(final ServiceReference<ResourceChangeListener> ref, final List<String> searchPaths) {
boolean configValid = true;
final Set<String> pathsSet = new HashSet<>();
final String paths[] = toStringArray(ref.getProperty(PATHS), null);
if ( paths != null ) {
for(final String p : paths) {
boolean isGlobPattern = false;
String normalisedPath = ResourceUtil.normalize(p);
if (p.startsWith(Path.GLOB_PREFIX)) {
isGlobPattern = true;
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(Path.GLOB_PREFIX + normalisedPath);
} else {
for(final String sp : searchPaths) {
if ( p.equals(".") ) {
pathsSet.add(sp);
} else {
if (isGlobPattern) {
pathsSet.add(Path.GLOB_PREFIX + ResourceUtil.normalize(sp + normalisedPath));
} else {
pathsSet.add(ResourceUtil.normalize(sp + normalisedPath));
}
}
}
}
}
}
if ( pathsSet.isEmpty() ) {
configValid = false;
} else {
// check for sub paths
final Iterator<String> iter = pathsSet.iterator();
while ( iter.hasNext() ) {
final String path = iter.next();
boolean remove = false;
for(final String p : pathsSet) {
if ( p.length() > path.length() && path.startsWith(p + "/") ) {
remove = true;
break;
}
}
if ( remove ) {
iter.remove();
}
}
}
this.paths = PathSet.fromStringCollection(pathsSet);
if (ref.getProperty(CHANGES) != null ) {
final Set<ChangeType> rts = new HashSet<>();
final Set<ChangeType> pts = new HashSet<>();
try {
for (final String changeName : toStringArray(ref.getProperty(CHANGES))) {
final ChangeType ct = ChangeType.valueOf(changeName);
if (ct.ordinal() < ChangeType.PROVIDER_ADDED.ordinal()) {
rts.add(ct);
} else {
pts.add(ct);
}
}
} catch (final Exception e) {
configValid = false;
}
if ( rts.isEmpty() ) {
this.resourceChangeTypes = Collections.emptySet();
} else if ( rts.size() == 3 ) {
this.resourceChangeTypes = DEFAULT_CHANGE_RESOURCE_TYPES;
} else {
this.resourceChangeTypes = Collections.unmodifiableSet(rts);
}
if ( pts.isEmpty() ) {
this.providerChangeTypes = Collections.emptySet();
} else if ( pts.size() == 2 ) {
this.providerChangeTypes = DEFAULT_CHANGE_PROVIDER_TYPES;
} else {
this.providerChangeTypes = Collections.unmodifiableSet(pts);
}
} else {
// default is added, changed, removed for resources and
// added and removed for providers
this.resourceChangeTypes = DEFAULT_CHANGE_RESOURCE_TYPES;
this.providerChangeTypes = DEFAULT_CHANGE_PROVIDER_TYPES;
}
if ( ref.getProperty(ResourceChangeListener.PROPERTY_NAMES_HINT) != null ) {
this.propertyNamesHint = new HashSet<>();
for(final String val : PropertiesUtil.toStringArray(ref.getProperty(ResourceChangeListener.PROPERTY_NAMES_HINT)) ) {
this.propertyNamesHint.add(val);
}
} else {
this.propertyNamesHint = null;
}
this.valid = configValid;
}
public boolean isValid() {
return this.valid;
}
public Set<ChangeType> getResourceChangeTypes() {
return this.resourceChangeTypes;
}
public Set<ChangeType> getProviderChangeTypes() {
return this.providerChangeTypes;
}
public PathSet getPaths() {
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;
}
public ResourceChangeListener getListener() {
return listener;
}
public void setListener(final ResourceChangeListener listener) {
this.listener = listener;
this.external = listener instanceof ExternalResourceChangeListener;
}
private int compareSet(final Set<String> t, final Set<String> o) {
if ( t == null && o == null ) {
return 0;
}
if ( t == null ) {
return -1;
}
if ( o == null ) {
return 1;
}
final Set<String> tPaths = new TreeSet<>(t);
final Set<String> oPaths = new TreeSet<>(o);
int result = tPaths.size() - oPaths.size();
if ( result == 0 ) {
final Iterator<String> tPathsIter = tPaths.iterator();
final Iterator<String> oPathsIter = oPaths.iterator();
while ( result == 0 && tPathsIter.hasNext() ) {
result = tPathsIter.next().compareTo(oPathsIter.next());
}
}
return result;
}
private int compareChangeTypes(final Set<ChangeType> t, final Set<ChangeType> o) {
int result = t.size() - o.size();
if ( result == 0 ) {
final Iterator<ChangeType> tIter = t.iterator();
final Iterator<ChangeType> oIter = o.iterator();
while ( result == 0 && tIter.hasNext() ) {
result = tIter.next().compareTo(oIter.next());
}
}
return result;
}
@Override
public int compareTo(final ResourceChangeListenerInfo o) {
// paths first
int result = compareSet(this.paths.toStringSet(), o.paths.toStringSet());
if ( result == 0 ) {
// hints
result = compareSet(this.propertyNamesHint, o.propertyNamesHint);
if ( result == 0 ) {
// external next
result = Boolean.valueOf(this.external).compareTo(o.external);
if ( result == 0 ) {
result = compareChangeTypes(this.resourceChangeTypes, o.resourceChangeTypes);
if ( result == 0 ) {
result = compareChangeTypes(this.providerChangeTypes, o.providerChangeTypes);
}
}
}
}
return result;
}
@Override
public String toString() {
return "ResourceChangeListenerInfo [paths=" + paths + ", resourceChangeTypes=" + resourceChangeTypes
+ ", providerChangeTypes=" + providerChangeTypes + ", propertyNamesHint=" + propertyNamesHint
+ ", valid=" + valid + ", external=" + external + ", listener=" + listener + "]";
}
}