/** * Copyright (c) Codice Foundation * <p> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package org.codice.ddf.catalog.content.monitor; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.util.List; import org.apache.camel.Exchange; import org.apache.camel.Message; import org.apache.camel.Processor; import org.apache.camel.component.file.GenericFile; import org.apache.camel.component.file.GenericFileConsumer; import org.apache.camel.component.file.GenericFileEndpoint; import org.apache.camel.component.file.GenericFileOperations; import org.apache.camel.spi.Synchronization; import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; import org.apache.commons.io.monitor.FileAlterationObserver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ddf.catalog.Constants; public class DurableFileConsumer extends GenericFileConsumer<EventfulFileWrapper> { private static final Logger LOGGER = LoggerFactory.getLogger(DurableFileConsumer.class); private static final Logger INGEST_LOGGER = LoggerFactory.getLogger(Constants.INGEST_LOGGER_NAME); private FileAlterationObserver observer; private FileSystemPersistenceProvider fileSystemPersistenceProvider; private DurableFileAlterationListener listener; public DurableFileConsumer(GenericFileEndpoint<EventfulFileWrapper> endpoint, Processor processor, GenericFileOperations<EventfulFileWrapper> operations) { super(endpoint, processor, operations); } @Override protected void updateFileHeaders(GenericFile<EventfulFileWrapper> file, Message message) { // noop } @Override protected boolean pollDirectory(String fileName, List list, int depth) { initialize(fileName); if (observer != null) { observer.addListener(listener); observer.checkAndNotify(); observer.removeListener(listener); fileSystemPersistenceProvider.store(String.valueOf(fileName.hashCode()), observer); return true; } else { return false; } } private void initialize(String fileName) { if (fileSystemPersistenceProvider == null) { fileSystemPersistenceProvider = new FileSystemPersistenceProvider(getClass().getSimpleName()); } if (observer == null && fileName != null) { if (fileSystemPersistenceProvider.loadAllKeys() .contains(String.valueOf(fileName.hashCode()))) { observer = (FileAlterationObserver) fileSystemPersistenceProvider.loadFromPersistence( String.valueOf(fileName.hashCode())); } else { observer = new FileAlterationObserver(new File(fileName)); } } if (listener == null) { listener = new DurableFileAlterationListener(); } } @Override protected boolean isMatched(GenericFile file, String doneFileName, List files) { return false; } private void createExchangeHelper(File file, WatchEvent.Kind<Path> fileEvent) { GenericFile<EventfulFileWrapper> genericFile = new GenericFile<>(); genericFile.setFile(new EventfulFileWrapper(fileEvent, 1, file.toPath())); genericFile.setEndpointPath(endpoint.getConfiguration() .getDirectory()); try { genericFile.setAbsoluteFilePath(file.getCanonicalPath()); } catch (IOException e) { LOGGER.warn("Unable to canonicalize {}. Verify location is accessible.", file.toString()); } Exchange exchange = endpoint.createExchange(genericFile); exchange.addOnCompletion(new ErrorLoggingSynchronization(file, fileEvent)); processExchange(exchange); } @Override public void shutdown() throws Exception { super.shutdown(); if (observer != null) { observer.destroy(); } } private static class ErrorLoggingSynchronization implements Synchronization { private final File file; private final WatchEvent.Kind<Path> fileEvent; public ErrorLoggingSynchronization(File file, WatchEvent.Kind<Path> fileEvent) { this.file = file; this.fileEvent = fileEvent; } @Override public void onComplete(Exchange exchange) { // no-op } @Override public void onFailure(Exchange exchange) { INGEST_LOGGER.error("Delivery failed for {} event on {}", file, fileEvent.name(), exchange.getException()); } } private class DurableFileAlterationListener extends FileAlterationListenerAdaptor { @Override public void onFileChange(File file) { createExchangeHelper(file, StandardWatchEventKinds.ENTRY_MODIFY); } @Override public void onFileCreate(File file) { createExchangeHelper(file, StandardWatchEventKinds.ENTRY_CREATE); } @Override public void onFileDelete(File file) { createExchangeHelper(file, StandardWatchEventKinds.ENTRY_DELETE); } } }