001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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/ecl1.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.core.web.ui;
017:
018: import java.util.ArrayList;
019: import java.util.Collection;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Map;
023:
024: import org.apache.commons.lang.StringUtils;
025: import org.kuali.RiceConstants;
026: import org.kuali.core.bo.BusinessObject;
027: import org.kuali.core.bo.Summarizable;
028: import org.kuali.core.datadictionary.CollectionDefinitionI;
029: import org.kuali.core.datadictionary.FieldDefinition;
030: import org.kuali.core.datadictionary.FieldDefinitionI;
031: import org.kuali.core.datadictionary.MaintainableCollectionDefinition;
032: import org.kuali.core.datadictionary.MaintainableFieldDefinition;
033: import org.kuali.core.datadictionary.MaintainableItemDefinition;
034: import org.kuali.core.datadictionary.MaintainableSectionDefinition;
035: import org.kuali.core.datadictionary.control.ApcSelectControlDefinition;
036: import org.kuali.core.datadictionary.control.ControlDefinition;
037: import org.kuali.core.datadictionary.mask.Mask;
038: import org.kuali.core.lookup.LookupUtils;
039: import org.kuali.core.lookup.keyvalues.ApcValuesFinder;
040: import org.kuali.core.lookup.keyvalues.KeyValuesFinder;
041: import org.kuali.core.lookup.valueFinder.ValueFinder;
042: import org.kuali.core.maintenance.Maintainable;
043: import org.kuali.core.util.FieldUtils;
044: import org.kuali.core.util.GlobalVariables;
045: import org.kuali.core.util.MaintenanceUtils;
046: import org.kuali.core.util.ObjectUtils;
047: import org.kuali.core.web.format.BooleanFormatter;
048: import org.kuali.core.web.format.Formatter;
049: import org.kuali.core.web.format.SummarizableFormatter;
050: import org.kuali.rice.KNSServiceLocator;
051:
052: public class FieldBridge {
053: private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
054: .getLogger(FieldBridge.class);
055:
056: /**
057: * This method creates a Field for an Inquiry Screen.
058: *
059: * @param field The field to populate.
060: * @param definition The DD specification for the field.
061: */
062: public static final void setupField(Field field,
063: FieldDefinitionI definition) {
064: if (definition instanceof MaintainableFieldDefinition) {
065:
066: MaintainableFieldDefinition maintainableFieldDefinition = ((MaintainableFieldDefinition) definition);
067: field.setFieldRequired(maintainableFieldDefinition
068: .isRequired());
069: field.setReadOnly(maintainableFieldDefinition.isReadOnly());
070:
071: // set onblur and callback functions
072: if (StringUtils.isNotBlank(maintainableFieldDefinition
073: .getWebUILeaveFieldFunction())) {
074: field.setWebOnBlurHandler(maintainableFieldDefinition
075: .getWebUILeaveFieldFunction());
076: }
077:
078: if (StringUtils.isNotBlank(maintainableFieldDefinition
079: .getWebUILeaveFieldCallbackFunction())) {
080: field
081: .setWebOnBlurHandlerCallback(maintainableFieldDefinition
082: .getWebUILeaveFieldCallbackFunction());
083: }
084:
085: }
086:
087: /* setup security of field (sensitive data) if needed, note this will always be true on old maintainables since
088: * maintenanceAction is not set
089: */
090: String displayEditMode = definition.getDisplayEditMode();
091: if (StringUtils.isNotBlank(displayEditMode)) {
092:
093: field.setSecure(true);
094: field.setDisplayEditMode(displayEditMode);
095: field.setDisplayMask(definition.getDisplayMask());
096:
097: }
098:
099: }
100:
101: /**
102: * Uses reflection to populate the rows of the inquiry from the business object value. Also formats if needed.
103: *
104: * @param field The Field to populate.
105: * @param bo The BusinessObject from which the Field will be popualated.
106: */
107: public static final void populateFieldFromBusinessObject(
108: Field field, BusinessObject bo) {
109: if (bo == null) {
110: throw new RuntimeException(
111: "Inquiry Business object is null.");
112: }
113:
114: field.setReadOnly(true); // inquiry fields are always read only
115:
116: Formatter formatter = field.getFormatter();
117: String propertyName = field.getPropertyName();
118:
119: // get the field type for the property
120: ControlDefinition fieldControl = KNSServiceLocator
121: .getDataDictionaryService()
122: .getAttributeControlDefinition(bo.getClass(),
123: propertyName);
124:
125: try {
126: String propValue = null;
127: Object prop = ObjectUtils.getPropertyValue(bo, field
128: .getPropertyName());
129:
130: // for boolean types always use BooleanFormatter
131: if (prop != null && prop instanceof Boolean) {
132: formatter = new BooleanFormatter();
133: }
134:
135: if (formatter == null && prop != null) {
136: Map<String, Class> references = KNSServiceLocator
137: .getPersistenceStructureService()
138: .getReferencesForForeignKey(bo.getClass(),
139: propertyName);
140: if (references != null && references.size() > 0) {
141: for (String fieldName : references.keySet()) {
142: if (propertyName.startsWith(fieldName)) {
143: Class referenceClass = references
144: .get(fieldName);
145: if (Summarizable.class
146: .isAssignableFrom(referenceClass)) {
147: prop = ObjectUtils.getPropertyValue(bo,
148: fieldName); // replace key with referenced object
149: formatter = new SummarizableFormatter();
150: }
151: }
152: }
153: }
154: }
155:
156: // for select fields, display the associated label (unless we've got a formatter from the reference found just above...)
157: if (fieldControl != null && fieldControl.isSelect()
158: && formatter == null) {
159: Class keyValuesFinderName = fieldControl
160: .getValuesFinderClass();
161: KeyValuesFinder finder = (KeyValuesFinder) keyValuesFinderName
162: .newInstance();
163:
164: propValue = lookupFinderValue(fieldControl, prop,
165: finder);
166: } else {
167: if (!ObjectUtils.isNull(prop)) {
168: if (formatter != null) {
169: propValue = (String) formatter.format(prop);
170: } else {
171: propValue = prop.toString();
172: }
173: } else {
174: propValue = RiceConstants.EMPTY_STRING;
175: }
176:
177: }
178:
179: // check if field contains sensitive data and user is authorized to see value
180: boolean viewAuthorized = KNSServiceLocator
181: .getAuthorizationService()
182: .isAuthorizedToViewAttribute(
183: GlobalVariables.getUserSession()
184: .getUniversalUser(),
185: bo.getClass().getName(), propertyName);
186: if (!viewAuthorized) {
187: // set mask as field value
188: Mask propertyMask = KNSServiceLocator
189: .getDataDictionaryService()
190: .getAttributeDisplayMask(bo.getClass(),
191: propertyName);
192: if (propertyMask == null) {
193: throw new RuntimeException(
194: "No mask specified for secure field.");
195: }
196:
197: field.setPropertyValue(propertyMask
198: .maskValue(propValue));
199: field.setDisplayMaskValue(propertyMask
200: .maskValue(propValue));
201:
202: } else {
203: field.setPropertyValue(propValue);
204: FieldUtils.setInquiryURL(field, bo, propertyName);
205: }
206:
207: // populatedColumns.add(col);
208: } catch (InstantiationException e) {
209: LOG.error("Unable to get instance of KeyValuesFinder: "
210: + e.getMessage());
211: throw new RuntimeException(
212: "Unable to get instance of KeyValuesFinder: "
213: + e.getMessage());
214: } catch (IllegalAccessException e) {
215: LOG.error("Unable to set columns: " + e.getMessage());
216: throw new RuntimeException("Unable to set columns: "
217: + e.getMessage());
218: }
219:
220: }
221:
222: /**
223: * This method looks up a value in a finder class.
224: * @param fieldControl the type of web control that is associated with this field.
225: * @param prop the property to look up - either a property name as a String, or a referenced object
226: * @param finder finder to look the value up in
227: * @return the value that was returned from the lookup
228: */
229: private static String lookupFinderValue(
230: ControlDefinition fieldControl, Object prop,
231: KeyValuesFinder finder) {
232: String propValue = null;
233:
234: if (finder != null) {
235: if (finder instanceof ApcValuesFinder
236: && fieldControl instanceof ApcSelectControlDefinition) {
237: ((ApcValuesFinder) finder)
238: .setParameterNamespace(((ApcSelectControlDefinition) fieldControl)
239: .getParameterNamespace());
240: ((ApcValuesFinder) finder)
241: .setParameterDetailType(((ApcSelectControlDefinition) fieldControl)
242: .getParameterDetailType());
243: ((ApcValuesFinder) finder)
244: .setParameterName(((ApcSelectControlDefinition) fieldControl)
245: .getParameterName());
246: }
247: }
248:
249: List keyValues = finder.getKeyValues();
250: if (prop != null) {
251: for (Iterator iter = keyValues.iterator(); iter.hasNext();) {
252: KeyLabelPair element = (KeyLabelPair) iter.next();
253: if (element.getKey().toString().equals(prop.toString())) {
254: propValue = element.getLabel();
255: }
256: }
257: }
258: return propValue;
259: }
260:
261: /**
262: * Determines whether field level help is enabled for the field corresponding to the businessObjectClass and attribute name
263: *
264: * If this value is true, then the field level help will be enabled.
265: * If false, then whether a field is enabled is determined by the value returned by {@link #isMaintenanceFieldLevelHelpDisabled(Maintainable, MaintainableFieldDefinition)}
266: * and the system-wide parameter setting. Note that if a field is read-only, that may cause field-level help to not be rendered.
267: *
268: * @param businessObjectClass the looked up class
269: * @param attributeName the attribute for the field
270: * @return true if field level help is enabled, false if the value of this method should NOT be used to determine whether this method's return value
271: * affects the enablement of field level help
272: */
273: protected static boolean isMaintenanceFieldLevelHelpEnabled(
274: Maintainable m, MaintainableFieldDefinition fieldDefinition) {
275: return false;
276: }
277:
278: /**
279: * Determines whether field level help is disabled for the field corresponding to the businessObjectClass and attribute name
280: *
281: * If this value is true and {@link #isMaintenanceFieldLevelHelpEnabled(Maintainable, MaintainableFieldDefinition)} returns false,
282: * then the field level help will not be rendered. If both this and {@link #isMaintenanceFieldLevelHelpEnabled(Maintainable, MaintainableFieldDefinition)} return false,
283: * then the system-wide setting will determine whether field level help is enabled. Note that if a field is read-only, that may cause
284: * field-level help to not be rendered.
285: *
286: * @param businessObjectClass the looked up class
287: * @param attributeName the attribute for the field
288: * @return true if field level help is disabled, false if the value of this method should NOT be used to determine whether this method's return value
289: * affects the enablement of field level help
290: */
291: protected static boolean isMaintenanceFieldLevelHelpDisabled(
292: Maintainable m, MaintainableFieldDefinition fieldDefinition) {
293: return false;
294: }
295:
296: /**
297: * This method creates a Field for display on a Maintenance Document.
298: *
299: * @param id The DD definition for the Field (can be a Collection).
300: * @param sd The DD definition for the Section in which the field will be displayed.
301: * @param o The BusinessObject will be populated from this BO.
302: * @param m
303: * @param s The Section in which the Field will be displayed.
304: * @param autoFillDefaultValues Should default values be filled in?
305: * @param autoFillBlankRequiredValues Should values be filled in for fields that are required but which were left blank when submitting the form from the UI?
306: * @param displayedFieldNames What fields are being displayed on the form in the UI?
307: *
308: * @return
309: *
310: * @throws InstantiationException
311: * @throws IllegalAccessException
312: */
313: public static final Field toField(MaintainableItemDefinition id,
314: MaintainableSectionDefinition sd, BusinessObject o,
315: Maintainable m, Section s, boolean autoFillDefaultValues,
316: boolean autoFillBlankRequiredValues,
317: List<String> displayedFieldNames)
318: throws InstantiationException, IllegalAccessException {
319: Field field = new Field();
320:
321: // if FieldDefiniton, simply add a Field UI object
322: if (id instanceof MaintainableFieldDefinition) {
323: MaintainableFieldDefinition maintainableFieldDefinition = (MaintainableFieldDefinition) id;
324: field = FieldUtils.getPropertyField(o.getClass(),
325: maintainableFieldDefinition.getName(), false);
326:
327: setupField(field, maintainableFieldDefinition);
328:
329: MaintenanceUtils.setFieldQuickfinder(o, field
330: .getPropertyName(), maintainableFieldDefinition,
331: field, displayedFieldNames, m);
332:
333: // set default value
334: //TODO St. Ailish says review this. A question was raised on 11-16-2006 Tuscon meeting as to why this is done here and not in the formatter.
335: if (autoFillDefaultValues) {
336: Object defaultValue = maintainableFieldDefinition
337: .getDefaultValue();
338: if (defaultValue != null) {
339: if (defaultValue.toString().equals("true")) {
340: defaultValue = "Yes";
341: } else if (defaultValue.toString().equals("false")) {
342: defaultValue = "No";
343: }
344: field.setPropertyValue(defaultValue);
345: }
346:
347: Class defaultValueFinderClass = maintainableFieldDefinition
348: .getDefaultValueFinderClass();
349: if (defaultValueFinderClass != null) {
350: field
351: .setPropertyValue(((ValueFinder) defaultValueFinderClass
352: .newInstance()).getValue());
353: }
354: }
355:
356: // if this flag is set, and the current field is required, and readonly, and blank, use the
357: // defaultValueFinder if one exists
358: if (autoFillBlankRequiredValues) {
359: if (maintainableFieldDefinition.isRequired()
360: && maintainableFieldDefinition.isReadOnly()) {
361: if (StringUtils.isBlank(field.getPropertyValue())) {
362: Class defaultValueFinderClass = maintainableFieldDefinition
363: .getDefaultValueFinderClass();
364: if (defaultValueFinderClass != null) {
365: field
366: .setPropertyValue(((ValueFinder) defaultValueFinderClass
367: .newInstance()).getValue());
368: }
369: }
370: }
371: }
372:
373: field
374: .setFieldLevelHelpEnabled(isMaintenanceFieldLevelHelpEnabled(
375: m, maintainableFieldDefinition));
376: field
377: .setFieldLevelHelpDisabled(isMaintenanceFieldLevelHelpDisabled(
378: m, maintainableFieldDefinition));
379: }
380:
381: return field;
382:
383: }
384:
385: /**
386: * This method will return a new form for adding in a BO for a collection.
387: * This should be customized in a subclass so the default behavior is to return nothing.
388: *
389: * @param collectionDefinition The DD definition for the Collection.
390: * @param o The BusinessObject form which the new Fields will be populated.
391: * @param m
392: * @param displayedFieldNames What Fields are being displayed on the form in the UI?
393: * @param containerRowErrorKey The error key for the Container/Collection used for displaying error messages.
394: * @param parents
395: * @param hideAdd Should the add line be hidden when displaying this Collection/Container in the UI?
396: * @param numberOfColumns How many columns the Fields in the Collection will be split into when displaying them in the UI.
397: *
398: * @return The List of new Fields.
399: */
400: public static final List<Field> getNewFormFields(
401: CollectionDefinitionI collectionDefinition,
402: BusinessObject o, Maintainable m,
403: List<String> displayedFieldNames,
404: StringBuffer containerRowErrorKey, String parents,
405: boolean hideAdd, int numberOfColumns) {
406: LOG.debug("getNewFormFields");
407: String collName = collectionDefinition.getName();
408:
409: List<Field> collFields = new ArrayList<Field>();
410: Collection<? extends FieldDefinitionI> collectionFields;
411: //Class boClass = collectionDefinition.getBusinessObjectClass();
412: BusinessObject collBO = null;
413: try {
414: collectionFields = collectionDefinition.getFields();
415: collBO = m.getNewCollectionLine(parents + collName);
416:
417: if (LOG.isDebugEnabled()) {
418: LOG.debug("newBO for add line: " + collBO);
419: }
420:
421: for (FieldDefinitionI fieldDefinition : collectionFields) {
422: // construct Field UI object from definition
423: Field collField = FieldUtils.getPropertyField(
424: collectionDefinition.getBusinessObjectClass(),
425: fieldDefinition.getName(), false);
426: FieldUtils.setInquiryURL(collField, o, fieldDefinition
427: .getName());
428:
429: if (fieldDefinition instanceof MaintainableFieldDefinition) {
430: setupField(
431: collField,
432: (MaintainableFieldDefinition) fieldDefinition);
433: }
434: //generate the error key for the add row
435: String[] nameParts = StringUtils.split(collField
436: .getPropertyName(), ".");
437: String fieldErrorKey = RiceConstants.MAINTENANCE_NEW_MAINTAINABLE
438: + RiceConstants.ADD_PREFIX + ".";
439: fieldErrorKey += collName + ".";
440: for (int i = 0; i < nameParts.length; i++) {
441: fieldErrorKey += nameParts[i];
442: containerRowErrorKey.append(fieldErrorKey);
443: if (i < nameParts.length) {
444: fieldErrorKey += ".";
445: containerRowErrorKey.append(",");
446: }
447: }
448:
449: // set the QuickFinderClass
450: BusinessObject collectionBoInstance = (BusinessObject) collectionDefinition
451: .getBusinessObjectClass().newInstance();
452: LookupUtils.setFieldQuickfinder(collectionBoInstance,
453: parents + collectionDefinition.getName(), true,
454: 0, fieldDefinition.getName(), collField,
455: displayedFieldNames, m);
456:
457: collFields.add(collField);
458: }
459:
460: } catch (InstantiationException e) {
461: LOG.error("Unable to create instance of object class"
462: + e.getMessage());
463: throw new RuntimeException(
464: "Unable to create instance of object class"
465: + e.getMessage());
466: } catch (IllegalAccessException e) {
467: LOG.error("Unable to create instance of object class"
468: + e.getMessage());
469: throw new RuntimeException(
470: "Unable to create instance of object class"
471: + e.getMessage());
472: }
473:
474: // populate field values from business object
475: collFields = FieldUtils.populateFieldsFromBusinessObject(
476: collFields, collBO);
477:
478: // need to append the prefix afterwards since the population command (above)
479: // does not handle the prefixes on the property names
480: for (Field field : collFields) {
481: // prefix name for add line
482: field.setPropertyName(RiceConstants.MAINTENANCE_ADD_PREFIX
483: + parents + collectionDefinition.getName() + "."
484: + field.getPropertyName());
485: }
486: LOG.debug("Error Key for section "
487: + collectionDefinition.getName() + " : "
488: + containerRowErrorKey.toString());
489: // get label for collection
490: String collectionLabel = KNSServiceLocator
491: .getDataDictionaryService().getCollectionLabel(
492: o.getClass(), collectionDefinition.getName());
493:
494: // retrieve the summary label either from the override or from the DD
495: String collectionElementLabel = collectionDefinition
496: .getSummaryTitle();
497: if (StringUtils.isEmpty(collectionElementLabel)) {
498: collectionElementLabel = KNSServiceLocator
499: .getDataDictionaryService()
500: .getCollectionElementLabel(
501: o.getClass().getName(),
502: collectionDefinition.getName(),
503: collectionDefinition
504: .getBusinessObjectClass());
505: }
506:
507: // container field
508: Field containerField;
509: containerField = FieldUtils.constructContainerField(collName,
510: collectionLabel, collFields, numberOfColumns);
511: if (StringUtils.isNotEmpty(collectionElementLabel)) {
512: containerField
513: .setContainerElementName(collectionElementLabel);
514: }
515: collFields = new ArrayList();
516: collFields.add(containerField);
517:
518: // field button for adding lines
519: if (!hideAdd) {
520: Field field = new Field();
521:
522: String addButtonName = RiceConstants.DISPATCH_REQUEST_PARAMETER
523: + "."
524: + RiceConstants.ADD_LINE_METHOD
525: + "."
526: + parents
527: + collectionDefinition.getName()
528: + "."
529: + RiceConstants.METHOD_TO_CALL_BOPARM_LEFT_DEL
530: + collectionDefinition.getBusinessObjectClass()
531: .getName()
532: + RiceConstants.METHOD_TO_CALL_BOPARM_RIGHT_DEL;
533: field.setPropertyName(addButtonName);
534: field.setFieldType(Field.IMAGE_SUBMIT);
535: field.setPropertyValue("images/tinybutton-add1.gif");
536: // collFields.add(field);
537: containerField.getContainerRows().add(new Row(field));
538: }
539:
540: if (collectionDefinition instanceof MaintainableCollectionDefinition) {
541: if (FieldUtils
542: .isCollectionMultipleLookupEnabled((MaintainableCollectionDefinition) collectionDefinition)) {
543: FieldUtils
544: .modifyFieldToSupportMultipleValueLookups(
545: containerField,
546: parents,
547: (MaintainableCollectionDefinition) collectionDefinition);
548: }
549: }
550:
551: return collFields;
552: }
553:
554: /**
555: * Call getNewFormFields with no parents.
556: *
557: * @see #getNewFormFields(CollectionDefinitionI, BusinessObject, Maintainable, List, StringBuffer, String, boolean, int)
558: */
559: public static final List<Field> getNewFormFields(
560: MaintainableCollectionDefinition collectionDefinition,
561: BusinessObject o, Maintainable m,
562: List<String> displayedFieldNames,
563: StringBuffer containerRowErrorKey, int numberOfColumns) {
564: String parent = "";
565: return getNewFormFields(collectionDefinition, o, m,
566: displayedFieldNames, containerRowErrorKey, parent,
567: false, numberOfColumns);
568: }
569:
570: /**
571: * Create a Field for display on an Inquiry screen.
572: *
573: * @param d The DD definition for the Field.
574: * @param o The BusinessObject from which the Field will be populated.
575: * @param s The Section in which the Field will be displayed.
576: *
577: * @return The populated Field.
578: */
579: public static final Field toField(FieldDefinition d,
580: BusinessObject o, Section s) {
581: Field field = new Field();
582: field = FieldUtils.getPropertyField(o.getClass(), d
583: .getAttributeName(), false);
584: field.setPropertyName(d.getAttributeName());
585: field.setBusinessObjectClassName(o.getClass().getName());
586: field.setFieldLabel(KNSServiceLocator
587: .getDataDictionaryService().getAttributeLabel(
588: o.getClass(), d.getAttributeName()));
589: FieldUtils.setInquiryURL(field, o, field.getPropertyName());
590:
591: Class formatterClass = KNSServiceLocator
592: .getDataDictionaryService().getAttributeFormatter(
593: o.getClass(), d.getAttributeName());
594: if (formatterClass != null) {
595: try {
596: field.setFormatter((Formatter) formatterClass
597: .newInstance());
598: } catch (InstantiationException e) {
599: LOG
600: .error("Unable to get new instance of formatter class: "
601: + formatterClass.getName());
602: throw new RuntimeException(
603: "Unable to get new instance of formatter class: "
604: + formatterClass.getName());
605: } catch (IllegalAccessException e) {
606: LOG
607: .error("Unable to get new instance of formatter class: "
608: + formatterClass.getName());
609: throw new RuntimeException(
610: "Unable to get new instance of formatter class: "
611: + formatterClass.getName());
612: }
613: }
614:
615: populateFieldFromBusinessObject(field, o);
616:
617: return field;
618:
619: }
620:
621: }
|