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