001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.component.file;
018
019import org.apache.camel.Exchange;
020import org.apache.camel.spi.ExceptionHandler;
021import org.apache.camel.spi.Synchronization;
022import org.apache.camel.support.LoggingExceptionHandler;
023import org.apache.camel.util.StringHelper;
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027/**
028 * On completion strategy that performs the required work after the {@link Exchange} has been processed.
029 * <p/>
030 * The work is for example to move the processed file into a backup folder, delete the file or
031 * in case of processing failure do a rollback. 
032 *
033 * @version 
034 */
035public class GenericFileOnCompletion<T> implements Synchronization {
036
037    private final Logger log = LoggerFactory.getLogger(GenericFileOnCompletion.class);
038    private GenericFileEndpoint<T> endpoint;
039    private GenericFileOperations<T> operations;
040    private GenericFileProcessStrategy<T> processStrategy;
041    private ExceptionHandler exceptionHandler;
042    private GenericFile<T> file;
043    private String absoluteFileName;
044
045    public GenericFileOnCompletion(GenericFileEndpoint<T> endpoint, GenericFileOperations<T> operations, GenericFileProcessStrategy processStrategy,
046                                   GenericFile<T> file, String absoluteFileName) {
047        this.endpoint = endpoint;
048        this.operations = operations;
049        this.processStrategy = processStrategy;
050        this.file = file;
051        this.absoluteFileName = absoluteFileName;
052        this.exceptionHandler = endpoint.getOnCompletionExceptionHandler();
053        if (this.exceptionHandler == null) {
054            this.exceptionHandler = new LoggingExceptionHandler(endpoint.getCamelContext(), getClass());
055        }
056    }
057
058    public void onComplete(Exchange exchange) {
059        onCompletion(exchange);
060    }
061
062    public void onFailure(Exchange exchange) {
063        onCompletion(exchange);
064    }
065
066    public ExceptionHandler getExceptionHandler() {
067        return exceptionHandler;
068    }
069
070    public void setExceptionHandler(ExceptionHandler exceptionHandler) {
071        this.exceptionHandler = exceptionHandler;
072    }
073
074    protected void onCompletion(Exchange exchange) {
075        log.debug("Done processing file: {} using exchange: {}", file, exchange);
076
077        // commit or rollback
078        boolean committed = false;
079        try {
080            boolean failed = exchange.isFailed();
081            if (!failed) {
082                // commit the file strategy if there was no failure or already handled by the DeadLetterChannel
083                processStrategyCommit(processStrategy, exchange, file);
084                committed = true;
085            }
086            // if we failed, then it will be handled by the rollback in the finally block below
087        } finally {
088            if (!committed) {
089                processStrategyRollback(processStrategy, exchange, file);
090            }
091
092            // remove file from the in progress list as its no longer in progress
093            // use the original file name that was used to add it to the repository
094            // as the name can be different when using preMove option
095            endpoint.getInProgressRepository().remove(absoluteFileName);
096        }
097    }
098
099    /**
100     * Strategy when the file was processed and a commit should be executed.
101     *
102     * @param processStrategy the strategy to perform the commit
103     * @param exchange        the exchange
104     * @param file            the file processed
105     */
106    protected void processStrategyCommit(GenericFileProcessStrategy<T> processStrategy,
107                                         Exchange exchange, GenericFile<T> file) {
108        if (endpoint.isIdempotent()) {
109
110            // use absolute file path as default key, but evaluate if an expression key was configured
111            String key = absoluteFileName;
112            if (endpoint.getIdempotentKey() != null) {
113                Exchange dummy = endpoint.createExchange(file);
114                key = endpoint.getIdempotentKey().evaluate(dummy, String.class);
115            }
116
117            // only add to idempotent repository if we could process the file
118            if (key != null) {
119                endpoint.getIdempotentRepository().add(key);
120            }
121        }
122
123        handleDoneFile(exchange);
124
125        try {
126            log.trace("Commit file strategy: {} for file: {}", processStrategy, file);
127            processStrategy.commit(operations, endpoint, exchange, file);
128        } catch (Exception e) {
129            handleException("Error during commit", exchange, e);
130        }
131    }
132
133    /**
134     * Strategy when the file was not processed and a rollback should be executed.
135     *
136     * @param processStrategy the strategy to perform the commit
137     * @param exchange        the exchange
138     * @param file            the file processed
139     */
140    protected void processStrategyRollback(GenericFileProcessStrategy<T> processStrategy,
141                                           Exchange exchange, GenericFile<T> file) {
142
143        if (log.isWarnEnabled()) {
144            log.warn("Rollback file strategy: {} for file: {}", processStrategy, file);
145        }
146
147        // only delete done file if moveFailed option is enabled, as otherwise on rollback,
148        // we should leave the done file so we can retry
149        if (endpoint.getMoveFailed() != null) {
150            handleDoneFile(exchange);
151        }
152
153        try {
154            processStrategy.rollback(operations, endpoint, exchange, file);
155        } catch (Exception e) {
156            handleException("Error during rollback", exchange, e);
157        }
158    }
159
160    protected void handleDoneFile(Exchange exchange) {
161        // must be last in batch to delete the done file name
162        // delete done file if used (and not noop=true)
163        boolean complete = exchange.getProperty(Exchange.BATCH_COMPLETE, false, Boolean.class);
164        if (endpoint.getDoneFileName() != null && !endpoint.isNoop()) {
165            // done file must be in same path as the original input file
166            String doneFileName = endpoint.createDoneFileName(absoluteFileName);
167            StringHelper.notEmpty(doneFileName, "doneFileName", endpoint);
168            // we should delete the dynamic done file
169            if (endpoint.getDoneFileName().indexOf("{file:name") > 0 || complete) {
170                try {
171                    // delete done file
172                    boolean deleted = operations.deleteFile(doneFileName);
173                    log.trace("Done file: {} was deleted: {}", doneFileName, deleted);
174                    if (!deleted) {
175                        log.warn("Done file: {} could not be deleted", doneFileName);
176                    }
177                } catch (Exception e) {
178                    handleException("Error deleting done file: " + doneFileName, exchange, e);
179                }
180            }
181        }
182    }
183
184    protected void handleException(String message, Exchange exchange, Throwable t) {
185        Throwable newt = (t == null) ? new IllegalArgumentException("Handling [null] exception") : t;
186        getExceptionHandler().handleException(message, exchange, newt);
187    }
188
189    @Override
190    public String toString() {
191        return "GenericFileOnCompletion";
192    }
193}