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