001 /*
002 *
003 * $Revision: 13085 $ $Date: 2008-02-06 18:27:24 +0100 (Mi, 06 Feb 2008) $
004 *
005 * This file is part of *** M y C o R e ***
006 * See http://www.mycore.de/ for details.
007 *
008 * This program is free software; you can use it, redistribute it
009 * and / or modify it under the terms of the GNU General Public License
010 * (GPL) as published by the Free Software Foundation; either version 2
011 * of the License or (at your option) any later version.
012 *
013 * This program is distributed in the hope that it will be useful, but
014 * WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program, in a file called gpl.txt or license.txt.
020 * If not, write to the Free Software Foundation Inc.,
021 * 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
022 */
023
024 package org.mycore.frontend.fileupload;
025
026 import java.io.BufferedInputStream;
027 import java.io.DataInputStream;
028 import java.io.DataOutputStream;
029 import java.io.File;
030 import java.io.FileInputStream;
031 import java.io.FileNotFoundException;
032 import java.io.IOException;
033 import java.io.InputStream;
034 import java.io.UnsupportedEncodingException;
035 import java.net.MalformedURLException;
036 import java.net.Socket;
037 import java.net.URL;
038 import java.net.URLConnection;
039 import java.net.URLEncoder;
040 import java.security.DigestInputStream;
041 import java.security.MessageDigest;
042 import java.util.Enumeration;
043 import java.util.Hashtable;
044 import java.util.Stack;
045 import java.util.StringTokenizer;
046 import java.util.Vector;
047 import java.util.zip.Deflater;
048 import java.util.zip.ZipEntry;
049 import java.util.zip.ZipOutputStream;
050
051 /**
052 * This class is used by applets to communicate with the corresponding
053 * MCRUploadServlet servlet to execute tasks on the server. For example, the
054 * applet may invoke the createDocument method and pass it a document instance
055 * to create this document in the persistent datastore on the server side. The
056 * MCRUploadCommunicator does some marshalling etc. and sends the request to the
057 * MCRUploadServlet servlet that does the job.
058 *
059 * @author Frank Lützenkirchen
060 * @author Harald Richter
061 * @author Jens Kupferschmidt
062 * @author Thomas Scheffler (yagee)
063 * @version $Revision: 13085 $ $Date: 2008-02-06 18:27:24 +0100 (Mi, 06 Feb 2008) $
064 * @see org.mycore.frontend.fileupload.MCRUploadServlet
065 */
066 public class MCRUploadCommunicator {
067 protected String url;
068
069 protected String uid;
070
071 protected MCRUploadProgressMonitor upm;
072
073 protected MCRUploadApplet applet;
074
075 protected final static int bufferSize = 65536; // 64 KByte
076
077 public MCRUploadCommunicator(String url, String uploadId, MCRUploadApplet applet) {
078 this.url = url;
079 this.uid = uploadId;
080 this.applet = applet;
081 }
082
083 public void uploadFiles(File[] selectedFiles) {
084 try {
085 Vector[] list = listFiles(selectedFiles);
086 upm = new MCRUploadProgressMonitor(list[0].size(), countTotalBytes(list[0]), applet);
087 startUploadSession(list[0].size());
088 loadFiles(list);
089 if (upm.isCanceled()) {
090 System.out.println("Upload is canceled by user.");
091 cancelUploadSession();
092 } else {
093 endUploadSession();
094 upm.finish();
095 }
096 } catch (Exception ex) {
097 String msg = ex.getClass().getName() + ": " + ex.getLocalizedMessage();
098
099 if (ex instanceof MCRUploadException) {
100 MCRUploadException uex = (MCRUploadException) ex;
101 msg = "Fehlermeldung des Servers: " + uex.getServerSideClassName() + ": " + uex.getMessage();
102 }
103
104 System.out.println("Exception caught: " + msg);
105 ex.printStackTrace(System.out);
106
107 if (upm != null) {
108 upm.cancel(ex);
109 } else {
110 MCRUploadProgressMonitor.reportException(ex);
111 }
112 }
113 }
114
115 protected long countTotalBytes(Vector files) {
116 long total = 0;
117
118 for (int i = 0; i < files.size(); i++)
119 total += ((File) files.get(i)).length();
120
121 return total;
122 }
123
124 public void loadFiles(Vector[] list) throws Exception {
125 if (list[0].size() == 0) {
126 throw new IllegalArgumentException("Sie haben keine Dateien ausgewählt!");
127 }
128
129 for (int i = 0; i < list[0].size(); i++) {
130 if (upm.isCanceled())
131 return;
132
133 File file = (File) (list[0].get(i));
134 String path = (String) (list[1].get(i));
135 upm.startFile(file.getName(), file.length());
136
137 uploadFile(path, file);
138
139 if (!upm.isCanceled())
140 upm.endFile();
141 }
142 }
143
144 public void uploadFile(String path, File file) throws Exception {
145 System.out.println("--- Starting filetransfer ---");
146
147 String md5 = buildMD5String(file);
148 System.out.println("MD5 checksum is " + md5);
149
150 if (upm.isCanceled())
151 return;
152
153 // TODO: Refactor method names in communication
154 Hashtable request = new Hashtable();
155 request.put("md5", md5);
156 request.put("method", "uploadFile");
157 request.put("path", path);
158 request.put("length", String.valueOf(file.length()) );
159
160 System.out.println("Sending filename to server: " + path);
161
162 String reply = (String) (send(request));
163 System.out.println("Received reply from server.");
164
165 if ("skip file".equals(reply)) {
166 System.out.println("File skipped.");
167 return;
168 }
169
170 StringTokenizer st = new StringTokenizer(reply, ":");
171 String host = st.nextToken();
172 int port = Integer.parseInt(st.nextToken());
173 System.out.println("Server says we should connect to " + host + ":" + port);
174
175 System.out.println("Trying to create client socket...");
176
177 if (upm.isCanceled())
178 return;
179
180 Socket socket = new Socket(host, port);
181 socket.setReceiveBufferSize(Math.max(socket.getReceiveBufferSize(),bufferSize));
182 socket.setSendBufferSize(Math.max(socket.getSendBufferSize(),bufferSize));
183
184 System.out.println("Socket created, connected to server.");
185 System.out.println("Socket send buffer size is " + socket.getSendBufferSize() );
186
187 ZipOutputStream zos = new ZipOutputStream(socket.getOutputStream());
188 DataInputStream din = new DataInputStream(socket.getInputStream());
189
190 // Large files like video already are compressed somehow
191 zos.setLevel(Deflater.NO_COMPRESSION);
192
193 ZipEntry ze = new ZipEntry(java.net.URLEncoder.encode(path, "UTF-8"));
194 StringBuffer extra = new StringBuffer();
195 extra.append(md5).append(" ").append(file.length()).append(" ").append(uid);
196 ze.setExtra(extra.toString().getBytes("UTF-8"));
197 zos.putNextEntry(ze);
198
199 int num = 0;
200 long sended = 0;
201 byte[] buffer = new byte[bufferSize];
202
203 System.out.println("Starting to send file content...");
204
205 InputStream source = new BufferedInputStream(new FileInputStream(file),buffer.length);
206
207 long lastPing = System.currentTimeMillis();
208 while ((num = source.read(buffer)) != -1) {
209 if (upm.isCanceled())
210 break;
211 zos.write(buffer, 0, num);
212 sended += num;
213 upm.progressFile(num);
214
215 // Send a "ping" to MCRUploadServlet so that server keeps HTTP Session alive
216 if( ( System.currentTimeMillis() - lastPing ) > 10000 )
217 {
218 lastPing = System.currentTimeMillis();
219 Hashtable ping = new Hashtable();
220 ping.put("method", "ping");
221 System.out.println( "Sending ping to servlet..." );
222 String pong = (String)( send(ping) );
223 System.out.println( "Server responded with " + pong );
224 }
225 }
226
227 zos.closeEntry();
228 zos.flush();
229 System.out.println("Releasing file: "+file);
230 source.close();
231 System.out.println("Finished sending file content.");
232
233 long numBytesStored = din.readLong();
234 System.out.println("Server reports that " + numBytesStored + " bytes have been stored." );
235
236 socket.close();
237
238 if (upm.isCanceled())
239 return;
240
241 System.out.println("Socket closed, file transfer successfully completed.");
242 }
243
244 /**
245 * Creates a list of all files in the given directories
246 *
247 * @param selectedFiles
248 * list of selected files or directories from filechooser
249 */
250 protected Vector[] listFiles(File[] selectedFiles) throws Exception {
251 Vector[] list = new Vector[2];
252 list[0] = new Vector();
253 list[1] = new Vector();
254
255 if ((null == selectedFiles) || (0 == selectedFiles.length)) {
256 return list;
257 }
258
259 for (int i = 0; i < selectedFiles.length; i++) {
260 File f = selectedFiles[i];
261
262 if (!f.exists()) {
263 throw new FileNotFoundException("Datei oder Verzeichnis " + f.getPath() + " nicht gefunden!");
264 }
265
266 if (!f.canRead()) {
267 throw new IOException("Datei oder Verzeichnis " + f.getPath() + " nicht lesbar!");
268 }
269
270 if (f.isFile()) {
271 list[0].addElement(f);
272 list[1].addElement(f.getName());
273 } else {
274 Stack dirStack = new Stack();
275 Stack baseStack = new Stack();
276
277 dirStack.push(f);
278 baseStack.push(f.getName() + "/");
279
280 while (!dirStack.empty()) {
281 File dir = (File) (dirStack.pop());
282 String base = (String) (baseStack.pop());
283
284 String[] files = dir.list();
285
286 for (int j = 0; j < files.length; j++) {
287 f = new File(dir, files[j]);
288
289 if (f.isFile()) {
290 list[0].addElement(f);
291 list[1].addElement(base + files[j]);
292 } else {
293 dirStack.push(f);
294 baseStack.push(base + files[j] + "/");
295 }
296 }
297 }
298 }
299 }
300
301 return list;
302 }
303
304 protected void startUploadSession(int numFiles) throws IOException, MCRUploadException {
305 Hashtable request = new Hashtable();
306 request.put("method", "startUploadSession");
307 request.put("numFiles", String.valueOf(numFiles));
308 send(request);
309 }
310
311 protected void endUploadSession() throws IOException, MCRUploadException {
312 Hashtable request = new Hashtable();
313 request.put("method", "endUploadSession");
314 send(request);
315 }
316
317 protected void cancelUploadSession() throws IOException, MCRUploadException {
318 Hashtable request = new Hashtable();
319 request.put("method", "cancelUploadSession");
320 send(request);
321 }
322
323 protected Object send(Hashtable parameters) throws IOException, MCRUploadException {
324 parameters.put("uploadId", uid);
325 Hashtable response = getResponse(doPost(parameters));
326 return response.get("return");
327 }
328
329 protected InputStream doPost(Hashtable parameters) throws IOException {
330 String data = encodeParameters(parameters);
331 String mime = "application/x-www-form-urlencoded";
332
333 URLConnection connection = null;
334
335 try {
336 connection = new URL(url).openConnection();
337 } catch (MalformedURLException ignored) {
338 } // will never happen if base URL is ok
339
340 connection.setDoInput(true);
341 connection.setDoOutput(true);
342 connection.setUseCaches(false);
343 connection.setDefaultUseCaches(false);
344 connection.setRequestProperty("Content-type", mime);
345 connection.setRequestProperty("Content-length", String.valueOf(data.length()));
346
347 DataOutputStream out = new DataOutputStream(connection.getOutputStream());
348 out.writeBytes(data);
349 out.flush();
350 out.close();
351
352 return connection.getInputStream();
353 }
354
355 protected String encodeParameters(Hashtable parameters) {
356 StringBuffer data = new StringBuffer();
357 Enumeration e = parameters.keys();
358
359 while (e.hasMoreElements()) {
360 String name = (String) e.nextElement();
361 String value = (String) parameters.get(name);
362
363 try {
364 data.append(URLEncoder.encode(name, "UTF-8")).append("=").append(URLEncoder.encode(value, "UTF-8")).append("&");
365 } catch (UnsupportedEncodingException ex) {
366 System.out.println(ex.getClass().getName());
367 System.out.println(ex.getMessage());
368 throw new RuntimeException("Could not encode parameters");
369 }
370 }
371
372 data.setLength(data.length() - 1);
373
374 return data.toString();
375 }
376
377 protected Hashtable getResponse(InputStream is) throws IOException, MCRUploadException {
378 DataInputStream dis = new DataInputStream(is);
379 String mime = dis.readUTF();
380 byte[] dummy = new byte[0];
381
382 Hashtable response = new Hashtable();
383
384 while (dis.read(dummy, 0, 0) != -1) {
385 String key = dis.readUTF();
386 String clname = dis.readUTF();
387 Object value = null;
388
389 if (clname.equals(String.class.getName())) {
390 value = dis.readUTF();
391 } else if (clname.equals(Integer.class.getName())) {
392 value = new Integer(dis.readInt());
393 } else {
394 value = dis.readUTF();
395 }
396
397 response.put(key, value);
398 }
399
400 if (mime.equals("upload/exception")) {
401 String clname = (String) (response.get("clname"));
402 String message = (String) (response.get("message"));
403 String strace = (String) (response.get("strace"));
404 throw new MCRUploadException(clname, message, strace);
405 }
406
407 return response;
408 }
409
410 /** Calculates the MD5 checksum of the given local file * */
411 protected String buildMD5String(File file) throws Exception {
412 MessageDigest digest = MessageDigest.getInstance("MD5");
413
414 InputStream fis = new FileInputStream(file);
415 BufferedInputStream bis = new BufferedInputStream(fis, bufferSize);
416 DigestInputStream in = new DigestInputStream(bis, digest);
417
418 byte[] buffer = new byte[bufferSize];
419
420 while (in.read(buffer, 0, buffer.length) != -1)
421 ;
422
423 in.close();
424
425 byte[] bytes = digest.digest();
426 StringBuffer sb = new StringBuffer();
427
428 for (int i = 0; i < bytes.length; i++) {
429 String sValue = "0" + Integer.toHexString(bytes[i]);
430 sb.append(sValue.substring(sValue.length() - 2));
431 }
432
433 return sb.toString();
434 }
435 }