75e7978e77dc786d96737b4fa936c9eb4d050c2d
[mir.git] / source / mircoders / localizer / basic / MirBasicPostingSessionHandler.java
1 /*
2  * Copyright (C) 2001, 2002 The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
6  * Mir is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Mir is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Mir; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * In addition, as a special exception, The Mir-coders gives permission to link
21  * the code of this program with  any library licensed under the Apache Software License,
22  * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
23  * (or with modified versions of the above that use the same license as the above),
24  * and distribute linked combinations including the two.  You must obey the
25  * GNU General Public License in all respects for all of the code used other than
26  * the above mentioned libraries.  If you modify this file, you may extend this
27  * exception to your version of the file, but you are not obligated to do so.
28  * If you do not wish to do so, delete this exception statement from your version.
29  */
30 package mircoders.localizer.basic;
31
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.FilenameFilter;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.HashMap;
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Random;
44
45 import mir.config.MirPropertiesConfiguration;
46 import mir.log.LoggerWrapper;
47 import mir.session.Request;
48 import mir.session.Response;
49 import mir.session.Session;
50 import mir.session.SessionExc;
51 import mir.session.SessionFailure;
52 import mir.session.SessionHandler;
53 import mir.session.UploadedFile;
54 import mir.session.ValidationError;
55 import mir.storage.Database;
56 import mir.util.ExceptionFunctions;
57 import mir.util.FileFunctions;
58 import mircoders.global.MirGlobal;
59 import mircoders.media.UnsupportedMediaTypeExc;
60
61 /**
62  * Extensible handler for open postings.
63  * Behaviour can be altered by overriding methods.
64  */
65 public abstract class MirBasicPostingSessionHandler implements SessionHandler {
66   protected static LoggerWrapper logger = new LoggerWrapper("Localizer.OpenPosting");
67   protected MirPropertiesConfiguration configuration = MirPropertiesConfiguration.instance();;
68
69   /** Previously uploaded files */
70   protected List attachments;
71   /** counter to generate unique field names for uploaded files */
72   protected int uploadedFileIndex = 0;
73
74   private String normalResponseGenerator;
75   private String dupeResponseGenerator;
76   private String unsupportedMediaTypeResponseGenerator;
77   private String finalResponseGenerator;
78
79   private boolean persistentUploadedFiles;
80
81   public MirBasicPostingSessionHandler(boolean aPersistentUploadedFiles) {
82     attachments = new ArrayList();
83     persistentUploadedFiles = aPersistentUploadedFiles;
84   }
85
86   protected void setNormalResponseGenerator(String aGenerator) {
87     normalResponseGenerator = aGenerator;
88   }
89
90   protected void setResponseGenerators(String aNormalResponseGenerator,
91         String aDupeResponseGenerator, String anUnsupportedMediaTypeResponseGenerator,
92         String aFinalResponseGenerator) {
93     setNormalResponseGenerator(aNormalResponseGenerator);
94     dupeResponseGenerator = aDupeResponseGenerator;
95     unsupportedMediaTypeResponseGenerator = anUnsupportedMediaTypeResponseGenerator;
96     finalResponseGenerator = aFinalResponseGenerator;
97   }
98
99   public void processRequest(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
100     if (MirGlobal.abuse().getOpenPostingDisabled()) {
101       makeOpenPostingDisabledResponse(aRequest, aSession, aResponse);
102       aSession.terminate();
103     }
104     else {
105       if (aSession.getAttribute("initialRequest") == null) {
106         initialRequest(aRequest, aSession, aResponse);
107         aSession.setAttribute("initialRequest", "no");
108       }
109       else {
110         subsequentRequest(aRequest, aSession, aResponse);
111       }
112     }
113   };
114
115   protected void initialRequest(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
116     initializeSession(aRequest, aSession);
117     initializeResponseData(aRequest, aSession, aResponse);
118     makeInitialResponse(aRequest, aSession, aResponse);
119   }
120
121   protected void processAttachments(Request aRequest, Session aSession, Response aResponse) {
122     Iterator i = attachments.iterator();
123     while (i.hasNext()) {
124       Attachment attachment = (Attachment) i.next();
125       try{
126         processAttachment(aRequest, aSession, attachment);
127       }
128       catch (Throwable t) {
129         try {
130           processAttachmentError(aRequest, aSession, attachment, t);
131         }
132         catch (Throwable u) {
133         }
134       }
135     }
136   }
137
138   public void subsequentRequest(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
139     try {
140       try {
141         List validationErrors = new ArrayList();
142
143         preprocessPreviousAttachments(aRequest, aSession);
144         preProcessNewAttachments(aRequest, aSession);
145
146         if (!shouldProcessRequest(aRequest, aSession, validationErrors)) {
147           initializeResponseData(aRequest, aSession, aResponse);
148           makeResponse(aRequest, aSession, aResponse, validationErrors);
149         }
150         else {
151           preProcessRequest(aRequest, aSession);
152
153           processAttachments(aRequest, aSession, aResponse);
154           postProcessRequest(aRequest, aSession);
155           initializeResponseData(aRequest, aSession, aResponse);
156           makeFinalResponse(aRequest, aSession, aResponse);
157           aSession.terminate();
158         }
159       }
160       catch (Throwable t) {
161         initializeResponseData(aRequest, aSession, aResponse);
162         makeErrorResponse(aRequest, aSession, aResponse, t);
163         aSession.terminate();
164       }
165     }
166     catch (Throwable t) {
167       aSession.terminate();
168
169       throw new SessionFailure(t);
170     }
171   }
172
173   /**
174    * Initializes a session.
175    * This may happen in the case of a new session being initiated, but also
176    * when an older session gets re-initiated after a session timeout.
177    */
178   protected void initializeSession(Request aRequest, Session aSession) throws SessionExc, SessionFailure {
179     if (MirGlobal.abuse().getOpenPostingPassword()) {
180       String password = (String) aSession.getAttribute("password");
181       if (password==null) {
182         password = generateOnetimePassword();
183         aSession.setAttribute("password", password);
184       }
185     }
186     else {
187       aSession.deleteAttribute("password");
188     }
189
190     aSession.setAttribute("referer", aRequest.getHeader("Referer"));
191   }
192
193   /**
194    * Called every time a response is being prepared.
195    * This may be at the initial response, or on a subsequent one.
196    */
197   protected void initializeResponseData(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
198     int nrMediaItems = configuration.getInt("ServletModule.OpenIndy.DefaultMediaUploadItems", 5);
199
200     if (aSession.getAttribute("nrmediaitems")!=null) {
201       nrMediaItems = ((Integer) aSession.getAttribute("nrmediaitems")).intValue();
202     }
203
204     try {
205       nrMediaItems = Math.min(configuration.getInt("ServletModule.OpenIndy.MaxMediaUploadItems"), Integer.parseInt(aRequest.getParameter("nrmediaitems")));
206     }
207     catch (Throwable t) {
208     }
209
210     aSession.setAttribute("nrmediaitems", new Integer(nrMediaItems));
211
212     List mediaItems = new ArrayList();
213     for (int i=1; i<=nrMediaItems; i++) {
214       mediaItems.add(new Integer(i));
215     }
216
217     aResponse.setResponseValue("nrmediaitems", new Integer(nrMediaItems));
218     aResponse.setResponseValue("mediaitems", mediaItems);
219     aResponse.setResponseValue("password", aSession.getAttribute("password"));
220     aResponse.setResponseValue("referer", aSession.getAttribute("referer"));
221     aResponse.setResponseValue("errors", null);
222
223     if (configuration.getBoolean("Localizer.OpenSession.AllowFTPUploads", false)) {
224       aResponse.setResponseValue("ftpfiles",
225           FileFunctions.getDirectoryContentsAsList(configuration.getFile("Localizer.OpenSession.FTPDirectory"),
226               new FilenameFilter() {
227                 public boolean accept(File aDir, String aName) {
228                   return !(new File(aDir, aName).isDirectory());
229                 }
230           }));
231     }
232     else {
233       aResponse.setResponseValue("ftpfiles", null);
234     }
235
236     initializeAttachmentResponseData(aRequest, aSession, aResponse);
237   }
238
239   /**
240    * Process possible changes to previously uploaded files
241    */
242   protected void initializeAttachmentResponseData(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
243     List result = new ArrayList();
244     if (persistentUploadedFiles) {
245       Iterator i = attachments.iterator();
246       while (i.hasNext()) {
247         Attachment attachment = (Attachment) i.next();
248         Map attachmentData = new HashMap();
249         attachmentData.putAll(attachment.getAllAttributes());
250         attachmentData.put("fieldname", attachment.getFieldName());
251         attachmentData.put("filename", attachment.getFileName());
252         result.add(attachmentData);
253       }
254     }
255
256     aResponse.setResponseValue("attachments", result);
257   }
258
259   /**
260    * Process possible changes to previously uploaded files
261    */
262   protected void preprocessPreviousAttachments(Request aRequest, Session aSession) throws SessionExc, SessionFailure {
263     synchronized (attachments) {
264       List previouslyUploadedFiles = new ArrayList(attachments);
265       attachments.clear();
266
267       if (persistentUploadedFiles) {
268         Iterator i = previouslyUploadedFiles.iterator();
269         while (i.hasNext()) {
270           Attachment uploadedFile = (Attachment) i.next();
271           if (!(aRequest.getParameter(uploadedFile.getFieldName()+"_cancel")!=null)) {
272             addAttachment(aRequest, aSession, uploadedFile, uploadedFile.getFieldName());
273           }
274         }
275       }
276     }
277   }
278
279   protected void addAttachment(Request aRequest, Session aSession, Attachment anAttachment, String aFieldName) throws SessionExc, SessionFailure {
280     List parameters = aRequest.getPrefixedParameterNames(aFieldName+"_");
281     Iterator j = parameters.iterator();
282     while (j.hasNext()) {
283       String parameter = ((String) j.next());
284       anAttachment.setAttribute(
285           parameter.substring(aFieldName.length()+1),
286           aRequest.getParameter(parameter));
287     }
288     attachments.add(anAttachment);
289   }
290
291   public void preprocessNewAttachment(Request aRequest, Session aSession, UploadedFile aFile) throws SessionExc, SessionFailure {
292       Attachment uploadedFile =
293           new Attachment(aFile, aFile.getFileName(), "attachment"+ ++uploadedFileIndex, aFile.getContentType());
294
295       addAttachment(aRequest, aSession, uploadedFile, aFile.getFieldName());
296   }
297
298
299   /**
300    * Process newly uploaded files
301    */
302   protected void preProcessNewAttachments(Request aRequest, Session aSession) throws SessionExc, SessionFailure {
303     Iterator i = aRequest.getUploadedFiles().iterator();
304     while (i.hasNext()) {
305       preprocessNewAttachment(aRequest, aSession, (UploadedFile) i.next());
306     }
307
308     if (configuration.getBoolean("Localizer.OpenSession.AllowFTPUploads", false)) {
309       File FTPDirectory = configuration.getFile("Localizer.OpenSession.FTPDirectory");
310
311       List ftpUploads = aRequest.getPrefixedParameterNames("ftpupload");
312       i = ftpUploads.iterator();
313       while (i.hasNext()) {
314         final String fieldName = (String) i.next();
315
316         if (fieldName.indexOf("_")<0) {
317           final String fileName = aRequest.getParameter(fieldName);
318
319           if (fileName!=null && fileName.trim().length()>0) {
320             final File sourceFile = new File(FTPDirectory, fileName);
321
322             if (sourceFile.getParentFile().equals(FTPDirectory)) {
323               preprocessNewAttachment(aRequest, aSession, new UploadedFile() {
324                 public void writeToFile(File aFile) throws SessionFailure {
325                   try {
326                     FileFunctions.move(sourceFile, aFile);
327                   }
328                   catch (IOException e) {
329                     throw new SessionFailure(e);
330                   }
331                 }
332
333                 public InputStream getInputStream() throws SessionFailure {
334                   try {
335                     return new FileInputStream(sourceFile);
336                   }
337                   catch (IOException e) {
338                     throw new SessionFailure(e);
339                   }
340                 }
341
342                 public String getFileName() {
343                   return fileName;
344                 }
345
346                 public String getFieldName() {
347                   return fieldName;
348                 }
349
350                 public String getContentType() {
351                   return null;
352                 }
353               });
354             }
355           }
356         }
357       }
358     }
359   }
360
361   protected void makeInitialResponse(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
362     aResponse.setResponseGenerator(normalResponseGenerator);
363   };
364
365   protected void makeResponse(Request aRequest, Session aSession, Response aResponse, List anErrors) throws SessionExc, SessionFailure {
366     aResponse.setResponseValue("errors", anErrors);
367     aResponse.setResponseGenerator(normalResponseGenerator);
368   };
369
370   protected void makeFinalResponse(Request aRequest, Session aSession, Response aResponse) throws SessionExc, SessionFailure {
371     aResponse.setResponseGenerator(finalResponseGenerator);
372   };
373
374   protected void makeErrorResponse(Request aRequest, Session aSession, Response aResponse, Throwable anError) throws SessionExc, SessionFailure {
375     Throwable rootCause = ExceptionFunctions.traceCauseException(anError);
376
377     if (rootCause instanceof DuplicatePostingExc)
378       aResponse.setResponseGenerator(dupeResponseGenerator);
379     if (rootCause instanceof UnsupportedMediaTypeExc) {
380       aResponse.setResponseValue("mimetype", ((UnsupportedMediaTypeExc) rootCause).getMimeType());
381       aResponse.setResponseGenerator(unsupportedMediaTypeResponseGenerator);
382     }
383     else {
384       List errors = new ArrayList();
385       errors.add(new ValidationError("", "general.unexpectederror",
386           new Object[] {anError.getMessage()}));
387       makeResponse(aRequest, aSession, aResponse, errors);
388     }
389   };
390
391   protected void makeOpenPostingDisabledResponse(Request aRequest, Session aSession, Response aResponse) {
392     aResponse.setResponseGenerator(configuration.getString("ServletModule.OpenIndy.PostingDisabledTemplate"));
393   }
394
395   /**
396    *
397    */
398   protected void preProcessRequest(Request aRequest, Session aSession) throws SessionExc, SessionFailure {
399   };
400   public void processAttachment(Request aRequest, Session aSession, Attachment aFile) throws SessionExc, SessionFailure {
401   };
402   public void processAttachmentError(Request aRequest, Session aSession, Attachment aFile, Throwable anError) {
403   };
404   protected void postProcessRequest(Request aRequest, Session aSession) throws SessionExc, SessionFailure {
405   };
406
407   /**
408    * Determine whether the request shoudl be processed: that is, the validate,
409    * preprocess, postprocess methods should be called to perform validations,
410    * store data, etc
411    */
412   protected boolean shouldProcessRequest(Request aRequest, Session aSession, List aValidationErrors) throws SessionExc, SessionFailure {
413     if (aRequest.getParameter("post")==null)
414       return false;
415     else {
416       validate(aValidationErrors, aRequest, aSession);
417       return (aValidationErrors == null || aValidationErrors.size() == 0);
418     }
419   }
420
421   /**
422    * Method used to validate user input.
423    * Multiple {@link ValidationError}s may be added to the
424    * <code>aResults</code> parameter.
425    * The request is considered validated if, after calling this method,
426    * <code>aResults</code> is empty.
427    */
428   protected void validate(List aResults, Request aRequest, Session aSession) throws SessionExc, SessionFailure {
429     String password = (String) aSession.getAttribute("password");
430
431     if (password!=null) {
432       String submittedPassword= aRequest.getParameter("password").trim();
433
434       if (!password.equals(submittedPassword)) {
435         aResults.add(new ValidationError("password", "passwordmismatch"));
436       }
437     }
438   }
439
440
441   /**
442    * Method to generate a one-time password
443    *
444    * @return a password, to be used once
445    */
446   protected String generateOnetimePassword() {
447     Random r = new Random();
448     int random = r.nextInt();
449
450     long l = System.currentTimeMillis();
451
452     l = (l*l*l*l)/random;
453     if (l<0)
454       l = l * -1;
455
456     String returnString = ""+l;
457
458     return returnString.substring(5);
459   }
460
461   /**
462    * Method to filter the attributes and their values of a request
463    * based on the fields of a database object.
464    */
465   protected static final Map getIntersectingValues(Request aRequest, Database aStorage) throws SessionFailure {
466     Map result = new HashMap();
467
468     Iterator i = aStorage.getFieldNames().iterator();
469
470     while (i.hasNext()) {
471       String fieldName = (String) i.next();
472       Object value = aRequest.getParameter(fieldName);
473       if (value != null)
474         result.put(fieldName, value);
475     }
476
477     return result;
478   }
479
480   /**
481    * Exception to be thrown when an article or comment was already posted
482    */
483   protected static class DuplicatePostingExc extends SessionExc {
484     public DuplicatePostingExc(String aMessage) {
485       super(aMessage);
486     }
487   }
488
489   /**
490    * A file that has been attached to a session
491    */
492   protected static class Attachment implements UploadedFile {
493     private UploadedFile uploadedFile;
494     private String filename;
495     private String fieldName;
496     private String contentType;
497     private Map attributes;
498
499     public Attachment(UploadedFile anUploadedFile, String aFilename, String aFieldName, String aContentType) {
500       attributes = new HashMap();
501       filename = aFilename;
502       fieldName = aFieldName;
503       contentType = aContentType;
504       uploadedFile = anUploadedFile;
505     }
506
507     public void writeToFile(File aFile) throws SessionExc, SessionFailure {
508       uploadedFile.writeToFile(aFile);
509     }
510
511     public InputStream getInputStream() throws SessionExc, SessionFailure {
512       return uploadedFile.getInputStream();
513     }
514
515     public String getFileName() {
516       return filename;
517     }
518
519     public String getFieldName() {
520       return fieldName;
521     }
522
523     public String getContentType() {
524       return contentType;
525     }
526
527     public String getAttribute(String anAttribute) {
528       return (String) attributes.get(anAttribute);
529     }
530
531     public void setAttribute(String anAttribute, String aValue) {
532       attributes.put(anAttribute, aValue);
533     }
534
535     public Map getAllAttributes() {
536       return Collections.unmodifiableMap(attributes);
537     }
538   }
539 }
540