001    /*
002     * 
003     * $Revision: 15202 $ $Date: 2009-05-15 17:00:44 +0200 (Fri, 15 May 2009) $
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.ByteArrayOutputStream;
027    import java.io.DataOutputStream;
028    import java.io.IOException;
029    import java.io.InputStream;
030    import java.io.OutputStream;
031    import java.net.InetAddress;
032    import java.net.InetSocketAddress;
033    import java.net.ServerSocket;
034    import java.net.Socket;
035    import java.net.URLDecoder;
036    import java.util.Enumeration;
037    import java.util.HashMap;
038    import java.util.Iterator;
039    import java.util.List;
040    import java.util.Map;
041    import java.util.Set;
042    import java.util.zip.ZipEntry;
043    import java.util.zip.ZipInputStream;
044    
045    import javax.servlet.ServletException;
046    import javax.servlet.http.HttpServletRequest;
047    import javax.servlet.http.HttpServletResponse;
048    
049    import org.apache.commons.fileupload.FileItem;
050    import org.apache.log4j.Logger;
051    import org.jdom.Element;
052    import org.mycore.common.MCRCache;
053    import org.mycore.common.MCRConfiguration;
054    import org.mycore.common.MCRConfigurationException;
055    import org.mycore.common.MCRException;
056    import org.mycore.common.MCRSession;
057    import org.mycore.common.MCRSessionMgr;
058    import org.mycore.frontend.MCRWebsiteWriteProtection;
059    import org.mycore.frontend.editor.MCREditorSubmission;
060    import org.mycore.frontend.editor.MCRRequestParameters;
061    import org.mycore.frontend.servlets.MCRServlet;
062    import org.mycore.frontend.servlets.MCRServletJob;
063    
064    /**
065     * This servlet implements the server side of communication with the upload
066     * applet. The content of the uploaded files are handled by a MCRUploadHandler
067     * subclass.
068     * 
069     * @author Frank Lützenkirchen
070     * @author Harald Richter
071     * @author Thomas Scheffler (yagee)
072     * @version $Revision: 15202 $ $Date: 2009-05-15 17:00:44 +0200 (Fri, 15 May 2009) $
073     * @see org.mycore.frontend.fileupload.MCRUploadHandler
074     */
075    public final class MCRUploadServlet extends MCRServlet implements Runnable {
076        private static final long serialVersionUID = -1452027276006825044L;
077    
078        static String serverIP;
079    
080        static int serverPort;
081    
082        static ServerSocket server;
083    
084        static Logger LOGGER = Logger.getLogger(MCRUploadServlet.class);
085    
086        static MCRCache sessionIDs = new MCRCache(100, "UploadServlet Upload sessions");
087    
088        final static int bufferSize = 65536; // 64 KByte
089    
090        public synchronized void init() throws ServletException {
091            super.init();
092    
093            if (server != null)
094                return; // already inited?
095    
096            try {
097                //query property directly (not via getBaseURL()), saves a stalled MCRSession
098                String host = new java.net.URL(MCRConfiguration.instance().getString("MCR.baseurl")).getHost();
099                String defIP = InetAddress.getByName(host).getHostAddress();
100                int defPort = 22471; // my birthday is the default upload port
101                serverIP = MCRConfiguration.instance().getString("MCR.FileUpload.IP", defIP);
102                serverPort = MCRConfiguration.instance().getInt("MCR.FileUpload.Port", defPort);
103    
104                LOGGER.info("Opening server socket: ip=" + serverIP + " port=" + serverPort);
105                server = new ServerSocket();
106                server.setReceiveBufferSize(Math.max(server.getReceiveBufferSize(), bufferSize));
107                server.bind(new InetSocketAddress(serverIP, serverPort));
108                LOGGER.debug("Server socket successfully created.");
109                LOGGER.debug("Server receive buffer size is " + server.getReceiveBufferSize());
110    
111                // Starts separate thread that will receive and store file content
112                new Thread(this).start();
113            } catch (Exception ex) {
114                if (ex instanceof MCRException) {
115                    throw (MCRException) ex;
116                }
117    
118                String msg = "Exception while opening file upload server port";
119                throw new MCRConfigurationException(msg, ex);
120            }
121        }
122    
123        public void finalize() throws Throwable {
124            try {
125                if (server != null) {
126                    server.close();
127                }
128            } catch (Exception ignored) {
129            }
130    
131            super.finalize();
132        }
133    
134        public void handleUpload(Socket socket) {
135            LOGGER.info("Client applet connected to socket now.");
136    
137            try {
138                DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
139                ZipInputStream zis = new ZipInputStream(socket.getInputStream());
140    
141                LOGGER.debug("Constructed ZipInputStream and DataOutputStream, receiving data soon.");
142    
143                ZipEntry ze = zis.getNextEntry();
144                String path = URLDecoder.decode(ze.getName(), "UTF-8");
145                String extra = new String(ze.getExtra(), "UTF-8");
146                String[] parts = extra.split("\\s");
147                String md5 = parts[0];
148                long length = Long.parseLong(parts[1]);
149                String uploadId = parts[2];
150    
151                LOGGER.debug("Received uploadID = " + uploadId);
152                LOGGER.debug("Received path     = " + path);
153                LOGGER.debug("Received length   = " + length);
154                LOGGER.debug("Received md5      = " + md5);
155    
156                // Remember current MCRSession for upload
157                String sessionID = (String) (sessionIDs.get(uploadId));
158                if (sessionID != null) {
159                    MCRSession session = MCRSessionMgr.getSession(sessionID);
160                    if (session != null)
161                        MCRSessionMgr.setCurrentSession(session);
162                }
163                //start transaction after MCRSession is initialized
164                long numBytesStored = MCRUploadHandlerManager.getHandler(uploadId).receiveFile(path, zis, length, md5);
165    
166                LOGGER.debug("Stored incoming file content");
167    
168                dos.writeLong(numBytesStored);
169                dos.flush();
170    
171                LOGGER.info("File transfer completed successfully.");
172            } catch (Exception ex) {
173                LOGGER.error("Exception while receiving and storing file content from applet:", ex);
174            } finally {
175                try {
176                    if (socket != null) {
177                        socket.close();
178                    }
179                } catch (Exception ignored) {
180                }
181    
182                LOGGER.debug("Socket closed.");
183            }
184        }
185    
186        public void run() {
187            LOGGER.debug("Server socket thread startet.");
188    
189            while (true) {
190                LOGGER.debug("Listening on " + serverIP + ":" + serverPort + " for incoming data...");
191    
192                try {
193                    final Socket socket = server.accept();
194                    socket.setReceiveBufferSize(bufferSize);
195                    socket.setSendBufferSize(bufferSize);
196                    LOGGER.debug("Socket receive buffer size is " + socket.getReceiveBufferSize());
197    
198                    Thread handlerThread = new Thread(new Runnable() {
199                        public void run() {
200                            handleUpload(socket);
201                        }
202                    });
203                    handlerThread.start();
204                } catch (Exception ex) {
205                    LOGGER.error("Exception while waiting for client connect to socket", ex);
206                }
207            }
208        }
209    
210        public void doGetPost(MCRServletJob job) throws Exception {
211            try {
212                invokeMethod(job);
213            } catch (Exception ex) {
214                LOGGER.error("Error while handling FileUpload", ex);
215                sendException(job.getResponse(), ex);
216                throw ex;
217            }
218        }
219    
220        protected void invokeMethod(MCRServletJob job) throws Exception {
221            HttpServletRequest req = job.getRequest();
222            HttpServletResponse res = job.getResponse();
223    
224            MCREditorSubmission sub = (MCREditorSubmission) (req.getAttribute("MCREditorSubmission"));
225            MCRRequestParameters parms = (sub == null ? new MCRRequestParameters(req) : sub.getParameters());
226    
227            String method = parms.getParameter("method");
228    
229            if (method.equals("redirecturl")) {
230                String uploadId = req.getParameter("uploadId");
231                MCRUploadHandler handler = MCRUploadHandlerManager.getHandler(uploadId);
232                String url = handler.getRedirectURL();
233                LOGGER.info("UploadServlet redirect to " + url);
234                res.sendRedirect(res.encodeRedirectURL(url));
235                handler.unregister();
236    
237                return;
238            }
239    
240            if (MCRWebsiteWriteProtection.isActive()) {
241                sendException(res, new MCRException("System is currently in read-only mode"));
242                return;
243            }
244    
245            MCRSession session = MCRSessionMgr.getCurrentSession();
246            if (method.equals("startUploadSession")) {
247                String uploadId = req.getParameter("uploadId");
248                if (uploadId == null) {
249                    StringBuilder sb = new StringBuilder("'uploadId' was not submitted. Request:\n");
250                    @SuppressWarnings("unchecked")
251                    Enumeration<String> headerNames = req.getHeaderNames();
252                    while (headerNames.hasMoreElements()) {
253                        String header = headerNames.nextElement();
254                        @SuppressWarnings("unchecked")
255                        Enumeration<String> headerValues = req.getHeaders(header);
256                        while (headerValues.hasMoreElements()) {
257                            sb.append(header).append(':').append(headerValues.nextElement()).append('\n');
258                        }
259                    }
260                    throw new MCRException(sb.toString());
261                }
262                int numFiles = Integer.parseInt(req.getParameter("numFiles"));
263                MCRUploadHandlerManager.getHandler(uploadId).startUpload(numFiles);
264    
265                // Remember current MCRSession for upload
266                String sessionID = session.getID();
267                sessionIDs.put(uploadId, sessionID);
268    
269                LOGGER.info("UploadServlet start session " + uploadId);
270                sendResponse(res, "OK");
271            } else if (method.equals("uploadFile")) {
272                final String path = req.getParameter("path");
273    
274                LOGGER.info("UploadServlet uploading " + path);
275    
276                String uploadId = req.getParameter("uploadId");
277                String md5 = req.getParameter("md5");
278                long length = Long.parseLong(req.getParameter("length"));
279    
280                LOGGER.debug("UploadServlet receives file " + path + " (" + length + " bytes)" + " with md5 " + md5);
281    
282                if (!MCRUploadHandlerManager.getHandler(uploadId).acceptFile(path, md5, length)) {
283                    LOGGER.debug("Skipping file " + path);
284                    sendResponse(res, "skip file");
285    
286                    return;
287                }
288    
289                LOGGER.debug("Applet wants to send content of file " + path);
290                sendResponse(res, serverIP + ":" + serverPort);
291            } else if (method.equals("ping")) {
292                sendResponse(res, "pong");
293            } else if (method.equals("endUploadSession")) {
294                String uploadId = req.getParameter("uploadId");
295                MCRUploadHandler uploadHandler = MCRUploadHandlerManager.getHandler(uploadId);
296                uploadHandler.finishUpload();
297                sendResponse(res, "OK");
298            } else if (method.equals("cancelUploadSession")) {
299                String uploadId = req.getParameter("uploadId");
300                MCRUploadHandler uploadHandler = MCRUploadHandlerManager.getHandler(uploadId);
301                uploadHandler.cancelUpload();
302                sendResponse(res, "OK");
303            } else if (method.equals("formBasedUpload")) {
304                String uploadId = parms.getParameter("uploadId");
305                MCRUploadHandler handler = MCRUploadHandlerManager.getHandler(uploadId);
306    
307                LOGGER.info("UploadHandler form based file upload for ID " + uploadId);
308    
309                Element uploads = sub.getXML().getRootElement();
310                List paths = uploads.getChildren("path");
311    
312                if ((paths != null) && (paths.size() >= 0)) {
313                    int numFiles = paths.size();
314                    LOGGER.info("UploadHandler uploading " + numFiles + " file(s)");
315                    handler.startUpload(numFiles);
316                    session.commitTransaction();
317    
318                    for (int i = 0; i < numFiles; i++) {
319                        FileItem item = sub.getFile(paths.get(i));
320    
321                        InputStream in = item.getInputStream();
322                        String path = ((Element) (paths.get(i))).getTextTrim();
323                        path = getFileName(path);
324    
325                        LOGGER.info("UploadServlet uploading " + path);
326                        if (path.toLowerCase().endsWith(".zip")) {
327                            uploadZipFile(handler, in);
328                        } else
329                            handler.receiveFile(path, in, 0, null);
330                    }
331                    session.beginTransaction();
332    
333                    handler.finishUpload();
334                }
335    
336                String url = handler.getRedirectURL();
337                LOGGER.info("UploadServlet redirect to " + url);
338                res.sendRedirect(res.encodeRedirectURL(url));
339                handler.unregister();
340    
341                return;
342            }
343        }
344    
345        private void uploadZipFile(MCRUploadHandler handler, InputStream in) throws IOException, Exception {
346            ZipInputStream zis = new ZipInputStream(in);
347            ZipEntry entry = null;
348            while ((entry = zis.getNextEntry()) != null) {
349                String path = entry.getName();
350    
351                // Convert absolute paths to relative paths:
352                int pos = path.indexOf(":");
353                if (pos >= 0)
354                    path = path.substring(pos + 1);
355                while (path.startsWith("\\") || path.startsWith("/"))
356                    path = path.substring(1);
357    
358                if (entry.isDirectory()) {
359                    LOGGER.debug("UploadServlet skipping ZIP entry " + path + ", is a directory");
360                    continue;
361                }
362    
363                LOGGER.info("UploadServlet unpacking ZIP entry " + path);
364                handler.receiveFile(path, zis, 0, null);
365            }
366        }
367    
368        protected String getFileName(String path) {
369            int pos = Math.max(path.lastIndexOf('\\'), path.lastIndexOf("/"));
370            return path.substring(pos + 1);
371        }
372    
373        protected void sendException(HttpServletResponse res, Exception ex) throws Exception {
374            HashMap response = new HashMap();
375            response.put("clname", ex.getClass().getName());
376            response.put("strace", MCRException.getStackTraceAsString(ex));
377    
378            if (ex.getLocalizedMessage() != null) {
379                response.put("message", ex.getLocalizedMessage());
380            }
381    
382            sendResponse(res, "upload/exception", response);
383        }
384    
385        protected void sendResponse(HttpServletResponse res, Object value) throws Exception {
386            HashMap parameters = new HashMap();
387    
388            if (value != null) {
389                parameters.put("return", value);
390            }
391    
392            sendResponse(res, "upload/response", parameters);
393        }
394    
395        protected void sendResponse(HttpServletResponse res, String mime, Map parameters) throws Exception {
396            ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
397            DataOutputStream dos = new DataOutputStream(baos);
398            Set entries = parameters.entrySet();
399    
400            dos.writeUTF(mime);
401    
402            Iterator it = entries.iterator();
403    
404            while (it.hasNext()) {
405                Map.Entry entry = (Map.Entry) it.next();
406    
407                // write out key
408                dos.writeUTF(entry.getKey().toString());
409    
410                // write out class
411                dos.writeUTF(entry.getValue().getClass().getName());
412    
413                if (entry.getValue().getClass() == Integer.class) {
414                    dos.writeInt(((Integer) (entry.getValue())).intValue());
415                } else {
416                    dos.writeUTF(entry.getValue().toString());
417                }
418            }
419    
420            dos.close();
421    
422            byte[] response = baos.toByteArray();
423            res.setContentType(mime);
424            res.setContentLength(response.length);
425    
426            OutputStream out = res.getOutputStream();
427            out.write(response, 0, response.length);
428            out.close();
429            res.flushBuffer();
430        }
431    }