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 }