/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * 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. */ package org.uberfire.backend.server.io.watch; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import javax.annotation.PreDestroy; import javax.enterprise.event.Event; import javax.enterprise.event.Observes; import javax.inject.Inject; import javax.naming.InitialContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.uberfire.backend.server.util.Filter; import org.uberfire.commons.async.DescriptiveRunnable; import org.uberfire.commons.async.DescriptiveThreadFactory; import org.uberfire.commons.services.cdi.ApplicationStarted; import org.uberfire.io.IOWatchService; import org.uberfire.java.nio.file.FileSystem; import org.uberfire.java.nio.file.WatchEvent; import org.uberfire.java.nio.file.WatchKey; import org.uberfire.java.nio.file.WatchService; import org.uberfire.workbench.events.ResourceAddedEvent; import org.uberfire.workbench.events.ResourceBatchChangesEvent; import org.uberfire.workbench.events.ResourceDeletedEvent; import org.uberfire.workbench.events.ResourceRenamedEvent; import org.uberfire.workbench.events.ResourceUpdatedEvent; public abstract class AbstractIOWatchService implements IOWatchService, Filter<WatchEvent<?>> { private static final Logger LOG = LoggerFactory.getLogger(AbstractIOWatchService.class); private static final Integer AWAIT_TERMINATION_TIMEOUT = Integer.parseInt(System.getProperty("org.uberfire.watcher.quitetimeout", "3")); private final ExecutorService executorService = Executors.newCachedThreadPool(new DescriptiveThreadFactory()); private final List<FileSystem> fileSystems = new ArrayList<FileSystem>(); private final List<WatchService> watchServices = new ArrayList<WatchService>(); private final Set<AsyncWatchService> watchThreads = new HashSet<AsyncWatchService>(); private final Set<Future<?>> jobs = new CopyOnWriteArraySet<Future<?>>(); protected boolean isDisposed = false; private boolean started; @Inject private Event<ResourceBatchChangesEvent> resourceBatchChanges; @Inject private Event<ResourceUpdatedEvent> resourceUpdatedEvent; @Inject private Event<ResourceRenamedEvent> resourceRenamedEvent; @Inject private Event<ResourceDeletedEvent> resourceDeletedEvent; @Inject private Event<ResourceAddedEvent> resourceAddedEvent; private IOWatchServiceExecutor executor = null; public AbstractIOWatchService() { final boolean autostart = Boolean.parseBoolean(System.getProperty("org.uberfire.watcher.autostart", "true")); if (autostart) { start(); } } public synchronized void start() { if (!started) { this.started = true; for (final AsyncWatchService watchThread : watchThreads) { final IOWatchServiceExecutor watchServiceExecutor = getWatchServiceExecutor(); jobs.add(executorService.submit(new DescriptiveRunnable() { @Override public String getDescription() { return watchThread.getDescription(); } @Override public void run() { watchThread.execute(watchServiceExecutor); } })); } watchThreads.clear(); } } @PreDestroy protected void dispose() { isDisposed = true; for (final WatchService watchService : watchServices) { watchService.close(); } for (final Future<?> job : jobs) { if (!job.isCancelled() && !job.isDone()) { job.cancel(true); } } executorService.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate if (!executorService.awaitTermination(AWAIT_TERMINATION_TIMEOUT, TimeUnit.SECONDS)) { executorService.shutdownNow(); // Cancel currently executing tasks // Wait a while for tasks to respond to being cancelled if (!executorService.awaitTermination(AWAIT_TERMINATION_TIMEOUT, TimeUnit.SECONDS)) { LOG.error("Thread pool did not terminate"); } } } catch (InterruptedException ie) { // (Re-)Cancel if current thread also interrupted executorService.shutdownNow(); // Preserve interrupt status Thread.currentThread().interrupt(); } } @Override public boolean hasWatchService(final FileSystem fs) { return fileSystems.contains(fs); } @Override public void addWatchService(final FileSystem fs, final WatchService ws) { fileSystems.add(fs); watchServices.add(ws); final AsyncWatchService asyncWatchService = new AsyncWatchService() { @Override public void execute(final IOWatchServiceExecutor wsExecutor) { while (!isDisposed) { final WatchKey wk; try { wk = ws.take(); } catch (final Exception ex) { break; } try { wsExecutor.execute(wk, AbstractIOWatchService.this); } catch (final Exception ex) { LOG.error("Unexpected error during WatchService execution", ex); } // Reset the key -- this step is critical if you want to // receive further watch events. If the key is no longer valid, // the directory is inaccessible so exit the loop. boolean valid = wk.reset(); if (!valid) { break; } } } @Override public String getDescription() { return AbstractIOWatchService.this.getClass().getName() + "(" + ws.toString() + ")"; } }; if (started) { final IOWatchServiceExecutor watchServiceExecutor = getWatchServiceExecutor(); executorService.execute(new DescriptiveRunnable() { @Override public String getDescription() { return asyncWatchService.getDescription(); } @Override public void run() { asyncWatchService.execute(watchServiceExecutor); } }); } else { watchThreads.add(asyncWatchService); } } public void configureOnEvent(@Observes ApplicationStarted applicationStartedEvent) { start(); } protected IOWatchServiceExecutor getWatchServiceExecutor() { if (executor == null) { IOWatchServiceExecutor _executor = null; try { _executor = InitialContext.doLookup("java:module/IOWatchServiceExecutorImpl"); } catch (final Exception ignored) { } if (_executor == null) { _executor = new IOWatchServiceExecutorImpl(); ((IOWatchServiceExecutorImpl) _executor).setEvents(resourceBatchChanges, resourceUpdatedEvent, resourceRenamedEvent, resourceDeletedEvent, resourceAddedEvent); } executor = _executor; } return executor; } }