View Javadoc

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