View Javadoc

1   /*
2    * ------------------------------------------------------------------------------
3    * Hermes FTP Server
4    * Copyright (c) 2005-2007 Lars Behnke
5    * ------------------------------------------------------------------------------
6    * 
7    * This file is part of Hermes FTP Server.
8    * 
9    * Hermes FTP Server is free software; you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as published by
11   * the Free Software Foundation; either version 2 of the License, or
12   * (at your option) any later version.
13   * 
14   * Hermes FTP Server is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public License
20   * along with Hermes FTP Server; if not, write to the Free Software
21   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22   * ------------------------------------------------------------------------------
23   */
24  
25  package net.sf.hermesftp.cmd;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.io.OutputStream;
30  import java.io.UnsupportedEncodingException;
31  import java.net.Socket;
32  import java.util.zip.DeflaterOutputStream;
33  
34  import net.sf.hermesftp.common.FtpConstants;
35  import net.sf.hermesftp.exception.FtpCmdException;
36  import net.sf.hermesftp.exception.FtpPermissionException;
37  import net.sf.hermesftp.exception.FtpQuotaException;
38  import net.sf.hermesftp.streams.BlockModeOutputStream;
39  import net.sf.hermesftp.streams.RecordOutputStream;
40  import net.sf.hermesftp.streams.RecordWriteSupport;
41  import net.sf.hermesftp.streams.TextOutputStream;
42  import net.sf.hermesftp.utils.TransferRateLimiter;
43  
44  import org.apache.commons.logging.Log;
45  import org.apache.commons.logging.LogFactory;
46  
47  /***
48   * Abstract base class for RETR command implementations.
49   * 
50   * @author Lars Behnke
51   */
52  public abstract class AbstractFtpCmdRetr extends AbstractFtpCmd implements FtpConstants {
53  
54      private static Log          log                 = LogFactory.getLog(AbstractFtpCmdRetr.class);
55  
56      private TransferRateLimiter transferRateLimiter = new TransferRateLimiter();
57  
58      private long                fileSize;
59  
60      private long                completed;
61  
62      private boolean             abortRequested;
63  
64      /***
65       * Checks availability and access rights for the current folder and passed file. The methods
66       * acts as a primitive operation that is called by the template method
67       * <code>execute(boolean)</code>;
68       * 
69       * @param file The destination file.
70       * @throws IOException Thrown if one of the following conditions occurred: (1) IO failed or (3)
71       *             access rights have been violated or (3) resource limits have been reached.
72       */
73      protected abstract void doPerformAccessChecks(File file) throws IOException;
74  
75      /***
76       * Retrieves record based data. Since native files generally do not support records, the
77       * assumption is made that each line of a text file corresponds to a record. The method acts as
78       * a primitive operation that is called by the template method <code>execute()</code>;
79       * Futhermore, text record data must be encoded by an 1-byte character set (ACII, ANSI or
80       * EBCDIC).
81       * 
82       * @param out The output stream.
83       * @param file The source file.
84       * @param fileOffset The file offset.
85       * @throws IOException Thrown if IO fails or if a resource limit has been reached.
86       */
87      protected abstract void doRetrieveRecordData(RecordWriteSupport out, File file, long fileOffset)
88              throws IOException;
89  
90      /***
91       * Retrieves file based data. The method acts as a primitive operation that is called by the
92       * template method <code>execute()</code>;
93       * 
94       * @param out The output stream.
95       * @param file The source file.
96       * @param fileOffset The file offset.
97       * @throws IOException Thrown if IO fails or if a resource limit has been reached.
98       */
99      protected abstract void doRetrieveFileData(OutputStream out, File file, long fileOffset)
100             throws IOException;
101 
102     /***
103      * {@inheritDoc}
104      */
105     public boolean handleAsyncCmd(String req) {
106         boolean result;
107         if (req == null || isResponded()) {
108             result = false;
109         } else if (req.toUpperCase().startsWith("STAT")) {
110             String stat = "STAT: " + getCompleted() + " from " + getFileSize() + " completed";
111             log.info(stat);
112             // TODO Return statistics response.
113             result = true;
114         } else if (req.toUpperCase().startsWith("ABOR")) {
115             abortRequested = true;
116             result = true;
117         } else {
118             result = false;
119         }
120         return result;
121     }
122 
123     /***
124      * {@inheritDoc}
125      */
126     public void execute() throws FtpCmdException {
127 
128         /* Get relevant information from context */
129         File file = new File(getPathArg());
130         int mode = getCtx().getTransmissionMode();
131         int struct = getCtx().getStorageStructure();
132         int type = getCtx().getDataType();
133         String charset = type == DT_ASCII || type == DT_EBCDIC ? getCtx().getCharset() : null;
134         long fileOffset = getAndResetFileOffset();
135         getTransferRateLimiter().init(getCtx().getMaxDownloadRate());
136         try {
137 
138             /* Check availability and access rights */
139             doPerformAccessChecks(file);
140 
141             msgOut(MSG150);
142 
143             /* Wrap outbound data stream and call handler method */
144             Socket dataSocket = getCtx().getDataSocketProvider().provideSocket();
145             OutputStream dataOut = dataSocket.getOutputStream();
146             if (struct == STRUCT_RECORD) {
147                 RecordWriteSupport recordOut = createRecOutputStream(dataOut, mode, charset);
148                 doRetrieveRecordData(recordOut, file, fileOffset);
149             } else if (struct == STRUCT_FILE) {
150                 OutputStream fileOut = createOutputStream(dataOut, mode, charset);
151                 doRetrieveFileData(fileOut, file, fileOffset);
152             } else {
153                 log.error("Unknown data type");
154                 msgOut(MSG550, "Unsupported data type");
155                 return;
156             }
157             // TODO delegate event to FtpEventListener
158 
159         } catch (FtpQuotaException e) {
160             msgOut(MSG550, e.getMessage());
161             log.warn(e.getMessage());
162         } catch (FtpPermissionException e) {
163             msgOut(MSG550_PERM);
164         } catch (UnsupportedEncodingException e) {
165             msgOut(MSG550, "Unsupported Encoding: " + charset);
166             log.error(e.toString());
167         } catch (IOException e) {
168             msgOut(MSG550);
169             log.error(e.toString());
170         } catch (RuntimeException e) {
171             msgOut(MSG550);
172             log.error(e.toString());
173         } finally {
174             getCtx().closeSockets();
175         }
176     }
177 
178     private OutputStream createOutputStream(OutputStream dataOut, int mode, String charset)
179             throws UnsupportedEncodingException {
180         OutputStream result = null;
181         if (mode == MODE_BLOCK) {
182             result = new BlockModeOutputStream(dataOut);
183         } else if (mode == MODE_STREAM) {
184             result = dataOut;
185         } else if (mode == MODE_ZIP) {
186             result = new DeflaterOutputStream(dataOut);
187         } else {
188             log.error("Unsupported file mode: " + mode);
189         }
190         if (charset != null) {
191             result = new TextOutputStream(result, charset);
192         }
193         return result;
194     }
195 
196     private RecordWriteSupport createRecOutputStream(OutputStream dataOut, int mode, String charset)
197             throws UnsupportedEncodingException {
198         RecordWriteSupport result = null;
199         if (mode == MODE_BLOCK) {
200             result = new BlockModeOutputStream(dataOut);
201         } else if (mode == MODE_STREAM) {
202             result = new RecordOutputStream(dataOut);
203         } else if (mode == MODE_ZIP) {
204             result = new RecordOutputStream(new DeflaterOutputStream(dataOut));
205         } else {
206             log.error("Unsupported record mode: " + mode);
207         }
208         if (charset != null) {
209             result = new TextOutputStream((OutputStream) result, charset);
210         }
211         return result;
212     }
213 
214     /***
215      * @return True, if transfer has been aborted.
216      */
217     protected boolean isAbortRequested() {
218         return abortRequested;
219     }
220 
221     /***
222      * Getter method for the java bean <code>completed</code>.
223      * 
224      * @return Returns the value of the java bean <code>completed</code>.
225      */
226     public synchronized long getCompleted() {
227         return completed;
228     }
229 
230     /***
231      * Setter method for the java bean <code>completed</code>.
232      * 
233      * @param completed The value of completed to set.
234      */
235     public synchronized void incCompleted(long completed) {
236         this.completed += completed;
237     }
238 
239     /***
240      * Getter method for the java bean <code>fileSize</code>.
241      * 
242      * @return Returns the value of the java bean <code>fileSize</code>.
243      */
244     public long getFileSize() {
245         return fileSize;
246     }
247 
248     /***
249      * Setter method for the java bean <code>fileSize</code>.
250      * 
251      * @param fileSize The value of fileSize to set.
252      */
253     public void setFileSize(long fileSize) {
254         this.fileSize = fileSize;
255     }
256 
257     /***
258      * Getter Methode fuer die Eigenschaft <code>transferRateLimiter</code>.
259      * 
260      * @return Wert der Eigenschaft <code>transferRateLimiter</code>.
261      */
262     public TransferRateLimiter getTransferRateLimiter() {
263         return transferRateLimiter;
264     }
265 
266     /***
267      * @param transferRateLimiter the transferRateLimiter to set
268      */
269     public void setTransferRateLimiter(TransferRateLimiter transferRateLimiter) {
270         this.transferRateLimiter = transferRateLimiter;
271     }
272 
273 }