1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 package org.melati.admin;
47
48 import java.util.Vector;
49 import java.util.Enumeration;
50
51 import org.melati.Melati;
52 import org.melati.servlet.FormDataAdaptor;
53 import org.melati.servlet.InvalidUsageException;
54 import org.melati.servlet.Form;
55 import org.melati.servlet.TemplateServlet;
56 import org.melati.template.ServletTemplateContext;
57 import org.melati.template.FormParameterException;
58
59 import org.melati.poem.AccessToken;
60 import org.melati.poem.AccessPoemException;
61 import org.melati.poem.BaseFieldAttributes;
62 import org.melati.poem.Capability;
63 import org.melati.poem.Column;
64 import org.melati.poem.ColumnInfo;
65 import org.melati.poem.ColumnInfoTable;
66 import org.melati.poem.ColumnTypePoemType;
67 import org.melati.poem.Database;
68 import org.melati.poem.DeletionIntegrityPoemException;
69 import org.melati.poem.DisplayLevel;
70 import org.melati.poem.ExecutingSQLPoemException;
71 import org.melati.poem.Field;
72 import org.melati.poem.Initialiser;
73 import org.melati.poem.Persistent;
74 import org.melati.poem.PoemException;
75 import org.melati.poem.PoemThread;
76 import org.melati.poem.PoemType;
77 import org.melati.poem.PoemTypeFactory;
78 import org.melati.poem.ReferencePoemType;
79 import org.melati.poem.Setting;
80 import org.melati.poem.Table;
81 import org.melati.poem.TableInfo;
82 import org.melati.poem.TableInfoTable;
83 import org.melati.poem.ValidationPoemException;
84
85 import org.melati.poem.util.EnumUtils;
86 import org.melati.poem.util.MappedEnumeration;
87 import org.melati.util.MelatiBugMelatiException;
88 import org.melati.util.MelatiRuntimeException;
89
90 /**
91 * Melati template servlet for database administration.
92 * <p>
93 * This class defines {@link #doTemplateRequest(Melati, ServletTemplateContext)}
94 * and methods it calls to interpret requests, depending on the current table
95 * and object, if any.
96 * <p>
97 * Java methods with names ending "<code>Template</code>" and taking a
98 * {@link ServletTemplateContext} and {@link Melati} as arguments are generally
99 * called by {@link #doTemplateRequest(Melati, ServletTemplateContext)}) to
100 * implement corresponding request methods.
101 * {@link #modifyTemplate(ServletTemplateContext, Melati)} and associated
102 * methods are slight variations.
103 * <p>
104 * {@link #adminTemplate(ServletTemplateContext, String)} is called in all cases
105 * to return the template path. The name of the template is usually the same as
106 * the request method but not if the same template is used for more than one
107 * method or the template served depends on how request processing proceeds.
108 * <p>
109 * These methods are called to modify the context:
110 * <ul>
111 * <li>{@link #popupSelect(ServletTemplateContext, Melati)}</li>
112 * <li>{@link #primarySelect(ServletTemplateContext, Melati)}</li>
113 * <li>{@link #selection(ServletTemplateContext, Melati)}</li>
114 * </ul>
115 *
116 * @todo Review working of where clause for dates
117 * @todo Move Nav icons into PrimarySelect
118 * @todo Make Top.login JS agnostic
119 * @todo Make Chooser JS agnostic
120 * @todo Make Navigation JS agnostic
121 * @todo Rename Left template to Table
122 * @FIXME primaryDisplayTable should not be static as this messes with DB switching
123 */
124
125 public class Admin extends TemplateServlet {
126 private static final long serialVersionUID = 1L;
127
128 private static String screenStylesheetURL = null;
129 private static String primaryDisplayTable = null;
130 private static String homepageURL = null;
131
132 /**
133 * Creates a row for a table using field data in a template context.
134 */
135 protected static Persistent create(Table table,
136 final ServletTemplateContext context) {
137 Persistent result = table.create(new Initialiser() {
138 public void init(Persistent object) throws AccessPoemException,
139 ValidationPoemException {
140 Form.extractFields(context, object);
141 }
142 });
143 result.postEdit(true);
144 return result;
145 }
146
147 /**
148 * Return the resource path for an admin template.
149 */
150 protected static final String adminTemplate(String name) {
151 return "org/melati/admin/" + name;
152 }
153
154 /**
155 * @return a DSD for the database
156 */
157 protected static String dsdTemplate(ServletTemplateContext context) {
158
159
160
161
162
163 String c = PoemThread.database().getClass().getName();
164 int dot = c.lastIndexOf('.');
165 String p = c.substring(0, dot);
166
167 context.put("package", p);
168 return adminTemplate("DSD");
169 }
170
171 /**
172 * @return primary select template
173 */
174 protected static String primarySelectTemplate(ServletTemplateContext context,
175 Melati melati) throws PoemException {
176 final Table table = melati.getTable();
177
178 Field primaryCriterion;
179
180 Column column = table.primaryCriterionColumn();
181 if (column != null) {
182 String sea = context.getForm("field_" + column.getName());
183 primaryCriterion = new Field(
184 sea == null ?
185 (
186 melati.getObject() == null ?
187 null : column.getRaw(melati.getObject()))
188 : column.getType().rawOfString(sea),
189 new BaseFieldAttributes(column,column.getType().withNullable(true)));
190 } else
191 primaryCriterion = null;
192
193 context.put("primaryCriterion", primaryCriterion);
194 return adminTemplate("PrimarySelect");
195 }
196
197
198 /**
199 * Return template for a selection of records from a table.
200 */
201 protected static String selectionTemplate(ServletTemplateContext context,
202 Melati melati) {
203 selection(context, melati);
204 return adminTemplate("Selection");
205 }
206
207 /**
208 * Implements request to display a selection of records from a table in the
209 * right hand pane.
210 *
211 * @return SelectionRight template.
212 */
213 protected static String selectionRightTemplate(
214 ServletTemplateContext context, Melati melati) {
215 selection(context, melati);
216 context.put("inRight", Boolean.TRUE);
217 return adminTemplate("Selection");
218 }
219
220 /**
221 * Modifies the context in preparation for serving a template to view a
222 * selection of rows.
223 * <p>
224 * Any form fields in the context with names starting "field_" are assumed to
225 * hold values that must be matched in selected rows (if not null).
226 * <p>
227 * An encoding of the resulting whereClause is added to the context. "AND" is
228 * replaced by an & separator.
229 * <p>
230 * The resulting orderClause is added to the context.
231 * <p>
232 * A form field with name "start" is assumed to hold the number of the start
233 * row in the result set. The default is zero. The next 20 rows are selected
234 * and added as to the context as "results".
235 *
236 * @return The modified context.
237 * @see #adminTemplate(ServletTemplateContext, String)
238 */
239 protected static ServletTemplateContext selection(
240 ServletTemplateContext context, Melati melati) {
241 final Table table = melati.getTable();
242
243 final Database database = table.getDatabase();
244
245
246
247 final Persistent criteria = table.newPersistent();
248
249 Vector whereClause = new Vector();
250
251 for (Enumeration c = table.columns(); c.hasMoreElements();) {
252 Column column = (Column) c.nextElement();
253 String name = "field_" + column.getName();
254 String fieldValue = Form.getFieldNulled(context, name);
255 if (fieldValue != null) {
256 column
257 .setRaw_unsafe(criteria, column.getType().rawOfString(fieldValue));
258
259
260 whereClause.addElement(name + "=" + melati.urlEncode(fieldValue));
261 }
262 }
263
264 context.put("whereClause", EnumUtils.concatenated("&", whereClause
265 .elements()));
266
267
268
269 PoemType searchColumnsType = getSearchColumnsType(database, table);
270
271 Vector orderings = new Vector();
272 Vector orderClause = new Vector();
273
274
275 for (int o = 1; o <= table.displayColumnsCount(DisplayLevel.summary); ++o) {
276 String name = "field_order-" + o;
277 String orderColumnIDString = Form.getFieldNulled(context, name);
278 Integer orderColumnID = null;
279
280 if (orderColumnIDString != null) {
281 String toggleName = "field_order-" + o + "-toggle";
282 String orderColumnSortOrderToggle = Form.getFieldNulled(context,
283 toggleName);
284 Boolean toggle = new Boolean(orderColumnSortOrderToggle);
285 orderColumnID = (Integer) searchColumnsType
286 .rawOfString(orderColumnIDString);
287 ColumnInfo info = (ColumnInfo) searchColumnsType
288 .cookedOfRaw(orderColumnID);
289 String desc = Boolean.TRUE.equals(info.getSortdescending()) ? (Boolean.TRUE
290 .equals(toggle) ? "" : " DESC")
291 : (Boolean.TRUE.equals(toggle) ? " DESC" : "");
292 orderings.addElement(database.quotedName(info.getName()) + desc);
293 orderClause.addElement(name + "=" + orderColumnIDString);
294 }
295 }
296
297 String orderBySQL = null;
298 if (orderings.elements().hasMoreElements())
299 orderBySQL = EnumUtils.concatenated(", ", orderings.elements());
300 context.put("orderClause", EnumUtils.concatenated("&", orderClause
301 .elements()));
302
303 int start = 0;
304 String startString = Form.getFieldNulled(context, "start");
305 if (startString != null) {
306 try {
307 start = Math.max(0, Integer.parseInt(startString));
308 } catch (NumberFormatException e) {
309 throw new MelatiBugMelatiException("How did you get that in there?",
310 new FormParameterException("start", "Param must be an Integer"));
311 }
312 }
313
314 final int resultsPerPage = 20;
315 context.put("results", table.selection(table.whereClause(criteria),
316 orderBySQL, false, start, resultsPerPage));
317
318 return context;
319 }
320
321 /**
322 * Implements the field search/selection request method.
323 */
324 protected static String popupSelectTemplate(ServletTemplateContext context,
325 Melati melati) throws PoemException {
326 popupSelect(context, melati);
327 return adminTemplate("PopupSelect");
328 }
329
330 protected static ServletTemplateContext popupSelect(ServletTemplateContext context,
331 Melati melati) throws PoemException {
332 final Table table = melati.getTable();
333
334 final Database database = table.getDatabase();
335
336
337
338 final Persistent criteria = table.newPersistent();
339
340 MappedEnumeration criterias = new MappedEnumeration(table
341 .getSearchCriterionColumns()) {
342 public Object mapped(Object c) {
343 return ((Column) c).asField(criteria).withNullable(true);
344 }
345 };
346
347 context.put("criteria", EnumUtils.vectorOf(criterias));
348 PoemType searchColumnsType = getSearchColumnsType(database, table);
349
350 Vector orderings = new Vector();
351
352 Enumeration searchColumns = searchColumnsType.possibleRaws();
353 int o = 0;
354 while (searchColumns.hasMoreElements()) {
355 String name = "order-" + o++;
356 orderings.addElement(new Field(searchColumns.nextElement(),
357 new BaseFieldAttributes(name, searchColumnsType)));
358 }
359
360 context.put("orderings", orderings);
361
362 return context;
363 }
364
365 /**
366 * @return a type whose whose possible members are the search columns of the table
367 */
368 private static PoemType getSearchColumnsType(final Database database, final Table table) {
369 PoemType searchColumnsType = new ReferencePoemType(database
370 .getColumnInfoTable(), false) {
371 protected Enumeration _possibleRaws() {
372 return new MappedEnumeration(table.getSearchCriterionColumns()) {
373 public Object mapped(Object column) {
374 return ((Column) column).getColumnInfo().getTroid();
375 }
376 };
377 }
378 };
379 return searchColumnsType;
380 }
381
382 /**
383 * @return primary select template
384 */
385 protected static String selectionWindowPrimarySelectTemplate(
386 ServletTemplateContext context, Melati melati) throws PoemException {
387 context.put("inPopup", Boolean.TRUE);
388 return primarySelectTemplate(context, melati);
389 }
390
391 /**
392 * @return select template (a selection of records from a table)
393 */
394 protected static String selectionWindowSelectionTemplate(
395 ServletTemplateContext context, Melati melati) {
396 selection(context, melati);
397 context.put("inPopup", Boolean.TRUE);
398 return adminTemplate("Selection");
399 }
400
401 /**
402 * Returns the Add template after placing the table and fields for the new row
403 * in the context using any field values already in the context.
404 *
405 * If the table is a table meta data table, or a column meta data table then
406 * the appropriate extras are added to the co0ntext.
407 *
408 * The Form does not normally contain values, but this could be used as a
409 * mechanism for providing defaults.
410 */
411 protected static String addTemplate(final ServletTemplateContext context,
412 Melati melati) throws PoemException {
413
414
415
416
417
418
419
420
421
422
423
424 Enumeration columns = melati.getTable().getDetailDisplayColumns();
425 Vector fields = new Vector();
426 while (columns.hasMoreElements()) {
427 Column column = (Column) columns.nextElement();
428 String stringValue = context.getForm("field_" + column.getName());
429 Object value = null;
430 if (stringValue != null)
431 value = column.getType().rawOfString(stringValue);
432 else if (column.getType() instanceof ColumnTypePoemType)
433 value = PoemTypeFactory.STRING.getCode();
434 fields.add(new Field(value, column));
435 }
436 if (melati.getTable() instanceof TableInfoTable) {
437 Database database = melati.getDatabase();
438
439
440
441
442 final int troidHeight = 1;
443 final int troidWidth = 20;
444 Field troidNameField = new Field("id", new BaseFieldAttributes(
445 "troidName", "Troid column", "Name of TROID column", database
446 .getColumnInfoTable().getNameColumn().getType(), troidWidth,
447 troidHeight, null, false, true, true));
448
449 fields.add(troidNameField);
450 }
451 context.put("fields", fields.elements());
452 return adminTemplate("Add");
453 }
454
455 /**
456 * Returns the Updated template after creating a new row using field data in
457 * the context.
458 * <p>
459 * If successful the template will say so while reloading according to the
460 * returnTarget and returnURL values from the Form in context.
461 */
462 protected static String addUpdateTemplate(ServletTemplateContext context,
463 Melati melati) throws PoemException {
464
465 Persistent newPersistent = create(melati.getTable(), context);
466
467 if (melati.getTable() instanceof TableInfoTable)
468 melati.getDatabase().addTableAndCommit((TableInfo) newPersistent,
469 context.getForm("field_troidName"));
470 if (melati.getTable() instanceof ColumnInfoTable)
471 ((ColumnInfo) newPersistent).getTableinfo().actualTable()
472 .addColumnAndCommit((ColumnInfo) newPersistent);
473
474 context.put("object", newPersistent);
475 return adminTemplate("Updated");
476 }
477
478 /**
479 * Returns the Updated template after modifying the current row according to
480 * field values in the context.
481 * <p>
482 * If successful the template will say so while reloading according to the
483 * returnTarget and returnURL values from the Form in context.
484 */
485 protected static String updateTemplate(ServletTemplateContext context,
486 Melati melati) throws PoemException {
487 Persistent object = melati.getObject();
488 object.preEdit();
489 Form.extractFields(context, object);
490 object.postEdit(false);
491 return adminTemplate("Updated");
492 }
493
494 protected static String deleteTemplate(ServletTemplateContext context,
495 Melati melati) throws PoemException {
496 try {
497 if (melati.getTable().getName().equals("tableinfo")) {
498 TableInfo tableInfo = (TableInfo) melati.getObject();
499 melati.getDatabase().deleteTableAndCommit(tableInfo);
500 } else if (melati.getTable().getName().equals("columninfo")) {
501 ColumnInfo columnInfo = (ColumnInfo) melati.getObject();
502 columnInfo.getTableinfo().actualTable().deleteColumnAndCommit(
503 columnInfo);
504 } else
505 melati.getObject().delete();
506
507 return adminTemplate("Updated");
508 } catch (DeletionIntegrityPoemException e) {
509 context.put("object", e.object);
510 context.put("references", e.references);
511 context.put("returnURL", melati.getSameURL() + "?action=Delete");
512 return adminTemplate("DeleteFailure");
513 }
514 }
515
516 protected static String duplicateTemplate(ServletTemplateContext context,
517 Melati melati) throws PoemException {
518 Persistent dup = melati.getObject().duplicated();
519 Form.extractFields(context, dup);
520 try {
521 dup.getTable().create(dup);
522 } catch (ExecutingSQLPoemException e) {
523 throw new NonUniqueKeyValueAnticipatedException(e);
524 }
525 context.put("object", dup);
526 return adminTemplate("Updated");
527 }
528
529 /**
530 * Implements request method "Update".
531 * <p>
532 * Calls another method depending on the requested action.
533 *
534 * @see #updateTemplate(ServletTemplateContext, Melati)
535 * @see #deleteTemplate(ServletTemplateContext, Melati)
536 * @see #duplicateTemplate(ServletTemplateContext, Melati)
537 */
538 protected static String modifyTemplate(ServletTemplateContext context,
539 Melati melati) throws FormParameterException {
540 String action = melati.getRequest().getParameter("action");
541 if ("Update".equals(action))
542 return updateTemplate(context, melati);
543 if ("Delete".equals(action))
544 return deleteTemplate(context, melati);
545 if ("Duplicate".equals(action))
546 return duplicateTemplate(context, melati);
547 else
548 throw new MelatiBugMelatiException("How did you get that in there?",
549 new FormParameterException(
550 "action", "Bad action from Edit: " + action));
551 }
552
553 protected static String uploadTemplate(ServletTemplateContext context)
554 throws PoemException {
555 context.put("field", context.getForm("field"));
556 return adminTemplate("Upload");
557 }
558
559 /**
560 * Finished uploading.
561 *
562 * If you want the system to display the file you need to set your melati-wide
563 * FormDataAdaptorFactory, in org.melati.MelatiConfig.properties, to something
564 * that returns a valid URL, for instance, PoemFileDataAdaptorFactory;
565 * (remember to set your UploadDir and UploadURL in the Setting table).
566 *
567 * @param context
568 * the {@link ServletTemplateContext} in use
569 * @return a template name
570 */
571
572 protected static String uploadDoneTemplate(ServletTemplateContext context)
573 throws PoemException {
574 String field = context.getForm("field");
575 context.put("field", field);
576 String url = "";
577 url = context.getMultipartForm("file").getDataURL();
578 if (url == null)
579 throw new NullUrlDataAdaptorException(context.getMultipartForm("file").getFormDataAdaptor());
580 context.put("url", url);
581 return adminTemplate("UploadDone");
582 }
583
584 static class NullUrlDataAdaptorException extends MelatiRuntimeException {
585 private static final long serialVersionUID = 1L;
586 private FormDataAdaptor fda;
587 NullUrlDataAdaptorException(FormDataAdaptor fda) {
588 this.fda = fda;
589 }
590
591 /** @return the message */
592 public String getMessage() {
593 return "The configured FormDataAdaptor (" + fda.getClass().getName() + ") returns a null URL.";
594 }
595 }
596
597 protected static String setupTemplate(ServletTemplateContext context,
598 Melati melati) {
599 screenStylesheetURL = melati.getDatabase().getSettingTable().ensure(
600 Admin.class.getName() + ".ScreenStylesheetURL", "/blue.css",
601 "ScreenStylesheetURL",
602 "path to stylesheet, relative to melati-static, starting with a slash")
603 .getValue();
604 primaryDisplayTable = melati.getDatabase().getSettingTable().ensure(
605 Admin.class.getName() + ".PrimaryDisplayTable", "setting",
606 "PrimaryDisplayTable", "The default table to display").getValue();
607 Setting homepageURLSetting = melati.getDatabase().getSettingTable().ensure(
608 Admin.class.getName() + ".HomepageURL", "http://www.melati.org/",
609 "HomepageURL", "The home page for this database");
610 homepageURL = homepageURLSetting.getValue();
611
612 context.put("object", homepageURLSetting);
613 return adminTemplate("Updated");
614 }
615
616 protected String doTemplateRequest(Melati melati,
617 ServletTemplateContext context) throws Exception {
618
619 if (Form.getField(context, "goto", null) != null)
620 melati.getResponse().sendRedirect(Form.getField(context, "goto", null));
621
622 melati.setPassbackExceptionHandling();
623 melati.setResponseContentType("text/html");
624
625 Capability admin = PoemThread.database().getCanAdminister();
626 AccessToken token = PoemThread.accessToken();
627 if (!token.givesCapability(admin))
628 throw new AccessPoemException(token, admin);
629
630 context.put("admin", new AdminUtils(melati));
631
632 if (melati.getMethod().equals("blank"))
633 return adminTemplate("blank");
634 if (melati.getMethod().equals("setup"))
635 return setupTemplate(context, melati);
636 if (melati.getMethod().equals("Main"))
637 return adminTemplate("Main");
638 if (melati.getMethod().equals("Top"))
639 return adminTemplate("Top");
640 if (melati.getMethod().equals("UploadDone"))
641 return uploadDoneTemplate(context);
642 if (melati.getMethod().equals("Record"))
643 return adminTemplate("Record");
644 if (melati.getMethod().equals("Selection"))
645 return selectionTemplate(context, melati);
646
647 if (melati.getObject() != null) {
648 if (melati.getMethod().equals("Update"))
649 return modifyTemplate(context, melati);
650 if (melati.getObject() instanceof AdminSpecialised) {
651 String templateName = ((AdminSpecialised) melati.getObject())
652 .adminHandle(melati, melati.getMarkupLanguage());
653 if (templateName != null)
654 return templateName;
655 }
656 }
657
658 if (melati.getTable() != null) {
659 if (melati.getMethod().equals("Tree"))
660 return adminTemplate("Tree");
661 if (melati.getMethod().equals("Bottom"))
662 return adminTemplate("Bottom");
663 if (melati.getMethod().equals("Table"))
664 return adminTemplate("Table");
665 if (melati.getMethod().equals("PrimarySelect"))
666 return primarySelectTemplate(context, melati);
667 if (melati.getMethod().equals("EditHeader"))
668 return adminTemplate("EditHeader");
669 if (melati.getMethod().equals("Edit"))
670 return adminTemplate("Edit");
671 if (melati.getMethod().equals("Upload"))
672 return uploadTemplate(context);
673
674 if (melati.getMethod().equals("SelectionRight"))
675 return selectionRightTemplate(context, melati);
676 if (melati.getMethod().equals("Navigation"))
677 return adminTemplate("Navigation");
678 if (melati.getMethod().equals("PopUp"))
679 return popupSelectTemplate(context, melati);
680 if (melati.getMethod().equals("SelectionWindow"))
681 return adminTemplate("SelectionWindow");
682 if (melati.getMethod().equals("SelectionWindowPrimarySelect"))
683 return selectionWindowPrimarySelectTemplate(context, melati);
684 if (melati.getMethod().equals("SelectionWindowSelection"))
685 return selectionWindowSelectionTemplate(context, melati);
686 if (melati.getMethod().equals("Add"))
687 return addTemplate(context, melati);
688 if (melati.getMethod().equals("Created"))
689 return addUpdateTemplate(context, melati);
690 }
691 if (melati.getMethod().equals("DSD"))
692 return dsdTemplate(context);
693
694 throw new InvalidUsageException(this, melati.getPoemContext());
695 }
696
697 /**
698 * @return the screenStylesheetURL
699 */
700 static String getScreenStylesheetURL() {
701 return screenStylesheetURL;
702 }
703
704 /**
705 * @param screenStylesheetURL the screenStylesheetURL to set
706 */
707 static void setScreenStylesheetURL(String screenStylesheetURL) {
708 Admin.screenStylesheetURL = screenStylesheetURL;
709 }
710
711 /**
712 * @return the primaryDisplayTable
713 */
714 static String getPrimaryDisplayTable() {
715 return primaryDisplayTable;
716 }
717
718 /**
719 * @param primaryDisplayTable the primaryDisplayTable to set
720 */
721 static void setPrimaryDisplayTable(String primaryDisplayTable) {
722 Admin.primaryDisplayTable = primaryDisplayTable;
723 }
724
725 /**
726 * @return the homepageURL
727 */
728 static String getHomepageURL() {
729 return homepageURL;
730 }
731
732 /**
733 * @param homepageURL the homepageURL to set
734 */
735 static void setHomepageURL(String homepageURL) {
736 Admin.homepageURL = homepageURL;
737 }
738 }