1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
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
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
139 doPerformAccessChecks(file);
140
141 msgOut(MSG150);
142
143
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
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 }