View Javadoc
1   /*
2    * $Source$
3    * $Revision$
4    *
5    * Copyright (C) 2000 William Chesters
6    *
7    * Part of Melati (http://melati.org), a framework for the rapid
8    * development of clean, maintainable web applications.
9    *
10   * Melati is free software; Permission is granted to copy, distribute
11   * and/or modify this software under the terms either:
12   *
13   * a) the GNU General Public License as published by the Free Software
14   *    Foundation; either version 2 of the License, or (at your option)
15   *    any later version,
16   *
17   *    or
18   *
19   * b) any version of the Melati Software License, as published
20   *    at http://melati.org
21   *
22   * You should have received a copy of the GNU General Public License and
23   * the Melati Software License along with this program;
24   * if not, write to the Free Software Foundation, Inc.,
25   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26   * GNU General Public License and visit http://melati.org to obtain the
27   * Melati Software License.
28   *
29   * Feel free to contact the Developers of Melati (http://melati.org),
30   * if you would like to work out a different arrangement than the options
31   * outlined here.  It is our intention to allow Melati to be used by as
32   * wide an audience as possible.
33   *
34   * This program is distributed in the hope that it will be useful,
35   * but WITHOUT ANY WARRANTY; without even the implied warranty of
36   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37   * GNU General Public License for more details.
38   *
39   * Contact details for copyright holder:
40   *
41   *     William Chesters <williamc At paneris.org>
42   *     http://paneris.org/~williamc
43   *     Obrechtstraat 114, 2517VX Den Haag, The Netherlands
44   */
45  
46  package org.melati.admin;
47  
48  import java.util.Vector;
49  import java.util.Enumeration;
50  
51  import javax.servlet.http.HttpServletResponse;
52  
53  import org.apache.commons.httpclient.Header;
54  import org.apache.commons.httpclient.HttpClient;
55  import org.apache.commons.httpclient.HttpMethod;
56  import org.apache.commons.httpclient.methods.GetMethod;
57  import org.apache.commons.httpclient.methods.HeadMethod;
58  import org.apache.commons.httpclient.methods.PostMethod;
59  import org.apache.commons.httpclient.methods.PutMethod;
60  import org.melati.Melati;
61  import org.melati.PoemContext;
62  import org.melati.servlet.FormDataAdaptor;
63  import org.melati.servlet.InvalidUsageException;
64  import org.melati.servlet.Form;
65  import org.melati.servlet.TemplateServlet;
66  import org.melati.template.ClassNameTempletLoader;
67  import org.melati.template.JSONMarkupLanguage;
68  import org.melati.template.MarkupLanguage;
69  import org.melati.template.ServletTemplateContext;
70  import org.melati.template.FormParameterException;
71  
72  import org.melati.poem.AccessToken;
73  import org.melati.poem.AccessPoemException;
74  import org.melati.poem.BaseFieldAttributes;
75  import org.melati.poem.Capability;
76  import org.melati.poem.Column;
77  import org.melati.poem.ColumnInfo;
78  import org.melati.poem.ColumnInfoTable;
79  import org.melati.poem.ColumnTypePoemType;
80  import org.melati.poem.Database;
81  import org.melati.poem.DeletionIntegrityPoemException;
82  import org.melati.poem.DisplayLevel;
83  import org.melati.poem.ExecutingSQLPoemException;
84  import org.melati.poem.Field;
85  import org.melati.poem.FieldAttributes;
86  import org.melati.poem.Initialiser;
87  import org.melati.poem.NoSuchColumnPoemException;
88  import org.melati.poem.Persistent;
89  import org.melati.poem.PoemException;
90  import org.melati.poem.PoemLocale;
91  import org.melati.poem.PoemThread;
92  import org.melati.poem.PoemTypeFactory;
93  import org.melati.poem.ReferencePoemType;
94  import org.melati.poem.Setting;
95  import org.melati.poem.Table;
96  import org.melati.poem.TableInfo;
97  import org.melati.poem.TableInfoTable;
98  import org.melati.poem.ValidationPoemException;
99  
100 import org.melati.util.CountedDumbPagedEnumeration;
101 import org.melati.poem.util.EnumUtils;
102 import org.melati.poem.util.MappedEnumeration;
103 import org.melati.util.MelatiBugMelatiException;
104 import org.melati.util.MelatiIOException;
105 import org.melati.util.MelatiRuntimeException;
106 
107 /**
108  * Melati template servlet for database administration.
109  * <p>
110  * This class defines {@link #doTemplateRequest(Melati, ServletTemplateContext)}
111  * and methods it calls to interpret requests, depending on the current table
112  * and object, if any.
113  * <p>
114  * Java methods with names ending "<code>Template</code>" and taking a
115  * {@link ServletTemplateContext} and {@link Melati} as arguments are generally
116  * called by {@link #doTemplateRequest(Melati, ServletTemplateContext)}) to
117  * implement corresponding request methods.
118  * {@link #modifyTemplate(ServletTemplateContext, Melati)} and associated
119  * methods are slight variations.
120  * <p>
121  * {@link #adminTemplate(String)} is called in all cases
122  * to return the template path. The name of the template is usually the same as
123  * the request method but not if the same template is used for more than one
124  * method or the template served depends on how request processing proceeds.
125  * <p>
126  * These methods are called to modify the context:
127  * <ul>
128  * <li>{@link #popupSelect(ServletTemplateContext, Melati)}</li>
129  * </ul>
130  * 
131  * TODO Review working of where clause for dates
132  * TODO Move Nav icons into PrimarySelect
133  * TODO Make Chooser JS agnostic
134  * TODO Make Navigation JS agnostic
135  * TODO Logout fails to work if remember me is ticked
136  * TODO Order by field f orders on fields troid, not field ordering
137  * TODO Enable non-paged output of selection by adding paged parameter to selectionTemplate
138  * FIXME primaryDisplayTable should not be static as this messes with DB switching
139  */
140 
141 public class Admin extends TemplateServlet {
142 
143   private static final long serialVersionUID = 8451412887121581757L;
144   private static String screenStylesheetURL = null;
145   private static String primaryDisplayTable = null;
146   private static String homepageURL = null;
147 
148   /**
149    * Creates a row for a table using field data in a template context.
150    */
151   protected static Persistent create(Table<?> table,
152       final ServletTemplateContext context) {
153     Persistent result = table.create(new Initialiser() {
154       public void init(Persistent object) throws AccessPoemException,
155           ValidationPoemException {
156         Form.extractFields(context, object);
157       }
158     });
159     result.postEdit(true);
160     return result;
161   }
162 
163   /**
164    * Return the resource path for an admin template.
165    */
166   protected static String adminTemplate(String name) {
167     return "org/melati/admin/" + name;
168   }
169 
170   /**
171    * @return a DSD for the database
172    */
173   protected static String dsdTemplate(ServletTemplateContext context) {
174     // Webmacro security prevents access from within template
175 
176     // Note: getPackage() can return null dependant upon
177     // the classloader so we have to chomp the class name
178 
179     String c = PoemThread.database().getClass().getName();
180     int dot = c.lastIndexOf('.');
181     String p = c.substring(0, dot);
182 
183     context.put("package", p);
184     return adminTemplate("DSD");
185   }
186 
187   /**
188    * @return primary select template
189    */
190    @SuppressWarnings({ "unchecked", "rawtypes" })
191   protected static String primarySelectTemplate(ServletTemplateContext context,
192       Melati melati) throws PoemException {
193     final Table table = melati.getTable();
194 
195     Field<Object> primaryCriterion;
196 
197     Column<?> column = table.primaryCriterionColumn();
198     if (column != null) {
199       String sea = context.getFormField("field_" + column.getName());
200       primaryCriterion = new Field<Object>(
201           sea == null ? 
202            (
203             melati.getObject() == null ? 
204                 null : column.getRaw(melati.getObject()))
205           : column.getType().rawOfString(sea), 
206           new BaseFieldAttributes(column,column.getType().withNullable(true)));
207     } else
208       primaryCriterion = null;
209 
210     context.put("primaryCriterion", primaryCriterion);
211     return adminTemplate("PrimarySelect");
212   }
213 
214 
215    /**
216     * Return template for a selection of records from a table.
217     */
218    protected static String selectionTemplate(ServletTemplateContext context,
219        Melati melati) {
220      String templateName = context.getFormField("template");
221      if (templateName == null) {
222        selection(context, melati, true);
223        return adminTemplate("Selection");
224      } else { 
225        selection(context, melati, false);
226        return adminTemplate(templateName);
227      }
228    }
229    
230    /**
231     * Select records based upon query parameters and return JSON template.
232     */
233    protected static String selectionJsonTemplate(ServletTemplateContext context,
234        Melati melati) {
235      MarkupLanguage ml = new JSONMarkupLanguage(
236          melati, 
237          ClassNameTempletLoader.getInstance(), 
238          PoemLocale.HERE);
239      melati.setMarkupLanguage(ml);
240      context.put("ml", ml); // an HTML ml has already been put into context
241      melati.setResponseContentType("application/json");
242      context.put("typeConverter", new PoemGvisTypeConverter());
243      selection(context, melati, false);
244      return adminTemplate("SelectionJSON");
245    }
246    
247 
248   /**
249    * Implements request to display a selection of records from a table in the
250    * right hand pane.
251    * 
252    * @return SelectionRight template.
253    */
254   protected static String selectionRightTemplate(
255       ServletTemplateContext context, Melati melati) {
256     selection(context, melati, true);
257     context.put("inRight", Boolean.TRUE);
258     return adminTemplate("Selection");
259   }
260 
261   /**
262    * Prepares the context in preparation for serving a template to view a
263    * selection of rows.
264    * <p>
265    * Any form fields in the context with names starting "field_" are assumed to
266    * hold values that must be matched in selected rows (if not null).
267    * <p>
268    * An encoding of the resulting whereClause is added to the context. "AND" is
269    * replaced by an &amp; separator.
270    * <p>
271    * A form field with name "start" is assumed to hold the number of the start
272    * row in the result set. The default is zero. The next 20 rows are selected
273    * and added as to the context as "results".
274    * 
275    * @return The prepared context.
276    */
277   @SuppressWarnings({ "unchecked", "rawtypes" })
278   protected static ServletTemplateContext selection(
279       ServletTemplateContext context, Melati melati, boolean paged) {
280     final Table<?> table = melati.getTable();
281 
282     final Database database = table.getDatabase();
283 
284     // sort out search criteria
285 
286     final Persistent criteria = table.newPersistent();
287 
288     Vector<Object> whereClause = new Vector<Object>();
289 
290     for (Enumeration<Column<?>> c = table.columns(); c.hasMoreElements();) {
291       Column<?> column = c.nextElement();
292       String name = "field_" + column.getName();
293       String fieldValue = Form.getFieldNulled(context, name);
294       if (fieldValue != null) {
295         column
296             .setRaw_unsafe(criteria, column.getType().rawOfString(fieldValue));
297 
298         // FIXME Needs to work for dates
299         whereClause.addElement(name + "=" + melati.urlEncode(fieldValue));
300       }
301     }
302 
303     context.put("whereClause", EnumUtils.concatenated("&", whereClause
304         .elements()));
305 
306     // sort out ordering 
307 
308     ReferencePoemType searchColumnsType = getSearchColumnsType(database, table);
309 
310     Vector<Object> orderings = new Vector<Object>();
311     Vector<Object> orderQuery = new Vector<Object>();
312 
313     
314     for (int o = 0; o <= table.displayColumnsCount(DisplayLevel.summary); ++o) {
315       String name = "field_order-" + o;
316       String orderColumnIDString = Form.getFieldNulled(context, name);
317       Integer orderColumnID;
318 
319       if (orderColumnIDString != null) {
320         String toggleName = "field_order-" + o + "-toggle";
321         String orderColumnSortOrderToggle = Form.getFieldNulled(context,
322             toggleName);
323         Boolean toggle = new Boolean(orderColumnSortOrderToggle);
324         orderColumnID = (Integer) searchColumnsType
325             .rawOfString(orderColumnIDString);
326         ColumnInfo info = (ColumnInfo) searchColumnsType
327             .cookedOfRaw(orderColumnID);
328         String desc = Boolean.TRUE.equals(info.getSortdescending()) ? (Boolean.TRUE
329             .equals(toggle) ? "" : " DESC")
330             : (Boolean.TRUE.equals(toggle) ? " DESC" : "");
331         orderings.addElement(database.quotedName(info.getName()) + desc);
332         orderQuery.addElement(name + "=" + orderColumnIDString);
333       }
334     }
335 
336     String orderBySQL = null;
337     if (orderings.elements().hasMoreElements())
338       orderBySQL = EnumUtils.concatenated(", ", orderings.elements());
339     context.put("orderClause", EnumUtils.concatenated("&", orderQuery
340         .elements()));
341 
342     context.put("inclusionColumns", inclusionColumns(context, table));      
343     
344     int start = 0;
345     String startString = Form.getFieldNulled(context, "start");
346     if (startString != null) {
347       try {
348         start = Math.max(0, Integer.parseInt(startString));
349       } catch (NumberFormatException e) {
350         throw new MelatiBugMelatiException("How did you get that in there?",
351             new FormParameterException("start", "Param must be an Integer"));
352       }
353     }
354     if (paged) { 
355       final int resultsPerPage = 20;
356       context.put("results", 
357                   new CountedDumbPagedEnumeration(
358                           table.selection(criteria, orderBySQL, false, false),
359                           start, resultsPerPage,
360                           table.cachedCount(criteria, false, false).count())
361       );
362     } else { 
363       context.put("results", table.selection(criteria, orderBySQL, false, false));
364     }
365     return context;
366   }
367 
368   static Vector<Column<?>> inclusionColumns(
369       ServletTemplateContext context, final Table<?> table) {
370     // find out which columns to return, default to summary columns
371     Vector<Column<?>> inclusionColumns = new Vector<Column<?>>();
372     for (int inc = 0; inc <= table.displayColumnsCount(DisplayLevel.record); ++inc) {
373       String formFieldName = "field_include-" + inc;
374       String includeColumnName = Form.getFieldNulled(context, formFieldName);
375       if (includeColumnName != null) {
376         Column<?> c;
377         try {
378           c = table.getColumn(includeColumnName);          
379           inclusionColumns.add(c);
380         } catch (NoSuchColumnPoemException e) { 
381           throw new IllegalArgumentException(
382               "Field named '" + includeColumnName + "' not found in table " + table.getName(),e);          
383         }
384       }
385     }
386     
387     if (inclusionColumns.size() == 0){ 
388       inclusionColumns = EnumUtils.vectorOf(table.getSummaryDisplayColumns());
389     }
390     return inclusionColumns;
391   }
392 
393   /**
394    * Implements the field search/selection request method.
395    */
396   protected static String popupSelectTemplate(ServletTemplateContext context,
397       Melati melati) throws PoemException {
398     popupSelect(context, melati);
399     return adminTemplate("PopupSelect");
400   }
401 
402   @SuppressWarnings({ "rawtypes", "unchecked"})
403   protected static ServletTemplateContext popupSelect(ServletTemplateContext context,
404       Melati melati) throws PoemException {
405     final Table table = melati.getTable();
406 
407     final Database database = table.getDatabase();
408 
409     // sort out search criteria
410 
411     final Persistent criteria = table.newPersistent();
412 
413     MappedEnumeration<Field<?>, Column<?>> criterias = new MappedEnumeration<Field<?>, Column<?>>(table
414         .getSearchCriterionColumns()) {
415       public Field<?> mapped(Column<?> c) {
416         return c.asField(criteria).withNullable(true);
417       }
418     };
419 
420     context.put("criteria", EnumUtils.vectorOf(criterias));
421     ReferencePoemType searchColumnsType = getSearchColumnsType(database, table);
422 
423     Vector<Field<?>> orderings = new Vector<Field<?>>();
424     // NOTE Order by searchable columns, this could be summary columns
425     Enumeration<Integer> searchColumns = searchColumnsType.possibleRaws();
426     int o = 0;
427     while (searchColumns.hasMoreElements()) {
428       String name = "order-" + o++;
429       orderings.addElement(new Field(searchColumns.nextElement(), 
430           new BaseFieldAttributes(name, searchColumnsType)));
431     }
432 
433     context.put("orderings", orderings);
434 
435     return context;
436   }
437 
438   /**
439    * @return a type whose whose possible members are the search columns of the table
440    */
441   private static ReferencePoemType getSearchColumnsType(final Database database, final Table<?> table) {
442     return new ReferencePoemType(database
443         .getColumnInfoTable(), false) {
444       protected Enumeration<Integer> _possibleRaws() {
445         return new MappedEnumeration<Integer, Column<?>>(table.getSearchCriterionColumns()) {
446           public Integer mapped(Column<?> column) {
447             return column.getColumnInfo().getTroid();
448           }
449         };
450       }
451     };
452   }
453 
454   /**
455    * @return primary select template
456    */
457   protected static String selectionWindowPrimarySelectTemplate(
458       ServletTemplateContext context, Melati melati) throws PoemException {
459     context.put("inPopup", Boolean.TRUE);
460     return primarySelectTemplate(context, melati);
461   }
462 
463   /**
464    * @return select template (a selection of records from a table)
465    */
466   protected static String selectionWindowSelectionTemplate(
467       ServletTemplateContext context, Melati melati) {
468     selection(context, melati, true);
469     context.put("inPopup", Boolean.TRUE);
470     return adminTemplate("Selection");
471   }
472 
473   /**
474    * Returns the Add template after placing the table and fields for the new row
475    * in the context using any field values already in the context.
476    * 
477    * If the table is a table meta data table, or a column meta data table then
478    * the appropriate extras are added to the co0ntext.
479    * 
480    * The Form does not normally contain values, but this could be used as a
481    * mechanism for providing defaults.
482    */
483   @SuppressWarnings("unchecked")
484   protected static String addTemplate(final ServletTemplateContext context,
485       Melati melati) throws PoemException {
486 
487     /*
488      * Enumeration fields = new MappedEnumeration(melati.getTable().columns()) {
489      * public Object mapped(Object column) { String stringValue =
490      * context.getForm("field_" + ((Column)column).getName()); Object value =
491      * null; if (stringValue != null) value =
492      * ((Column)column).getType().rawOfString(stringValue); return new
493      * Field(value, (Column)column); } }; context.put("fields", fields);
494      */
495 
496     // getDetailDisplayColumns() == columns() but could exclude some in theory
497     Enumeration<Column<?>> columns = melati.getTable().getDetailDisplayColumns();
498     Vector<Field<?>> fields = new Vector<Field<?>>();
499     while (columns.hasMoreElements()) {
500       Column<?> column = columns.nextElement();
501       String stringValue = context.getFormField("field_" + column.getName());
502       Object value = null;
503       if (stringValue != null)
504         value = column.getType().rawOfString(stringValue);
505       else if (column.getType() instanceof ColumnTypePoemType)
506         value = PoemTypeFactory.STRING.getCode();
507       fields.add(new Field<Object>(value, (FieldAttributes<Object>) column));
508     }
509     if (melati.getTable() instanceof TableInfoTable) {
510       Database database = melati.getDatabase();
511 
512       // Compose field for naming the TROID column: the display name and
513       // description are redundant, since they not used in the template
514 
515       final int troidHeight = 1;
516       final int troidWidth = 20;
517       Field<String> troidNameField = new Field<String>("id", new BaseFieldAttributes<String>(
518           "troidName", "Troid column", "Name of TROID column", database
519               .getColumnInfoTable().getNameColumn().getType(), troidWidth,
520           troidHeight, null, false, true, true));
521 
522       fields.add(troidNameField);
523     }
524     context.put("fields", fields.elements());
525     return adminTemplate("Add");
526   }
527 
528   /**
529    * Returns the Updated template after creating a new row using field data in
530    * the context.
531    * <p>
532    * If successful the template will say so while reloading according to the
533    * returnTarget and returnURL values from the Form in context.
534    */
535   protected static String addUpdateTemplate(ServletTemplateContext context,
536       Melati melati) throws PoemException {
537 
538     Persistent newPersistent = create(melati.getTable(), context);
539 
540     if (melati.getTable() instanceof TableInfoTable)
541       melati.getDatabase().addTableAndCommit((TableInfo) newPersistent,
542           context.getFormField("field_troidName"));
543     if (melati.getTable() instanceof ColumnInfoTable)
544       ((ColumnInfo) newPersistent).getTableinfo().actualTable()
545           .addColumnAndCommit((ColumnInfo) newPersistent);
546     melati.setPoemContext(new PoemContext(newPersistent));
547     melati.loadTableAndObject();
548     //context.put("object", newPersistent);
549     melati.getResponse().setStatus(201);
550     return adminTemplate("Updated");
551   }
552 
553   /**
554    * Returns the Updated template after modifying the current row according to
555    * field values in the context.
556    * <p>
557    * If successful the template will say so while reloading according to the
558    * returnTarget and returnURL values from the Form in context.
559    */
560   protected static String updateTemplate(ServletTemplateContext context,
561       Melati melati) throws PoemException {
562     Persistent object = melati.getObject();
563     object.preEdit();
564     Form.extractFields(context, object);
565     object.postEdit(false);
566     return adminTemplate("Updated");
567   }
568 
569   protected static String deleteTemplate(ServletTemplateContext context,
570       Melati melati) throws PoemException {
571     try {
572       if (melati.getTable().getName().equalsIgnoreCase("tableinfo")) {
573         TableInfo tableInfo = (TableInfo) melati.getObject();
574         melati.getDatabase().deleteTableAndCommit(tableInfo);
575       } else if (melati.getTable().getName().equalsIgnoreCase("columninfo")) {
576         ColumnInfo columnInfo = (ColumnInfo) melati.getObject();
577         columnInfo.getTableinfo().actualTable().deleteColumnAndCommit(
578             columnInfo);
579       } else
580         melati.getObject().delete();
581       melati.getPoemContext().setTroid(null);
582       melati.loadTableAndObject();
583       
584       return adminTemplate("Updated");
585     } catch (DeletionIntegrityPoemException e) {
586       context.put("references", e.references);
587       context.put("returnURL", melati.getSameURL() + "?action=Delete");
588       return adminTemplate("DeleteFailure");
589     }
590   }
591 
592   protected static String duplicateTemplate(ServletTemplateContext context,
593       Melati melati) throws PoemException {
594     Persistent dup = melati.getObject().duplicated();
595     Form.extractFields(context, dup);
596     try {
597       dup.getTable().create(dup);
598     } catch (ExecutingSQLPoemException e) {
599       throw new NonUniqueKeyValueAnticipatedException(e);
600     }
601     melati.setPoemContext(new PoemContext(dup));
602     melati.loadTableAndObject();
603     //context.put("object", dup);
604     return adminTemplate("Updated");
605   }
606 
607   /**
608    * Implements request method "Update".
609    * <p>
610    * Calls another method depending on the requested action.
611    * 
612    * @see #updateTemplate(ServletTemplateContext, Melati)
613    * @see #deleteTemplate(ServletTemplateContext, Melati)
614    * @see #duplicateTemplate(ServletTemplateContext, Melati)
615    */
616   protected static String modifyTemplate(ServletTemplateContext context,
617       Melati melati) throws FormParameterException {
618     String action = melati.getRequest().getParameter("action");
619     if ("Update".equals(action))
620       return updateTemplate(context, melati);
621     if ("Delete".equals(action))
622       return deleteTemplate(context, melati);
623     if ("Duplicate".equals(action))
624       return duplicateTemplate(context, melati);
625     else
626       throw new MelatiBugMelatiException("How did you get that in there?",
627           new FormParameterException(
628             "action", "Bad action from Edit: " + action));
629   }
630 
631   protected static String uploadTemplate(ServletTemplateContext context)
632       throws PoemException {
633     context.put("field", context.getFormField("field"));
634     return adminTemplate("Upload");
635   }
636 
637   /**
638    * Finished uploading.
639    * 
640    * If you want the system to display the file you need to set your melati-wide
641    * FormDataAdaptorFactory, in org.melati.MelatiConfig.properties, to something
642    * that returns a valid URL, for instance, PoemFileDataAdaptorFactory;
643    * (remember to set your UploadDir and UploadURL in the Setting table).
644    * 
645    * @param context
646    *          the {@link ServletTemplateContext} in use
647    * @return a template name
648    */
649 
650   protected static String uploadDoneTemplate(ServletTemplateContext context)
651       throws PoemException {
652     String field = context.getFormField("field");
653     context.put("field", field);
654     String url = context.getMultipartFormField("file").getDataURL();
655     if (url == null)
656       throw new NullUrlDataAdaptorException(context.getMultipartFormField("file").getFormDataAdaptor());
657     context.put("url", url);
658     return adminTemplate("UploadDone");
659   }
660 
661   static class NullUrlDataAdaptorException extends MelatiRuntimeException {
662     private static final long serialVersionUID = 1L;
663     private FormDataAdaptor fda;
664     NullUrlDataAdaptorException(FormDataAdaptor fda) { 
665       this.fda = fda;
666     }
667 
668     /** @return the message */
669     public String getMessage() {
670       return "The configured FormDataAdaptor (" + fda.getClass().getName() + ") returns a null URL.";
671     }
672   }
673 
674   protected static String setupTemplate(ServletTemplateContext context,
675       Melati melati) {
676     screenStylesheetURL = melati.getDatabase().getSettingTable().ensure(
677         Admin.class.getName() + ".ScreenStylesheetURL", "/blue.css",
678         "ScreenStylesheetURL",
679         "path to stylesheet, relative to melati-static, starting with a slash")
680         .getValue();
681     primaryDisplayTable = melati.getDatabase().getSettingTable().ensure(
682         Admin.class.getName() + ".PrimaryDisplayTable", "setting",
683         "PrimaryDisplayTable", "The default table to display").getValue();
684     Setting homepageURLSetting = melati.getDatabase().getSettingTable().ensure(
685         Admin.class.getName() + ".HomepageURL", "http://www.melati.org/",
686         "HomepageURL", "The home page for this database");
687     homepageURL = homepageURLSetting.getValue();
688     // HACK Not very satisfactory, but only to enable testing
689     //context.put("object", homepageURLSetting);
690     // If we wanted to get RESTful at this point, but it is a bit nasty as a UI
691     // melati.getResponse().setHeader("Location",melati.sameURLWith("action", ""));
692     
693     return adminTemplate("Updated");
694   }
695 
696   protected String doTemplateRequest(Melati melati,
697       ServletTemplateContext context) throws Exception {
698     if (melati.getMethod().equals("Proxy"))
699       return proxy(melati, context);
700     melati.getSession().setAttribute("generatedByMelatiClass",this.getClass().getName());
701 
702     context.put("admin", new AdminUtils(melati));
703     
704     String table = Form.getFieldNulled(context, "table");
705     if (table != null) {
706       if (!table.equals(melati.getTable().getName())) {
707         melati.getPoemContext().setTable(table);
708         melati.getPoemContext().setTroid(null);
709         melati.loadTableAndObject();
710       }
711     }
712     if (Form.getFieldNulled(context, "goto") != null)
713       melati.getResponse().sendRedirect(Form.getField(context, "goto", null));
714 
715     melati.setPassbackExceptionHandling();
716     melati.setResponseContentType("text/html");
717 
718     Capability admin = PoemThread.database().getCanAdminister();
719     AccessToken token = PoemThread.accessToken();
720     if (!token.givesCapability(admin))
721       throw new AccessPoemException(token, admin);
722 
723 
724     if (melati.getMethod() == null)
725       return adminTemplate("Main");
726     if (melati.getMethod().equals("blank"))
727       return adminTemplate("blank");
728     if (melati.getMethod().equals("setup"))
729       return setupTemplate(context, melati);
730     if (melati.getMethod().equals("Main"))
731       return adminTemplate("Main");
732     if (melati.getMethod().equals("Top"))
733       return adminTemplate("Top");
734     if (melati.getMethod().equals("Summary"))
735       return adminTemplate("Summary");
736     if (melati.getMethod().equals("UploadDone"))
737       return uploadDoneTemplate(context);
738     if (melati.getMethod().equals("Record"))
739       return adminTemplate("Record");
740     if (melati.getMethod().equals("Selection"))
741       return selectionTemplate(context, melati);
742     if (melati.getMethod().equals("SelectionJSON"))
743       return selectionJsonTemplate(context, melati);
744 
745     if (melati.getObject() != null) {
746       if (melati.getMethod().equals("Update"))
747         return modifyTemplate(context, melati);
748       if (melati.getObject() instanceof AdminSpecialised) {
749         String templateName = ((AdminSpecialised) melati.getObject())
750             .adminHandle(melati, melati.getMarkupLanguage());
751         if (templateName != null)
752           return templateName;
753       }
754     }
755 
756     if (melati.getTable() != null) {
757       if (melati.getMethod().equals("Tree"))
758         return adminTemplate("Tree");
759       if (melati.getMethod().equals("Bottom"))
760         return adminTemplate("Bottom");
761       if (melati.getMethod().equals("Table"))
762         return adminTemplate("Table");
763       if (melati.getMethod().equals("PrimarySelect"))
764         return primarySelectTemplate(context, melati);
765       if (melati.getMethod().equals("EditHeader"))
766         return adminTemplate("EditHeader");
767       if (melati.getMethod().equals("Edit"))
768         return adminTemplate("Edit");
769       if (melati.getMethod().equals("Upload"))
770         return uploadTemplate(context);
771 
772       if (melati.getMethod().equals("SelectionRight"))
773         return selectionRightTemplate(context, melati);
774       if (melati.getMethod().equals("Navigation"))
775         return adminTemplate("Navigation");
776       if (melati.getMethod().equals("PopUp"))
777         return popupSelectTemplate(context, melati);
778       if (melati.getMethod().equals("SelectionWindow"))
779         return adminTemplate("SelectionWindow");
780       if (melati.getMethod().equals("SelectionWindowPrimarySelect"))
781         return selectionWindowPrimarySelectTemplate(context, melati);
782       if (melati.getMethod().equals("SelectionWindowSelection"))
783         return selectionWindowSelectionTemplate(context, melati);
784       if (melati.getMethod().equals("Add"))
785         return addTemplate(context, melati);
786       if (melati.getMethod().equals("Created"))
787         return addUpdateTemplate(context, melati);
788     }
789     if (melati.getMethod().equals("DSD"))
790       return dsdTemplate(context);
791 
792     throw new InvalidUsageException(this, melati.getPoemContext());
793   }
794 
795   private String proxy(Melati melati, ServletTemplateContext context) {
796     if (melati.getSession().getAttribute("generatedByMelatiClass") == null)
797       throw new AnticipatedException("Only available from within an Admin generated page");
798     String method = melati.getRequest().getMethod();
799     String url =  melati.getRequest().getQueryString();
800     HttpServletResponse response = melati.getResponse();
801     HttpMethod httpMethod = null; 
802     try { 
803 
804       HttpClient client = new HttpClient();
805       if (method.equals("GET"))
806         httpMethod = new GetMethod(url);
807       else if (method.equals("POST"))
808         httpMethod = new PostMethod(url);
809       else if (method.equals("PUT"))
810         httpMethod = new PutMethod(url);
811       else if (method.equals("HEAD"))
812         httpMethod = new HeadMethod(url);
813       else
814         throw new RuntimeException("Unexpected method '" + method + "'");
815       try {
816         httpMethod.setFollowRedirects(true);
817         client.executeMethod(httpMethod);
818         for (Header h : httpMethod.getResponseHeaders()) { 
819           response.setHeader(h.getName(), h.getValue());
820         }
821         response.setStatus(httpMethod.getStatusCode());
822         response.setHeader("Cache-Control", "no-cache");
823         byte[] outputBytes = httpMethod.getResponseBody();
824         if (outputBytes != null) { 
825           response.setBufferSize(outputBytes.length);
826           response.getWriter().write(new String(outputBytes));
827           response.getWriter().flush();
828         }
829       } catch (Exception e) {
830         throw new MelatiIOException(e);
831       }
832     } finally {
833       if (httpMethod != null)
834         httpMethod.releaseConnection();
835     }
836     return null;
837   }
838 
839   /**
840    * @return the screenStylesheetURL
841    */
842   static String getScreenStylesheetURL() {
843     return screenStylesheetURL;
844   }
845 
846   /**
847    * @param screenStylesheetURL the screenStylesheetURL to set
848    */
849   static void setScreenStylesheetURL(String screenStylesheetURL) {
850     Admin.screenStylesheetURL = screenStylesheetURL;
851   }
852 
853   /**
854    * @return the primaryDisplayTable
855    */
856   static String getPrimaryDisplayTable() {
857     return primaryDisplayTable;
858   }
859 
860   /**
861    * @param primaryDisplayTable the primaryDisplayTable to set
862    */
863   static void setPrimaryDisplayTable(String primaryDisplayTable) {
864     Admin.primaryDisplayTable = primaryDisplayTable;
865   }
866 
867   /**
868    * @return the homepageURL
869    */
870   static String getHomepageURL() {
871     return homepageURL;
872   }
873 
874   /**
875    * @param homepageURL the homepageURL to set
876    */
877   static void setHomepageURL(String homepageURL) {
878     Admin.homepageURL = homepageURL;
879   }
880 }