View Javadoc

1   /* ===================================================================
2    * Copyright 2002-06 SRI International.
3    * Released under the MOZILLA PUBLIC LICENSE Version 1.1
4    * which can be obtained at http://www.mozilla.org/MPL/MPL-1.1.html
5    * This software is distributed on an "AS IS"
6    * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
7    * See the License for the specific language governing rights and
8    * limitations under the License.
9    * =================================================================== */
10  package com.sri.common.controller;
11  
12  import com.jcorporate.expresso.core.controller.*;
13  import com.jcorporate.expresso.core.dataobjects.DataFieldMetaData;
14  import com.jcorporate.expresso.core.dataobjects.DataObjectMetaData;
15  import com.jcorporate.expresso.core.db.DBException;
16  import com.jcorporate.expresso.core.dbobj.DBField;
17  import com.jcorporate.expresso.core.dbobj.DBObject;
18  import com.jcorporate.expresso.core.dbobj.RowSecuredDBObject;
19  import com.jcorporate.expresso.core.misc.ConfigManager;
20  import com.jcorporate.expresso.core.registry.RequestRegistry;
21  import com.jcorporate.expresso.core.security.User;
22  import com.jcorporate.expresso.core.security.filters.Filter;
23  import com.jcorporate.expresso.core.security.filters.RawFilter;
24  import com.jcorporate.expresso.services.dbobj.Setup;
25  import com.sri.common.taglib.InputTag;
26  import com.sri.emo.controller.PermissionController;
27  import org.apache.struts.config.ActionConfig;
28  import org.apache.struts.config.ForwardConfig;
29  import org.dom4j.Document;
30  import org.dom4j.DocumentHelper;
31  import org.dom4j.Element;
32  import org.dom4j.io.HTMLWriter;
33  import org.dom4j.io.OutputFormat;
34  import org.dom4j.io.XMLWriter;
35  
36  import javax.servlet.http.HttpServletRequest;
37  import javax.servlet.http.HttpServletResponse;
38  import java.io.IOException;
39  import java.io.StringWriter;
40  import java.util.ArrayList;
41  import java.util.Iterator;
42  import java.util.List;
43  import java.util.Map;
44  
45  /***
46   * Superclass for all TI controllers, for common behavior like logging.
47   */
48  public abstract class AbstractDBController extends DBController {
49  
50      /***
51       * Footer constant.
52       */
53      public static final String FOOTER = "FOOTER";
54  
55      /***
56       * Column Constant.
57       */
58      public static final String COLUMN = "COLUMN";
59  
60      /***
61       * Row Block Constant.
62       */
63      public static final String ROW_BLOCK = "rowblock";
64  
65      /***
66       * Row Constant.
67       */
68      public static final String ROW = "ROW";
69  
70      /***
71       * Constant for warning list location.
72       */
73      public static final String WARNING_LIST = "WARNING_LIST";
74  
75      /***
76       * Constant for common footer handler.
77       */
78      public static final String COMMON_FOOTER_HANDLER = "commonFooter";
79  
80      /***
81       * Constant for common header handler.
82       */
83      public static final String COMMON_HEADER_HANDLER = "commonHeader";
84  
85      /***
86       * Limit number of chars that appear in display for a given field.
87       */
88      public static final int MAX_CHARS_OUTPUT = 75;
89  
90      /***
91       * Number of lines for a text area.
92       */
93      public static final int SINGLE_TEXTAREA_NUM_LINES = 6;
94  
95      /***
96       * Number of lines for multiple text area.
97       */
98      public static final int MULTIPLE_TEXTAREA_NUM_LINES = 4;
99  
100     /***
101      * Number of columns for a test area.
102      */
103     public static final int TEXTAREA_NUM_COLS = 40;
104 
105     /***
106      * Max Text Area Length constant.
107      */
108     public static final int MAX_TEXTAREA_LENGTH = 4000;
109 
110     /***
111      * Delimiter constant.
112      */
113     public static final String DELIMIT = "|";
114 
115     /***
116      * Jsp constant.
117      */
118     public static final String EDIT_GROUP_DISPLAY = "EDIT_GROUP_DISPLAY";
119 
120     /***
121      * Parameter to indicate 'embedded mode'.  Most likely it will just need
122      * to be passed around to the save states.  Necessary for 'view'
123      */
124     public static final String EMBEDDED_MODE = "Embedded";
125 
126 
127     /***
128      * Key where the RequestContainer is stored in the session.
129      */
130     public static final String REQUEST_CONTAINER = "RequestContainer";
131 
132     /***
133      * Key where the session container is stored.
134      */
135     public static final String SESSION_CONTAINER = "SessionContainer";
136 
137     /***
138      * Populates DBObject with any parameters which match field names.
139      * Will forward any validation errors to failureState
140      *
141      * @param object         the DBObject to check
142      * @param requiredFields an array of strings that are required to check
143      * @param failureState   the state to transition to on failure
144      * @param request        the <code>ExpressoRequest</code> object
145      * @param response       the <code>ExpressoResponse</code> object
146      * @return true if NO errors exist
147      * @throws ControllerException upon error.
148      * @throws DBException         upon database metadata access/validation error.
149      */
150     public boolean isValidAndPopulated(final DBObject object,
151                                        final String[] requiredFields, final String failureState,
152                                        final ExpressoRequest request, final ExpressoResponse response) throws
153             ControllerException,
154             DBException {
155         ErrorCollection errorCollection = new ErrorCollection();
156 
157         // no trimming in DefaultAutoElement
158 //        DefaultAutoElement.getAutoControllerElement().parseDBObject(request, object, errorCollection);
159         populateDBObject(object, request);
160 
161         // test for presence of required fields
162         try {
163             for (int i = 0;
164                  (requiredFields != null) && (i < requiredFields.length);
165                  i++) {
166 
167                 String value = object.getField(requiredFields[i]);
168 
169                 if ((value == null) || (value.length() == 0)) {
170                     // create error message
171                     String friendlyFieldName = null;
172 
173                     try {
174                         friendlyFieldName = object.getMetaData().getDescription(requiredFields[i]);
175                     } catch (Throwable e) {
176                         if (getLogger().isDebugEnabled()) {
177                             getLogger().debug("TODO: Internationalize strings", e);
178                         }
179                         /***
180                          * @todo ignore internationalization string missing errors for now; add later
181                          */
182                     }
183 
184                     if ((friendlyFieldName == null) || (friendlyFieldName.length() == 0)) {
185                         friendlyFieldName = requiredFields[i];
186                     }
187 
188                     errorCollection.addError("The " + friendlyFieldName
189                             + " field is required. Please complete this field.");
190                 }
191             }
192         } catch (DBException e) {
193             throw new ControllerException(e);
194         }
195 
196         if (errorCollection.size() > 0) {
197             response.saveErrors(errorCollection);
198             getLogger().debug("There were errors. Redirecting to " + failureState);
199 
200             response.setFormCache();
201 
202             if (failureState != null) {
203                 transition(failureState, request, response);
204             }
205         }
206 
207         return errorCollection.size() <= 0;
208     }
209 
210     /***
211      * fill in any fields found in request
212      */
213     public static void populateDBObject(DBObject obj, ExpressoRequest request) throws DBException {
214         String oneFieldName = null;
215         Map allParams = request.getAllParameters();
216         for (Iterator fieldIterator = obj.getMetaData().getFieldNamesList().iterator(); fieldIterator.hasNext();) {
217             oneFieldName = (String) fieldIterator.next();
218             DataFieldMetaData fieldMetadata = obj.getMetaData().getFieldMetadata(oneFieldName);
219             if (!fieldMetadata.isReadOnly() && !fieldMetadata.isVirtual() && allParams.containsKey(oneFieldName)) {
220                 String val = request.getParameter(oneFieldName);
221                 if (fieldMetadata.isQuotedTextType()) {
222                     val = val.trim();
223                 }
224                 obj.setField(oneFieldName, val);
225             }
226         }
227     }
228 
229     public static void trimAllTextFields(final DBObject object) throws DBException {
230         // trim every text field:
231         DataObjectMetaData meta = object.getMetaData();
232         Filter old = object.setFilterClass(new RawFilter());
233 
234         for (Iterator iterator = meta.getFieldNamesList().iterator(); iterator.hasNext();) {
235             String fname = (String) iterator.next();
236 
237             DBField dbfield = (DBField) meta.getFieldMetadata(fname);
238             if (!dbfield.isVirtual() && !object.isEmpty() && dbfield.isQuotedTextType()) {
239                 object.set(fname, object.getField(fname).trim());
240             }
241         }
242 
243         object.setFilterClass(old);
244     }
245 
246     /***
247      * Make sure output strings are not empty for tables, and insure that
248      * anchor tags are completed.
249      *
250      * @param item the current value for the table.
251      * @return Resulting String.
252      */
253     public static String str(String item) {
254         if ((item == null) || (item.length() == 0)) {
255             item = "&nbsp;";
256         }
257 
258         return item;
259     }
260 
261     /***
262      * Override to permit any logged in user.
263      *
264      * @param newState  The name of the new state that is being requested
265      * @param myRequest the <code>ExpressoRequest</code> object
266      * @return True if the state is permitted for this user, else false
267      * @throws ControllerException if another undefined error takes place while
268      *                             checking security.
269      */
270     public synchronized boolean stateAllowed(final String newState, final ExpressoRequest myRequest) throws
271             ControllerException {
272         try {
273             return (!User.isUnknownUser(RequestRegistry.getUser().getLoginName()));
274         } catch (DBException ex) {
275             throw new ControllerException("Error querying login name from User object");
276         }
277     }
278 
279     /***
280      * Output XML for given document.
281      *
282      * @param document The XML Document (Dom4J)
283      * @param request  The ExpressoRequest object.
284      * @throws IOException         upon io error.
285      * @throws ControllerException upon error getting to the ServletResponse.
286      */
287     public void outputXML(final Document document, final ExpressoRequest request)
288             throws IOException, ControllerException {
289         if (document == null) {
290             throw new ControllerException("null document");
291         }
292 
293         StringWriter sw = new StringWriter();
294 
295         //        OutputFormat format = OutputFormat.createCompactFormat();
296         OutputFormat format = OutputFormat.createPrettyPrint();
297         XMLWriter writer = new XMLWriter(sw, format);
298 
299         try {
300             writer.write(document);
301         } catch (IOException e) {
302             throw new ControllerException(e);
303         }
304 
305         // mime type of plain may make it easier for browser copying
306         String mime = "text/xml";
307 
308         if (request.getParameter("text") != null) {
309             mime = "text/plain";
310         }
311 
312         ServletControllerRequest servletRequest = (ServletControllerRequest) request;
313         HttpServletResponse httpResponse = (HttpServletResponse) servletRequest.getServletResponse();
314 
315         httpResponse.setContentType(mime);
316         httpResponse.getWriter().print(sw.toString());
317     }
318 
319     /***
320      * Get nicely formatted XML.
321      *
322      * @param elem The XML Element to pretty print.
323      * @return formatting String.
324      * @throws IOException upon printing exception.
325      */
326     public static String getPrettyXML(final Element elem) throws IOException {
327         elem.detach();
328 
329         Document document = DocumentHelper.createDocument();
330         document.setRootElement(elem);
331 
332         OutputFormat outformat = OutputFormat.createPrettyPrint();
333 
334         //outformat.setEncoding(aEncodingScheme);
335         StringWriter sw = new StringWriter();
336         HTMLWriter writer = new HTMLWriter(sw, outformat);
337         writer.write(document);
338         writer.flush();
339 
340         return "<pre>" + sw.toString().replaceAll("//<", "&lt;") + "</pre>";
341     }
342 
343     /***
344      * Template Method, allowing a subclass to into actions before
345      * any state in this controller is performed.
346      * For example, a common footer for web pages
347      * could be created here, so long as the web pages in question,
348      * with the common footer, were all in the same controller which overrides
349      * this method.
350      *
351      * @param nextState the state to be performed
352      * @param request   the request object
353      * @param response  the response object
354      * @throws ControllerException upon controller error.
355      */
356     protected void postPerform(final State nextState, final ExpressoRequest request,
357                                final ExpressoResponse response) throws ControllerException {
358         // is there a setting for a common header?
359         String header = Setup.getValueUnrequired(RequestRegistry.getDataContext(),
360                 COMMON_HEADER_HANDLER);
361 
362         if (header != null && !isEmbeddedMode(request)) {
363             IHeaderStateHandler handler = (IHeaderStateHandler) ConfigManager.getControllerFactory()
364                     .getController(header);
365             handler.runHeaderState((ControllerRequest) request, (ControllerResponse) response);
366         }
367 
368         // is there a setting for a common footer?
369         String footer = Setup.getValueUnrequired(request.getDataContext(),
370                 COMMON_FOOTER_HANDLER);
371 
372         if (footer != null && !isEmbeddedMode(request)) {
373             IFooterStateHandler handler = (IFooterStateHandler) ConfigManager.getControllerFactory()
374                     .getController(footer);
375             handler.runFooterState((ControllerRequest) request, (ControllerResponse) response);
376         }
377     }
378 
379     /***
380      * Checks the controller request for the 'embedded mode' parameter.  Embedded
381      * mode does not display any headers and footers and must be passed to other
382      * links and submit buttons.
383      *
384      * @param request ExpressoRequest the request parameters to query.
385      * @return boolean true if the embedded mode parameter is detected.
386      */
387     public static boolean isEmbeddedMode(final ExpressoRequest request) {
388         return (request.getParameter(EMBEDDED_MODE) != null);
389     }
390 
391     /***
392      * Adds the 'embedded mode' parameter to a transition.
393      *
394      * @param transitionToModify Transition
395      * @return Transition
396      */
397     public static Transition addEmbeddedParameter(final Transition transitionToModify) {
398         transitionToModify.addParam(EMBEDDED_MODE, "true");
399         return transitionToModify;
400     }
401 
402 
403     /***
404      * Add list to request scope.
405      *
406      * @param request the ExpressoRequest object.
407      * @return list of warnings.
408      */
409     protected List addWarningList(final ExpressoRequest request) {
410         List list;
411         list = new ArrayList();
412 
413         ServletControllerRequest servletRequest = (ServletControllerRequest) request;
414         HttpServletRequest hreq = (HttpServletRequest) servletRequest.getServletRequest();
415         hreq.setAttribute(WARNING_LIST, list);
416 
417         return list;
418     }
419 
420     /***
421      * Get list from request scope.
422      *
423      * @param request The ExpressoRequest object.
424      * @return list, or null
425      */
426     protected List getWarningList(final ExpressoRequest request) {
427         try {
428 
429             return (List) request.getSession().getAttribute(WARNING_LIST);
430         } catch (ControllerException ex) {
431             this.getLogger().error("Error getting warning list from session", ex);
432             throw new IllegalStateException("Error getting warning list from session:" + ex.getMessage()
433                     + ".  Stack Trace Logged");
434         }
435     }
436 
437     /***
438      * Adds warning to request scope, creating list if necessary.
439      *
440      * @param request The ExpressoRequest object.
441      * @param s       the warning string.
442      */
443     public void addWarning(final ExpressoRequest request, final String s) {
444         List list = getWarningList(request);
445 
446         if (list == null) {
447             list = addWarningList(request);
448         }
449 
450         list.add(s);
451     }
452 
453     /***
454      * Get a checkbox with the given name, label, default value, and
455      * optionally select it.
456      *
457      * @param name       The input's name.
458      * @param label      The Input's label.
459      * @param defVal     The default value.
460      * @param isSelected true if the checkbox is selected.
461      * @return and input of type checkbox
462      */
463     public static Input getCheckbox(final String name, final String label, final String defVal,
464                                     final boolean isSelected) {
465         Input checkbox = new Input(name, label);
466         checkbox.setType(InputTag.TYPE_CHECKBOX);
467         checkbox.setDefaultValue(defVal);
468 
469         if (isSelected) {
470             checkbox.setAttribute(Input.SELECTED, "Y");
471         }
472 
473         return checkbox;
474     }
475 
476     /***
477      * Get a text area with the given name and default value.
478      *
479      * @param name   Name for the text area
480      * @param defVal Default value for the text area
481      * @param rows   Rows for the text area
482      * @param cols   Columns for the text area
483      * @return a text area with specified values
484      */
485     public static Input getTextArea(final String name, final String defVal, final int rows, final int cols) {
486         Input textarea = new Input(name);
487         textarea.setType(InputTag.TYPE_TEXTAREA);
488         if (defVal != null) {
489             textarea.setDefaultValue(defVal);
490         }
491         textarea.setLines(rows);
492         textarea.setDisplayLength(cols);
493         textarea.setMaxLength(MAX_TEXTAREA_LENGTH);
494         return textarea;
495     }
496 
497     /***
498      * Get a text area with 4 rows and 40 columns.
499      *
500      * @param name   Name for the text area
501      * @param defVal Default value for the text area
502      * @return a text area with 4 lines and 40 columns
503      */
504     public static Input getTextArea(final String name, final String defVal) {
505         return getTextArea(name, defVal, MULTIPLE_TEXTAREA_NUM_LINES, TEXTAREA_NUM_COLS);
506     }
507 
508     /***
509      * Look up the forward used for 'state' param, suitable for use with setStyle().
510      *
511      * @param forward default forwarding path to use in case none is found
512      * @param state   final state for which forward is looked up
513      * @return forward for given state
514      * @see ExpressoResponse#setStyle(java.lang.String)
515      */
516     protected String getActionForwarding(final String forward, final String state) {
517         ActionConfig mapping = ConfigManager.getActionConfig(getClass()
518                 .getName(),
519                 state);
520 
521         String returnValue = forward;
522 
523         if (mapping != null) {
524             ForwardConfig actionforward = mapping.findForwardConfig(state);
525 
526             if (actionforward != null) {
527                 returnValue = actionforward.getPath();
528             }
529         }
530 
531         return returnValue;
532     }
533 
534     /***
535      * Ensure a non-empty string (for &lt;td columns), and also truncate as specified
536      * WHILE INSURING that an anchor link (&lt;a ) is not cut in half.
537      * (extend max if neccesary)
538      *
539      * @param item The item to parse
540      * @param max  ?
541      * @return the modified string.
542      */
543     public String strTrunc(String item, final int max) {
544         if ((item == null) || (item.length() == 0)) {
545             item = "&nbsp;";
546         } else {
547             int index = item.lastIndexOf("<a");
548 
549             if (index != -1 && item.lastIndexOf("</a>", index) == -1) {
550                 // need to add more; use recursion
551                 if (max >= item.length()) {
552                     // finished; cannot satisfy; just return item
553                     if (getLogger().isDebugEnabled()) {
554                         getLogger().debug("cannot satisfy anchor line for : '" + item
555                                 + "' ; returning all that we had.");
556                     }
557                 } else {
558                     item = strTrunc(item, max * 2);
559                 }
560             }
561         }
562 
563         return item;
564     }
565 
566     /***
567      * Retrieve the permission transition for the row secured dbobject.
568      *
569      * @param obj RowSecuredDBObject the object to get the transition for.
570      * @return Transition the transition for permissions.
571      */
572     public Transition getPermsTrans(final RowSecuredDBObject obj) {
573         Transition trans = new Transition("permit", PermissionController.class, PermissionController.PROMPT_EDIT_PERMS);
574         trans.addParam(PermissionController.KEY_FIELD_VALUES, obj.getKey());
575         trans.addParam(PermissionController.OBJ_TYPE, obj.getClass().getName());
576         return trans;
577     }
578 
579     /***
580      * Template Method, allowing a subclass to do an action after any state
581      * in this controller is performed.
582      * <p>This implementation initializes any request-level services if needed.
583      *
584      * @param nextState the state to be performed
585      * @param request   the request object
586      * @param response  the response object
587      * @throws ControllerException
588      */
589     protected void prePerform(final State nextState, final ExpressoRequest request,
590                               final ExpressoResponse response) throws ControllerException {
591         this.getSchemaInstance(); // todo is this necessary?
592     }
593 }