001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.sys.document.web;
017    
018    import java.text.MessageFormat;
019    import java.util.List;
020    import java.util.Map;
021    
022    import javax.servlet.jsp.JspException;
023    import javax.servlet.jsp.PageContext;
024    import javax.servlet.jsp.tagext.Tag;
025    
026    import org.apache.commons.collections.BidiMap;
027    import org.apache.commons.collections.bidimap.DualHashBidiMap;
028    import org.apache.commons.lang.StringUtils;
029    import org.kuali.kfs.coa.service.AccountService;
030    import org.kuali.kfs.sys.KFSConstants;
031    import org.kuali.kfs.sys.KFSKeyConstants;
032    import org.kuali.kfs.sys.businessobject.AccountingLine;
033    import org.kuali.kfs.sys.context.SpringContext;
034    import org.kuali.kfs.sys.document.AccountingDocument;
035    import org.kuali.kfs.sys.document.datadictionary.AccountingLineViewFieldDefinition;
036    import org.kuali.kfs.sys.document.service.AccountingLineFieldRenderingTransformation;
037    import org.kuali.kfs.sys.document.service.AccountingLineRenderingService;
038    import org.kuali.kfs.sys.document.web.renderers.DynamicNameLabelRenderer;
039    import org.kuali.kfs.sys.document.web.renderers.FieldRenderer;
040    import org.kuali.rice.kns.bo.PersistableBusinessObject;
041    import org.kuali.rice.kns.lookup.LookupUtils;
042    import org.kuali.rice.kns.service.KualiConfigurationService;
043    import org.kuali.rice.kns.service.PersistenceStructureService;
044    import org.kuali.rice.kns.util.FieldUtils;
045    import org.kuali.rice.kns.util.ObjectUtils;
046    import org.kuali.rice.kns.web.ui.Field;
047    
048    /**
049     * Represents a field (plus, optionally, a dynamic name field) to be rendered as part of an accounting line.
050     */
051    public class AccountingLineViewField extends FieldTableJoiningWithHeader implements HeaderLabelPopulating, ReadOnlyable {
052        public static final String ACCOUNTING_LINE_NAME_PREFIX_PLACE_HOLDER = "${accountingLineName}";
053    
054        private Field field;
055        private AccountingLineViewFieldDefinition definition;
056        private int arbitrarilyHighIndex;
057        private List<AccountingLineViewOverrideField> overrideFields;
058        private PersistenceStructureService persistenceStructureService;
059    
060        /**
061         * Gets the definition attribute.
062         * 
063         * @return Returns the definition.
064         */
065        public AccountingLineViewFieldDefinition getDefinition() {
066            return definition;
067        }
068    
069        /**
070         * Sets the definition attribute value.
071         * 
072         * @param definition The definition to set.
073         */
074        public void setDefinition(AccountingLineViewFieldDefinition definition) {
075            this.definition = definition;
076        }
077    
078        /**
079         * Determines if this field should use the short label or not
080         * 
081         * @return true if the short label should be used, false otherwise
082         */
083        private boolean shouldUseShortLabel() {
084            return definition.shouldUseShortLabel();
085        }
086    
087        /**
088         * Gets the field attribute.
089         * 
090         * @return Returns the field.
091         */
092        public Field getField() {
093            return field;
094        }
095    
096        /**
097         * Sets the field attribute value.
098         * 
099         * @param field The field to set.
100         */
101        public void setField(Field field) {
102            this.field = field;
103        }
104    
105        /**
106         * Gets the overrideFields attribute.
107         * 
108         * @return Returns the overrideFields.
109         */
110        public List<AccountingLineViewOverrideField> getOverrideFields() {
111            return overrideFields;
112        }
113    
114        /**
115         * Sets the overrideFields attribute value.
116         * 
117         * @param overrideFields The overrideFields to set.
118         */
119        public void setOverrideFields(List<AccountingLineViewOverrideField> overrideFields) {
120            this.overrideFields = overrideFields;
121        }
122    
123        /**
124         * Checks the field to see if the field itself is hidden
125         * 
126         * @see org.kuali.kfs.sys.document.web.AccountingLineViewRenderableElementField#isHidden()
127         */
128        public boolean isHidden() {
129            return (field.getFieldType().equals(Field.HIDDEN) || definition.isHidden());
130        }
131    
132        /**
133         * Asks the wrapped field if it is read only (dynamic fields are, of course, always read only and therefore don't count in this
134         * determination)
135         * 
136         * @see org.kuali.kfs.sys.document.web.AccountingLineViewRenderableElementField#isReadOnly()
137         */
138        public boolean isReadOnly() {
139            return field.isReadOnly() || isHidden();
140        }
141    
142        /**
143         * @see org.kuali.kfs.sys.document.web.TableJoining#getName()
144         */
145        public String getName() {
146            return field.getPropertyName();
147        }
148    
149        /**
150         * @see org.kuali.kfs.sys.document.web.TableJoining#readOnlyize()
151         */
152        public void readOnlyize() {
153            if (!isHidden()) {
154                this.field.setReadOnly(true);
155            }
156        }
157    
158        /**
159         * @see org.kuali.kfs.sys.document.web.TableJoiningWithHeader#getHeaderLabelProperty()
160         */
161        public String getHeaderLabelProperty() {
162            return this.field.getPropertyName();
163        }
164    
165        /**
166         * @see org.kuali.kfs.sys.document.web.RenderableElement#renderElement(javax.servlet.jsp.PageContext,
167         *      javax.servlet.jsp.tagext.Tag)
168         */
169        public void renderElement(PageContext pageContext, Tag parentTag, AccountingLineRenderingContext renderingContext) throws JspException {
170            renderField(pageContext, parentTag, renderingContext);
171    
172            if (getOverrideFields() != null && getOverrideFields().size() > 0) {
173                renderOverrideFields(pageContext, parentTag, renderingContext);
174            }
175            if (shouldRenderDynamicFeldLabel() && renderingContext.fieldsCanRenderDynamicLabels()) {
176                renderDynamicNameLabel(pageContext, parentTag, renderingContext);
177            }
178        }
179    
180        /**
181         * Renders the field portion of this tag
182         * 
183         * @param pageContext the page context to render to
184         * @param parentTag the tag requesting rendering
185         * @param renderingContext the rendering context of the accounting line
186         * @throws JspException thrown if something goes wrong
187         */
188        protected void renderField(PageContext pageContext, Tag parentTag, AccountingLineRenderingContext renderingContext) throws JspException {
189            AccountingLine accountingLine = renderingContext.getAccountingLine();
190            String accountingLineProperty = renderingContext.getAccountingLinePropertyPath();
191            List<String> fieldNames = renderingContext.getFieldNamesForAccountingLine();
192            List errors = renderingContext.getErrors();
193            
194            this.getField().setPropertyPrefix(accountingLineProperty);
195            boolean chartSetByAccount = getName().equals(KFSConstants.CHART_OF_ACCOUNTS_CODE_PROPERTY_NAME) && !SpringContext.getBean(AccountService.class).accountsCanCrossCharts();
196            //set chartOfAccountsCode readOnly if account can't cross charts
197            if (!renderingContext.isFieldModifyable(this.getName()) || chartSetByAccount) {
198                this.getField().setReadOnly(true);
199            }
200    
201            FieldRenderer renderer = SpringContext.getBean(AccountingLineRenderingService.class).getFieldRendererForField(getField(), accountingLine);
202            if (renderer != null) {
203                prepareFieldRenderer(renderer, getField(), renderingContext.getAccountingDocument(), accountingLine, accountingLineProperty, fieldNames);
204                if (fieldInError(errors)) {
205                    renderer.setShowError(true);
206                }
207    
208                if (!isHidden()) {
209                    renderer.openNoWrapSpan(pageContext, parentTag);
210                }
211    
212                // dynamically set the accessible title to the current field
213                if (!this.isReadOnly()) {
214                    String accessibleTitle = getField().getFieldLabel();
215    
216                    if (renderingContext.isNewLine()) {
217                        String format = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(KFSKeyConstants.LABEL_NEW_ACCOUNTING_LINE_FIELD);
218                        accessibleTitle = MessageFormat.format(format, accessibleTitle, renderingContext.getGroupLabel());
219                    }
220                    else {
221                        Integer lineNumber = renderingContext.getCurrentLineCount() + 1;
222                        String format = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(KFSKeyConstants.LABEL_ACCOUNTING_LINE_FIELD);
223                        accessibleTitle = MessageFormat.format(format, accessibleTitle, renderingContext.getGroupLabel(), lineNumber);
224                    }
225    
226                    renderer.setAccessibleTitle(accessibleTitle);
227                }
228    
229                renderer.render(pageContext, parentTag);
230                if (!isHidden()) {
231                    renderer.closeNoWrapSpan(pageContext, parentTag);
232                }
233                renderer.clear();
234            }
235        }
236    
237        /**
238         * Updates the field so that it can have a quickfinder and inquiry link if need be
239         * 
240         * @param accountingDocument the accounting document the accounting line the field will render part of is on or will at some
241         *        point be on
242         * @param accountingLine the accounting line that is being rendered
243         * @param fieldNames the list of all fields being displayed on this accounting line
244         * @param accountingLinePrefix the prefix of all field names in the accounting line
245         */
246        protected void populateFieldForLookupAndInquiry(AccountingDocument accountingDocument, AccountingLine accountingLine, List<String> fieldNames, String accountingLinePrefix) {
247            if (!isHidden()) {
248                LookupUtils.setFieldQuickfinder(accountingLine, getField().getPropertyName(), getField(), fieldNames);
249    
250                // apply the customized lookup parameters if any
251                String overrideLookupParameters = definition.getOverrideLookupParameters();
252                if (StringUtils.isNotBlank(overrideLookupParameters)) {
253                    String lookupParameters = getField().getLookupParameters();
254    
255                    Map<String, String> lookupParametersMap = this.getActualParametersMap(lookupParameters, overrideLookupParameters, accountingLinePrefix);
256    
257                    getField().setLookupParameters(lookupParametersMap);
258    
259                    // if there are any any lookup parameters present, make sure the other lookup fields are populated.
260                    // this can be necessary if there wouldnt natually be a lookup, via DD or OJB relationships, but one
261                    // is forced.
262                    if (!lookupParametersMap.isEmpty()) {
263                        if (getDefinition().getOverrideLookupClass() != null) {
264                            getField().setQuickFinderClassNameImpl(getDefinition().getOverrideLookupClass().getName());
265                        }
266                    }
267                }
268    
269                // apply the customized field conversions if any
270                String overrideFieldConversions = definition.getOverrideFieldConversions();
271                if (StringUtils.isNotBlank(overrideFieldConversions)) {
272                    String fieldConversions = getField().getFieldConversions();
273    
274                    Map<String, String> fieldConversionsMap = this.getActualParametersMap(fieldConversions, overrideFieldConversions, accountingLinePrefix);
275    
276                    getField().setFieldConversions(fieldConversionsMap);
277                }
278    
279                if (isRenderingInquiry(accountingDocument, accountingLine)) {
280                    FieldUtils.setInquiryURL(getField(), accountingLine, getField().getPropertyName());
281                }
282            }
283        }
284    
285        /**
286         * Lazily retrieves the persistence structure service
287         * 
288         * @return an implementation of PersistenceStructureService
289         */
290        protected PersistenceStructureService getPersistenceStructureService() {
291            if (persistenceStructureService == null) {
292                persistenceStructureService = SpringContext.getBean(PersistenceStructureService.class);
293            }
294            return persistenceStructureService;
295        }
296    
297        /**
298         * Does some initial set up on the field renderer - sets the field and the business object being rendered
299         * 
300         * @param fieldRenderer the field renderer to prepare
301         * @param accountingLine the accounting line being rendered
302         * @param accountingLineProperty the property to get the accounting line from the form
303         * @param fieldNames the names of all the fields that will be rendered as part of this accounting line
304         */
305        protected void prepareFieldRenderer(FieldRenderer fieldRenderer, Field field, AccountingDocument document, AccountingLine accountingLine, String accountingLineProperty, List<String> fieldNames) {
306            fieldRenderer.setField(field);
307    
308            getField().setPropertyPrefix(accountingLineProperty);
309            populateFieldForLookupAndInquiry(document, accountingLine, fieldNames, getField().getPropertyPrefix());
310    
311            if (definition.getDynamicNameLabelGenerator() != null) {
312                fieldRenderer.overrideOnBlur(definition.getDynamicNameLabelGenerator().getDynamicNameLabelOnBlur(accountingLine, accountingLineProperty));
313            }
314            else if (!StringUtils.isBlank(definition.getDynamicLabelProperty())) {
315                fieldRenderer.setDynamicNameLabel(accountingLineProperty + "." + definition.getDynamicLabelProperty());
316            }
317    
318            fieldRenderer.setArbitrarilyHighTabIndex(arbitrarilyHighIndex);
319        }
320    
321        /**
322         * Determines if a dynamic field label should be rendered for the given field
323         * 
324         * @return true if a dynamic field label should be rendered, false otherwise
325         */
326        protected boolean shouldRenderDynamicFeldLabel() {
327            return (!getField().getFieldType().equals(Field.HIDDEN) && ((!StringUtils.isBlank(getField().getWebOnBlurHandler()) && !StringUtils.isBlank(definition.getDynamicLabelProperty())) || definition.getDynamicNameLabelGenerator() != null));
328        }
329    
330        /**
331         * @see org.kuali.kfs.sys.document.web.TableJoining#performFieldTransformation(org.kuali.kfs.sys.document.service.AccountingLineFieldRenderingTransformation,
332         *      org.kuali.kfs.sys.businessobject.AccountingLine, java.util.Map, java.util.Map)
333         */
334        @Override
335        public void performFieldTransformations(List<AccountingLineFieldRenderingTransformation> fieldTransformations, AccountingLine accountingLine, Map unconvertedValues) {
336            for (AccountingLineFieldRenderingTransformation fieldTransformation : fieldTransformations) {
337                fieldTransformation.transformField(accountingLine, getField(), getDefinition(), unconvertedValues);
338                if (getOverrideFields() != null && getOverrideFields().size() > 0) {
339                    transformOverrideFields(fieldTransformation, accountingLine, unconvertedValues);
340                }
341            }
342        }
343    
344        /**
345         * Runs a field transformation against all the overrides encapsulated within this field
346         * 
347         * @param fieldTransformation the field transformation which will utterly change our fields
348         * @param accountingLine the accounting line being rendered
349         * @param editModes the current document edit modes
350         * @param unconvertedValues a Map of unconvertedValues
351         */
352        protected void transformOverrideFields(AccountingLineFieldRenderingTransformation fieldTransformation, AccountingLine accountingLine, Map unconvertedValues) {
353            for (AccountingLineViewOverrideField overrideField : getOverrideFields()) {
354                overrideField.transformField(fieldTransformation, accountingLine, unconvertedValues);
355            }
356        }
357    
358        /**
359         * Renders the override fields for the line
360         * 
361         * @param pageContext the page context to render to
362         * @param parentTag the tag requesting all this rendering
363         * @param accountingLine the accounting line we're rendering
364         * @param accountingLinePropertyPath the path to get to that accounting
365         * @throws JspException thrown if rendering fails
366         */
367        public void renderOverrideFields(PageContext pageContext, Tag parentTag, AccountingLineRenderingContext renderingContext) throws JspException {
368            for (AccountingLineViewOverrideField overrideField : getOverrideFields()) {
369                overrideField.setAccountingLineProperty(renderingContext.getAccountingLinePropertyPath());
370                overrideField.renderElement(pageContext, parentTag, renderingContext);
371            }
372        }
373    
374        /**
375         * Renders a dynamic field label
376         * 
377         * @param pageContext the page context to render to
378         * @param parentTag the parent tag requesting this rendering
379         * @param accountingLine the line which owns the field being rendered
380         * @param accountingLinePropertyPath the path from the form to the accounting line
381         */
382        protected void renderDynamicNameLabel(PageContext pageContext, Tag parentTag, AccountingLineRenderingContext renderingContext) throws JspException {
383            AccountingLine accountingLine = renderingContext.getAccountingLine();
384            String accountingLinePropertyPath = renderingContext.getAccountingLinePropertyPath();
385    
386            DynamicNameLabelRenderer renderer = new DynamicNameLabelRenderer();
387            if (definition.getDynamicNameLabelGenerator() != null) {
388                renderer.setFieldName(definition.getDynamicNameLabelGenerator().getDynamicNameLabelFieldName(accountingLine, accountingLinePropertyPath));
389                renderer.setFieldValue(definition.getDynamicNameLabelGenerator().getDynamicNameLabelValue(accountingLine, accountingLinePropertyPath));
390            }
391            else {
392                if (!StringUtils.isBlank(getField().getPropertyValue())) {
393                    if (getField().isSecure()) {
394                        renderer.setFieldValue(getField().getDisplayMask().maskValue(getField().getPropertyValue()));
395                    }
396                    else {
397                        renderer.setFieldValue(getDynamicNameLabelDisplayedValue(accountingLine));
398                    }
399                }
400                renderer.setFieldName(accountingLinePropertyPath + "." + definition.getDynamicLabelProperty());
401            }
402            renderer.render(pageContext, parentTag);
403            renderer.clear();
404        }
405    
406        /**
407         * Gets the value from the accounting line to display as the field value
408         * 
409         * @param accountingLine the accounting line to get the value from
410         * @return the value to display for the dynamic name label
411         */
412        protected String getDynamicNameLabelDisplayedValue(AccountingLine accountingLine) {
413            String dynamicLabelProperty = definition.getDynamicLabelProperty();
414            Object value = accountingLine;
415            while (!ObjectUtils.isNull(value) && dynamicLabelProperty.indexOf('.') > -1) {
416                String currentProperty = StringUtils.substringBefore(dynamicLabelProperty, ".");
417                dynamicLabelProperty = StringUtils.substringAfter(dynamicLabelProperty, ".");
418                if (value instanceof PersistableBusinessObject) {
419                    ((PersistableBusinessObject) value).refreshReferenceObject(currentProperty);
420                }
421                value = ObjectUtils.getPropertyValue(value, currentProperty);
422            }
423            if (!ObjectUtils.isNull(value)) {
424                value = ObjectUtils.getPropertyValue(value, dynamicLabelProperty);
425                if (value != null)
426                    return value.toString();
427            }
428            return null;
429        }
430    
431        /**
432         * @see org.kuali.kfs.sys.document.web.TableJoiningWithHeader#createHeaderLabel()
433         */
434        public HeaderLabel createHeaderLabel() {
435            return new FieldHeaderLabel(this);
436        }
437    
438        /**
439         * If the field definition had an override col span greater than 1 and it doesn't seem as if the given cell had its colspan
440         * lengthened already, this method will increase the colspan of the table cell to whatever is listed
441         * 
442         * @param cell the cell to possibly lengthen
443         */
444        protected void updateTableCellWithColSpanOverride(AccountingLineTableCell cell) {
445            if (definition.getOverrideColSpan() > 1 && cell.getColSpan() == 1) {
446                cell.setColSpan(definition.getOverrideColSpan());
447            }
448        }
449    
450        /**
451         * Overridden to allow for colspan override
452         * 
453         * @see org.kuali.kfs.sys.document.web.FieldTableJoiningWithHeader#createHeaderLabelTableCell()
454         */
455        @Override
456        protected AccountingLineTableCell createHeaderLabelTableCell() {
457            AccountingLineTableCell cell = super.createHeaderLabelTableCell();
458            updateTableCellWithColSpanOverride(cell);
459            return cell;
460        }
461    
462        /**
463         * Overridden to allow for colspan override
464         * 
465         * @see org.kuali.kfs.sys.document.web.FieldTableJoining#createTableCell()
466         */
467        @Override
468        protected AccountingLineTableCell createTableCell() {
469            AccountingLineTableCell cell = super.createTableCell();
470            updateTableCellWithColSpanOverride(cell);
471            return cell;
472        }
473    
474        /**
475         * @return the colspan override of this field
476         */
477        public int getColSpanOverride() {
478            return definition.getOverrideColSpan();
479        }
480    
481        /**
482         * @see org.kuali.kfs.sys.document.web.HeaderLabelPopulating#populateHeaderLabel(org.kuali.kfs.sys.document.web.HeaderLabel,
483         *      org.kuali.kfs.sys.document.web.AccountingLineRenderingContext)
484         */
485        public void populateHeaderLabel(HeaderLabel headerLabel, AccountingLineRenderingContext renderingContext) {
486            FieldHeaderLabel label = (FieldHeaderLabel) headerLabel;
487            label.setLabel(getField().getFieldLabel());
488            label.setLabeledFieldEmptyOrHidden(isEmpty() || isHidden());
489            label.setReadOnly(getField().isReadOnly());
490            label.setRequired(getField().isFieldRequired());
491            if (renderingContext.fieldsShouldRenderHelp()) {
492                label.setFullClassNameForHelp(renderingContext.getAccountingLine().getClass().getName());
493                label.setAttributeEntryForHelp(getField().getPropertyName());
494            }
495        }
496    
497        /**
498         * Adds the wrapped field to the list; adds any override fields this field encapsulates as well
499         * 
500         * @see org.kuali.kfs.sys.document.web.RenderableElement#appendFieldNames(java.util.List)
501         */
502        public void appendFields(List<Field> fields) {
503            fields.add(getField());
504            if (getOverrideFields() != null && getOverrideFields().size() > 0) {
505                for (AccountingLineViewOverrideField field : getOverrideFields()) {
506                    field.appendFields(fields);
507                }
508            }
509        }
510    
511        /**
512         * @see org.kuali.kfs.sys.document.web.RenderableElement#populateWithTabIndexIfRequested(int[], int)
513         */
514        public void populateWithTabIndexIfRequested(int reallyHighIndex) {
515            this.arbitrarilyHighIndex = reallyHighIndex;
516        }
517    
518        /**
519         * Determines if this field is among the fields that are in error
520         * 
521         * @param errors the errors on the form
522         * @return true if this field is in error, false otherwise
523         */
524        protected boolean fieldInError(List errors) {
525            if (errors != null) {
526                String fieldName = getField().getPropertyName();
527                if (!StringUtils.isBlank(getField().getPropertyPrefix())) {
528                    fieldName = getField().getPropertyPrefix() + "." + fieldName;
529                }
530                for (Object errorKeyAsObject : errors) {
531                    final String errorKey = (String) errorKeyAsObject;
532                    if (fieldName.equals(errorKey)) {
533                        return true;
534                    }
535                }
536            }
537            return false;
538        }
539    
540        /**
541         * @see org.kuali.kfs.sys.document.web.ReadOnlyable#setEditable()
542         */
543        public void setEditable() {
544            if (!isHidden()) {
545                this.field.setReadOnly(false);
546            }
547        }
548    
549        /**
550         * Determines whether to render the inquiry for this field
551         * 
552         * @param document the document which the accounting line is part of or hopefully sometime will be part of
553         * @param line the accounting line being rendered
554         * @return true if inquiry links should be rendered, false otherwise
555         */
556        protected boolean isRenderingInquiry(AccountingDocument document, AccountingLine line) {
557            return isReadOnly();
558        }
559    
560        /**
561         * build the lookup parameter map through applying the override parameters onto the defaults
562         * 
563         * @param lookupParameters the default lookup parameter string
564         * @param overrideLookupParameters the override lookup parameter string
565         * @param accountingLinePrefix the actual accounting line prefix
566         * @return the actual lookup parameter map
567         */
568        private Map<String, String> getActualParametersMap(String parameters, String overrideParameters, String accountingLinePrefix) {
569            BidiMap parametersMap = this.buildBidirecionalMapFromParameters(parameters, accountingLinePrefix);
570            BidiMap overrideParametersMap = this.buildBidirecionalMapFromParameters(overrideParameters, accountingLinePrefix);
571            parametersMap.putAll(overrideParametersMap);
572    
573            return parametersMap;
574        }
575    
576        /**
577         * parse the given lookup parameter string into a bidirectinal map
578         * 
579         * @param lookupParameters the lookup parameter string
580         * @param accountingLinePrefix the actual accounting line prefix
581         * @return a bidirectinal map that holds all the given lookup parameters
582         */
583        private BidiMap buildBidirecionalMapFromParameters(String parameters, String accountingLinePrefix) {
584            BidiMap parameterMap = new DualHashBidiMap();
585    
586            //  if we didnt get any incoming parameters, then just return an empty parameterMap 
587            if (StringUtils.isBlank(parameters)) {
588                return parameterMap;
589            }
590            
591            String[] parameterArray = StringUtils.split(parameters, KFSConstants.FIELD_CONVERSIONS_SEPERATOR);
592    
593            for (String parameter : parameterArray) {
594                String[] entrySet = StringUtils.split(parameter, KFSConstants.FIELD_CONVERSION_PAIR_SEPERATOR);
595    
596                if (entrySet != null) {
597                    String parameterKey = escapeAccountingLineName(entrySet[0], accountingLinePrefix);
598                    String parameterValue = escapeAccountingLineName(entrySet[1], accountingLinePrefix);
599    
600                    parameterMap.put(parameterKey, parameterValue);
601                }
602            }
603    
604            return parameterMap;
605        }
606    
607        /**
608         * Escapes the String ${accountingLineName} within a field and replaces it with the actual prefix of an accounting line
609         * 
610         * @param propertyName the name of the property to escape the special string ${accountingLineName} out of
611         * @param accountingLinePrefix the actual accounting line prefix
612         * @return the property name with the correct accounting line prefix
613         */
614        protected String escapeAccountingLineName(String propertyName, String accountingLinePrefix) {
615            return StringUtils.replace(propertyName, ACCOUNTING_LINE_NAME_PREFIX_PLACE_HOLDER, accountingLinePrefix + ".");
616        }
617    }