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.krad.bo.DataObjectRelationship;
20  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
21  import org.kuali.rice.krad.uif.UifParameters;
22  import org.kuali.rice.krad.uif.container.CollectionGroup;
23  import org.kuali.rice.krad.uif.field.InputField;
24  import org.kuali.rice.krad.uif.view.View;
25  import org.kuali.rice.krad.uif.component.BindingInfo;
26  import org.kuali.rice.krad.uif.component.Component;
27  import org.kuali.rice.krad.uif.field.ActionField;
28  import org.kuali.rice.krad.uif.util.ViewModelUtils;
29  import org.kuali.rice.krad.util.KRADUtils;
30  
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  
35  /**
36   * Widget for navigating to a lookup from a field (called a quickfinder)
37   *
38   * @author Kuali Rice Team (rice.collab@kuali.org)
39   */
40  public class QuickFinder extends WidgetBase {
41      private static final long serialVersionUID = 3302390972815386785L;
42  
43      // lookup configuration
44      private String baseLookupUrl;
45      private String dataObjectClassName;
46      private String viewName;
47  
48      private String referencesToRefresh;
49  
50      private Map<String, String> fieldConversions;
51      private Map<String, String> lookupParameters;
52  
53      // lookup view options
54      private String readOnlySearchFields;
55  
56      private Boolean hideReturnLink;
57      private Boolean suppressActions;
58      private Boolean autoSearch;
59      private Boolean lookupCriteriaEnabled;
60      private Boolean supplementalActionsEnabled;
61      private Boolean disableSearchButtons;
62      private Boolean headerBarEnabled;
63      private Boolean showMaintenanceLinks;
64  
65      private Boolean multipleValuesSelect;
66      private String lookupCollectionName;
67  
68      private ActionField quickfinderActionField;
69  
70      public QuickFinder() {
71          super();
72  
73          fieldConversions = new HashMap<String, String>();
74          lookupParameters = new HashMap<String, String>();
75      }
76  
77      /**
78       * The following finalization is performed:
79       *
80       * <ul>
81       * <li>
82       * Sets defaults on collectionLookup such as collectionName, and the class if not set
83       *
84       * <p>
85       * If the data object class was not configured for the lookup, the class configured for the collection group will
86       * be used if it has a lookup defined. If not data object class is found for the lookup it will be disabled. The
87       * collection name is also defaulted to the binding path for this collection group, so the results returned from
88       * the lookup will populate this collection. Finally field conversions will be generated based on the PK fields of
89       * the collection object class
90       * </p>
91       * </li>
92       * </ul>
93       *
94       * @see org.kuali.rice.krad.uif.widget.Widget#performFinalize(org.kuali.rice.krad.uif.view.View,
95       *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
96       */
97      @Override
98      public void performFinalize(View view, Object model, Component parent) {
99          super.performFinalize(view, model, parent);
100 
101         if (!isRender()) {
102             return;
103         }
104 
105         if (parent instanceof InputField) {
106             InputField field = (InputField) parent;
107 
108             // determine lookup class, field conversions and lookup parameters in
109             // not set
110             if (StringUtils.isBlank(dataObjectClassName)) {
111                 DataObjectRelationship relationship = getRelationshipForField(view, model, field);
112 
113                 // if no relationship found cannot have a quickfinder
114                 if (relationship == null) {
115                     setRender(false);
116                     return;
117                 }
118 
119                 dataObjectClassName = relationship.getRelatedClass().getName();
120 
121                 if ((fieldConversions == null) || fieldConversions.isEmpty()) {
122                     generateFieldConversions(field, relationship);
123                 }
124 
125                 if ((lookupParameters == null) || lookupParameters.isEmpty()) {
126                     generateLookupParameters(field, relationship);
127                 }
128             }
129 
130             // adjust paths based on associated attribute field
131             updateFieldConversions(field.getBindingInfo());
132             updateLookupParameters(field.getBindingInfo());
133         } else if (parent instanceof CollectionGroup) {
134             CollectionGroup collectionGroup = (CollectionGroup) parent;
135 
136             // check to see if data object class is configured for lookup, if so we will assume it should be enabled
137             // if not and the class configured for the collection group is lookupable, use that
138             if (StringUtils.isBlank(getDataObjectClassName())) {
139                 Class<?> collectionObjectClass = collectionGroup.getCollectionObjectClass();
140                 boolean isCollectionClassLookupable = KRADServiceLocatorWeb.getViewDictionaryService().isLookupable(
141                         collectionObjectClass);
142                 if (isCollectionClassLookupable) {
143                     setDataObjectClassName(collectionObjectClass.getName());
144 
145                     if ((fieldConversions == null) || fieldConversions.isEmpty()) {
146                         // use PK fields for collection class
147                         List<String> collectionObjectPKFields =
148                                 KRADServiceLocatorWeb.getDataObjectMetaDataService().listPrimaryKeyFieldNames(
149                                         collectionObjectClass);
150 
151                         for (String pkField : collectionObjectPKFields) {
152                             fieldConversions.put(pkField, pkField);
153                         }
154                     }
155                 } else {
156                     // no available data object class to lookup so need to disable quickfinder
157                     setRender(false);
158                 }
159             }
160 
161             // set the lookup return collection name to this collection path
162             if (isRender() && StringUtils.isBlank(getLookupCollectionName())) {
163                 setLookupCollectionName(collectionGroup.getBindingInfo().getBindingPath());
164             }
165         }
166 
167         quickfinderActionField.addActionParameter(UifParameters.BASE_LOOKUP_URL, baseLookupUrl);
168         quickfinderActionField.addActionParameter(UifParameters.DATA_OBJECT_CLASS_NAME, dataObjectClassName);
169 
170         if (!fieldConversions.isEmpty()) {
171             quickfinderActionField.addActionParameter(UifParameters.CONVERSION_FIELDS,
172                     KRADUtils.buildMapParameterString(fieldConversions));
173         }
174 
175         if (!lookupParameters.isEmpty()) {
176             quickfinderActionField.addActionParameter(UifParameters.LOOKUP_PARAMETERS,
177                     KRADUtils.buildMapParameterString(lookupParameters));
178         }
179 
180         addActionParameterIfNotNull(UifParameters.VIEW_NAME, viewName);
181         addActionParameterIfNotNull(UifParameters.READ_ONLY_FIELDS, readOnlySearchFields);
182         addActionParameterIfNotNull(UifParameters.HIDE_RETURN_LINK, hideReturnLink);
183         addActionParameterIfNotNull(UifParameters.SUPRESS_ACTIONS, suppressActions);
184         addActionParameterIfNotNull(UifParameters.REFERENCES_TO_REFRESH, referencesToRefresh);
185         addActionParameterIfNotNull(UifParameters.AUTO_SEARCH, autoSearch);
186         addActionParameterIfNotNull(UifParameters.LOOKUP_CRITERIA_ENABLED, lookupCriteriaEnabled);
187         addActionParameterIfNotNull(UifParameters.SUPPLEMENTAL_ACTIONS_ENABLED, supplementalActionsEnabled);
188         addActionParameterIfNotNull(UifParameters.DISABLE_SEARCH_BUTTONS, disableSearchButtons);
189         addActionParameterIfNotNull(UifParameters.HEADER_BAR_ENABLED, headerBarEnabled);
190         addActionParameterIfNotNull(UifParameters.SHOW_MAINTENANCE_LINKS, showMaintenanceLinks);
191         addActionParameterIfNotNull(UifParameters.MULTIPLE_VALUES_SELECT, multipleValuesSelect);
192         addActionParameterIfNotNull(UifParameters.LOOKUP_COLLECTION_NAME, lookupCollectionName);
193 
194         // TODO:
195         // org.kuali.rice.kns.util.FieldUtils.populateQuickfinderDefaultsForLookup(Class,
196         // String, Field)
197     }
198 
199     protected void addActionParameterIfNotNull(String parameterName, Object parameterValue) {
200         if ((parameterValue != null) && StringUtils.isNotBlank(parameterValue.toString())) {
201             quickfinderActionField.addActionParameter(parameterName, parameterValue.toString());
202         }
203     }
204 
205     protected DataObjectRelationship getRelationshipForField(View view, Object model, InputField field) {
206         String propertyName = field.getBindingInfo().getBindingName();
207 
208         // get object instance and class for parent
209         Object parentObject = ViewModelUtils.getParentObjectForMetadata(view, model, field);
210         Class<?> parentObjectClass = null;
211         if (parentObject != null) {
212             parentObjectClass = parentObject.getClass();
213         }
214 
215         // get relationship from metadata service
216         return KRADServiceLocatorWeb.getDataObjectMetaDataService().getDataObjectRelationship(parentObject,
217                 parentObjectClass, propertyName, "", true, true, false);
218     }
219 
220     protected void generateFieldConversions(InputField field, DataObjectRelationship relationship) {
221         fieldConversions = new HashMap<String, String>();
222         for (Map.Entry<String, String> entry : relationship.getParentToChildReferences().entrySet()) {
223             String fromField = entry.getValue();
224             String toField = entry.getKey();
225 
226             // TODO: displayedFieldnames in
227             // org.kuali.rice.kns.lookup.LookupUtils.generateFieldConversions(BusinessObject,
228             // String, DataObjectRelationship, String, List, String)
229 
230             fieldConversions.put(fromField, toField);
231         }
232     }
233 
234     protected void generateLookupParameters(InputField field, DataObjectRelationship relationship) {
235         lookupParameters = new HashMap<String, String>();
236         for (Map.Entry<String, String> entry : relationship.getParentToChildReferences().entrySet()) {
237             String fromField = entry.getKey();
238             String toField = entry.getValue();
239 
240             // TODO: displayedFieldnames and displayedQFFieldNames in
241             // generateLookupParameters(BusinessObject,
242             // String, DataObjectRelationship, String, List, String)
243 
244             if (relationship.getUserVisibleIdentifierKey() == null || relationship.getUserVisibleIdentifierKey().equals(
245                     fromField)) {
246                 lookupParameters.put(fromField, toField);
247             }
248         }
249     }
250 
251     /**
252      * Adjusts the path on the field conversion to property to match the binding
253      * path prefix of the given <code>BindingInfo</code>
254      *
255      * @param bindingInfo - binding info instance to copy binding path prefix from
256      */
257     public void updateFieldConversions(BindingInfo bindingInfo) {
258         Map<String, String> adjustedFieldConversions = new HashMap<String, String>();
259         for (String fromField : fieldConversions.keySet()) {
260             String toField = fieldConversions.get(fromField);
261             String adjustedToFieldPath = bindingInfo.getPropertyAdjustedBindingPath(toField);
262 
263             adjustedFieldConversions.put(fromField, adjustedToFieldPath);
264         }
265 
266         this.fieldConversions = adjustedFieldConversions;
267     }
268 
269     /**
270      * Adjusts the path on the lookup parameter from property to match the binding
271      * path prefix of the given <code>BindingInfo</code>
272      *
273      * @param bindingInfo - binding info instance to copy binding path prefix from
274      */
275     public void updateLookupParameters(BindingInfo bindingInfo) {
276         Map<String, String> adjustedLookupParameters = new HashMap<String, String>();
277         for (String fromField : lookupParameters.keySet()) {
278             String toField = lookupParameters.get(fromField);
279             String adjustedFromFieldPath = bindingInfo.getPropertyAdjustedBindingPath(fromField);
280 
281             adjustedLookupParameters.put(adjustedFromFieldPath, toField);
282         }
283 
284         this.lookupParameters = adjustedLookupParameters;
285     }
286 
287     /**
288      * @see org.kuali.rice.krad.uif.component.ComponentBase#getComponentsForLifecycle()
289      */
290     @Override
291     public List<Component> getComponentsForLifecycle() {
292         List<Component> components = super.getComponentsForLifecycle();
293 
294         components.add(quickfinderActionField);
295 
296         return components;
297     }
298 
299     /**
300      * Returns the URL for the lookup for which parameters will be added
301      *
302      * <p>
303      * The base URL includes the domain, context, and controller mapping for the lookup invocation. Parameters are
304      * then added based on configuration to complete the URL. This is generally defaulted to the application URL and
305      * internal KRAD servlet mapping, but can be changed to invoke another application such as the Rice standalone
306      * server
307      * </p>
308      *
309      * @return String lookup base URL
310      */
311     public String getBaseLookupUrl() {
312         return this.baseLookupUrl;
313     }
314 
315     /**
316      * Setter for the lookup base url (comain, context, and controller)
317      *
318      * @param baseLookupUrl
319      */
320     public void setBaseLookupUrl(String baseLookupUrl) {
321         this.baseLookupUrl = baseLookupUrl;
322     }
323 
324     /**
325      * Full class name the lookup should be provided for
326      * 
327      * <p>
328      * This is passed on to the lookup request for the data object the lookup should be rendered for. This is then 
329      * used by the lookup framework to select the lookup view (if more than one lookup view exists for the same
330      * data object class name, the {@link #getViewName()} property should be specified to select the view to render).
331      * </p>
332      * 
333      * @return String lookup class name
334      */
335     public String getDataObjectClassName() {
336         return this.dataObjectClassName;
337     }
338 
339     /**
340      * Setter for the class name that lookup should be provided for
341      * 
342      * @param dataObjectClassName
343      */
344     public void setDataObjectClassName(String dataObjectClassName) {
345         this.dataObjectClassName = dataObjectClassName;
346     }
347 
348     /**
349      * Specifies the name of the lookup view that should be render when the quickfinder is clicked
350      * 
351      * <p>
352      * When more than one lookup exists for the {@link #getDataObjectClassName()}, the view name must be specified
353      * to select which one to render. Note when a view name is not specified, it receives a name of 'DEFAULT'.
354      * Therefore this name can be sent to select the lookup view without a view name specified.
355      * </p>
356      * 
357      * @return String name of lookup view
358      */
359     public String getViewName() {
360         return this.viewName;
361     }
362 
363     /**
364      * Setter for the lookup view name
365      * 
366      * @param viewName
367      */
368     public void setViewName(String viewName) {
369         this.viewName = viewName;
370     }
371 
372     public String getReferencesToRefresh() {
373         return this.referencesToRefresh;
374     }
375 
376     public void setReferencesToRefresh(String referencesToRefresh) {
377         this.referencesToRefresh = referencesToRefresh;
378     }
379 
380     public Map<String, String> getFieldConversions() {
381         return this.fieldConversions;
382     }
383 
384     public void setFieldConversions(Map<String, String> fieldConversions) {
385         this.fieldConversions = fieldConversions;
386     }
387 
388     public Map<String, String> getLookupParameters() {
389         return this.lookupParameters;
390     }
391 
392     public void setLookupParameters(Map<String, String> lookupParameters) {
393         this.lookupParameters = lookupParameters;
394     }
395 
396     public String getReadOnlySearchFields() {
397         return this.readOnlySearchFields;
398     }
399 
400     public void setReadOnlySearchFields(String readOnlySearchFields) {
401         this.readOnlySearchFields = readOnlySearchFields;
402     }
403 
404     public Boolean getHideReturnLink() {
405         return this.hideReturnLink;
406     }
407 
408     public void setHideReturnLink(Boolean hideReturnLink) {
409         this.hideReturnLink = hideReturnLink;
410     }
411 
412     public Boolean getSuppressActions() {
413         return suppressActions;
414     }
415 
416     public void setSuppressActions(Boolean suppressActions) {
417         this.suppressActions = suppressActions;
418     }
419 
420     public Boolean getAutoSearch() {
421         return this.autoSearch;
422     }
423 
424     public void setAutoSearch(Boolean autoSearch) {
425         this.autoSearch = autoSearch;
426     }
427 
428     public Boolean getLookupCriteriaEnabled() {
429         return this.lookupCriteriaEnabled;
430     }
431 
432     public void setLookupCriteriaEnabled(Boolean lookupCriteriaEnabled) {
433         this.lookupCriteriaEnabled = lookupCriteriaEnabled;
434     }
435 
436     public Boolean getSupplementalActionsEnabled() {
437         return this.supplementalActionsEnabled;
438     }
439 
440     public void setSupplementalActionsEnabled(Boolean supplementalActionsEnabled) {
441         this.supplementalActionsEnabled = supplementalActionsEnabled;
442     }
443 
444     public Boolean getDisableSearchButtons() {
445         return this.disableSearchButtons;
446     }
447 
448     public void setDisableSearchButtons(Boolean disableSearchButtons) {
449         this.disableSearchButtons = disableSearchButtons;
450     }
451 
452     public Boolean getHeaderBarEnabled() {
453         return this.headerBarEnabled;
454     }
455 
456     public void setHeaderBarEnabled(Boolean headerBarEnabled) {
457         this.headerBarEnabled = headerBarEnabled;
458     }
459 
460     public Boolean getShowMaintenanceLinks() {
461         return this.showMaintenanceLinks;
462     }
463 
464     public void setShowMaintenanceLinks(Boolean showMaintenanceLinks) {
465         this.showMaintenanceLinks = showMaintenanceLinks;
466     }
467 
468     public ActionField getQuickfinderActionField() {
469         return this.quickfinderActionField;
470     }
471 
472     public void setQuickfinderActionField(ActionField quickfinderActionField) {
473         this.quickfinderActionField = quickfinderActionField;
474     }
475 
476     /**
477      * Indicates whether a multi-values lookup should be requested
478      *
479      * @return boolean true if multi-value lookup should be requested, false for normal lookup
480      */
481     public Boolean getMultipleValuesSelect() {
482         return multipleValuesSelect;
483     }
484 
485     /**
486      * Setter for the multi-values lookup indicator
487      *
488      * @param multipleValuesSelect
489      */
490     public void setMultipleValuesSelect(Boolean multipleValuesSelect) {
491         this.multipleValuesSelect = multipleValuesSelect;
492     }
493 
494     /**
495      * For the case of multi-value lookup, indicates the collection that should be populated with
496      * the return results
497      *
498      * <p>
499      * Note when the quickfinder is associated with a <code>CollectionGroup</code>, this property is
500      * set automatically from the collection name associated with the group
501      * </p>
502      *
503      * @return String collection name (must be full binding path)
504      */
505     public String getLookupCollectionName() {
506         return lookupCollectionName;
507     }
508 
509     /**
510      * Setter for the name of the collection that should be populated with lookup results
511      *
512      * @param lookupCollectionName
513      */
514     public void setLookupCollectionName(String lookupCollectionName) {
515         this.lookupCollectionName = lookupCollectionName;
516     }
517 }