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.servicemix.ftp;
018    
019    import java.io.IOException;
020    import java.io.OutputStream;
021    import java.net.URI;
022    import java.util.StringTokenizer;
023    
024    import javax.jbi.JBIException;
025    import javax.jbi.management.DeploymentException;
026    import javax.jbi.messaging.MessageExchange;
027    import javax.jbi.messaging.NormalizedMessage;
028    import javax.jbi.servicedesc.ServiceEndpoint;
029    
030    import org.apache.commons.net.ftp.FTPClient;
031    import org.apache.servicemix.common.endpoints.ProviderEndpoint;
032    import org.apache.servicemix.components.util.DefaultFileMarshaler;
033    import org.apache.servicemix.components.util.FileMarshaler;
034    
035    /**
036     * An FTP endpoint
037     *
038     * @version $Revision: $
039     * @org.apache.xbean.XBean element="sender"
040     */
041    public class FtpSenderEndpoint extends ProviderEndpoint implements FtpEndpointType {
042    
043        private FTPClientPool clientPool;
044        private FileMarshaler marshaler = new DefaultFileMarshaler();
045        private String uniqueFileName = "ServiceMix";
046        private boolean overwrite;
047        private URI uri;
048        @Deprecated
049        private String uploadPrefix;
050        @Deprecated
051        private String uploadSuffix;
052        private boolean checkDuplicates = true;
053        private boolean autoCreateDirectory = true;
054        
055        public FtpSenderEndpoint() {
056        }
057    
058        public FtpSenderEndpoint(FtpComponent component, ServiceEndpoint endpoint) {
059            super(component, endpoint);
060        }
061        
062        public void validate() throws DeploymentException {
063            super.validate();
064            if (uri == null && (getClientPool() == null || getClientPool().getHost() == null)) {
065                throw new DeploymentException("Property uri or clientPool.host must be configured");
066            }
067            if (uri != null && getClientPool() != null && getClientPool().getHost() != null) {
068                throw new DeploymentException("Properties uri and clientPool.host can not be configured at the same time");
069            }
070        }
071    
072        /**
073         * Configures the endpoint from a URI
074         */
075        public void setUri(URI uri) {
076            this.uri = uri;
077        }
078    
079        public boolean isCheckDuplicates() {
080            return checkDuplicates;
081        }
082    
083        public void setCheckDuplicates(boolean checkDuplicates) {
084            this.checkDuplicates = checkDuplicates;
085        }
086    
087        public void start() throws Exception {
088            super.start();
089            if (clientPool == null) {
090                clientPool = createClientPool();
091            }
092            if (uri != null) {
093                clientPool.setHost(uri.getHost());
094                clientPool.setPort(uri.getPort());
095                if (uri.getUserInfo() != null) {
096                    String[] infos = uri.getUserInfo().split(":");
097                    clientPool.setUsername(infos[0]);
098                    if (infos.length > 1) {
099                        clientPool.setPassword(infos[1]);
100                    }
101                }
102            }
103            
104            // borrow client from pool
105            FTPClient ftp = borrowClient();
106            String folderName = "";
107            try {
108                StringTokenizer strTok = null;
109                if (isAutoCreateDirectory() && !ftp.changeWorkingDirectory(getWorkingPath())) {
110                    // it seems the folder isn't there, so create it
111                    strTok = new StringTokenizer(getWorkingPath(), "/");
112                    
113                    while (strTok.hasMoreTokens()) {
114                        folderName += '/';
115                        folderName += strTok.nextToken();
116                        if (!ftp.changeWorkingDirectory(folderName)) {
117                            if (ftp.makeDirectory(folderName)) {
118                                // the folder now exists
119                            } else {
120                                // unable to create the folder
121                                throw new IOException("The defined folder " + getWorkingPath() + " doesn't exist on the server and it can't be created automatically.");
122                            }
123                        }
124                    }
125                }
126            } finally {
127                // give back the client
128                returnClient(ftp);
129            }
130        }
131        
132        private String getWorkingPath() {
133            return (uri != null && uri.getPath() != null) ? uri.getPath() : ".";
134        }
135    
136        // Properties
137        //-------------------------------------------------------------------------
138        public FTPClientPool getClientPool() {
139            return clientPool;
140        }
141    
142        public void setClientPool(FTPClientPool clientPool) {
143            this.clientPool = clientPool;
144        }
145    
146        public FileMarshaler getMarshaler() {
147            return marshaler;
148        }
149    
150        public void setMarshaler(FileMarshaler marshaler) {
151            this.marshaler = marshaler;
152        }
153    
154        public String getUniqueFileName() {
155            return uniqueFileName;
156        }
157    
158        /**
159         * Sets the name used to make a unique name if no file name is available on the message.
160         *
161         * @param uniqueFileName the new value of the unique name to use for generating unique names
162         */
163        public void setUniqueFileName(String uniqueFileName) {
164            this.uniqueFileName = uniqueFileName;
165        }
166    
167        public boolean isOverwrite() {
168            return overwrite;
169        }
170    
171        public void setOverwrite(boolean overwrite) {
172            this.overwrite = overwrite;
173        }
174    
175        @Deprecated
176        public String getUploadPrefix() {
177            return uploadPrefix;
178        }
179    
180        /**
181         * Set the file name prefix used during upload.  The prefix will be automatically removed as soon as the upload has completed.  
182         * This allows other processes to discern completed files from files that are being uploaded.
183         * 
184         * @param uploadPrefix
185         */
186        @Deprecated
187        public void setUploadPrefix(String uploadPrefix) {
188            this.uploadPrefix = uploadPrefix;
189        }
190    
191        @Deprecated
192        public String getUploadSuffix() {
193            return uploadSuffix;
194        }
195    
196        /**
197         * Set the file name suffix used during upload.  The suffix will be automatically removed as soon as the upload has completed.  
198         * This allows other processes to discern completed files from files that are being uploaded.
199         * 
200         * @param uploadSuffix
201         */
202        @Deprecated
203        public void setUploadSuffix(String uploadSuffix) {
204            this.uploadSuffix = uploadSuffix;
205        }
206        
207        /**
208         * Specifies if the endpoint should create the target directory, if it does
209         * not already exist. If you set this to <code>false</code> and the
210         * directory does not exist, the endpoint will not do anything. Default
211         * value is <code>true</code>.
212         * 
213         * @param autoCreateDirectory a boolean specifying if the endpoint creates
214         *            directories.
215         */
216        public void setAutoCreateDirectory(boolean autoCreateDirectory) {
217            this.autoCreateDirectory = autoCreateDirectory;
218        }
219    
220        public boolean isAutoCreateDirectory() {
221            return autoCreateDirectory;
222        }
223    
224        // Implementation methods
225        //-------------------------------------------------------------------------
226    
227        protected void processInOnly(MessageExchange exchange, NormalizedMessage message) throws Exception {
228            FTPClient client = null;
229            OutputStream out = null;
230            String name = null;
231            String uploadName = null;
232            try {
233                client = borrowClient();
234                // Change to the directory specified by the URI path if any
235                if (uri != null && uri.getPath() != null) {
236                    if (!client.changeWorkingDirectory(uri.getPath())) {
237                        logger.warn("Unable to change ftp directory to '" + uri.getPath() + "'");
238                    }
239                }
240    
241                name = marshaler.getOutputName(exchange, message);
242                if (name == null) {
243                    if (uniqueFileName != null) {
244                        out = client.storeUniqueFileStream(uniqueFileName);
245                    } else {
246                        out = client.storeUniqueFileStream();
247                    }
248                } else {
249                    if (checkDuplicates && client.listFiles(name).length > 0) {
250                        if (overwrite) {
251                            client.deleteFile(name);
252                        } else {
253                            throw new IOException("Can not send " + name
254                                    + " : file already exists and overwrite has not been enabled");
255                        }
256                    }
257                    uploadName = marshaler.getTempOutputName(exchange, message) != null ? marshaler.getTempOutputName(exchange, message) : name;
258                    out = client.storeFileStream(uploadName);
259                }
260                if (out == null) {
261                    throw new IOException("No output stream available for output name: " + uploadName + ". Maybe the file already exists?");
262                }
263                marshaler.writeMessage(exchange, message, out, uploadName);
264            } finally {
265                if (out != null) {
266                    try {
267                        out.close();
268                        client.completePendingCommand();
269                        if (name != null && !name.equals(uploadName) && !client.rename(uploadName, name)) {
270                            throw new IOException("File " + uploadName + " could not be renamed to " + name);
271                        }
272                    } catch (IOException e) {
273                        logger.error("Caught exception while closing stream on error: " + e, e);
274                    }
275                }
276                returnClient(client);
277            }
278        }
279    
280        @Deprecated
281        protected String getUploadName(String name) {
282            String result = uploadPrefix == null ? name : uploadPrefix + name;
283            result = uploadSuffix == null ? result : result + uploadSuffix;
284            return result;
285        }
286    
287        protected FTPClientPool createClientPool() throws Exception {
288            FTPClientPool pool = new FTPClientPool();
289            pool.afterPropertiesSet();
290            return pool;
291        }
292    
293        protected FTPClient borrowClient() throws JBIException {
294            try {
295                return (FTPClient) getClientPool().borrowClient();
296            } catch (Exception e) {
297                throw new JBIException(e);
298            }
299        }
300    
301        protected void returnClient(FTPClient client) {
302            if (client != null) {
303                try {
304                    getClientPool().returnClient(client);
305                } catch (Exception e) {
306                    logger.error("Failed to return client to pool: " + e, e);
307                }
308            }
309        }
310    }