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