001    /**
002     * Copyright 2005-2013 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        /**
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    }