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 /**
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 }