View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.service.impl;
17  
18  import com.google.common.collect.Sets;
19  import org.apache.commons.lang.ObjectUtils;
20  import org.apache.commons.lang.StringUtils;
21  import org.apache.log4j.Logger;
22  import org.kuali.rice.core.api.CoreApiServiceLocator;
23  import org.kuali.rice.core.api.config.property.ConfigurationService;
24  import org.kuali.rice.core.api.util.RiceKeyConstants;
25  import org.kuali.rice.core.api.util.io.SerializationUtils;
26  import org.kuali.rice.kim.api.identity.Person;
27  import org.kuali.rice.krad.bo.PersistableBusinessObjectBaseAdapter;
28  import org.kuali.rice.krad.data.DataObjectService;
29  import org.kuali.rice.krad.data.DataObjectWrapper;
30  import org.kuali.rice.krad.data.KradDataServiceLocator;
31  import org.kuali.rice.krad.inquiry.Inquirable;
32  import org.kuali.rice.krad.messages.MessageService;
33  import org.kuali.rice.krad.service.DataDictionaryService;
34  import org.kuali.rice.krad.service.KRADServiceLocator;
35  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
36  import org.kuali.rice.krad.service.LegacyDataAdapter;
37  import org.kuali.rice.krad.service.ModuleService;
38  import org.kuali.rice.krad.uif.UifConstants;
39  import org.kuali.rice.krad.uif.UifPropertyPaths;
40  import org.kuali.rice.krad.uif.component.BindingInfo;
41  import org.kuali.rice.krad.uif.component.Component;
42  import org.kuali.rice.krad.uif.component.PropertyReplacer;
43  import org.kuali.rice.krad.uif.component.RequestParameter;
44  import org.kuali.rice.krad.uif.container.CollectionGroup;
45  import org.kuali.rice.krad.uif.container.Container;
46  import org.kuali.rice.krad.uif.field.DataField;
47  import org.kuali.rice.krad.uif.layout.LayoutManager;
48  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
49  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
50  import org.kuali.rice.krad.uif.lifecycle.ViewPostMetadata;
51  import org.kuali.rice.krad.uif.service.ViewDictionaryService;
52  import org.kuali.rice.krad.uif.service.ViewHelperService;
53  import org.kuali.rice.krad.uif.util.BooleanMap;
54  import org.kuali.rice.krad.uif.util.CopyUtils;
55  import org.kuali.rice.krad.uif.util.LifecycleElement;
56  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
57  import org.kuali.rice.krad.uif.util.RecycleUtils;
58  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
59  import org.kuali.rice.krad.uif.view.ExpressionEvaluatorFactory;
60  import org.kuali.rice.krad.uif.view.RequestAuthorizationCache;
61  import org.kuali.rice.krad.uif.view.View;
62  import org.kuali.rice.krad.uif.view.ViewAuthorizer;
63  import org.kuali.rice.krad.uif.view.ViewModel;
64  import org.kuali.rice.krad.uif.view.ViewPresentationController;
65  import org.kuali.rice.krad.uif.widget.Inquiry;
66  import org.kuali.rice.krad.util.ErrorMessage;
67  import org.kuali.rice.krad.util.GlobalVariables;
68  import org.kuali.rice.krad.util.GrowlMessage;
69  import org.kuali.rice.krad.util.KRADConstants;
70  import org.kuali.rice.krad.util.KRADUtils;
71  import org.kuali.rice.krad.util.MessageMap;
72  import org.kuali.rice.krad.valuefinder.ValueFinder;
73  import org.kuali.rice.krad.web.form.UifFormBase;
74  import org.kuali.rice.krad.web.service.impl.CollectionControllerServiceImpl.CollectionActionParameters;
75  import org.springframework.beans.PropertyAccessorUtils;
76  
77  import java.io.Serializable;
78  import java.lang.annotation.Annotation;
79  import java.lang.reflect.Field;
80  import java.text.MessageFormat;
81  import java.util.ArrayList;
82  import java.util.Collection;
83  import java.util.Collections;
84  import java.util.HashMap;
85  import java.util.HashSet;
86  import java.util.LinkedList;
87  import java.util.List;
88  import java.util.Map;
89  import java.util.Map.Entry;
90  import java.util.Queue;
91  import java.util.Set;
92  
93  /**
94   * Default Implementation of {@code ViewHelperService}
95   *
96   * @author Kuali Rice Team (rice.collab@kuali.org)
97   */
98  public class ViewHelperServiceImpl implements ViewHelperService, Serializable {
99      private static final long serialVersionUID = 1772618197133239852L;
100     private static final Logger LOG = Logger.getLogger(ViewHelperServiceImpl.class);
101 
102     private transient ConfigurationService configurationService;
103     private transient DataDictionaryService dataDictionaryService;
104     private transient LegacyDataAdapter legacyDataAdapter;
105     private transient DataObjectService dataObjectService;
106     private transient ViewDictionaryService viewDictionaryService;
107     private transient ExpressionEvaluatorFactory expressionEvaluatorFactory;
108 
109     /**
110      * {@inheritDoc}
111      */
112     @Override
113     public void addCustomContainerComponents(ViewModel model, Container container) {
114 
115     }
116 
117     /**
118      * Finds the <code>Inquirable</code> configured for the given data object class and delegates to
119      * it for building the inquiry URL
120      *
121      * {@inheritDoc}
122      */
123     public void buildInquiryLink(Object dataObject, String propertyName, Inquiry inquiry) {
124         Inquirable inquirable = getViewDictionaryService().getInquirable(dataObject.getClass(), inquiry.getViewName());
125         if (inquirable != null) {
126             inquirable.buildInquirableLink(dataObject, propertyName, inquiry);
127         } else {
128             // TODO: should we really not render the inquiry just because the top parent doesn't have an inquirable?
129             // it is possible the path is nested and there does exist an inquiry for the property
130             // inquirable not found, no inquiry link can be set
131             inquiry.setRender(false);
132         }
133     }
134 
135     /**
136      * {@inheritDoc}
137      */
138     @Override
139     public void performCustomApplyModel(LifecycleElement element, Object model) {
140 
141     }
142 
143     /**
144      * {@inheritDoc}
145      */
146     @Override
147     public void performCustomFinalize(LifecycleElement element, Object model, LifecycleElement parent) {
148 
149     }
150 
151     /**
152      * {@inheritDoc}
153      */
154     @Override
155     public void performCustomInitialization(LifecycleElement element) {
156 
157     }
158 
159     /**
160      * {@inheritDoc}
161      */
162     @Override
163     public void performCustomViewFinalize(Object model) {
164 
165     }
166 
167     /**
168      * {@inheritDoc}
169      */
170     @Override
171     public void performCustomViewInitialization(Object model) {
172 
173     }
174 
175     /**
176      * {@inheritDoc}
177      */
178     @Override
179     public void processAfterAddLine(ViewModel model, Object lineObject, String collectionId, String collectionPath,
180             boolean isValidLine) {
181 
182     }
183 
184     /**
185      * {@inheritDoc}
186      */
187     @Override
188     public void processAfterDeleteLine(ViewModel model, String collectionId, String collectionPath, int lineIndex) {
189 
190     }
191 
192     /**
193      * {@inheritDoc}
194      */
195     @Override
196     public void processAfterSaveLine(ViewModel model, Object lineObject, String collectionId, String collectionPath) {
197 
198     }
199 
200     /**
201      * {@inheritDoc}
202      */
203     @Override
204     public void processBeforeAddLine(ViewModel model, Object addLine, String collectionId, String collectionPath) {
205 
206     }
207 
208     /**
209      * {@inheritDoc}
210      */
211     @Override
212     public void processBeforeSaveLine(ViewModel model, Object lineObject, String collectionId, String collectionPath) {
213 
214     }
215 
216     /**
217      * {@inheritDoc}
218      */
219     @Override
220     public void processBeforeEditLine(ViewModel model, Object lineObject, String collectionId, String collectionPath) {
221 
222     }
223 
224     /**
225      * {@inheritDoc}
226      */
227     @Override
228     public void processAfterEditLine(ViewModel model, Object lineObject, String collectionId, String collectionPath) {
229 
230     }
231 
232     /**
233      * {@inheritDoc}
234      */
235     @SuppressWarnings("unchecked")
236     @Override
237     public void processCollectionAddBlankLine(ViewModel model, String collectionId, String collectionPath) {
238         if (!(model instanceof ViewModel)) {
239             return;
240         }
241 
242         ViewModel viewModel = (ViewModel) model;
243 
244         if (collectionId == null) {
245             logAndThrowRuntime(
246                     "Unable to get collection group component for Id: " + collectionPath + " path: " + collectionPath);
247         }
248 
249         // get the collection instance for adding the new line
250         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(model, collectionPath);
251         if (collection == null) {
252             logAndThrowRuntime("Unable to get collection property from model for path: " + collectionPath);
253         }
254 
255         Class<?> collectionObjectClass = (Class<?>) viewModel.getViewPostMetadata().getComponentPostData(collectionId,
256                 UifConstants.PostMetadata.COLL_OBJECT_CLASS);
257         Object newLine = KRADUtils.createNewObjectFromClass(collectionObjectClass);
258 
259         List<Object> lineDataObjects = new ArrayList<Object>();
260         lineDataObjects.add(newLine);
261         viewModel.getViewPostMetadata().getAddedCollectionObjects().put(collectionId, lineDataObjects);
262         processAndAddLineObject(viewModel, newLine, collectionId, collectionPath);
263     }
264 
265     /**
266      * {@inheritDoc}
267      */
268     @SuppressWarnings("unchecked")
269     @Override
270     public void processCollectionAddLine(ViewModel model, String collectionId, String collectionPath) {
271         // now get the new line we need to add
272         BindingInfo addLineBindingInfo = (BindingInfo) model.getViewPostMetadata().getComponentPostData(
273                 collectionId, UifConstants.PostMetadata.ADD_LINE_BINDING_INFO);
274         Object addLine = ObjectPropertyUtils.getPropertyValue(model, addLineBindingInfo.getBindingPath());
275         if (addLine == null) {
276             logAndThrowRuntime("Add line instance not found for path: " + addLineBindingInfo.getBindingPath());
277         }
278 
279         processAndAddLineObject(model, addLine, collectionId, collectionPath);
280     }
281 
282     /**
283      * Do all processing related to adding a line: calls processBeforeAddLine, performAddLineValidation, addLine,
284      * processAfterAddLine
285      *
286      * @param viewModel object instance that contain's the view's data
287      * @param newLine the new line instance to be processed
288      * @param collectionId the id of the collection being added to
289      * @param collectionPath the path to the collection being modified
290      */
291     public void processAndAddLineObject(ViewModel viewModel, Object newLine, String collectionId, String collectionPath) {
292         String addLinePlacement = (String) viewModel.getViewPostMetadata().getComponentPostData(collectionId, UifConstants.PostMetadata.ADD_LINE_PLACEMENT);
293 
294         // get the collection instance for adding the new line
295         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(viewModel, collectionPath);
296         if (collection == null) {
297             logAndThrowRuntime("Unable to get collection property from model for path: " + collectionPath);
298         }
299 
300         processBeforeAddLine(viewModel, newLine, collectionId, collectionPath);
301 
302         boolean isValidLine = performAddLineValidation(viewModel, newLine, collectionId, collectionPath);
303         if (isValidLine) {
304 
305             // Adding an empty list because this item does not need to be further processed, but needs to init
306             // a new add line
307             List<Object> lineDataObjects = new ArrayList<Object>();
308             viewModel.getViewPostMetadata().getAddedCollectionObjects().put(collectionId, lineDataObjects);
309 
310             int addedIndex = addLine(collection, newLine, addLinePlacement.equals("TOP"));
311 
312             // now link the added line, this is important in situations where perhaps the collection element is
313             // bi-directional and needs to point back to it's parent
314             boolean linkToAddedCollection = linkAddedLine(viewModel, collectionPath, addedIndex);
315 
316             if (viewModel instanceof UifFormBase && linkToAddedCollection) {
317                 KRADServiceLocatorWeb.getLegacyDataAdapter().refreshAllNonUpdatingReferences(newLine);
318                 ((UifFormBase) viewModel).getAddedCollectionItems().add(newLine);
319             }
320             processAfterAddLine(viewModel, newLine, collectionId, collectionPath, isValidLine);
321         }
322     }
323 
324     /**
325      * {@inheritDoc}
326      */
327     @Override
328     public void processCollectionDeleteLine(ViewModel model, String collectionId, String collectionPath,
329             int lineIndex) {
330         // get the collection instance for deleting the line
331         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(model, collectionPath);
332         if (collection == null) {
333             logAndThrowRuntime("Unable to get collection property from model for path: " + collectionPath);
334         }
335 
336         // TODO: look into other ways of identifying a line so we can deal with
337         // unordered collections
338         if (collection instanceof List) {
339             Object deleteLine = ((List<Object>) collection).get(lineIndex);
340 
341             // validate the delete action is allowed for this line
342             boolean isValid = performDeleteLineValidation(model, collectionId, collectionPath, deleteLine);
343             if (isValid) {
344                 ((List<Object>) collection).remove(lineIndex);
345 
346                 String collectionLabel = (String) model.getViewPostMetadata().getComponentPostData(collectionId,
347                         UifConstants.PostMetadata.COLL_LABEL);
348                 GlobalVariables.getMessageMap().putInfoForSectionId(collectionId,
349                         RiceKeyConstants.MESSAGE_COLLECTION_LINE_DELETED, collectionLabel);
350 
351                 processAfterDeleteLine(model, collectionId, collectionPath, lineIndex);
352             }
353         } else {
354             logAndThrowRuntime("Only List collection implementations are supported for the delete by index method");
355         }
356     }
357 
358     /**
359      * {@inheritDoc}
360      */
361     @Override
362     public void processCollectionRetrieveEditLineDialog(ViewModel model, String collectionId, String collectionPath,
363             int selectedLineIndex) {
364         String objectPath = collectionPath + "[" + selectedLineIndex + "]";
365 
366         // get the line instance for editing the line
367         Object dataObject = ObjectPropertyUtils.getPropertyValue(model, objectPath);
368         if (dataObject == null) {
369             logAndThrowRuntime("Unable to get collection property from model for path: " + objectPath);
370         }
371 
372         // don't update the dialog object unless its null cause it means there are unsaved changes
373         if (((UifFormBase) model).getDialogDataObject() == null) {
374             ((UifFormBase) model).setDialogDataObject(SerializationUtils.deepCopy((Serializable) dataObject));
375         }
376     }
377 
378     /**
379      * {@inheritDoc}
380      */
381     @Override
382     public void processCollectionEditLine(ViewModel model, CollectionActionParameters parameterData) {
383         String collectionId = parameterData.getSelectedCollectionId();
384         String collectionPath = parameterData.getSelectedCollectionPath();
385         int selectedLineIndex = parameterData.getSelectedLineIndex();
386 
387         // get the collection instance for editing the line
388         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(model, collectionPath);
389         if (collection == null) {
390             logAndThrowRuntime("Unable to get collection property from model for path: " + collectionPath);
391         }
392 
393         // save the dialog data object to the current line
394         if (collection instanceof List) {
395             Object editLine = ((List<Object>) collection).get(selectedLineIndex);
396             Object dialogDataObject = ((UifFormBase) model).getDialogDataObject();
397 
398             if (dialogDataObject != null) {
399                 editLine = SerializationUtils.deepCopy((Serializable) dialogDataObject);
400                 ((UifFormBase) model).setDialogDataObject(null);
401             }
402 
403             processBeforeEditLine(model, editLine, collectionId, collectionPath);
404 
405             ((List<Object>) collection).remove(selectedLineIndex);
406             ((List<Object>) collection).add(selectedLineIndex, editLine);
407 
408             processAfterEditLine(model, editLine, collectionId, collectionPath);
409 
410         } else {
411             logAndThrowRuntime("Only List collection implementations are supported for the edit by index method");
412         }
413     }
414 
415     /**
416      * {@inheritDoc}
417      */
418     @Override
419     public void processCollectionCloseEditLineDialog(ViewModel model, String collectionId, String collectionPath,
420             int selectedLineIndex) {
421         String objectPath = collectionPath + "[" + selectedLineIndex + "]";
422 
423         // get the line instance for editing the line
424         Object dataObject = ObjectPropertyUtils.getPropertyValue(model, objectPath);
425         if (dataObject == null) {
426             logAndThrowRuntime("Unable to get collection property from model for path: " + objectPath);
427         }
428         ((UifFormBase) model).setDialogDataObject(null);
429     }
430 
431     /**
432      * {@inheritDoc}
433      */
434     @Override
435     public void processCollectionSaveLine(ViewModel model, CollectionActionParameters parameterData) {
436         String collectionId = parameterData.getSelectedCollectionId();
437         String collectionPath = parameterData.getSelectedCollectionPath();
438         int selectedLineIndex = parameterData.getSelectedLineIndex();
439 
440         Map<String, String[]> parameters = parameterData.getParameters();
441         final ViewPostMetadata viewPostMetadata = model.getViewPostMetadata();
442         BindingInfo addLineBindingInfo = (BindingInfo) viewPostMetadata.getComponentPostData(collectionId, UifConstants.PostMetadata.BINDING_INFO);
443         List<Object> collection = ObjectPropertyUtils.getPropertyValue(model, addLineBindingInfo.getBindingPath());
444 
445         Object saveLine = extractNewValuesAndAssign(collectionPath, selectedLineIndex, parameters, collection);
446 
447         processBeforeSaveLine(model, saveLine, collectionId, collectionPath);
448         boolean isValidLine = performAddLineValidation(model, saveLine, collectionId, collectionPath);
449         if (isValidLine) {
450             collection.set(selectedLineIndex, saveLine);
451             KRADServiceLocatorWeb.getLegacyDataAdapter().refreshAllNonUpdatingReferences(saveLine);
452             processAfterSaveLine(model, saveLine, collectionId, collectionPath);
453         }
454     }
455 
456     /**
457      * Gets the current object from the collection and assigns the new values to it.
458      *
459      * @param collectionPath the path to the collection being modified
460      * @param selectedLineIndex The index within the collection of the line to save.
461      * @param parameters the path to the collection being modified
462      * @param collection the collection of the lines of data
463      * @return true if the action is allowed and the line should be removed, false if the line should not be removed
464      */
465     protected Object extractNewValuesAndAssign(String collectionPath, int selectedLineIndex, Map<String, String[]> parameters, List<Object> collection) {
466         String[] fieldList = new String[]{"field1", "field2", "field3", "field4", "field5", "field6"};
467         Object saveLine = collection.get(selectedLineIndex);
468         for(String field : fieldList){
469             String index = String.format("%s[%s].%s", collectionPath, selectedLineIndex, field);
470             String fieldValue = extractSingleValue(parameters.get(index));
471             setValue(saveLine, field, fieldValue);
472         }
473         return saveLine;
474     }
475 
476     /**
477      * Set the private field of an object to a particular value using reflection
478      *
479      * @param object object the object to set
480      * @param fieldName the name of the field in the object to set
481      * @param value the value of the field in the object to set
482      */
483     private void setValue(Object object, String fieldName, Object value){
484         try {
485             Class<?> clazz = object.getClass();
486             Field field = clazz.getDeclaredField(fieldName);
487             field.setAccessible(true);
488             field.set(object, value);
489         } catch(Exception e){
490             LOG.error("Unable to access private variable "+fieldName+" in object " + object.getClass()+". " + e.getMessage());
491         }
492     }
493 
494     /**
495      * Extract a single value form an array of strings
496      *
497      * @param data the array of strings
498      */
499     protected String extractSingleValue(String[] data){
500         if (data == null) return null;
501         if (data.length < 1) return null;
502         return data[0];
503     }
504 
505     /**
506      * {@inheritDoc}
507      */
508     @SuppressWarnings("unchecked")
509     public void processMultipleValueLookupResults(ViewModel model, String collectionId, String collectionPath,
510             String multiValueReturnFields, String lookupResultValues) {
511         // if no line values returned, no population is needed
512         if (StringUtils.isBlank(lookupResultValues) || !(model instanceof ViewModel)) {
513             return;
514         }
515 
516         ViewModel viewModel = (ViewModel) model;
517 
518         if (StringUtils.isBlank(collectionId)) {
519             throw new RuntimeException(
520                     "Id is not set for this collection lookup: " + collectionId + ", " + "path: " + collectionPath);
521         }
522 
523         // retrieve the collection group so we can get the collection class and collection lookup
524         Class<?> collectionObjectClass = (Class<?>) viewModel.getViewPostMetadata().getComponentPostData(collectionId,
525                 UifConstants.PostMetadata.COLL_OBJECT_CLASS);
526         Collection<Object> collection = ObjectPropertyUtils.getPropertyValue(model, collectionPath);
527         if (collection == null) {
528             Class<?> collectionClass = ObjectPropertyUtils.getPropertyType(model, collectionPath);
529             collection = (Collection<Object>) KRADUtils.createNewObjectFromClass(collectionClass);
530             ObjectPropertyUtils.setPropertyValue(model, collectionPath, collection);
531         }
532 
533         // get the field conversions
534         Map<String, String> fieldConversions =
535                 (Map<String, String>) viewModel.getViewPostMetadata().getComponentPostData(collectionId,
536                         UifConstants.PostMetadata.COLL_LOOKUP_FIELD_CONVERSIONS);
537 
538         // filter the field conversions by what was returned from the multi value lookup return fields
539         Map <String, String> returnedFieldConversions = filterByReturnedFieldConversions(multiValueReturnFields,
540                 fieldConversions);
541 
542         List<String> toFieldNamesColl = new ArrayList<String>(returnedFieldConversions.values());
543         Collections.sort(toFieldNamesColl);
544         String[] toFieldNames = new String[toFieldNamesColl.size()];
545         toFieldNamesColl.toArray(toFieldNames);
546 
547         // first split to get the line value sets
548         String[] lineValues = StringUtils.split(lookupResultValues, ",");
549 
550         List<Object> lineDataObjects = new ArrayList<Object>();
551         // for each returned set create a new instance of collection class and populate with returned line values
552         for (String lineValue : lineValues) {
553             Object lineDataObject = null;
554 
555             // TODO: need to put this in data object service so logic can be reused
556             ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(
557                     collectionObjectClass);
558             if (moduleService != null && moduleService.isExternalizable(collectionObjectClass)) {
559                 lineDataObject = moduleService.createNewObjectFromExternalizableClass(collectionObjectClass.asSubclass(
560                         org.kuali.rice.krad.bo.ExternalizableBusinessObject.class));
561             } else {
562                 lineDataObject = KRADUtils.createNewObjectFromClass(collectionObjectClass);
563             }
564 
565             String[] fieldValues = StringUtils.splitByWholeSeparatorPreserveAllTokens(lineValue, ":");
566             if (fieldValues.length != toFieldNames.length) {
567                 throw new RuntimeException(
568                         "Value count passed back from multi-value lookup does not match field conversion count");
569             }
570 
571             // set each field value on the line
572             for (int i = 0; i < fieldValues.length; i++) {
573                 String fieldName = toFieldNames[i];
574                 ObjectPropertyUtils.setPropertyValue(lineDataObject, fieldName, fieldValues[i]);
575             }
576 
577             lineDataObjects.add(lineDataObject);
578             processAndAddLineObject(viewModel, lineDataObject, collectionId, collectionPath);
579         }
580 
581         viewModel.getViewPostMetadata().getAddedCollectionObjects().put(collectionId, lineDataObjects);
582     }
583 
584     /**
585      * Add addLine to collection while giving derived classes an opportunity to override for things
586      * like sorting.
587      *
588      * @param collection the Collection to add the given addLine to
589      * @param addLine the line to add to the given collection
590      * @param insertFirst indicates if the item should be inserted as the first item
591      *
592      * @return the index at which the item was added to the collection, or -1 if it was not added
593      */
594     protected int addLine(Collection<Object> collection, Object addLine, boolean insertFirst) {
595         int index = -1;
596         if (insertFirst && (collection instanceof List)) {
597             ((List<Object>) collection).add(0, addLine);
598             index = 0;
599         } else {
600             boolean added = collection.add(addLine);
601             if (added) {
602                 index = collection.size() - 1;
603             }
604         }
605         return index;
606     }
607 
608     /**
609      * Set the parent for bi-directional relationships when adding a line to a collection.
610      *
611      * @param model the view data
612      * @param collectionPath the path to the collection being linked
613      * @param addedIndex the index of the added line
614      * @return whether the linked line needs further processing
615      */
616     protected boolean linkAddedLine(Object model, String collectionPath, int addedIndex) {
617         int lastSepIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(collectionPath);
618         if (lastSepIndex != -1) {
619             String collectionParentPath = collectionPath.substring(0, lastSepIndex);
620             Object parent = ObjectPropertyUtils.getPropertyValue(model, collectionParentPath);
621             if (parent != null && getDataObjectService().supports(parent.getClass())) {
622                 DataObjectWrapper<?> wrappedParent = getDataObjectService().wrap(parent);
623                 String collectionName = collectionPath.substring(lastSepIndex + 1);
624                 wrappedParent.linkChanges(Sets.newHashSet(collectionName + "[" + addedIndex + "]"));
625             }
626 
627             // check if its edit line in dialog line action, and if it is we want to set the collection as
628             // the dialog's parent and do not want further processing of the dialog object
629             if (collectionParentPath.equalsIgnoreCase(UifPropertyPaths.DIALOG_DATA_OBJECT)) {
630                 ((UifFormBase) model).setDialogDataObject(parent);
631                 return false;
632             }
633         }
634         return true;
635     }
636 
637     /**
638      * Performs validation on the new collection line before it is added to the corresponding collection.
639      *
640      * @param viewModel object instance that contain's the view's data
641      * @param newLine the new line instance to be processed
642      * @param collectionId the id of the collection being added to
643      * @param collectionPath the path to the collection being modified
644      */
645     protected boolean performAddLineValidation(ViewModel viewModel, Object newLine, String collectionId,
646             String collectionPath) {
647         boolean isValid = true;
648 
649         Collection<Object> collectionItems = ObjectPropertyUtils.getPropertyValue(viewModel, collectionPath);
650 
651         if (viewModel.getViewPostMetadata().getComponentPostData(collectionId,
652                 UifConstants.PostMetadata.DUPLICATE_LINE_PROPERTY_NAMES) == null) {
653             return isValid;
654         }
655 
656         List<String> duplicateLinePropertyNames = (List<String>) viewModel.getViewPostMetadata().getComponentPostData(
657                 collectionId, UifConstants.PostMetadata.DUPLICATE_LINE_PROPERTY_NAMES);
658 
659         String collectionLabel = null;
660         if (viewModel.getViewPostMetadata().getComponentPostData(collectionId, UifConstants.PostMetadata.COLL_LABEL)
661                 != null) {
662             collectionLabel = (String) viewModel.getViewPostMetadata().getComponentPostData(collectionId,
663                     UifConstants.PostMetadata.COLL_LABEL);
664         }
665 
666         String duplicateLineLabelString = null;
667         if (viewModel.getViewPostMetadata().getComponentPostData(collectionId,
668                 UifConstants.PostMetadata.DUPLICATE_LINE_LABEL_STRING) != null) {
669             duplicateLineLabelString = (String) viewModel.getViewPostMetadata().getComponentPostData(collectionId,
670                     UifConstants.PostMetadata.DUPLICATE_LINE_LABEL_STRING);
671         }
672 
673         if (containsDuplicateLine(newLine, collectionItems, duplicateLinePropertyNames)) {
674             isValid = false;
675             String propertyId = UifPropertyPaths.NEW_COLLECTION_LINES + "['" + collectionPath + "']." +
676                         duplicateLinePropertyNames.get(0);
677             GlobalVariables.getMessageMap().putError(propertyId, RiceKeyConstants.ERROR_DUPLICATE_ELEMENT,
678                         collectionLabel, duplicateLineLabelString);
679         }
680 
681         return isValid;
682     }
683 
684     /**
685      * Filters the field conversions by the multi value return fields
686      * @param multiValueReturnFields the return fields to filter by, as a comma separated string
687      * @param fieldConversions the map of field conversions to filter
688      * @return a {@link java.util.Map} containing the filtered field conversions
689      */
690     protected Map<String, String> filterByReturnedFieldConversions(String multiValueReturnFields,
691             Map<String, String> fieldConversions) {
692 
693         Map <String, String> returnedFieldConversions = new HashMap<String, String>();
694         returnedFieldConversions.putAll(fieldConversions);
695 
696         // parse the multi value return fields string
697         String[] returnedFieldsStrArr = StringUtils.split(multiValueReturnFields, ",");
698         // iterate over the returned fields and get the conversion values.
699         if (returnedFieldsStrArr != null && returnedFieldsStrArr.length > 0) {
700             returnedFieldConversions.clear();
701             for (String fieldConversion : returnedFieldsStrArr) {
702                 if (fieldConversions.containsKey(fieldConversion)) {
703                     returnedFieldConversions.put(fieldConversion, fieldConversions.get(fieldConversion));
704                 }
705             }
706         }
707 
708         return returnedFieldConversions;
709     }
710 
711     /**
712      * Determines whether the new line matches one of the entries in the existing collection, based on the
713      * {@code duplicateLinePropertyNames}.
714      *
715      * @param addLine new line instance to validate
716      * @param collectionItems items in the collection
717      * @param duplicateLinePropertyNames property names to check for duplicates
718      * @return true if there is a duplicate line, false otherwise
719      */
720     protected boolean containsDuplicateLine(Object addLine, Collection<Object> collectionItems,
721             List<String> duplicateLinePropertyNames) {
722         if (collectionItems.isEmpty() || duplicateLinePropertyNames.isEmpty()) {
723             return false;
724         }
725 
726         for (Object collectionItem : collectionItems) {
727             if (isDuplicateLine(addLine, collectionItem, duplicateLinePropertyNames)) {
728                 return true;
729             }
730         }
731 
732         return false;
733     }
734 
735     /**
736      * Determines whether the new {@code addLine} is a duplicate of {@code collectionItem}, based on the
737      * {@code duplicateLinePropertyNames}.
738      *
739      * @param addLine new line instance to validate
740      * @param collectionItem existing instance to validate
741      * @param duplicateLinePropertyNames the property names to check for duplicates
742      * @return true if {@code addLine} is a duplicate of {@code collectionItem}, false otherwise
743      */
744     protected boolean isDuplicateLine(Object addLine, Object collectionItem, List<String> duplicateLinePropertyNames) {
745         if (duplicateLinePropertyNames.isEmpty()) {
746             return false;
747         }
748 
749         for (String duplicateLinePropertyName : duplicateLinePropertyNames) {
750             Object addLinePropertyValue = ObjectPropertyUtils.getPropertyValue(addLine, duplicateLinePropertyName);
751             Object duplicateLinePropertyValue = ObjectPropertyUtils.getPropertyValue(collectionItem,
752                     duplicateLinePropertyName);
753 
754             if (!ObjectUtils.equals(addLinePropertyValue, duplicateLinePropertyValue)) {
755                 return false;
756             }
757         }
758 
759         return true;
760     }
761 
762     /**
763      * Performs validation on the collection line before it is removed from the corresponding collection.
764      *
765      * @param model object instance that contain's the view's data
766      * @param collectionId the id of the collection being added to
767      * @param collectionPath the path to the collection being modified
768      * @param deleteLine line that will be removed
769      * @return true if the action is allowed and the line should be removed, false if the line should not be removed
770      */
771     protected boolean performDeleteLineValidation(ViewModel model, String collectionId, String collectionPath,
772             Object deleteLine) {
773         return true;
774     }
775 
776     /**
777      * {@inheritDoc}
778      */
779     @Override
780     public void applyDefaultValuesForCollectionLine(CollectionGroup collectionGroup, Object line) {
781         // retrieve all data fields for the collection line
782         List<DataField> dataFields = ViewLifecycleUtils.getElementsOfTypeDeep(collectionGroup.getAddLineItems(),
783                 DataField.class);
784         for (DataField dataField : dataFields) {
785             String bindingPath = "";
786             if (StringUtils.isNotBlank(dataField.getBindingInfo().getBindByNamePrefix())) {
787                 bindingPath = dataField.getBindingInfo().getBindByNamePrefix() + ".";
788             }
789             bindingPath += dataField.getBindingInfo().getBindingName();
790 
791             populateDefaultValueForField(line, dataField, bindingPath);
792         }
793     }
794 
795     /**
796      * {@inheritDoc}
797      */
798     @Override
799     public void applyDefaultValues(Component component) {
800         if (component == null) {
801             return;
802         }
803 
804         View view = ViewLifecycle.getView();
805         Object model = ViewLifecycle.getModel();
806 
807         @SuppressWarnings("unchecked") Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance(
808                 LinkedList.class);
809         elementQueue.offer(component);
810         try {
811             while (!elementQueue.isEmpty()) {
812                 LifecycleElement currentElement = elementQueue.poll();
813 
814                 // if component is a data field apply default value
815                 if (currentElement instanceof DataField) {
816                     DataField dataField = ((DataField) currentElement);
817 
818                     // need to make sure binding is initialized since this could be on a page we have not initialized yet
819                     dataField.getBindingInfo().setDefaults(view, dataField.getPropertyName());
820 
821                     populateDefaultValueForField(model, dataField, dataField.getBindingInfo().getBindingPath());
822                 }
823 
824                 elementQueue.addAll(ViewLifecycleUtils.getElementsForLifecycle(currentElement).values());
825             }
826         } finally {
827             elementQueue.clear();
828             RecycleUtils.recycle(elementQueue);
829         }
830     }
831 
832     /**
833      * {@inheritDoc}
834      */
835     @Override
836     public void populateViewFromRequestParameters(Map<String, String> parameters) {
837         View view = ViewLifecycle.getView();
838 
839         // build Map of property replacers by property name so that we can remove them
840         // if the property was set by a request parameter
841         Map<String, Set<PropertyReplacer>> viewPropertyReplacers = new HashMap<String, Set<PropertyReplacer>>();
842         List<PropertyReplacer> propertyReplacerSource = view.getPropertyReplacers();
843         if (propertyReplacerSource != null) {
844             for (PropertyReplacer replacer : propertyReplacerSource) {
845                 String replacerPropertyName = replacer.getPropertyName();
846                 Set<PropertyReplacer> propertyReplacers = viewPropertyReplacers.get(replacerPropertyName);
847 
848                 if (propertyReplacers == null) {
849                     viewPropertyReplacers.put(replacerPropertyName,
850                             propertyReplacers = new HashSet<PropertyReplacer>());
851                 }
852 
853                 propertyReplacers.add(replacer);
854             }
855         }
856 
857         Map<String, Annotation> annotatedFields = CopyUtils.getFieldsWithAnnotation(view.getClass(),
858                 RequestParameter.class);
859 
860         // for each request parameter allowed on the view, if the request contains a value use
861         // to set on View, and clear and conditional expressions or property replacers for that field
862         Map<String, String> viewRequestParameters = new HashMap<String, String>();
863         for (String fieldToPopulate : annotatedFields.keySet()) {
864             RequestParameter requestParameter = (RequestParameter) annotatedFields.get(fieldToPopulate);
865 
866             // use specified parameter name if given, else use field name to retrieve parameter value
867             String requestParameterName = requestParameter.parameterName();
868             if (StringUtils.isBlank(requestParameterName)) {
869                 requestParameterName = fieldToPopulate;
870             }
871 
872             if (!parameters.containsKey(requestParameterName)) {
873                 continue;
874             }
875 
876             String fieldValue = parameters.get(requestParameterName);
877             if (StringUtils.isNotBlank(fieldValue)) {
878                 viewRequestParameters.put(requestParameterName, fieldValue);
879                 ObjectPropertyUtils.setPropertyValue(view, fieldToPopulate, fieldValue);
880 
881                 // remove any conditional configuration so value is not
882                 // overridden later during the apply model phase
883                 if (view.getPropertyExpressions().containsKey(fieldToPopulate)) {
884                     view.getPropertyExpressions().remove(fieldToPopulate);
885                 }
886 
887                 if (viewPropertyReplacers.containsKey(fieldToPopulate)) {
888                     Set<PropertyReplacer> propertyReplacers = viewPropertyReplacers.get(fieldToPopulate);
889                     for (PropertyReplacer replacer : propertyReplacers) {
890                         view.getPropertyReplacers().remove(replacer);
891                     }
892                 }
893             }
894         }
895 
896         view.setViewRequestParameters(viewRequestParameters);
897     }
898 
899     /**
900      * {@inheritDoc}
901      */
902     @Override
903     public String buildGrowlScript() {
904         View view = ViewLifecycle.getView();
905         String growlScript = "";
906 
907         MessageService messageService = KRADServiceLocatorWeb.getMessageService();
908 
909         MessageMap messageMap = GlobalVariables.getMessageMap();
910         for (GrowlMessage growl : messageMap.getGrowlMessages()) {
911             if (view.isGrowlMessagingEnabled()) {
912                 String message = messageService.getMessageText(growl.getNamespaceCode(), growl.getComponentCode(),
913                         growl.getMessageKey());
914 
915                 if (StringUtils.isNotBlank(message)) {
916                     if (growl.getMessageParameters() != null) {
917                         message = message.replace("'", "''");
918                         message = MessageFormat.format(message, (Object[]) growl.getMessageParameters());
919                     }
920 
921                     // escape single quotes in message or title since that will cause problem with plugin
922                     message = message.replace("'", "\\'");
923 
924                     String title = growl.getTitle();
925                     if (StringUtils.isNotBlank(growl.getTitleKey())) {
926                         title = messageService.getMessageText(growl.getNamespaceCode(), growl.getComponentCode(),
927                                 growl.getTitleKey());
928                     }
929                     title = title.replace("'", "\\'");
930 
931                     growlScript =
932                             growlScript + "showGrowl('" + message + "', '" + title + "', '" + growl.getTheme() + "');";
933                 }
934             } else {
935                 ErrorMessage infoMessage = new ErrorMessage(growl.getMessageKey(), growl.getMessageParameters());
936                 infoMessage.setNamespaceCode(growl.getNamespaceCode());
937                 infoMessage.setComponentCode(growl.getComponentCode());
938 
939                 messageMap.putInfoForSectionId(KRADConstants.GLOBAL_INFO, infoMessage);
940             }
941         }
942 
943         return growlScript;
944     }
945 
946     /**
947      * {@inheritDoc}
948      */
949     @Override
950     public void populateDefaultValueForField(Object object, DataField dataField, String bindingPath) {
951 
952         try{
953             if (!ObjectPropertyUtils.isReadableProperty(object, bindingPath) || !ObjectPropertyUtils.isWritableProperty(
954                     object, bindingPath)) {
955                 return;
956             }
957         }catch(RuntimeException e){
958             /*
959               ApplyDefaultValues is run even on components with skipLifeCycle phase set to true. This can lead to cases
960               where the Object may not be present on the model causing ObjectPropertyUtils to throw an exception. For
961               more details and a specific case refer to KULRICE-14084
962              */
963             LOG.warn("No property with binding path '" +bindingPath+"' is readable on '"+object+"'"+e.getMessage() );
964             return;
965         }
966 
967         // check if we have a current value
968         if (ObjectPropertyUtils.getPropertyValue(object, bindingPath) != null) {
969             return;
970         }
971 
972         Object defaultValue = getDefaultValueForField(object, dataField);
973 
974         if (defaultValue != null) {
975             ObjectPropertyUtils.setPropertyValue(object, bindingPath, defaultValue);
976         }
977     }
978 
979     /**
980      * {@inheritDoc}
981      */
982     @Override
983     public Object getDefaultValueForField(Object object, DataField dataField) {
984         View view = ViewLifecycle.getView();
985         Object defaultValue = null;
986 
987         // if dataField.defaultValue is not null and not empty empty string use it
988         if (dataField.getDefaultValue() != null && !(dataField.getDefaultValue() instanceof String && StringUtils
989                 .isBlank((String) dataField.getDefaultValue()))) {
990             defaultValue = dataField.getDefaultValue();
991         } else if ((dataField.getExpressionGraph() != null) && dataField.getExpressionGraph().containsKey(
992                 UifConstants.ComponentProperties.DEFAULT_VALUE)) {
993             defaultValue = dataField.getExpressionGraph().get(UifConstants.ComponentProperties.DEFAULT_VALUE);
994         } else if (dataField.getDefaultValueFinderClass() != null) {
995             ValueFinder defaultValueFinder = KRADUtils.createNewObjectFromClass(dataField.getDefaultValueFinderClass());
996 
997             defaultValue = defaultValueFinder.getValue();
998         } else if ((dataField.getExpressionGraph() != null) && dataField.getExpressionGraph().containsKey(
999                 UifConstants.ComponentProperties.DEFAULT_VALUES)) {
1000             defaultValue = dataField.getExpressionGraph().get(UifConstants.ComponentProperties.DEFAULT_VALUES);
1001         } else if (dataField.getDefaultValues() != null) {
1002             defaultValue = dataField.getDefaultValues();
1003         }
1004 
1005         ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
1006 
1007         if ((defaultValue != null) && (defaultValue instanceof String) && expressionEvaluator.containsElPlaceholder(
1008                 (String) defaultValue)) {
1009             Map<String, Object> context = new HashMap<String, Object>(view.getPreModelContext());
1010             context.putAll(dataField.getContext());
1011 
1012             defaultValue = expressionEvaluator.replaceBindingPrefixes(view, object, (String) defaultValue);
1013             defaultValue = expressionEvaluator.evaluateExpressionTemplate(context, (String) defaultValue);
1014         }
1015 
1016         return defaultValue;
1017     }
1018 
1019     /**
1020      * {@inheritDoc}
1021      */
1022     @Override
1023     public void refreshReference(Object parentObject, String referenceObjectName) {
1024 
1025         if (!(parentObject instanceof PersistableBusinessObjectBaseAdapter)) {
1026             KRADServiceLocator.getDataObjectService().wrap(parentObject).fetchRelationship(referenceObjectName);
1027         }
1028         else {
1029             //support legacy data objects
1030             LegacyDataAdapter legacyDataAdapter = KRADServiceLocatorWeb.getLegacyDataAdapter();
1031             DataDictionaryService dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
1032 
1033             if (legacyDataAdapter.hasReference(parentObject.getClass(), referenceObjectName) || legacyDataAdapter
1034                     .hasCollection(parentObject.getClass(), referenceObjectName)) {
1035                 // refresh via database mapping
1036                 legacyDataAdapter.retrieveReferenceObject(parentObject, referenceObjectName);
1037             } else if (dataDictionaryService.hasRelationship(parentObject.getClass().getName(), referenceObjectName)) {
1038                 // refresh via data dictionary mapping
1039                 Object referenceObject = KradDataServiceLocator.getDataObjectService().wrap(parentObject).getPropertyValue(
1040                         referenceObjectName);
1041                 if (!(referenceObject instanceof PersistableBusinessObjectBaseAdapter)) {
1042                     LOG.warn("Could not refresh reference " + referenceObjectName + " off class " + parentObject.getClass()
1043                             .getName() + ". Class not of type PersistableBusinessObject");
1044                     return;
1045                 }
1046 
1047                 referenceObject = legacyDataAdapter.retrieve(referenceObject);
1048                 if (referenceObject == null) {
1049                     LOG.warn("Could not refresh reference " + referenceObjectName + " off class " + parentObject.getClass()
1050                             .getName() + ".");
1051                     return;
1052                 }
1053 
1054                 try {
1055                     KRADUtils.setObjectProperty(parentObject, referenceObjectName, referenceObject);
1056                 } catch (Exception e) {
1057                     LOG.error("Unable to refresh persistable business object: " + referenceObjectName + "\n" + e
1058                             .getMessage());
1059                     throw new RuntimeException(
1060                             "Unable to refresh persistable business object: " + referenceObjectName + "\n" + e
1061                                     .getMessage());
1062                 }
1063             } else {
1064                 LOG.warn("Could not refresh reference " + referenceObjectName + " off class " + parentObject.getClass()
1065                         .getName() + ".");
1066             }
1067         }
1068     }
1069 
1070     /**
1071      * {@inheritDoc}
1072      */
1073     @Override
1074     public void refreshReferences(String referencesToRefresh) {
1075         Object model = ViewLifecycle.getModel();
1076         for (String reference : StringUtils.split(referencesToRefresh, KRADConstants.REFERENCES_TO_REFRESH_SEPARATOR)) {
1077             if (StringUtils.isBlank(reference)) {
1078                 continue;
1079             }
1080 
1081             //ToDo: handle add line
1082 
1083             if (PropertyAccessorUtils.isNestedOrIndexedProperty(reference)) {
1084                 String parentPath = KRADUtils.getNestedAttributePrefix(reference);
1085                 Object parentObject = ObjectPropertyUtils.getPropertyValue(model, parentPath);
1086                 String referenceObjectName = KRADUtils.getNestedAttributePrimitive(reference);
1087 
1088                 if (parentObject == null) {
1089                     LOG.warn("Unable to refresh references for " + referencesToRefresh +
1090                             ". Object not found in model. Nothing refreshed.");
1091                     continue;
1092                 }
1093 
1094                 refreshReference(parentObject, referenceObjectName);
1095             } else {
1096                 refreshReference(model, reference);
1097             }
1098         }
1099     }
1100 
1101     /**
1102      * {@inheritDoc}
1103      */
1104     @Override
1105     public void retrieveEditModesAndActionFlags() {
1106         View view = ViewLifecycle.getView();
1107         UifFormBase model = (UifFormBase) ViewLifecycle.getModel();
1108         ViewPresentationController presentationController = view.getPresentationController();
1109         ViewAuthorizer authorizer = view.getAuthorizer();
1110 
1111         RequestAuthorizationCache requestAuthorizationCache;
1112         try {
1113             requestAuthorizationCache = view.getRequestAuthorizationCacheClass().newInstance();
1114         } catch (Exception e) {
1115             throw new RuntimeException("Cannot create instance of request authorization cache class", e);
1116         }
1117 
1118         presentationController.setRequestAuthorizationCache(requestAuthorizationCache);
1119         authorizer.setRequestAuthorizationCache(requestAuthorizationCache);
1120 
1121         Set<String> actionFlags = presentationController.getActionFlags(view, model);
1122         Set<String> editModes = presentationController.getEditModes(view, model);
1123 
1124         // if user session is not established cannot invoke authorizer
1125         if (GlobalVariables.getUserSession() != null) {
1126             Person user = GlobalVariables.getUserSession().getPerson();
1127 
1128             actionFlags = authorizer.getActionFlags(view, model, user, actionFlags);
1129             editModes = authorizer.getEditModes(view, model, user, editModes);
1130         }
1131 
1132         view.setActionFlags(new BooleanMap(actionFlags));
1133         view.setEditModes(new BooleanMap(editModes));
1134     }
1135 
1136     /**
1137      * {@inheritDoc}
1138      */
1139     @Override
1140     public void setViewContext() {
1141         View view = ViewLifecycle.getView();
1142         view.pushAllToContext(view.getPreModelContext());
1143 
1144         // evaluate view expressions for further context
1145         for (Entry<String, String> variableExpression : view.getExpressionVariables().entrySet()) {
1146             String variableName = variableExpression.getKey();
1147             Object value = ViewLifecycle.getExpressionEvaluator().evaluateExpression(view.getContext(),
1148                     variableExpression.getValue());
1149             view.pushObjectToContext(variableName, value);
1150         }
1151     }
1152 
1153     /**
1154      * {@inheritDoc}
1155      */
1156     @Override
1157     public void setElementContext(LifecycleElement element, LifecycleElement parent) {
1158         Map<String, Object> context = new HashMap<String, Object>();
1159 
1160         View view = ViewLifecycle.getView();
1161         Map<String, Object> viewContext = view.getContext();
1162         if (viewContext != null) {
1163             context.putAll(viewContext);
1164         }
1165 
1166         context.put(UifConstants.ContextVariableNames.COMPONENT, element instanceof Component ? element : parent);
1167 
1168         if (parent != null) {
1169             context.put(UifConstants.ContextVariableNames.PARENT, parent);
1170         }
1171 
1172         if (element instanceof LayoutManager) {
1173             context.put(UifConstants.ContextVariableNames.MANAGER, element);
1174         }
1175 
1176         element.pushAllToContext(context);
1177     }
1178 
1179     /**
1180      * Gets the configuration service
1181      *
1182      * @return configuration service
1183      */
1184     protected ConfigurationService getConfigurationService() {
1185         if (this.configurationService == null) {
1186             this.configurationService = CoreApiServiceLocator.getKualiConfigurationService();
1187         }
1188         return this.configurationService;
1189     }
1190 
1191     /**
1192      * Sets the configuration service
1193      *
1194      * @param configurationService The configuration service.
1195      */
1196     public void setConfigurationService(ConfigurationService configurationService) {
1197         this.configurationService = configurationService;
1198     }
1199 
1200     /**
1201      * Gets the data dictionary service
1202      *
1203      * @return data dictionary service
1204      */
1205     protected DataDictionaryService getDataDictionaryService() {
1206         if (this.dataDictionaryService == null) {
1207             this.dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
1208         }
1209 
1210         return this.dataDictionaryService;
1211     }
1212 
1213     /**
1214      * Sets the data dictionary service
1215      *
1216      * @param dataDictionaryService The dictionary service.
1217      */
1218     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
1219         this.dataDictionaryService = dataDictionaryService;
1220     }
1221 
1222     /**
1223      * Gets the view dictionary service
1224      *
1225      * @return view dictionary service
1226      */
1227     protected ViewDictionaryService getViewDictionaryService() {
1228         if (this.viewDictionaryService == null) {
1229             this.viewDictionaryService = KRADServiceLocatorWeb.getViewDictionaryService();
1230         }
1231         return this.viewDictionaryService;
1232     }
1233 
1234     /**
1235      * Sets the view dictionary service
1236      *
1237      * @param viewDictionaryService The view dictionary service.
1238      */
1239     public void setViewDictionaryService(ViewDictionaryService viewDictionaryService) {
1240         this.viewDictionaryService = viewDictionaryService;
1241     }
1242 
1243     /**
1244      * {@inheritDoc}
1245      */
1246     @Override
1247     public ExpressionEvaluatorFactory getExpressionEvaluatorFactory() {
1248         if (expressionEvaluatorFactory == null) {
1249             expressionEvaluatorFactory = KRADServiceLocatorWeb.getExpressionEvaluatorFactory();
1250         }
1251 
1252         return expressionEvaluatorFactory;
1253     }
1254 
1255     /**
1256      * Setter for {@link #getExpressionEvaluatorFactory()}.
1257      *
1258      * @param expressionEvaluatorFactory expression evaluator factory
1259      */
1260     public void setExpressionEvaluatorFactory(ExpressionEvaluatorFactory expressionEvaluatorFactory) {
1261         this.expressionEvaluatorFactory = expressionEvaluatorFactory;
1262     }
1263 
1264     /**
1265      * Get the legacy data adapter.
1266      *
1267      * @return The legacy data adapter.
1268      */
1269     protected LegacyDataAdapter getLegacyDataAdapter() {
1270         if (legacyDataAdapter == null) {
1271             legacyDataAdapter = KRADServiceLocatorWeb.getLegacyDataAdapter();
1272         }
1273         return legacyDataAdapter;
1274     }
1275 
1276     protected DataObjectService getDataObjectService() {
1277         if (dataObjectService == null) {
1278             dataObjectService = KRADServiceLocator.getDataObjectService();
1279         }
1280         return dataObjectService;
1281     }
1282 
1283     protected void setDataObjectService(DataObjectService dataObjectService) {
1284         this.dataObjectService = dataObjectService;
1285     }
1286 
1287     /**
1288      * Set the legacy data adapter.
1289      *
1290      * @param legacyDataAdapter The legacy data adapter.
1291      */
1292     public void setLegacyDataAdapter(LegacyDataAdapter legacyDataAdapter) {
1293         this.legacyDataAdapter = legacyDataAdapter;
1294     }
1295 
1296     /**
1297      * Log an error message using log4j, then throw a runtime exception with the provided message.
1298      *
1299      * @param message The error message.
1300      */
1301     protected void logAndThrowRuntime(String message) {
1302         LOG.error(message);
1303         throw new RuntimeException(message);
1304     }
1305 
1306 }