| /* |
| * 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 + "]"; |
| } |
| } |