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.InputStream;
30 import java.io.UnsupportedEncodingException;
31 import java.net.Socket;
32 import java.util.HashMap;
33 import java.util.Map;
34 import java.util.zip.InflaterInputStream;
35
36 import net.sf.hermesftp.exception.FtpCmdException;
37 import net.sf.hermesftp.exception.FtpException;
38 import net.sf.hermesftp.exception.FtpPermissionException;
39 import net.sf.hermesftp.exception.FtpUniqueConstraintException;
40 import net.sf.hermesftp.streams.BlockModeInputStream;
41 import net.sf.hermesftp.streams.RecordInputStream;
42 import net.sf.hermesftp.streams.RecordReadSupport;
43 import net.sf.hermesftp.streams.TextInputStream;
44 import net.sf.hermesftp.utils.TransferRateLimiter;
45
46 import org.apache.commons.logging.Log;
47 import org.apache.commons.logging.LogFactory;
48
49 /***
50 * Ancestor command class that is extended by commands that store data on the remote file system.
51 *
52 * @author Lars Behnke
53 */
54 public abstract class AbstractFtpCmdStor extends AbstractFtpCmd {
55
56 private static Log log = LogFactory.getLog(AbstractFtpCmdStor.class);
57
58 private TransferRateLimiter transferRateLimiter = new TransferRateLimiter();
59
60 private long fileSize;
61
62 private long completed;
63
64 private boolean abortRequested;
65
66 /***
67 * Executes the command. This operation acts as a template method calling primitive operations
68 * implemented by the sub classes.
69 *
70 * @param unique True, if file that is supposed to be stored may not exist on the remote file
71 * system.
72 * @throws FtpCmdException Wrapper class for any exception thrown in the command.
73 */
74 public void execute(boolean unique) throws FtpCmdException {
75
76
77 File file = new File(getPathArg());
78 int mode = getCtx().getTransmissionMode();
79 int struct = getCtx().getStorageStructure();
80 int type = getCtx().getDataType();
81 String charset = type == DT_ASCII || type == DT_EBCDIC ? getCtx().getCharset() : null;
82 long fileOffset = getAndResetFileOffset();
83 getTransferRateLimiter().init(getCtx().getMaxUploadRate());
84
85 try {
86
87 doPerformAccessChecks(unique, file, fileOffset);
88
89
90 Map<Long, Long> restartMarkers = new HashMap<Long, Long>();
91 getCtx().setAttribute(ATTR_RESTART_MARKERS, restartMarkers);
92
93
94 msgOut(MSG150);
95
96 Socket dataSocket = getCtx().getDataSocketProvider().provideSocket();
97 InputStream dataIn = dataSocket.getInputStream();
98 if (struct == STRUCT_RECORD) {
99 RecordReadSupport recordIn = createRecInputStream(dataIn, mode, charset, restartMarkers);
100 doStoreRecordData(recordIn, file, fileOffset);
101 } else if (struct == STRUCT_FILE) {
102 InputStream fileIn = createInputStream(dataIn, mode, restartMarkers, charset);
103 doStoreFileData(fileIn, file, fileOffset);
104 } else {
105 log.error("Unknown data type");
106 msgOut(MSG550, "Unsupported data type");
107 }
108
109
110
111 } catch (FtpUniqueConstraintException e) {
112 msgOut(MSG553);
113 } catch (FtpPermissionException e) {
114 msgOut(MSG550_PERM);
115 } catch (FtpException e) {
116 msgOut(MSG550_MSG, e.getMessage());
117 log.warn(e.getMessage());
118 } catch (UnsupportedEncodingException e) {
119 msgOut(MSG550_MSG, "Unsupported Encoding: " + charset);
120 log.error(e.toString());
121 } catch (IOException e) {
122 msgOut(MSG550);
123 log.error(e.toString());
124 } catch (RuntimeException e) {
125 msgOut(MSG550);
126 log.error(e.toString());
127 } finally {
128 getCtx().closeSockets();
129 }
130 }
131
132 /***
133 * Creates an input stream that supports unstructured file data.
134 *
135 * @param is The nested input stream.
136 * @param mode The transmission mode.
137 * @param charset The encoding or null if binary.
138 * @param restartMarkers Optional map that stores restart markers.
139 * @return The stream object.
140 * @throws UnsupportedEncodingException Thrown if encoding is unknown.
141 */
142 private InputStream createInputStream(InputStream is, int mode, Map<Long, Long> restartMarkers,
143 String charset) throws UnsupportedEncodingException {
144 InputStream result = null;
145 if (mode == MODE_BLOCK) {
146 byte[] eorBytes = getEorBytes(null);
147 result = new BlockModeInputStream(is, eorBytes, restartMarkers);
148 } else if (mode == MODE_STREAM) {
149 result = is;
150 } else if (mode == MODE_ZIP) {
151 result = new InflaterInputStream(is);
152 } else {
153 log.error("Unsupported file mode: " + mode);
154 }
155 if (charset != null) {
156 result = new TextInputStream(is, charset);
157 }
158 return result;
159
160 }
161
162 /***
163 * Creates an input stream that supports reading records.
164 *
165 * @param is The nested input stream.
166 * @param mode The transmission mode.
167 * @param charset The encoding or null if binary.
168 * @param restartMarkers Optional map that stores restart markers.
169 * @return The stream object.
170 * @throws UnsupportedEncodingException Thrown if encoding unknown.
171 */
172 private RecordReadSupport createRecInputStream(InputStream is, int mode, String charset,
173 Map<Long, Long> restartMarkers)
174 throws UnsupportedEncodingException {
175 RecordReadSupport result = null;
176 byte[] eorBytes = charset == null ? new byte[0] : getEorBytes(charset);
177 if (mode == MODE_BLOCK) {
178 result = new BlockModeInputStream(is, eorBytes, restartMarkers);
179 } else if (mode == MODE_STREAM) {
180 result = new RecordInputStream(is, getEorBytes(charset));
181 } else if (mode == MODE_ZIP) {
182 result = new RecordInputStream(new InflaterInputStream(is), getEorBytes(charset));
183 } else {
184 log.error("Unsupported record mode: " + mode);
185 }
186 if (charset != null) {
187 result = new TextInputStream((InputStream) result, charset);
188 }
189 return result;
190
191 }
192
193 /***
194 * Returns the EOR-byte representation in non-record text files, which corresponds to the line
195 * break sequence of the passed character set.
196 *
197 * @param charset The character set.
198 * @return The EOR marker.
199 */
200 private static byte[] getEorBytes(String charset) {
201 String lineSep = System.getProperty("line.separator");
202 try {
203 if (charset == null) {
204 return lineSep.getBytes();
205 } else {
206 return lineSep.getBytes(charset);
207 }
208 } catch (UnsupportedEncodingException e) {
209 log.error(e);
210 return lineSep.getBytes();
211 }
212 }
213
214 /***
215 * {@inheritDoc}
216 */
217 public boolean handleAsyncCmd(String req) {
218 boolean result;
219 if (req == null || isResponded()) {
220 result = false;
221 } else if (req.toUpperCase().startsWith("STAT")) {
222 String stat = "STAT: " + getCompleted() + " from " + getFileSize() + " completed";
223 log.info(stat);
224
225 result = true;
226 } else if (req.toUpperCase().startsWith("ABOR")) {
227 abortRequested = true;
228 result = true;
229 } else {
230 result = false;
231 }
232 return result;
233 }
234
235 /***
236 * Checks availability and access rights for the current folder and passed file. The methods
237 * acts as a primitive operation that is called by the template method
238 * <code>execute(boolean)</code>;
239 *
240 * @param unique True, if destination file may not exist already.
241 * @param file The destination file.
242 * @param offset The file offset (-1 on append).
243 * @throws FtpException Thrown if permission rules have been violated or resource limits have
244 * been exceeded.
245 */
246 protected abstract void doPerformAccessChecks(boolean unique, File file, long offset) throws FtpException;
247
248 /***
249 * Stores record based data as file. The method acts as a primitive operation that is called by
250 * the template method <code>execute(boolean)</code>;
251 *
252 * @param rrs The wrapped input stream.
253 * @param file Destination file.
254 * @param offset The file offset (-1 on append).
255 * @throws IOException Thrown if IO fails or if at least one resource limit was reached.
256 */
257 protected abstract void doStoreRecordData(RecordReadSupport rrs, File file, long offset)
258 throws IOException;
259
260 /***
261 * Stores unstructured data as file. The method acts as a primitive operation that is called by
262 * the template method <code>execute(boolean)</code>;
263 *
264 * @param is The input stream.
265 * @param file Destination file.
266 * @param offset The file offset (-1 on append).
267 * @throws IOException Thrown if IO fails or if at least one resource limit was reached
268 */
269 protected abstract void doStoreFileData(InputStream is, File file, long offset) throws IOException;
270
271 /***
272 * Getter method for the java bean <code>completed</code>.
273 *
274 * @return Returns the value of the java bean <code>completed</code>.
275 */
276 public synchronized long getCompleted() {
277 return completed;
278 }
279
280 /***
281 * Setter method for the java bean <code>completed</code>.
282 *
283 * @param completed The value of completed to set.
284 */
285 public synchronized void incCompleted(long completed) {
286 this.completed += completed;
287 }
288
289 /***
290 * Getter method for the java bean <code>fileSize</code>.
291 *
292 * @return Returns the value of the java bean <code>fileSize</code>.
293 */
294 public long getFileSize() {
295 return fileSize;
296 }
297
298 /***
299 * Setter method for the java bean <code>fileSize</code>.
300 *
301 * @param fileSize The value of fileSize to set.
302 */
303 public void setFileSize(long fileSize) {
304 this.fileSize = fileSize;
305 }
306
307 /***
308 * @return True if abort has been requested.
309 */
310 protected boolean isAbortRequested() {
311 return abortRequested;
312 }
313
314 /***
315 * Getter Methode fuer die Eigenschaft <code>transferRateLimiter</code>.
316 *
317 * @return Wert der Eigenschaft <code>transferRateLimiter</code>.
318 */
319 public TransferRateLimiter getTransferRateLimiter() {
320 return transferRateLimiter;
321 }
322
323 /***
324 * @param transferRateLimiter the transferRateLimiter to set
325 */
326 public void setTransferRateLimiter(TransferRateLimiter transferRateLimiter) {
327 this.transferRateLimiter = transferRateLimiter;
328 }
329
330 }