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     */
017    package org.apache.camel.component.file.remote;
018    
019    import org.apache.camel.Exchange;
020    import org.apache.camel.ServicePoolAware;
021    import org.apache.camel.component.file.GenericFileOperationFailedException;
022    import org.apache.camel.component.file.GenericFileProducer;
023    import org.apache.camel.util.ExchangeHelper;
024    import org.apache.camel.util.ObjectHelper;
025    
026    /**
027     * Generic remote file producer for all the FTP variations.
028     */
029    public class RemoteFileProducer<T> extends GenericFileProducer<T> implements ServicePoolAware {
030    
031        private boolean loggedIn;
032        
033        protected RemoteFileProducer(RemoteFileEndpoint<T> endpoint, RemoteFileOperations<T> operations) {
034            super(endpoint, operations);
035        }
036        
037        @Override
038        public String getFileSeparator() {
039            return "/";
040        }
041        
042        @Override
043        public String normalizePath(String name) {
044            return name;
045        }
046    
047        public void process(Exchange exchange) throws Exception {
048            Exchange remoteExchange = getEndpoint().createExchange(exchange);
049            String target = createFileName(exchange);
050            processExchange(remoteExchange, target);
051            ExchangeHelper.copyResults(exchange, remoteExchange);
052        }
053    
054        protected RemoteFileOperations getOperations() {
055            return (RemoteFileOperations) operations;
056        }
057    
058        @Override
059        @SuppressWarnings("unchecked")
060        public RemoteFileEndpoint<T> getEndpoint() {
061            return (RemoteFileEndpoint<T>) super.getEndpoint();
062        }
063    
064        /**
065         * The file could not be written. We need to disconnect from the remote server.
066         */
067        public void handleFailedWrite(Exchange exchange, Exception exception) throws Exception {
068            loggedIn = false;
069            if (isStopping() || isStopped()) {
070                // if we are stopping then ignore any exception during a poll
071                log.debug("Exception occurred during stopping: " + exception.getMessage());
072            } else {
073                log.warn("Writing file failed with: " + exception.getMessage());
074                try {
075                    disconnect();
076                } catch (Exception e) {
077                    // ignore exception
078                    log.debug("Ignored exception during disconnect: " + e.getMessage());
079                }
080                // rethrow the original exception*/
081                throw exception;
082            }
083        }
084    
085        public void disconnect() throws GenericFileOperationFailedException {
086            loggedIn = false;
087            if (getOperations().isConnected()) {
088                if (log.isDebugEnabled()) {
089                    log.debug("Disconnecting from: " + getEndpoint());
090                }
091                getOperations().disconnect();
092            }
093        }
094    
095        @Override
096        public void preWriteCheck() throws Exception {
097            // before writing send a noop to see if the connection is alive and works
098            boolean noop = false;
099            if (loggedIn) {
100                try {
101                    noop = getOperations().sendNoop();
102                } catch (Exception e) {
103                    // ignore as we will try to recover connection
104                    noop = false;
105                }
106            }
107    
108            if (log.isTraceEnabled()) {
109                log.trace("preWriteCheck send noop success: " + noop);
110            }
111    
112            // if not alive then reconnect
113            if (!noop) {
114                try {
115                    if (getEndpoint().getMaximumReconnectAttempts() > 0) {
116                        // only use recoverable if we are allowed any re-connect attempts
117                        recoverableConnectIfNecessary();
118                    } else {
119                        connectIfNecessary();
120                    }
121                } catch (Exception e) {
122                    loggedIn = false;
123    
124                    // must be logged in to be able to upload the file
125                    throw e;
126                }
127            }
128        }
129    
130        @Override
131        public void postWriteCheck() {
132            try {
133                if (getEndpoint().isDisconnect()) {
134                    if (log.isTraceEnabled()) {
135                        log.trace("postWriteCheck disconnect from: " + getEndpoint());
136                    }
137                    disconnect();
138                }
139            } catch (GenericFileOperationFailedException e) {
140                // ignore just log a warning
141                log.warn("Exception occurred during disconnecting from: " + getEndpoint() + " " + e.getMessage());
142            }
143        }
144    
145        @Override
146        protected void doStart() throws Exception {
147            log.debug("Starting");
148            // do not connect when component starts, just wait until we process as we will
149            // connect at that time if needed
150            super.doStart();
151        }
152    
153        @Override
154        protected void doStop() throws Exception {
155            try {
156                disconnect();
157            } catch (Exception e) {
158                log.debug("Exception occurred during disconnecting from: " + getEndpoint() + " " + e.getMessage());
159            }
160            super.doStop();
161        }
162    
163        protected void recoverableConnectIfNecessary() throws Exception {
164            try {
165                connectIfNecessary();
166            } catch (Exception e) {
167                loggedIn = false;
168    
169                // are we interrupted
170                InterruptedException ie = ObjectHelper.getException(InterruptedException.class, e);
171                if (ie != null) {
172                    if (log.isDebugEnabled()) {
173                        log.debug("Interrupted during connect to: " + getEndpoint(), ie);
174                    }
175                    throw ie;
176                }
177    
178                if (log.isDebugEnabled()) {
179                    log.debug("Could not connect to: " + getEndpoint() + ". Will try to recover.", e);
180                }
181            }
182    
183            // recover by re-creating operations which should most likely be able to recover
184            if (!loggedIn) {
185                if (log.isDebugEnabled()) {
186                    log.debug("Trying to recover connection to: " + getEndpoint() + " with a fresh client.");
187                }
188                setOperations(getEndpoint().createRemoteFileOperations());
189                connectIfNecessary();
190            }
191        }
192    
193        protected void connectIfNecessary() throws GenericFileOperationFailedException {
194            if (!loggedIn) {
195                if (log.isDebugEnabled()) {
196                    log.debug("Not already connected/logged in. Connecting to: " + getEndpoint());
197                }
198                RemoteFileConfiguration config = getEndpoint().getConfiguration();
199                loggedIn = getOperations().connect(config);
200                if (!loggedIn) {
201                    return;
202                }
203                log.info("Connected and logged in to: " + getEndpoint());
204            }
205        }
206    
207        public boolean isSingleton() {
208            // this producer is stateful because the remote file operations is not thread safe
209            return false;
210        }
211    
212    }