blob: fed3bf9c1b30764b4a170a61ef8873e00ea382b8 [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.installer.provider.jcr.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import org.apache.sling.installer.api.InstallableResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Watch a single folder in the JCR Repository, detecting changes
* to it and providing InstallableData for its contents.
*/
class WatchedFolder implements EventListener{
private final Logger log = LoggerFactory.getLogger(getClass());
private final String path;
private final String pathWithSlash;
private final int priority;
private final Session session;
private final Collection <JcrInstaller.NodeConverter> converters;
private final Set<String> existingResourceUrls = new HashSet<String>();
private volatile boolean needsScan;
static class ScanResult {
List<InstallableResource> toAdd = new ArrayList<InstallableResource>();
List<String> toRemove = new ArrayList<String>();
};
/** Store the digests of the last returned resources, keyed by path, to detect changes */
private final Map<String, String> digests = new HashMap<String, String>();
WatchedFolder(final Session session,
final String path,
final int priority,
final Collection<JcrInstaller.NodeConverter> converters)
throws RepositoryException {
if (priority < 1) {
throw new IllegalArgumentException("Cannot watch folder with priority 0:" + path);
}
this.path = path;
this.pathWithSlash = path.concat("/");
this.converters = converters;
this.priority = priority;
this.session = session;
}
public void start() throws RepositoryException {
// observe any changes in our folder (and under it, as changes to properties
// might be lower in the hierarchy)
final int eventTypes = Event.NODE_ADDED | Event.NODE_REMOVED
| Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
final boolean isDeep = true;
final boolean noLocal = true;
// session.getWorkspace().getObservationManager().addEventListener(this, eventTypes, path,
// isDeep, null, null, noLocal);
this.needsScan = true;
log.info("Watching folder {} (priority {})", path, priority);
}
public void stop() {
/* try {
session.getWorkspace().getObservationManager().removeEventListener(this);
} catch(RepositoryException re) {
log.warn("RepositoryException in stop()", re);
}
*/ }
@Override
public String toString() {
return getClass().getSimpleName() + ":" + path;
}
public String getPath() {
return path;
}
public String getPathWithSlash() {
return this.pathWithSlash;
}
/**
* Update scan flag whenever an observation event occurs.
*/
public void onEvent(final EventIterator it) {
log.debug("JCR events received for path {}", path);
needsScan = true;
}
/**
* Did an observation event occur in the meantime?
*/
public boolean needsScan() {
return needsScan;
}
/**
* Scan the contents of our folder and return the corresponding
* <code>ScanResult</code> containing the <code>InstallableResource</code>s.
*/
public ScanResult scan() throws RepositoryException {
log.debug("Scanning {}", path);
needsScan = false;
Node folder = null;
if (session.itemExists(path)) {
final Item i = session.getItem(path);
if(i.isNode()) {
folder = (Node)i;
}
}
// Return an InstallableResource for all child nodes for which we have a NodeConverter
final ScanResult result = new ScanResult();
final Set<String> resourcesSeen = new HashSet<String>();
if (folder != null) {
scanNode(folder, result, resourcesSeen);
}
// Resources that existed but are not in resourcesSeen need to be
// unregistered from OsgiInstaller
for(final String url : existingResourceUrls) {
if(!resourcesSeen.contains(url)) {
result.toRemove.add(url);
}
}
for(final String u : result.toRemove) {
existingResourceUrls.remove(u);
digests.remove(u);
}
// Update saved digests of the resources that we're returning
for(final InstallableResource r : result.toAdd) {
existingResourceUrls.add(r.getId());
digests.put(r.getId(), r.getDigest());
}
return result;
}
private void scanNode(final Node folder, final ScanResult result, final Set<String> resourcesSeen)
throws RepositoryException {
final NodeIterator it = folder.getNodes();
while(it.hasNext()) {
final Node n = it.nextNode();
boolean processed = false;
for (JcrInstaller.NodeConverter nc : converters) {
final InstallableResource r = nc.convertNode(n, priority);
if(r != null) {
processed = true;
resourcesSeen.add(r.getId());
final String oldDigest = digests.get(r.getId());
if(r.getDigest().equals(oldDigest)) {
log.debug("Digest didn't change, ignoring " + r);
} else {
result.toAdd.add(r);
}
break;
}
}
if ( !processed ) {
this.scanNode(n, result, resourcesSeen);
}
}
}
}