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.core.api.CoreApiServiceLocator;
020 import org.kuali.rice.core.web.format.Formatter;
021 import org.kuali.rice.krad.datadictionary.parse.BeanTag;
022 import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
023 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
024 import org.kuali.rice.krad.service.ModuleService;
025 import org.kuali.rice.krad.uif.UifConstants;
026 import org.kuali.rice.krad.uif.UifParameters;
027 import org.kuali.rice.krad.uif.component.BindingInfo;
028 import org.kuali.rice.krad.uif.component.Component;
029 import org.kuali.rice.krad.uif.element.Action;
030 import org.kuali.rice.krad.uif.element.Link;
031 import org.kuali.rice.krad.uif.field.DataField;
032 import org.kuali.rice.krad.uif.field.InputField;
033 import org.kuali.rice.krad.uif.util.LookupInquiryUtils;
034 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
035 import org.kuali.rice.krad.uif.util.ViewModelUtils;
036 import org.kuali.rice.krad.uif.view.View;
037 import org.kuali.rice.krad.util.UrlFactory;
038
039 import java.security.GeneralSecurityException;
040 import java.util.HashMap;
041 import java.util.List;
042 import java.util.Map;
043 import java.util.Map.Entry;
044 import java.util.Properties;
045
046 /**
047 * Widget for rendering an Inquiry link or DirectInquiry action field
048 *
049 * <p>
050 * The inquiry widget will render a button for the field value when
051 * that field is editable. When read only the widget will create a link on the display value.
052 * It points to the associated inquiry view for the field. The inquiry can be configured to point to a certain
053 * {@code InquiryView}, or the framework will attempt to associate the
054 * field with a inquiry based on its metadata (in particular its
055 * relationships in the model).
056 * </p>
057 *
058 * @author Kuali Rice Team (rice.collab@kuali.org)
059 */
060 @BeanTag(name = "inquiry-bean", parent = "Uif-Inquiry")
061 public class Inquiry extends WidgetBase {
062 private static final long serialVersionUID = -2154388007867302901L;
063 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(Inquiry.class);
064
065 public static final String INQUIRY_TITLE_PREFIX = "title.inquiry.url.value.prependtext";
066
067 private String baseInquiryUrl;
068
069 private String dataObjectClassName;
070 private String viewName;
071
072 private Map<String, String> inquiryParameters;
073
074 private Link inquiryLink;
075
076 private Action directInquiryAction;
077 private boolean enableDirectInquiry;
078
079 private boolean adjustInquiryParameters;
080 private BindingInfo fieldBindingInfo;
081
082 private boolean parentReadOnly;
083
084 public Inquiry() {
085 super();
086
087 inquiryParameters = new HashMap<String, String>();
088 }
089
090 /**
091 * @see org.kuali.rice.krad.uif.widget.WidgetBase#performFinalize(org.kuali.rice.krad.uif.view.View,
092 * java.lang.Object, org.kuali.rice.krad.uif.component.Component)
093 */
094 @Override
095 public void performFinalize(View view, Object model, Component parent) {
096 super.performFinalize(view, model, parent);
097
098 if (!isRender()) {
099 return;
100 }
101
102 // set render to false until we find an inquiry class
103 setRender(false);
104
105 // used to determine whether a normal or direct inquiry should be enabled
106 setParentReadOnly(parent.isReadOnly());
107
108 // Do checks for inquiry when read only
109 if (isParentReadOnly()) {
110 if (StringUtils.isBlank(((DataField) parent).getBindingInfo().getBindingPath())
111 || ((DataField) parent).getBindingInfo().getBindingPath().equals("null")) {
112 return;
113 }
114
115 // check if field value is null, if so no inquiry
116 try {
117 Object propertyValue = ObjectPropertyUtils.getPropertyValue(model,
118 ((DataField) parent).getBindingInfo().getBindingPath());
119
120 if ((propertyValue == null) || StringUtils.isBlank(propertyValue.toString())) {
121 return;
122 }
123 } catch (Exception e) {
124 // if we can't get the value just swallow the exception and don't set an inquiry
125 return;
126 }
127 }
128
129 // Do checks for direct inquiry when editable
130 if (!isParentReadOnly() && parent instanceof InputField) {
131 if (!enableDirectInquiry) {
132 return;
133 }
134
135 // determine whether inquiry parameters will need adjusted
136 if (StringUtils.isBlank(getDataObjectClassName())
137 || (getInquiryParameters() == null)
138 || getInquiryParameters().isEmpty()) {
139 // if inquiry parameters not given, they will not be adjusted by super
140 adjustInquiryParameters = true;
141 fieldBindingInfo = ((InputField) parent).getBindingInfo();
142 }
143 }
144
145 setupLink(view, model, (DataField) parent);
146 }
147
148 /**
149 * Get parent object and field name and build the inquiry link
150 *
151 * <p>
152 * This was moved from the performFinalize because overlapping and to be used
153 * by DirectInquiry.
154 * </p>
155 *
156 * @param view Container View
157 * @param model model
158 * @param field The parent Attribute field
159 */
160 public void setupLink(View view, Object model, DataField field) {
161 String propertyName = field.getBindingInfo().getBindingName();
162
163 // if class and parameters configured, build link from those
164 if (StringUtils.isNotBlank(getDataObjectClassName()) && (getInquiryParameters() != null) &&
165 !getInquiryParameters().isEmpty()) {
166 Class<?> inquiryObjectClass;
167 try {
168 inquiryObjectClass = Class.forName(getDataObjectClassName());
169 } catch (ClassNotFoundException e) {
170 LOG.error("Unable to get class for: " + getDataObjectClassName());
171 throw new RuntimeException(e);
172 }
173
174 updateInquiryParameters(field.getBindingInfo());
175
176 buildInquiryLink(model, propertyName, inquiryObjectClass, getInquiryParameters());
177 }
178 // get inquiry class and parameters from view helper
179 else {
180 // get parent object for inquiry metadata
181 Object parentObject = ViewModelUtils.getParentObjectForMetadata(view, model, field);
182 view.getViewHelperService().buildInquiryLink(parentObject, propertyName, this);
183 }
184 }
185
186 /**
187 * Adjusts the path on the inquiry parameter property to match the binding
188 * path prefix of the given {@code BindingInfo}
189 *
190 * @param bindingInfo binding info instance to copy binding path prefix from
191 */
192 public void updateInquiryParameters(BindingInfo bindingInfo) {
193 Map<String, String> adjustedInquiryParameters = new HashMap<String, String>();
194 for (Entry<String, String> stringEntry : inquiryParameters.entrySet()) {
195 String toField = stringEntry.getValue();
196 String adjustedFromFieldPath = bindingInfo.getPropertyAdjustedBindingPath(stringEntry.getKey());
197
198 adjustedInquiryParameters.put(adjustedFromFieldPath, toField);
199 }
200
201 this.inquiryParameters = adjustedInquiryParameters;
202 }
203
204 /**
205 * Builds the inquiry link based on the given inquiry class and parameters
206 *
207 * @param dataObject parent object that contains the data (used to pull inquiry
208 * parameters)
209 * @param propertyName name of the property the inquiry is set on
210 * @param inquiryObjectClass class of the object the inquiry should point to
211 * @param inquiryParams map of key field mappings for the inquiry
212 */
213 public void buildInquiryLink(Object dataObject, String propertyName, Class<?> inquiryObjectClass,
214 Map<String, String> inquiryParams) {
215
216 Properties urlParameters = new Properties();
217
218 urlParameters.setProperty(UifParameters.DATA_OBJECT_CLASS_NAME, inquiryObjectClass.getName());
219 urlParameters.setProperty(UifParameters.METHOD_TO_CALL, UifConstants.MethodToCallNames.START);
220 if(StringUtils.isNotBlank(this.viewName)){
221 urlParameters.setProperty(UifParameters.VIEW_NAME, this.viewName);
222 }
223 // add inquiry specific parms to url
224 if (getInquiryLink().getLightBox() != null) {
225 getInquiryLink().getLightBox().setAddAppParms(true);
226 }
227
228 // configure inquiry when read only
229 if (isParentReadOnly()) {
230 for (Entry<String, String> inquiryParameter : inquiryParams.entrySet()) {
231 String parameterName = inquiryParameter.getKey();
232
233 Object parameterValue = ObjectPropertyUtils.getPropertyValue(dataObject, parameterName);
234
235 // TODO: need general format util that uses spring
236 if (parameterValue == null) {
237 parameterValue = "";
238 } else if (parameterValue instanceof java.sql.Date) {
239 if (Formatter.findFormatter(parameterValue.getClass()) != null) {
240 Formatter formatter = Formatter.getFormatter(parameterValue.getClass());
241 parameterValue = formatter.format(parameterValue);
242 }
243 } else {
244 parameterValue = parameterValue.toString();
245 }
246
247 // Encrypt value if it is a field that has restriction that prevents a value from being shown to
248 // user, because we don't want the browser history to store the restricted attributes value in the URL
249 if (KRADServiceLocatorWeb.getDataObjectAuthorizationService()
250 .attributeValueNeedsToBeEncryptedOnFormsAndLinks(inquiryObjectClass,
251 inquiryParameter.getValue())) {
252 try {
253 parameterValue = CoreApiServiceLocator.getEncryptionService().encrypt(parameterValue);
254 } catch (GeneralSecurityException e) {
255 throw new RuntimeException("Exception while trying to encrypted value for inquiry framework.",
256 e);
257 }
258 }
259
260 // add inquiry parameter to URL
261 urlParameters.put(inquiryParameter.getValue(), parameterValue);
262 }
263
264 /* build inquiry URL */
265 String inquiryUrl;
266
267 // check for EBOs for an alternate inquiry URL
268 ModuleService responsibleModuleService =
269 KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(inquiryObjectClass);
270 if (responsibleModuleService != null && responsibleModuleService.isExternalizable(inquiryObjectClass)) {
271 inquiryUrl = responsibleModuleService.getExternalizableDataObjectInquiryUrl(inquiryObjectClass,
272 urlParameters);
273 } else {
274 inquiryUrl = UrlFactory.parameterizeUrl(getBaseInquiryUrl(), urlParameters);
275 }
276
277 getInquiryLink().setHref(inquiryUrl);
278
279 // set inquiry title
280 String linkTitle = createTitleText(inquiryObjectClass);
281 linkTitle = LookupInquiryUtils.getLinkTitleText(linkTitle, inquiryObjectClass, getInquiryParameters());
282 getInquiryLink().setTitle(linkTitle);
283
284 setRender(true);
285 }
286 // configure direct inquiry when editable
287 else {
288 // Direct inquiry
289 String inquiryUrl = UrlFactory.parameterizeUrl(getBaseInquiryUrl(), urlParameters);
290
291 StringBuilder paramMapString = new StringBuilder();
292
293 // Build parameter string using the actual names of the fields as on the html page
294 for (Entry<String, String> inquiryParameter : inquiryParams.entrySet()) {
295 String inquiryParameterFrom = inquiryParameter.getKey();
296
297 if (adjustInquiryParameters && (fieldBindingInfo != null)) {
298 inquiryParameterFrom = fieldBindingInfo.getPropertyAdjustedBindingPath(inquiryParameterFrom);
299 }
300
301 paramMapString.append(inquiryParameterFrom);
302 paramMapString.append(":");
303 paramMapString.append(inquiryParameter.getValue());
304 paramMapString.append(",");
305 }
306 paramMapString.deleteCharAt(paramMapString.length() - 1);
307
308 // Check if lightbox is set. Get lightbox options.
309 String lightBoxOptions = "";
310 boolean lightBoxShow = (getInquiryLink().getLightBox() != null);
311 if (lightBoxShow) {
312 lightBoxOptions = getInquiryLink().getLightBox().getTemplateOptionsJSString();
313 }
314
315 // Create onlick script to open the inquiry window on the click event
316 // of the direct inquiry
317 StringBuilder onClickScript = new StringBuilder("showDirectInquiry(\"");
318 onClickScript.append(inquiryUrl);
319 onClickScript.append("\", \"");
320 onClickScript.append(paramMapString);
321 onClickScript.append("\", ");
322 onClickScript.append(lightBoxShow);
323 onClickScript.append(", ");
324 onClickScript.append(lightBoxOptions);
325 onClickScript.append(");");
326
327 directInquiryAction.setPerformDirtyValidation(false);
328 directInquiryAction.setActionScript(onClickScript.toString());
329
330 setRender(true);
331 }
332 }
333
334 /**
335 * Gets text to prepend to the inquiry link title
336 *
337 * @param dataObjectClass data object class being inquired into
338 * @return title prepend text
339 */
340 public String createTitleText(Class<?> dataObjectClass) {
341 String titleText = "";
342
343 String titlePrefixProp = CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
344 INQUIRY_TITLE_PREFIX);
345 if (StringUtils.isNotBlank(titlePrefixProp)) {
346 titleText += titlePrefixProp + " ";
347 }
348
349 String objectLabel = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDataObjectEntry(
350 dataObjectClass.getName()).getObjectLabel();
351 if (StringUtils.isNotBlank(objectLabel)) {
352 titleText += objectLabel + " ";
353 }
354
355 return titleText;
356 }
357
358 /**
359 * @see org.kuali.rice.krad.uif.component.ComponentBase#getComponentsForLifecycle()
360 */
361 @Override
362 public List<Component> getComponentsForLifecycle() {
363 List<Component> components = super.getComponentsForLifecycle();
364
365 components.add(getInquiryLink());
366 components.add(getDirectInquiryAction());
367
368 return components;
369 }
370
371 /**
372 * Returns the URL for the inquiry for which parameters will be added
373 *
374 * <p>
375 * The base URL includes the domain, context, and controller mapping for the inquiry invocation. Parameters are
376 * then added based on configuration to complete the URL. This is generally defaulted to the application URL and
377 * internal KRAD servlet mapping, but can be changed to invoke another application such as the Rice standalone
378 * server
379 * </p>
380 *
381 * @return inquiry base URL
382 */
383 @BeanTagAttribute(name="baseInquiryUrl")
384 public String getBaseInquiryUrl() {
385 return this.baseInquiryUrl;
386 }
387
388 /**
389 * Setter for the inquiry base url (domain, context, and controller)
390 *
391 * @param baseInquiryUrl
392 */
393 public void setBaseInquiryUrl(String baseInquiryUrl) {
394 this.baseInquiryUrl = baseInquiryUrl;
395 }
396
397 /**
398 * Full class name the inquiry should be provided for
399 *
400 * <p>
401 * This is passed on to the inquiry request for the data object the lookup should be rendered for. This is then
402 * used by the inquiry framework to select the lookup view (if more than one inquiry view exists for the same
403 * data object class name, the {@link #getViewName()} property should be specified to select the view to render).
404 * </p>
405 *
406 * @return inquiry class name
407 */
408 @BeanTagAttribute(name="dataObjectClassName")
409 public String getDataObjectClassName() {
410 return this.dataObjectClassName;
411 }
412
413 /**
414 * Setter for the class name that inquiry should be provided for
415 *
416 * @param dataObjectClassName
417 */
418 public void setDataObjectClassName(String dataObjectClassName) {
419 this.dataObjectClassName = dataObjectClassName;
420 }
421
422 /**
423 * When multiple target inquiry views exists for the same data object class, the view name can be set to
424 * determine which one to use
425 *
426 * <p>
427 * When creating multiple inquiry views for the same data object class, the view name can be specified for the
428 * different versions (for example 'simple' and 'advanced'). When multiple inquiry views exist the view name must
429 * be sent with the data object class for the request. Note the view id can be alternatively used to uniquely
430 * identify the inquiry view
431 * </p>
432 */
433 @BeanTagAttribute(name="viewName")
434 public String getViewName() {
435 return this.viewName;
436 }
437
438 /**
439 * Setter for the view name configured on the inquiry view that should be invoked by the inquiry widget
440 *
441 * @param viewName
442 */
443 public void setViewName(String viewName) {
444 this.viewName = viewName;
445 }
446
447 /**
448 * Map that determines what properties from a calling view will be sent to properties on the inquiry data object
449 *
450 * <p>
451 * When invoking an inquiry view, a query is done against the inquiries configured data object and the resulting
452 * record is display. The values for the properties configured within the inquiry parameters Map will be
453 * pulled and passed along as values for the inquiry data object properties (thus they form the criteria for
454 * the inquiry)
455 * </p>
456 *
457 * @return mapping of calling view properties to inquiry data object properties
458 */
459 @BeanTagAttribute(name="inquiryParameters",type= BeanTagAttribute.AttributeType.MAPVALUE)
460 public Map<String, String> getInquiryParameters() {
461 return this.inquiryParameters;
462 }
463
464 /**
465 * Setter for the map that determines what property values on the calling view will be sent to properties on the
466 * inquiry data object
467 *
468 * @param inquiryParameters
469 */
470 public void setInquiryParameters(Map<String, String> inquiryParameters) {
471 this.inquiryParameters = inquiryParameters;
472 }
473
474 /**
475 * {@code Link} that will be rendered for an inquiry
476 *
477 * @return the inquiry link
478 */
479 @BeanTagAttribute(name="inquiryLink",type= BeanTagAttribute.AttributeType.SINGLEBEAN)
480 public Link getInquiryLink() {
481 return this.inquiryLink;
482 }
483
484 /**
485 * Setter for the inquiry {@code Link}
486 *
487 * @param inquiryLink the inquiry {@link Link} object
488 */
489 public void setInquiryLink(Link inquiryLink) {
490 this.inquiryLink = inquiryLink;
491 }
492
493 /**
494 * {@code Action} that will be rendered next to the field for a direct inquiry
495 *
496 * @return the directInquiryAction
497 */
498 @BeanTagAttribute(name="directInquiryAction",type= BeanTagAttribute.AttributeType.SINGLEBEAN)
499 public Action getDirectInquiryAction() {
500 return this.directInquiryAction;
501 }
502
503 /**
504 * Setter for the direct inquiry {@code Action}
505 *
506 * @param directInquiryAction the direct inquiry {@link Action}
507 */
508 public void setDirectInquiryAction(Action directInquiryAction) {
509 this.directInquiryAction = directInquiryAction;
510 }
511
512 /**
513 * Indicates that the direct inquiry will not be rendered
514 *
515 * @return true if the direct inquiry should be rendered, false if not
516 */
517 @BeanTagAttribute(name="enableDirectInquiry")
518 public boolean isEnableDirectInquiry() {
519 return enableDirectInquiry;
520 }
521
522 /**
523 * Setter for the hideDirectInquiry flag
524 *
525 * @param enableDirectInquiry
526 */
527 public void setEnableDirectInquiry(boolean enableDirectInquiry) {
528 this.enableDirectInquiry = enableDirectInquiry;
529 }
530
531 /**
532 * Determines whether a normal or direct inquiry should be enabled
533 *
534 * @return true if parent component is read only, false otherwise
535 */
536 protected boolean isParentReadOnly() {
537 return parentReadOnly;
538 }
539
540 /**
541 * Determines whether a normal or direct inquiry should be enabled
542 *
543 * <p>
544 * Used by unit tests and internally
545 * </p>
546 *
547 * @param parentReadOnly true if parent component is read only, false otherwise
548 */
549 protected void setParentReadOnly(boolean parentReadOnly) {
550 this.parentReadOnly = parentReadOnly;
551 }
552 }