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 }