001    /**
002     * Copyright 2005-2012 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     */
016    package org.kuali.rice.krad.uif.widget;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.kuali.rice.krad.bo.DataObjectRelationship;
020    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
021    import org.kuali.rice.krad.uif.UifParameters;
022    import org.kuali.rice.krad.uif.container.CollectionGroup;
023    import org.kuali.rice.krad.uif.field.InputField;
024    import org.kuali.rice.krad.uif.view.View;
025    import org.kuali.rice.krad.uif.component.BindingInfo;
026    import org.kuali.rice.krad.uif.component.Component;
027    import org.kuali.rice.krad.uif.field.ActionField;
028    import org.kuali.rice.krad.uif.util.ViewModelUtils;
029    import org.kuali.rice.krad.util.KRADUtils;
030    
031    import java.util.HashMap;
032    import java.util.List;
033    import 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     */
040    public 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        public String getViewName() {
349            return this.viewName;
350        }
351    
352        public void setViewName(String viewName) {
353            this.viewName = viewName;
354        }
355    
356        public String getReferencesToRefresh() {
357            return this.referencesToRefresh;
358        }
359    
360        public void setReferencesToRefresh(String referencesToRefresh) {
361            this.referencesToRefresh = referencesToRefresh;
362        }
363    
364        public Map<String, String> getFieldConversions() {
365            return this.fieldConversions;
366        }
367    
368        public void setFieldConversions(Map<String, String> fieldConversions) {
369            this.fieldConversions = fieldConversions;
370        }
371    
372        public Map<String, String> getLookupParameters() {
373            return this.lookupParameters;
374        }
375    
376        public void setLookupParameters(Map<String, String> lookupParameters) {
377            this.lookupParameters = lookupParameters;
378        }
379    
380        public String getReadOnlySearchFields() {
381            return this.readOnlySearchFields;
382        }
383    
384        public void setReadOnlySearchFields(String readOnlySearchFields) {
385            this.readOnlySearchFields = readOnlySearchFields;
386        }
387    
388        public Boolean getHideReturnLink() {
389            return this.hideReturnLink;
390        }
391    
392        public void setHideReturnLink(Boolean hideReturnLink) {
393            this.hideReturnLink = hideReturnLink;
394        }
395    
396        public Boolean getSuppressActions() {
397            return suppressActions;
398        }
399    
400        public void setSuppressActions(Boolean suppressActions) {
401            this.suppressActions = suppressActions;
402        }
403    
404        public Boolean getAutoSearch() {
405            return this.autoSearch;
406        }
407    
408        public void setAutoSearch(Boolean autoSearch) {
409            this.autoSearch = autoSearch;
410        }
411    
412        public Boolean getLookupCriteriaEnabled() {
413            return this.lookupCriteriaEnabled;
414        }
415    
416        public void setLookupCriteriaEnabled(Boolean lookupCriteriaEnabled) {
417            this.lookupCriteriaEnabled = lookupCriteriaEnabled;
418        }
419    
420        public Boolean getSupplementalActionsEnabled() {
421            return this.supplementalActionsEnabled;
422        }
423    
424        public void setSupplementalActionsEnabled(Boolean supplementalActionsEnabled) {
425            this.supplementalActionsEnabled = supplementalActionsEnabled;
426        }
427    
428        public Boolean getDisableSearchButtons() {
429            return this.disableSearchButtons;
430        }
431    
432        public void setDisableSearchButtons(Boolean disableSearchButtons) {
433            this.disableSearchButtons = disableSearchButtons;
434        }
435    
436        public Boolean getHeaderBarEnabled() {
437            return this.headerBarEnabled;
438        }
439    
440        public void setHeaderBarEnabled(Boolean headerBarEnabled) {
441            this.headerBarEnabled = headerBarEnabled;
442        }
443    
444        public Boolean getShowMaintenanceLinks() {
445            return this.showMaintenanceLinks;
446        }
447    
448        public void setShowMaintenanceLinks(Boolean showMaintenanceLinks) {
449            this.showMaintenanceLinks = showMaintenanceLinks;
450        }
451    
452        public ActionField getQuickfinderActionField() {
453            return this.quickfinderActionField;
454        }
455    
456        public void setQuickfinderActionField(ActionField quickfinderActionField) {
457            this.quickfinderActionField = quickfinderActionField;
458        }
459    
460        /**
461         * Indicates whether a multi-values lookup should be requested
462         *
463         * @return boolean true if multi-value lookup should be requested, false for normal lookup
464         */
465        public Boolean getMultipleValuesSelect() {
466            return multipleValuesSelect;
467        }
468    
469        /**
470         * Setter for the multi-values lookup indicator
471         *
472         * @param multipleValuesSelect
473         */
474        public void setMultipleValuesSelect(Boolean multipleValuesSelect) {
475            this.multipleValuesSelect = multipleValuesSelect;
476        }
477    
478        /**
479         * For the case of multi-value lookup, indicates the collection that should be populated with
480         * the return results
481         *
482         * <p>
483         * Note when the quickfinder is associated with a <code>CollectionGroup</code>, this property is
484         * set automatically from the collection name associated with the group
485         * </p>
486         *
487         * @return String collection name (must be full binding path)
488         */
489        public String getLookupCollectionName() {
490            return lookupCollectionName;
491        }
492    
493        /**
494         * Setter for the name of the collection that should be populated with lookup results
495         *
496         * @param lookupCollectionName
497         */
498        public void setLookupCollectionName(String lookupCollectionName) {
499            this.lookupCollectionName = lookupCollectionName;
500        }
501    }