001    /**
002     * Copyright 2005-2011 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        public String getBaseLookupUrl() {
300            return this.baseLookupUrl;
301        }
302    
303        public void setBaseLookupUrl(String baseLookupUrl) {
304            this.baseLookupUrl = baseLookupUrl;
305        }
306    
307        public String getDataObjectClassName() {
308            return this.dataObjectClassName;
309        }
310    
311        public void setDataObjectClassName(String dataObjectClassName) {
312            this.dataObjectClassName = dataObjectClassName;
313        }
314    
315        public String getViewName() {
316            return this.viewName;
317        }
318    
319        public void setViewName(String viewName) {
320            this.viewName = viewName;
321        }
322    
323        public String getReferencesToRefresh() {
324            return this.referencesToRefresh;
325        }
326    
327        public void setReferencesToRefresh(String referencesToRefresh) {
328            this.referencesToRefresh = referencesToRefresh;
329        }
330    
331        public Map<String, String> getFieldConversions() {
332            return this.fieldConversions;
333        }
334    
335        public void setFieldConversions(Map<String, String> fieldConversions) {
336            this.fieldConversions = fieldConversions;
337        }
338    
339        public Map<String, String> getLookupParameters() {
340            return this.lookupParameters;
341        }
342    
343        public void setLookupParameters(Map<String, String> lookupParameters) {
344            this.lookupParameters = lookupParameters;
345        }
346    
347        public String getReadOnlySearchFields() {
348            return this.readOnlySearchFields;
349        }
350    
351        public void setReadOnlySearchFields(String readOnlySearchFields) {
352            this.readOnlySearchFields = readOnlySearchFields;
353        }
354    
355        public Boolean getHideReturnLink() {
356            return this.hideReturnLink;
357        }
358    
359        public void setHideReturnLink(Boolean hideReturnLink) {
360            this.hideReturnLink = hideReturnLink;
361        }
362    
363        public Boolean getSuppressActions() {
364            return suppressActions;
365        }
366    
367        public void setSuppressActions(Boolean suppressActions) {
368            this.suppressActions = suppressActions;
369        }
370    
371        public Boolean getAutoSearch() {
372            return this.autoSearch;
373        }
374    
375        public void setAutoSearch(Boolean autoSearch) {
376            this.autoSearch = autoSearch;
377        }
378    
379        public Boolean getLookupCriteriaEnabled() {
380            return this.lookupCriteriaEnabled;
381        }
382    
383        public void setLookupCriteriaEnabled(Boolean lookupCriteriaEnabled) {
384            this.lookupCriteriaEnabled = lookupCriteriaEnabled;
385        }
386    
387        public Boolean getSupplementalActionsEnabled() {
388            return this.supplementalActionsEnabled;
389        }
390    
391        public void setSupplementalActionsEnabled(Boolean supplementalActionsEnabled) {
392            this.supplementalActionsEnabled = supplementalActionsEnabled;
393        }
394    
395        public Boolean getDisableSearchButtons() {
396            return this.disableSearchButtons;
397        }
398    
399        public void setDisableSearchButtons(Boolean disableSearchButtons) {
400            this.disableSearchButtons = disableSearchButtons;
401        }
402    
403        public Boolean getHeaderBarEnabled() {
404            return this.headerBarEnabled;
405        }
406    
407        public void setHeaderBarEnabled(Boolean headerBarEnabled) {
408            this.headerBarEnabled = headerBarEnabled;
409        }
410    
411        public Boolean getShowMaintenanceLinks() {
412            return this.showMaintenanceLinks;
413        }
414    
415        public void setShowMaintenanceLinks(Boolean showMaintenanceLinks) {
416            this.showMaintenanceLinks = showMaintenanceLinks;
417        }
418    
419        public ActionField getQuickfinderActionField() {
420            return this.quickfinderActionField;
421        }
422    
423        public void setQuickfinderActionField(ActionField quickfinderActionField) {
424            this.quickfinderActionField = quickfinderActionField;
425        }
426    
427        /**
428         * Indicates whether a multi-values lookup should be requested
429         *
430         * @return boolean true if multi-value lookup should be requested, false for normal lookup
431         */
432        public Boolean getMultipleValuesSelect() {
433            return multipleValuesSelect;
434        }
435    
436        /**
437         * Setter for the multi-values lookup indicator
438         *
439         * @param multipleValuesSelect
440         */
441        public void setMultipleValuesSelect(Boolean multipleValuesSelect) {
442            this.multipleValuesSelect = multipleValuesSelect;
443        }
444    
445        /**
446         * For the case of multi-value lookup, indicates the collection that should be populated with
447         * the return results
448         *
449         * <p>
450         * Note when the quickfinder is associated with a <code>CollectionGroup</code>, this property is
451         * set automatically from the collection name associated with the group
452         * </p>
453         *
454         * @return String collection name (must be full binding path)
455         */
456        public String getLookupCollectionName() {
457            return lookupCollectionName;
458        }
459    
460        /**
461         * Setter for the name of the collection that should be populated with lookup results
462         *
463         * @param lookupCollectionName
464         */
465        public void setLookupCollectionName(String lookupCollectionName) {
466            this.lookupCollectionName = lookupCollectionName;
467        }
468    }