View Javadoc

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