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