001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.widget;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.krad.bo.DataObjectRelationship;
020import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
021import org.kuali.rice.krad.uif.UifParameters;
022import org.kuali.rice.krad.uif.container.CollectionGroup;
023import org.kuali.rice.krad.uif.field.InputField;
024import org.kuali.rice.krad.uif.view.View;
025import org.kuali.rice.krad.uif.component.BindingInfo;
026import org.kuali.rice.krad.uif.component.Component;
027import org.kuali.rice.krad.uif.field.ActionField;
028import org.kuali.rice.krad.uif.util.ViewModelUtils;
029import org.kuali.rice.krad.util.KRADUtils;
030
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
034
035/**
036 * Widget for navigating to a lookup from a field (called a quickfinder)
037 *
038 * @author Kuali Rice Team (rice.collab@kuali.org)
039 */
040public class QuickFinder extends WidgetBase {
041    private static final long serialVersionUID = 3302390972815386785L;
042
043    // lookup configuration
044    private String baseLookupUrl;
045    private String dataObjectClassName;
046    private String viewName;
047
048    private String referencesToRefresh;
049
050    private Map<String, String> fieldConversions;
051    private Map<String, String> lookupParameters;
052
053    // lookup view options
054    private String readOnlySearchFields;
055
056    private Boolean hideReturnLink;
057    private Boolean suppressActions;
058    private Boolean autoSearch;
059    private Boolean lookupCriteriaEnabled;
060    private Boolean supplementalActionsEnabled;
061    private Boolean disableSearchButtons;
062    private Boolean headerBarEnabled;
063    private Boolean showMaintenanceLinks;
064
065    private Boolean multipleValuesSelect;
066    private String lookupCollectionName;
067
068    private ActionField quickfinderActionField;
069
070    public QuickFinder() {
071        super();
072
073        fieldConversions = new HashMap<String, String>();
074        lookupParameters = new HashMap<String, String>();
075    }
076
077    /**
078     * The following finalization is performed:
079     *
080     * <ul>
081     * <li>
082     * Sets defaults on collectionLookup such as collectionName, and the class if not set
083     *
084     * <p>
085     * If the data object class was not configured for the lookup, the class configured for the collection group will
086     * be used if it has a lookup defined. If not data object class is found for the lookup it will be disabled. The
087     * collection name is also defaulted to the binding path for this collection group, so the results returned from
088     * the lookup will populate this collection. Finally field conversions will be generated based on the PK fields of
089     * the collection object class
090     * </p>
091     * </li>
092     * </ul>
093     *
094     * @see org.kuali.rice.krad.uif.widget.Widget#performFinalize(org.kuali.rice.krad.uif.view.View,
095     *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
096     */
097    @Override
098    public void performFinalize(View view, Object model, Component parent) {
099        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}