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    }