package org.ow2.chameleon.fuchsia.discovery.filebased.monitor;
/*
* #%L
* OW2 Chameleon - Fuchsia Discovery FileBased
* %%
* Copyright (C) 2009 - 2014 OW2 Chameleon
* %%
* Licensed 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.
* #L%
*/
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class DirectoryMonitor implements BundleActivator, ServiceTrackerCustomizer {
/**
* logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(DirectoryMonitor.class);
/**
* List of deployers.
*/
private final List<Deployer> deployers = new ArrayList<Deployer>();
/**
* The directory.
*/
private final File directory;
/**
* Polling period.
* -1 to disable polling.
*/
private final long polling;
/**
* A monitor listening file changes.
*/
private final FileAlterationMonitor monitor;
/**
* The lock avoiding concurrent modifications of the deployers map.
*/
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final String trackedClassName;
/**
* Service tracking to retrieve deployers.
*/
private ServiceTracker tracker;
private BundleContext context;
public DirectoryMonitor(String directorypath, long polling, String classname) {
this.directory = new File(directorypath);
this.trackedClassName = classname;
this.polling = polling;
if (!directory.isDirectory()) {
LOG.info("Monitored directory {} not existing - creating directory", directory.getAbsolutePath());
if (!this.directory.mkdirs()) {
throw new IllegalStateException("Monitored directory doesn't exist and cannot be created.");
}
}
// We observes all files.
FileAlterationObserver observer = new FileAlterationObserver(directory, TrueFileFilter.INSTANCE);
observer.checkAndNotify();
observer.addListener(new FileMonitor());
monitor = new FileAlterationMonitor(polling, observer);
}
/**
* Acquires the write lock only and only if the write lock is not already held by the current thread.
*
* @return {@literal true} if the lock was acquired within the method, {@literal false} otherwise.
*/
private boolean acquireWriteLockIfNotHeld() {
if (!lock.isWriteLockedByCurrentThread()) {
lock.writeLock().lock();
return true;
}
return false;
}
/**
* Releases the write lock only and only if the write lock is held by the current thread.
*
* @return {@literal true} if the lock has no more holders, {@literal false} otherwise.
*/
private boolean releaseWriteLockIfHeld() {
if (lock.isWriteLockedByCurrentThread()) {
lock.writeLock().unlock();
}
return lock.getWriteHoldCount() == 0;
}
/**
* Acquires the read lock only and only if no read lock is already held by the current thread.
*
* @return {@literal true} if the lock was acquired within the method, {@literal false} otherwise.
*/
private boolean acquireReadLockIfNotHeld() {
if (lock.getReadHoldCount() == 0) {
lock.readLock().lock();
return true;
}
return false;
}
/**
* Releases the read lock only and only if the read lock is held by the current thread.
*
* @return {@literal true} if the lock has no more holders, {@literal false} otherwise.
*/
private boolean releaseReadLockIfHeld() {
if (lock.getReadHoldCount() != 0) {
lock.readLock().unlock();
}
return lock.getReadHoldCount() == 0;
}
public void start(final BundleContext context) throws DirectoryMonitoringException {
this.context = context;
LOG.info("Starting installing bundles from {}", directory.getAbsolutePath());
this.tracker = new ServiceTracker(context, this.trackedClassName, this);
// To avoid concurrency, we take the write lock here.
try {
acquireWriteLockIfNotHeld();
// Arrives will be blocked until we release teh write lock
this.tracker.open();
// Register file monitor
startFileMonitoring();
} finally {
releaseWriteLockIfHeld();
}
// Initialization does not need the write lock, read is enough.
try {
acquireReadLockIfNotHeld();
// Per extension, open deployer.
Collection<File> files = FileUtils.listFiles(directory, null, true);
for (File file : files) {
for (Deployer deployer : deployers) {
if (deployer.accept(file)) {
deployer.open(files);
}
}
}
} finally {
releaseReadLockIfHeld();
}
}
private void startFileMonitoring() throws DirectoryMonitoringException {
if (polling == -1L) {
LOG.debug("No file monitoring for {}", directory.getAbsolutePath());
return;
}
LOG.info("Starting file monitoring for {} - polling : {} ms", directory.getName(), polling);
try {
monitor.start();
} catch (Exception e) {
throw new DirectoryMonitoringException("Exception while starting the FileAlterationMonitor.", e);
}
}
public void stop(BundleContext context) throws DirectoryMonitoringException {
// To avoid concurrency, we take the write lock here.
try {
acquireWriteLockIfNotHeld();
this.tracker.close();
if (monitor != null) {
LOG.debug("Stopping file monitoring of {}", directory.getAbsolutePath());
// Wait 5 milliseconds.
monitor.stop(5);
}
} catch (Exception e) {
throw new DirectoryMonitoringException("Exception while stopping the FileAlterationMonitor.", e);
} finally {
releaseWriteLockIfHeld();
}
// No concurrency involved from here.
for (Deployer deployer : deployers) {
deployer.close();
}
}
public Object addingService(ServiceReference reference) {
Deployer deployer = (Deployer) context.getService(reference);
try {
acquireWriteLockIfNotHeld();
deployers.add(deployer);
Collection<File> files = FileUtils.listFiles(directory, null, true);
List<File> accepted = new ArrayList<File>();
for (File file : files) {
if (deployer.accept(file)) {
accepted.add(file);
}
}
deployer.open(accepted);
} finally {
releaseWriteLockIfHeld();
}
return deployer;
}
public void modifiedService(ServiceReference reference, Object o) {
// Cannot happen, deployers do not have properties.
}
public void removedService(ServiceReference reference, Object o) {
Deployer deployer = (Deployer) o;
try {
acquireWriteLockIfNotHeld();
deployers.remove(deployer);
} finally {
releaseWriteLockIfHeld();
}
}
private class FileMonitor extends FileAlterationListenerAdaptor {
/**
* A jar file was created.
*
* @param file the file
*/
@Override
public void onFileCreate(File file) {
LOG.info("File " + file + " created in " + directory);
// Callback called outside the protected region.
for (Deployer deployer : getDeployers(file)) {
try {
deployer.onFileCreate(file);
} catch (Exception e) {
LOG.error("Error during the management of " + file.getAbsolutePath() + " (created) by " + deployer, e);
}
}
}
@Override
public void onFileChange(File file) {
LOG.info("File " + file + " from " + directory + " changed");
for (Deployer deployer : getDeployers(file)) {
try {
deployer.onFileChange(file);
} catch (Exception e) {
LOG.error("Error during the management of " + file.getAbsolutePath() + " (change) by " + deployer,
e);
}
}
}
@Override
public void onFileDelete(File file) {
LOG.info("File " + file + " deleted from " + directory);
for (Deployer deployer : getDeployers(file)) {
try {
deployer.onFileDelete(file);
} catch (Exception e) {
LOG.error("Error during the management of " + file.getAbsolutePath() + " (delete) by " + deployer, e);
}
}
}
}
private Set<Deployer> getDeployers(File file) {
Set<Deployer> depl = new HashSet<Deployer>();
try {
acquireReadLockIfNotHeld();
for (Deployer deployer : deployers) {
if (deployer.accept(file)) {
depl.add(deployer);
}
}
} finally {
releaseReadLockIfHeld();
}
return depl;
}
}