View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.widget;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.CoreApiServiceLocator;
20  import org.kuali.rice.core.web.format.Formatter;
21  import org.kuali.rice.krad.service.KRADServiceLocator;
22  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
23  import org.kuali.rice.krad.service.ModuleService;
24  import org.kuali.rice.krad.uif.UifConstants;
25  import org.kuali.rice.krad.uif.UifParameters;
26  import org.kuali.rice.krad.uif.component.BindingInfo;
27  import org.kuali.rice.krad.uif.component.Component;
28  import org.kuali.rice.krad.uif.element.Action;
29  import org.kuali.rice.krad.uif.element.Link;
30  import org.kuali.rice.krad.uif.field.DataField;
31  import org.kuali.rice.krad.uif.field.InputField;
32  import org.kuali.rice.krad.uif.util.LookupInquiryUtils;
33  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
34  import org.kuali.rice.krad.uif.util.ViewModelUtils;
35  import org.kuali.rice.krad.uif.view.View;
36  import org.kuali.rice.krad.util.UrlFactory;
37  
38  import java.security.GeneralSecurityException;
39  import java.util.HashMap;
40  import java.util.List;
41  import java.util.Map;
42  import java.util.Map.Entry;
43  import java.util.Properties;
44  
45  /**
46   * Widget for rendering an Inquiry link or DirectInquiry action field
47   *
48   * <p>
49   * The inquiry widget will render a button for the field value when
50   * that field is editable. When read only the widget will create a link on the display value.
51   * It points to the associated inquiry view for the field. The inquiry can be configured to point to a certain
52   * {@code InquiryView}, or the framework will attempt to associate the
53   * field with a inquiry based on its metadata (in particular its
54   * relationships in the model).
55   * </p>
56   *
57   * @author Kuali Rice Team (rice.collab@kuali.org)
58   */
59  public class Inquiry extends WidgetBase {
60      private static final long serialVersionUID = -2154388007867302901L;
61      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(Inquiry.class);
62  
63      public static final String INQUIRY_TITLE_PREFIX = "title.inquiry.url.value.prependtext";
64  
65      private String baseInquiryUrl;
66  
67      private String dataObjectClassName;
68      private String viewName;
69  
70      private Map<String, String> inquiryParameters;
71  
72      private Link inquiryLink;
73  
74      private Action directInquiryAction;
75      private boolean enableDirectInquiry;
76  
77      private boolean adjustInquiryParameters;
78      private BindingInfo fieldBindingInfo;
79  
80      public Inquiry() {
81          super();
82  
83          inquiryParameters = new HashMap<String, String>();
84      }
85  
86      /**
87       * @see org.kuali.rice.krad.uif.widget.WidgetBase#performFinalize(org.kuali.rice.krad.uif.view.View,
88       *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
89       */
90      @Override
91      public void performFinalize(View view, Object model, Component parent) {
92          super.performFinalize(view, model, parent);
93  
94          if (!isRender()) {
95              return;
96          }
97  
98          // set render to false until we find an inquiry class
99          setRender(false);
100 
101         // Do checks for inquiry when read only
102         if (isReadOnly()) {
103             if (StringUtils.isBlank(((DataField) parent).getBindingInfo().getBindingPath())) {
104                 return;
105             }
106 
107             // check if field value is null, if so no inquiry
108             Object propertyValue = ObjectPropertyUtils.getPropertyValue(model,
109                     ((DataField) parent).getBindingInfo().getBindingPath());
110             if ((propertyValue == null) || StringUtils.isBlank(propertyValue.toString())) {
111                 return;
112             }
113         }
114 
115         // Do checks for direct inquiry when editable
116         if (!isReadOnly() && parent instanceof InputField) {
117             if (!enableDirectInquiry) {
118                 return;
119             }
120 
121             // determine whether inquiry parameters will need adjusted
122             if (StringUtils.isBlank(getDataObjectClassName())
123                     || (getInquiryParameters() == null)
124                     || getInquiryParameters().isEmpty()) {
125                 // if inquiry parameters not given, they will not be adjusted by super
126                 adjustInquiryParameters = true;
127                 fieldBindingInfo = ((InputField) parent).getBindingInfo();
128             }
129         }
130 
131         setupLink(view, model, (DataField) parent);
132     }
133 
134     /**
135      * Get parent object and field name and build the inquiry link
136      *
137      * <p>
138      * This was moved from the performFinalize because overlapping and to be used
139      * by DirectInquiry.
140      * </p>
141      *
142      * @param view - Container View
143      * @param model - model
144      * @param field - The parent Attribute field
145      */
146     public void setupLink(View view, Object model, DataField field) {
147         String propertyName = field.getBindingInfo().getBindingName();
148 
149         // if class and parameters configured, build link from those
150         if (StringUtils.isNotBlank(getDataObjectClassName()) && (getInquiryParameters() != null) &&
151                 !getInquiryParameters().isEmpty()) {
152             Class<?> inquiryObjectClass;
153             try {
154                 inquiryObjectClass = Class.forName(getDataObjectClassName());
155             } catch (ClassNotFoundException e) {
156                 LOG.error("Unable to get class for: " + getDataObjectClassName());
157                 throw new RuntimeException(e);
158             }
159 
160             updateInquiryParameters(field.getBindingInfo());
161 
162             buildInquiryLink(model, propertyName, inquiryObjectClass, getInquiryParameters());
163         }
164         // get inquiry class and parameters from view helper
165         else {
166             // get parent object for inquiry metadata
167             Object parentObject = ViewModelUtils.getParentObjectForMetadata(view, model, field);
168             view.getViewHelperService().buildInquiryLink(parentObject, propertyName, this);
169         }
170     }
171 
172     /**
173      * Adjusts the path on the inquiry parameter property to match the binding
174      * path prefix of the given {@code BindingInfo}
175      *
176      * @param bindingInfo - binding info instance to copy binding path prefix from
177      */
178     public void updateInquiryParameters(BindingInfo bindingInfo) {
179         Map<String, String> adjustedInquiryParameters = new HashMap<String, String>();
180         for (Entry<String, String> stringEntry : inquiryParameters.entrySet()) {
181             String toField = stringEntry.getValue();
182             String adjustedFromFieldPath = bindingInfo.getPropertyAdjustedBindingPath(stringEntry.getKey());
183 
184             adjustedInquiryParameters.put(adjustedFromFieldPath, toField);
185         }
186 
187         this.inquiryParameters = adjustedInquiryParameters;
188     }
189 
190     /**
191      * Builds the inquiry link based on the given inquiry class and parameters
192      *
193      * @param dataObject - parent object that contains the data (used to pull inquiry
194      * parameters)
195      * @param propertyName - name of the property the inquiry is set on
196      * @param inquiryObjectClass - class of the object the inquiry should point to
197      * @param inquiryParams - map of key field mappings for the inquiry
198      */
199     public void buildInquiryLink(Object dataObject, String propertyName, Class<?> inquiryObjectClass,
200             Map<String, String> inquiryParams) {
201 
202         Properties urlParameters = new Properties();
203 
204         urlParameters.setProperty(UifParameters.DATA_OBJECT_CLASS_NAME, inquiryObjectClass.getName());
205         urlParameters.setProperty(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.START);
206 
207         // add inquiry specific parms to url
208         if (getInquiryLink().getLightBox() != null) {
209             getInquiryLink().getLightBox().setAddAppParms(true);
210         }
211 
212         // configure inquiry when read only
213         if (isReadOnly()) {
214             for (Entry<String, String> inquiryParameter : inquiryParams.entrySet()) {
215                 String parameterName = inquiryParameter.getKey();
216 
217                 Object parameterValue = ObjectPropertyUtils.getPropertyValue(dataObject, parameterName);
218 
219                 // TODO: need general format util that uses spring
220                 if (parameterValue == null) {
221                     parameterValue = "";
222                 } else if (parameterValue instanceof java.sql.Date) {
223                     if (Formatter.findFormatter(parameterValue.getClass()) != null) {
224                         Formatter formatter = Formatter.getFormatter(parameterValue.getClass());
225                         parameterValue = formatter.format(parameterValue);
226                     }
227                 } else {
228                     parameterValue = parameterValue.toString();
229                 }
230 
231                 // Encrypt value if it is a field that has restriction that prevents a value from being shown to
232                 // user, because we don't want the browser history to store the restricted attributes value in the URL
233                 if (KRADServiceLocatorWeb.getDataObjectAuthorizationService()
234                         .attributeValueNeedsToBeEncryptedOnFormsAndLinks(inquiryObjectClass,
235                                 inquiryParameter.getValue())) {
236                     try {
237                         parameterValue = CoreApiServiceLocator.getEncryptionService().encrypt(parameterValue);
238                     } catch (GeneralSecurityException e) {
239                         throw new RuntimeException("Exception while trying to encrypted value for inquiry framework.",
240                                 e);
241                     }
242                 }
243 
244                 // add inquiry parameter to URL
245                 urlParameters.put(inquiryParameter.getValue(), parameterValue);
246             }
247 
248             /* build inquiry URL */
249             String inquiryUrl;
250 
251             // check for EBOs for an alternate inquiry URL
252             ModuleService responsibleModuleService =
253                     KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(inquiryObjectClass);
254             if (responsibleModuleService != null && responsibleModuleService.isExternalizable(inquiryObjectClass)) {
255                 inquiryUrl = responsibleModuleService.getExternalizableDataObjectInquiryUrl(inquiryObjectClass,
256                         urlParameters);
257             } else {
258                 inquiryUrl = UrlFactory.parameterizeUrl(getBaseInquiryUrl(), urlParameters);
259             }
260 
261             getInquiryLink().setHref(inquiryUrl);
262 
263             // set inquiry title
264             String linkTitle = createTitleText(inquiryObjectClass);
265             linkTitle = LookupInquiryUtils.getLinkTitleText(linkTitle, inquiryObjectClass, getInquiryParameters());
266             getInquiryLink().setTitle(linkTitle);
267 
268             setRender(true);
269         }
270 
271         // configure direct inquiry when editable
272         if (!isReadOnly()) {
273             // Direct inquiry
274             String inquiryUrl = UrlFactory.parameterizeUrl(getBaseInquiryUrl(), urlParameters);
275 
276             StringBuilder paramMapString = new StringBuilder();
277 
278             // Build parameter string using the actual names of the fields as on the html page
279             for (Entry<String, String> inquiryParameter : inquiryParams.entrySet()) {
280                 String inquiryParameterFrom = inquiryParameter.getKey();
281 
282                 if (adjustInquiryParameters && (fieldBindingInfo != null)) {
283                     inquiryParameterFrom = fieldBindingInfo.getPropertyAdjustedBindingPath(inquiryParameterFrom);
284                 }
285 
286                 paramMapString.append(inquiryParameterFrom);
287                 paramMapString.append(":");
288                 paramMapString.append(inquiryParameter.getValue());
289                 paramMapString.append(",");
290             }
291             paramMapString.deleteCharAt(paramMapString.length() - 1);
292 
293             // Check if lightbox is set. Get lightbox options.
294             String lightBoxOptions = "";
295             boolean lightBoxShow = (getInquiryLink().getLightBox() != null);
296             if (lightBoxShow) {
297                 lightBoxOptions = getInquiryLink().getLightBox().getTemplateOptionsJSString();
298             }
299 
300             // Create onlick script to open the inquiry window on the click event
301             // of the direct inquiry
302             StringBuilder onClickScript = new StringBuilder("showDirectInquiry(\"");
303             onClickScript.append(inquiryUrl);
304             onClickScript.append("\", \"");
305             onClickScript.append(paramMapString);
306             onClickScript.append("\", ");
307             onClickScript.append(lightBoxShow);
308             onClickScript.append(", ");
309             onClickScript.append(lightBoxOptions);
310             onClickScript.append(");");
311 
312             directInquiryAction.setPerformDirtyValidation(false);
313             directInquiryAction.setActionScript(onClickScript.toString());
314 
315             setRender(true);
316         }
317     }
318 
319     /**
320      * Gets text to prepend to the inquiry link title
321      *
322      * @param dataObjectClass - data object class being inquired into
323      * @return String title prepend text
324      */
325     public String createTitleText(Class<?> dataObjectClass) {
326         String titleText = "";
327 
328         String titlePrefixProp = KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
329                 INQUIRY_TITLE_PREFIX);
330         if (StringUtils.isNotBlank(titlePrefixProp)) {
331             titleText += titlePrefixProp + " ";
332         }
333 
334         String objectLabel = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDataObjectEntry(
335                 dataObjectClass.getName()).getObjectLabel();
336         if (StringUtils.isNotBlank(objectLabel)) {
337             titleText += objectLabel + " ";
338         }
339 
340         return titleText;
341     }
342 
343     /**
344      * @see org.kuali.rice.krad.uif.component.ComponentBase#getComponentsForLifecycle()
345      */
346     @Override
347     public List<Component> getComponentsForLifecycle() {
348         List<Component> components = super.getComponentsForLifecycle();
349 
350         components.add(getInquiryLink());
351         components.add(getDirectInquiryAction());
352 
353         return components;
354     }
355 
356     /**
357      * Returns the URL for the inquiry for which parameters will be added
358      *
359      * <p>
360      * The base URL includes the domain, context, and controller mapping for the inquiry invocation. Parameters are
361      * then added based on configuration to complete the URL. This is generally defaulted to the application URL and
362      * internal KRAD servlet mapping, but can be changed to invoke another application such as the Rice standalone
363      * server
364      * </p>
365      *
366      * @return String inquiry base URL
367      */
368     public String getBaseInquiryUrl() {
369         return this.baseInquiryUrl;
370     }
371 
372     /**
373      * Setter for the inquiry base url (domain, context, and controller)
374      *
375      * @param baseInquiryUrl
376      */
377     public void setBaseInquiryUrl(String baseInquiryUrl) {
378         this.baseInquiryUrl = baseInquiryUrl;
379     }
380 
381     /**
382      * Full class name the inquiry should be provided for
383      *
384      * <p>
385      * This is passed on to the inquiry request for the data object the lookup should be rendered for. This is then
386      * used by the inquiry framework to select the lookup view (if more than one inquiry view exists for the same
387      * data object class name, the {@link #getViewName()} property should be specified to select the view to render).
388      * </p>
389      *
390      * @return String inquiry class name
391      */
392     public String getDataObjectClassName() {
393         return this.dataObjectClassName;
394     }
395 
396     /**
397      * Setter for the class name that inquiry should be provided for
398      *
399      * @param dataObjectClassName
400      */
401     public void setDataObjectClassName(String dataObjectClassName) {
402         this.dataObjectClassName = dataObjectClassName;
403     }
404 
405     /**
406      * When multiple target inquiry views exists for the same data object class, the view name can be set to
407      * determine which one to use
408      *
409      * <p>
410      * When creating multiple inquiry views for the same data object class, the view name can be specified for the
411      * different versions (for example 'simple' and 'advanced'). When multiple inquiry views exist the view name must
412      * be sent with the data object class for the request. Note the view id can be alternatively used to uniquely
413      * identify the inquiry view
414      * </p>
415      */
416     public String getViewName() {
417         return this.viewName;
418     }
419 
420     /**
421      * Setter for the view name configured on the inquiry view that should be invoked by the inquiry widget
422      *
423      * @param viewName
424      */
425     public void setViewName(String viewName) {
426         this.viewName = viewName;
427     }
428 
429     /**
430      * Map that determines what properties from a calling view will be sent to properties on the inquiry data object
431      *
432      * <p>
433      * When invoking an inquiry view, a query is done against the inquiries configured data object and the resulting
434      * record is display. The values for the properties configured within the inquiry parameters Map will be
435      * pulled and passed along as values for the inquiry data object properties (thus they form the criteria for
436      * the inquiry)
437      * </p>
438      *
439      * @return Map<String, String> mapping of calling view properties to inquiry data object properties
440      */
441     public Map<String, String> getInquiryParameters() {
442         return this.inquiryParameters;
443     }
444 
445     /**
446      * Setter for the map that determines what property values on the calling view will be sent to properties on the
447      * inquiry data object
448      *
449      * @param inquiryParameters
450      */
451     public void setInquiryParameters(Map<String, String> inquiryParameters) {
452         this.inquiryParameters = inquiryParameters;
453     }
454 
455     /**
456      * {@code Link} that will be rendered for an inquiry
457      *
458      * @return the inquiry link
459      */
460     public Link getInquiryLink() {
461         return this.inquiryLink;
462     }
463 
464     /**
465      * Setter for the inquiry {@code Link}
466      *
467      * @param inquiryLink - the inquiry {@link Link} object
468      */
469     public void setInquiryLink(Link inquiryLink) {
470         this.inquiryLink = inquiryLink;
471     }
472 
473     /**
474      * {@code Action} that will be rendered next to the field for a direct inquiry
475      *
476      * @return the directInquiryAction
477      */
478     public Action getDirectInquiryAction() {
479         return this.directInquiryAction;
480     }
481 
482     /**
483      * Setter for the direct inquiry {@code Action}
484      *
485      * @param directInquiryAction the direct inquiry {@link Action}
486      */
487     public void setDirectInquiryAction(Action directInquiryAction) {
488         this.directInquiryAction = directInquiryAction;
489     }
490 
491     /**
492      * Indicates that the direct inquiry will not be rendered
493      *
494      * @return boolean true if the direct inquiry should be rendered, false if not
495      */
496     public boolean isEnableDirectInquiry() {
497         return enableDirectInquiry;
498     }
499 
500     /**
501      * Setter for the hideDirectInquiry flag
502      *
503      * @param enableDirectInquiry
504      */
505     public void setEnableDirectInquiry(boolean enableDirectInquiry) {
506         this.enableDirectInquiry = enableDirectInquiry;
507     }
508 }