/* * 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 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 { private final Logger logger = 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() { logger.info("Watching folder {} (priority {})", path, priority); this.needsScan = true; } @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 markForScan() { logger.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 { logger.debug("Scanning {}", path); 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()); } needsScan = false; 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)) { logger.debug("Digest didn't change, ignoring " + r); } else { result.toAdd.add(r); } break; } } if ( !processed ) { this.scanNode(n, result, resourcesSeen); } } } }