001/**
002 * Copyright 2005-2018 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 */
016package org.kuali.rice.krad.uif.service.impl;
017
018import com.google.common.collect.Sets;
019import org.apache.commons.lang.ObjectUtils;
020import org.apache.commons.lang.StringUtils;
021import org.apache.log4j.Logger;
022import org.kuali.rice.core.api.CoreApiServiceLocator;
023import org.kuali.rice.core.api.config.property.ConfigurationService;
024import org.kuali.rice.core.api.util.RiceKeyConstants;
025import org.kuali.rice.core.api.util.io.SerializationUtils;
026import org.kuali.rice.kim.api.identity.Person;
027import org.kuali.rice.krad.bo.PersistableBusinessObjectBaseAdapter;
028import org.kuali.rice.krad.data.DataObjectService;
029import org.kuali.rice.krad.data.DataObjectWrapper;
030import org.kuali.rice.krad.data.KradDataServiceLocator;
031import org.kuali.rice.krad.inquiry.Inquirable;
032import org.kuali.rice.krad.messages.MessageService;
033import org.kuali.rice.krad.service.DataDictionaryService;
034import org.kuali.rice.krad.service.KRADServiceLocator;
035import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
036import org.kuali.rice.krad.service.LegacyDataAdapter;
037import org.kuali.rice.krad.service.ModuleService;
038import org.kuali.rice.krad.uif.UifConstants;
039import org.kuali.rice.krad.uif.UifPropertyPaths;
040import org.kuali.rice.krad.uif.component.BindingInfo;
041import org.kuali.rice.krad.uif.component.Component;
042import org.kuali.rice.krad.uif.component.PropertyReplacer;
043import org.kuali.rice.krad.uif.component.RequestParameter;
044import org.kuali.rice.krad.uif.container.CollectionGroup;
045import org.kuali.rice.krad.uif.container.Container;
046import org.kuali.rice.krad.uif.field.DataField;
047import org.kuali.rice.krad.uif.layout.LayoutManager;
048import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
049import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
050import org.kuali.rice.krad.uif.lifecycle.ViewPostMetadata;
051import org.kuali.rice.krad.uif.service.ViewDictionaryService;
052import org.kuali.rice.krad.uif.service.ViewHelperService;
053import org.kuali.rice.krad.uif.util.BooleanMap;
054import org.kuali.rice.krad.uif.util.CopyUtils;
055import org.kuali.rice.krad.uif.util.LifecycleElement;
056import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
057import org.kuali.rice.krad.uif.util.RecycleUtils;
058import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
059import org.kuali.rice.krad.uif.view.ExpressionEvaluatorFactory;
060import org.kuali.rice.krad.uif.view.RequestAuthorizationCache;
061import org.kuali.rice.krad.uif.view.View;
062import org.kuali.rice.krad.uif.view.ViewAuthorizer;
063import org.kuali.rice.krad.uif.view.ViewModel;
064import org.kuali.rice.krad.uif.view.ViewPresentationController;
065import org.kuali.rice.krad.uif.widget.Inquiry;
066import org.kuali.rice.krad.util.ErrorMessage;
067import org.kuali.rice.krad.util.GlobalVariables;
068import org.kuali.rice.krad.util.GrowlMessage;
069import org.kuali.rice.krad.util.KRADConstants;
070import org.kuali.rice.krad.util.KRADUtils;
071import org.kuali.rice.krad.util.MessageMap;
072import org.kuali.rice.krad.valuefinder.ValueFinder;
073import org.kuali.rice.krad.web.form.UifFormBase;
074import org.kuali.rice.krad.web.service.impl.CollectionControllerServiceImpl.CollectionActionParameters;
075import org.springframework.beans.PropertyAccessorUtils;
076
077import java.io.Serializable;
078import java.lang.annotation.Annotation;
079import java.lang.reflect.Field;
080import java.text.MessageFormat;
081import java.util.ArrayList;
082import java.util.Collection;
083import java.util.Collections;
084import java.util.HashMap;
085import java.util.HashSet;
086import java.util.LinkedList;
087import java.util.List;
088import java.util.Map;
089import java.util.Map.Entry;
090import java.util.Queue;
091import java.util.Set;
092
093/**
094 * Default Implementation of {@code ViewHelperService}
095 *
096 * @author Kuali Rice Team (rice.collab@kuali.org)
097 */
098public class ViewHelperServiceImpl implements ViewHelperService, Serializable {
099    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}