com.aurel.track.item.history.HistoryLoaderBL.java Source code

Java tutorial

Introduction

Here is the source code for com.aurel.track.item.history.HistoryLoaderBL.java

Source

/**
 * Genji Scrum Tool and Issue Tracker
 * Copyright (C) 2015 Steinbeis GmbH & Co. KG Task Management Solutions
    
 * <a href="http://www.trackplus.com">Genji Scrum Tool</a>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/* $Id:$ */

package com.aurel.track.item.history;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import com.aurel.track.accessControl.AccessBeans;
import com.aurel.track.admin.customize.projectType.ProjectTypesBL;
import com.aurel.track.admin.customize.treeConfig.field.FieldBL;
import com.aurel.track.admin.customize.treeConfig.field.FieldConfigBL;
import com.aurel.track.beans.TActualEstimatedBudgetBean;
import com.aurel.track.beans.TBudgetBean;
import com.aurel.track.beans.TCostBean;
import com.aurel.track.beans.TFieldBean;
import com.aurel.track.beans.TFieldChangeBean;
import com.aurel.track.beans.TFieldConfigBean;
import com.aurel.track.beans.THistoryTransactionBean;
import com.aurel.track.beans.TProjectTypeBean;
import com.aurel.track.beans.TRoleFieldBean;
import com.aurel.track.beans.TWorkItemBean;
import com.aurel.track.fieldType.constants.SystemFields;
import com.aurel.track.fieldType.constants.ValueType;
import com.aurel.track.fieldType.runtime.base.IFieldTypeRT;
import com.aurel.track.fieldType.runtime.base.LocalLookupContainer;
import com.aurel.track.fieldType.runtime.base.LookupContainer;
import com.aurel.track.fieldType.runtime.bl.FieldRuntimeBL;
import com.aurel.track.fieldType.runtime.helpers.MergeUtil;
import com.aurel.track.fieldType.types.FieldTypeManager;
import com.aurel.track.item.ItemBL;
import com.aurel.track.item.ItemDetailBL;
import com.aurel.track.item.ItemLoaderException;
import com.aurel.track.item.budgetCost.AccountingBL;
import com.aurel.track.prop.ApplicationBean;
import com.aurel.track.resources.LocalizeUtil;
import com.aurel.track.util.EqualUtils;
import com.aurel.track.util.GeneralUtils;
import com.aurel.track.util.HTMLDiff;
import com.aurel.track.util.IntegerStringBean;
import com.aurel.track.util.emailHandling.Html2Text;
import com.aurel.track.util.numberFormatter.DoubleNumberFormatUtil;

/**
 * a business logic class for history
 */
public class HistoryLoaderBL {
    private static final Logger LOGGER = LogManager.getLogger(HistoryLoaderBL.class);

    public static enum LONG_TEXT_TYPE {
        ISPLAIN, ISSIMPLIFIEDHTML, ISFULLHTML
    };

    public static String COMMON_FIELD_KEY = "common.lbl.common";
    public static String ATTACHMENT_ADDED_FIELD_KEY = "common.lbl.attachment.added";
    public static String ATTACHMENT_DELETED_FIELD_KEY = "common.lbl.attachment.deleted";
    public static String ATTACHMENT_MODIFIED_FIELD_KEY = "common.lbl.attachment.modified";
    public static String ATTACHMENT_FILE = "common.lbl.attachment.file";
    public static String ATTACHMENT_URL = "URL";
    public static String ATTACHMENT_DESCRIPTION = "common.lbl.attachment.description";
    public static String ATTACHMENT_SIZE = "common.lbl.size";
    public static String BUDGET = "common.lbl.budget";
    public static String PLAN = "common.lbl.plan";
    public static String COST = "common.lbl.cost";

    public static String VERSION_CONTROL = "common.lbl.versionControl";

    public static String COMMENT_DELETED_FIELD_KEY = "common.lbl.comment.deleted";
    public static String COMMENT_MODIFIED_FIELD_KEY = "common.lbl.comment.modified";

    /**
     * Gets the possible change types
     * @param locale
     * @return
     */
    public static List<IntegerStringBean> getPossibleHistoryFields(Locale locale) {
        List<IntegerStringBean> historyFields = new LinkedList<IntegerStringBean>();
        //all fields with explicit history
        Map<Integer, String> historyFieldLabelsMap = FieldConfigBL.loadAllWithExplicitHistory(locale);
        for (Map.Entry<Integer, String> historyFieldEntry : historyFieldLabelsMap.entrySet()) {
            historyFields.add(new IntegerStringBean(historyFieldEntry.getValue(), historyFieldEntry.getKey()));
        }
        //compound fields (string value concatenated of all fields without explicit history)
        historyFields.add(new IntegerStringBean(
                LocalizeUtil.getLocalizedTextFromApplicationResources(HistoryLoaderBL.COMMON_FIELD_KEY, locale),
                TFieldChangeBean.COMPOUND_HISTORY_FIELD));
        //attachment changes
        historyFields
                .add(new IntegerStringBean(
                        LocalizeUtil.getLocalizedTextFromApplicationResources(
                                HistoryLoaderBL.ATTACHMENT_ADDED_FIELD_KEY, locale),
                        SystemFields.ATTACHMENT_ADD_HISTORY_FIELD));
        historyFields
                .add(new IntegerStringBean(
                        LocalizeUtil.getLocalizedTextFromApplicationResources(
                                HistoryLoaderBL.ATTACHMENT_MODIFIED_FIELD_KEY, locale),
                        SystemFields.ATTACHMENT_MODIFY_HISTORY_FIELD));
        historyFields
                .add(new IntegerStringBean(
                        LocalizeUtil.getLocalizedTextFromApplicationResources(
                                HistoryLoaderBL.ATTACHMENT_DELETED_FIELD_KEY, locale),
                        SystemFields.ATTACHMENT_DELETE_HISTORY_FIELD));
        //comment changes
        if (historyFieldLabelsMap.containsKey(SystemFields.INTEGER_COMMENT)) {
            //comment is historized
            historyFields.add(new IntegerStringBean(
                    LocalizeUtil.getLocalizedTextFromApplicationResources(
                            HistoryLoaderBL.COMMENT_MODIFIED_FIELD_KEY, locale),
                    SystemFields.COMMENT_MODIFY_HISTORY_FIELD));
            historyFields
                    .add(new IntegerStringBean(
                            LocalizeUtil.getLocalizedTextFromApplicationResources(
                                    HistoryLoaderBL.COMMENT_DELETED_FIELD_KEY, locale),
                            SystemFields.COMMENT_DELETE_HISTORY_FIELD));
        } else {
            //comment is not historized but that means that modification and delete of comments is not historized. Adding a comment should always appear in history
            TFieldConfigBean fieldConfigBean = FieldRuntimeBL.getDefaultFieldConfig(SystemFields.INTEGER_COMMENT,
                    locale);
            historyFields.add(new IntegerStringBean(fieldConfigBean.getLabel(), SystemFields.INTEGER_COMMENT));
        }
        List<TProjectTypeBean> projectBeans = ProjectTypesBL.loadAll();
        boolean planBudgetExpense = false;
        boolean versionControl = false;
        if (projectBeans != null) {
            for (TProjectTypeBean projectTypeBean : projectBeans) {
                if (projectTypeBean.getUseAccounting()) {
                    planBudgetExpense = true;
                }
                if (projectTypeBean.getUseVersionControl()) {
                    versionControl = true;
                }
                if (planBudgetExpense && versionControl) {
                    break;
                }
            }
        }
        if (!ApplicationBean.getInstance().isGenji() && planBudgetExpense) {
            historyFields.add(new IntegerStringBean(
                    LocalizeUtil.getLocalizedTextFromApplicationResources(HistoryLoaderBL.COST, locale),
                    SystemFields.INTEGER_COST_HISTORY));
            historyFields.add(new IntegerStringBean(
                    LocalizeUtil.getLocalizedTextFromApplicationResources(HistoryLoaderBL.PLAN, locale),
                    SystemFields.INTEGER_PLAN_HISTORY));
            if (ApplicationBean.getInstance().getSiteBean().getSummaryItemsBehavior()) {
                historyFields.add(new IntegerStringBean(
                        LocalizeUtil.getLocalizedTextFromApplicationResources(HistoryLoaderBL.BUDGET, locale),
                        SystemFields.INTEGER_BUDGET_HISTORY));
            }
        }
        if (versionControl) {
            historyFields.add(new IntegerStringBean(
                    LocalizeUtil.getLocalizedTextFromApplicationResources(HistoryLoaderBL.VERSION_CONTROL, locale),
                    SystemFields.INTEGER_VERSION_CONTROL));
        }
        Collections.sort(historyFields);
        return historyFields;
    }

    /**
     * Add the names of the changed by persons 
     * @param historyTransactionBeanList
     */
    public static void addPersonNamesToHistoryTransactionBeans(
            List<THistoryTransactionBean> historyTransactionBeanList) {
        if (historyTransactionBeanList != null) {
            /*Set<Integer> personsSet = new HashSet<Integer>();
            for (THistoryTransactionBean historyTransactionBean : historyTransactionBeanList) {
               personsSet.add(historyTransactionBean.getChangedByID());
            }
            List<TPersonBean> personBeansList = PersonBL.loadByKeys(
                  GeneralUtils.createIntegerListFromCollection(personsSet));
            Map<Integer, String> personIDToName = new HashMap<Integer, String>();
            for (TPersonBean personBean : personBeansList) {
               personIDToName.put(personBean.getObjectID(), personBean.getLabel());
            }*/
            for (THistoryTransactionBean historyTransactionBean : historyTransactionBeanList) {
                Integer personID = historyTransactionBean.getChangedByID();
                historyTransactionBean.setChangedByName(
                        LookupContainer.getNotLocalizedLabelBeanLabel(SystemFields.INTEGER_PERSON, personID));
            }
        }
    }

    /**
     * Get the raw history (only the values, no show values, field names) for a workItemID
     * @param workItemID
     * @param filterFieldIDs
     * @param personID
     * @param fromDate
     * @param toDate
     * @return
     */
    public static SortedMap<Integer, Map<Integer, HistoryValues>> getWorkItemRawHistory(Integer workItemID,
            Integer[] filterFieldIDs, List<Integer> personIDs, Date fromDate, Date toDate) {

        int[] workItemIDs = new int[] { workItemID };
        List<THistoryTransactionBean> historyTransactionBeanList = HistoryTransactionBL
                .getByWorkItemsAndFields(workItemIDs, filterFieldIDs, true, personIDs, fromDate, toDate);
        addPersonNamesToHistoryTransactionBeans(historyTransactionBeanList);
        Map<Integer, THistoryTransactionBean> historyTransactionBeanMap = GeneralUtils
                .createMapFromList(historyTransactionBeanList);
        List<TFieldChangeBean> fieldChangeBeanList = FieldChangeBL.getByWorkItemsAndFields(workItemIDs,
                filterFieldIDs, true, personIDs, fromDate, toDate);
        Map<Integer, Map<Integer, Map<String, Object>>> workItemsFieldChangeBeansMap = getFieldChangeBeansByWorkItemAndTransactionAndMergedField(
                historyTransactionBeanMap, fieldChangeBeanList);
        Map<Integer, SortedMap<Integer, Map<Integer, HistoryValues>>> workItemsHistoryValuesMap = new HashMap<Integer, SortedMap<Integer, Map<Integer, HistoryValues>>>();
        Iterator<Integer> workItemIDsIterator = workItemsFieldChangeBeansMap.keySet().iterator();
        while (workItemIDsIterator.hasNext()) {
            workItemID = workItemIDsIterator.next();
            Map<Integer, Map<String, Object>> workItemFieldChangeBeansMap = workItemsFieldChangeBeansMap
                    .get(workItemID);
            Iterator<Integer> transactionIDsIterator = workItemFieldChangeBeansMap.keySet().iterator();
            while (transactionIDsIterator.hasNext()) {
                Integer historyTransactionID = transactionIDsIterator.next();
                Map<String, Object> transactionFieldChangeBeansMap = workItemFieldChangeBeansMap
                        .get(historyTransactionID);
                Iterator<String> mergeKeysIterator = transactionFieldChangeBeansMap.keySet().iterator();
                Map<String, Object> newValuesHistoryMap = new HashMap<String, Object>();
                Map<String, Object> oldValuesHistoryMap = new HashMap<String, Object>();
                while (mergeKeysIterator.hasNext()) {
                    String mergeKey = mergeKeysIterator.next();
                    Integer[] parts = MergeUtil.getParts(mergeKey);
                    Integer fieldID = parts[0];
                    Integer parameterCode = parts[1];
                    if (parameterCode == null || parameterCode.intValue() == 1) {
                        IFieldTypeRT fieldTypeRT = null;
                        if (TFieldChangeBean.COMPOUND_HISTORY_FIELD.equals(fieldID)) {
                            //load the Compound field is loaded similar like Description (longText)  
                            fieldTypeRT = FieldTypeManager.getFieldTypeRT(SystemFields.INTEGER_DESCRIPTION);
                        } else {
                            fieldTypeRT = FieldTypeManager.getFieldTypeRT(fieldID);
                        }
                        if (fieldTypeRT == null) {
                            LOGGER.info("Fieldtype unknown for field " + fieldID);
                            continue;
                        }
                        fieldTypeRT.processHistoryLoad(fieldID, null, transactionFieldChangeBeansMap,
                                newValuesHistoryMap, oldValuesHistoryMap);
                        SortedMap<Integer, Map<Integer, HistoryValues>> workItemHistoryValuesMap = workItemsHistoryValuesMap
                                .get(workItemID);
                        if (workItemHistoryValuesMap == null) {
                            workItemHistoryValuesMap = new TreeMap<Integer, Map<Integer, HistoryValues>>();
                            workItemsHistoryValuesMap.put(workItemID, workItemHistoryValuesMap);
                        }
                        Map<Integer, HistoryValues> historyTransactionMap = workItemHistoryValuesMap
                                .get(historyTransactionID);
                        if (historyTransactionMap == null) {
                            historyTransactionMap = new TreeMap<Integer, HistoryValues>();
                            workItemHistoryValuesMap.put(historyTransactionID, historyTransactionMap);
                        }
                        HistoryValues historyValues = initHistoryValues(
                                historyTransactionBeanMap.get(historyTransactionID));
                        historyValues.setDate(fieldTypeRT.getValueType() == ValueType.DATE
                                || fieldTypeRT.getValueType() == ValueType.DATETIME);
                        historyValues.setObjectID(getFirstFieldChangeID(fieldID, parameterCode,
                                transactionFieldChangeBeansMap.get(mergeKey)));
                        historyValues.setTimesEdited(
                                getTimesEdited(fieldID, transactionFieldChangeBeansMap.get(mergeKey)));
                        historyValues.setTransactionID(historyTransactionID);
                        historyValues.setFieldID(fieldID);
                        historyValues.setLongField(fieldTypeRT.isLong());
                        Object newValue = getAttribute(newValuesHistoryMap, fieldID, fieldTypeRT);
                        Object oldValue = getAttribute(oldValuesHistoryMap, fieldID, fieldTypeRT);
                        historyValues.setNewValue(newValue);
                        historyValues.setOldValue(oldValue);
                        historyTransactionMap.put(fieldID, historyValues);
                    }
                }
            }
        }
        return workItemsHistoryValuesMap.get(workItemID);
    }

    /**
     * Dropdowns are needed for system and custom options 
     * @param includeField
     * @param filterFieldIDs
     * @return
     */
    /*private static boolean dropDownNeeded(boolean includeField, Integer[] filterFieldIDs) {
       if (filterFieldIDs==null || !includeField) {
     //if all fields the included dropDowns are needed
     return true;
       }
       for (int i = 0; i < filterFieldIDs.length; i++) {
     IFieldTypeRT fieldTypeRT = FieldTypeManager.getFieldTypeRT(filterFieldIDs[i]);
     if (fieldTypeRT!=null) {
        int valueType = fieldTypeRT.getValueType();
        //dropdown label or only the locale needed from dropdown container
        if (valueType==ValueType.SYSTEMOPTION || valueType==ValueType.CUSTOMOPTION || 
              valueType==ValueType.DATE || valueType==ValueType.DATETIME || 
              valueType==ValueType.DOUBLE) {
           return true;
        }
     }
       }
       //none of the fields 
       return false;
    }*/

    /**
     * Gets the list of comments as history values for each item
     * @param workItemIDs
     * @param personID
     * @param locale
     * @return
     */
    public static Map<Integer, List<HistoryValues>> getComments(int[] workItemIDs, Integer personID,
            Locale locale) {
        Map<Integer, List<HistoryValues>> commentMap = new HashMap<Integer, List<HistoryValues>>();
        Map<Integer, SortedMap<Integer, Map<Integer, HistoryValues>>> historyMap = getWorkItemsHistory(workItemIDs,
                new Integer[] { SystemFields.INTEGER_COMMENT }, true, null, null, null, locale, false,
                LONG_TEXT_TYPE.ISFULLHTML, true, personID);
        if (historyMap != null) {
            for (Map.Entry<Integer, SortedMap<Integer, Map<Integer, HistoryValues>>> itemEntry : historyMap
                    .entrySet()) {
                Integer itemID = itemEntry.getKey();
                SortedMap<Integer, Map<Integer, HistoryValues>> commentTransaction = itemEntry.getValue();
                if (commentTransaction != null) {
                    for (Map.Entry<Integer, Map<Integer, HistoryValues>> transactionEntry : commentTransaction
                            .entrySet()) {
                        Map<Integer, HistoryValues> commentFieldEntry = transactionEntry.getValue();
                        if (commentFieldEntry != null) {
                            for (Map.Entry<Integer, HistoryValues> commentEntry : commentFieldEntry.entrySet()) {
                                HistoryValues historyValues = commentEntry.getValue();
                                if (historyValues != null) {
                                    List<HistoryValues> commentsForItem = commentMap.get(itemID);
                                    if (commentsForItem == null) {
                                        commentsForItem = new LinkedList<HistoryValues>();
                                        commentMap.put(itemID, commentsForItem);
                                    }
                                    commentsForItem.add(historyValues);
                                }
                            }
                        }
                    }
                }
            }
        }
        return commentMap;
    }

    /**
     * Get the history for an array of workItemIDs
     * @param workItemIDs
     * @param filterFieldIDs
     * @param includeField
     * @param filterHistoryPersonIDs
     * @param fromDate
     * @param toDate
     * @param locale
     * @param isISO
     * @param longTextIsPlain
     * @param excludeHidden
     * @param loggedPersonID
     * @return
     */
    public static Map<Integer, SortedMap<Integer, Map<Integer, HistoryValues>>> getWorkItemsHistory(
            int[] workItemIDs, Integer[] filterFieldIDs, boolean includeField, List<Integer> filterHistoryPersonIDs,
            Date fromDate, Date toDate, Locale locale, boolean isISO, LONG_TEXT_TYPE longTextIsPlain,
            boolean excludeHidden, Integer loggedPersonID) {
        List<THistoryTransactionBean> historyTransactionBeanList = HistoryTransactionBL.getByWorkItemsAndFields(
                workItemIDs, filterFieldIDs, includeField, filterHistoryPersonIDs, fromDate, toDate);
        List<TFieldChangeBean> fieldChangeBeanList = FieldChangeBL.getByWorkItemsAndFields(workItemIDs,
                filterFieldIDs, includeField, filterHistoryPersonIDs, fromDate, toDate);
        Integer[] accessFields = null;
        if (includeField) {
            accessFields = filterFieldIDs;
        }
        return getWorkItemsHistory(historyTransactionBeanList, fieldChangeBeanList, accessFields,
                filterHistoryPersonIDs, locale, isISO, longTextIsPlain, excludeHidden, loggedPersonID);
    }

    /**
     * Get the HistoryValues map for a list of of historyTransactions and the corresponding fieldChangeBeanList
     * @param workItemIDs
     * @param filterFieldID if null do not filter by it
     * @param includeField not null: if true include this field, if false exclude this field
     * @param filterHistoryPersonID if null do not filter by it
     * @param fromDate
     * @param toDate
     * @param locale
     * @param isISO
     * @param longTextIsPlain
     * @param excludeHidden exclude the fields not visible by current user
     * @param loggedPersonID
     * @return
     */
    public static Map<Integer, SortedMap<Integer, Map<Integer, HistoryValues>>> getWorkItemsHistory(
            List<THistoryTransactionBean> historyTransactionBeanList, List<TFieldChangeBean> fieldChangeBeanList,
            Integer[] filterFieldIDs, List<Integer> filterHistoryPersonIDs, Locale locale, boolean isISO,
            LONG_TEXT_TYPE longTextIsPlain, boolean excludeHidden, Integer loggedPersonID) {
        Date start = null;
        Date loadingHistoryData = null;
        Date populatingHistoryData = null;
        if (LOGGER.isDebugEnabled()) {
            start = new Date();
        }
        Map<Integer, Map<Integer, Map<Integer, Integer>>> fieldRestrictions = null;
        Map<Integer, TWorkItemBean> workItemBeansMap = null;
        Set<Integer> itemIDSet = new HashSet<Integer>();

        if (historyTransactionBeanList != null) {
            for (THistoryTransactionBean historyTransactionBean : historyTransactionBeanList) {
                Integer workItemID = historyTransactionBean.getWorkItem();
                itemIDSet.add(workItemID);
            }
        }
        List<TWorkItemBean> workItemBeansList = ItemBL
                .loadByWorkItemKeys(GeneralUtils.createIntArrFromSet(itemIDSet));
        LocalLookupContainer localLookupContainer = ItemBL.getItemHierarchyContainer(workItemBeansList);
        if (excludeHidden) {
            workItemBeansMap = GeneralUtils.createMapFromList(workItemBeansList);
            Map<Integer, Set<Integer>> projectToIssueTypesMap = AccessBeans
                    .getProjectToIssueTypesMap(workItemBeansList);
            List<Integer> fieldIDs = null;
            if (filterFieldIDs != null && filterFieldIDs.length > 0) {
                fieldIDs = GeneralUtils.createIntegerListFromIntegerArr(filterFieldIDs);
            }
            fieldRestrictions = AccessBeans.getFieldRestrictions(loggedPersonID, projectToIssueTypesMap, fieldIDs,
                    false);
            if (fieldRestrictions != null) {
                //whether the Common field from history should be restricted or not. This is a conservative restriction:
                //if any field which is restricted has not explicit history set (i.e. it might appear in Common changes) then the Common field is hidden
                Set<Integer> restrictedFieldsInAnyContext = new HashSet<Integer>();
                for (Map<Integer, Map<Integer, Integer>> projectFieldRestrictions : fieldRestrictions.values()) {
                    if (projectFieldRestrictions != null) {
                        for (Map<Integer, Integer> itemTypeFieldRestrictions : projectFieldRestrictions.values()) {
                            if (itemTypeFieldRestrictions != null) {
                                for (Integer fieldID : itemTypeFieldRestrictions.keySet()) {
                                    restrictedFieldsInAnyContext.add(fieldID);
                                }
                            }
                        }
                    }
                }
                if (!restrictedFieldsInAnyContext.isEmpty()) {
                    LOGGER.debug("Field restrictions found for person " + loggedPersonID + " for "
                            + restrictedFieldsInAnyContext.size() + " fields");
                    Map<Integer, Map<Integer, Map<Integer, TFieldConfigBean>>> projectsIssueTypesFieldConfigsMap = FieldRuntimeBL
                            .loadFieldConfigsInContextsAndTargetProjectAndIssueType(projectToIssueTypesMap,
                                    restrictedFieldsInAnyContext, locale, null, null);
                    for (Map.Entry<Integer, Map<Integer, Map<Integer, Integer>>> projectFieldRestrictionsEntry : fieldRestrictions
                            .entrySet()) {
                        Integer projectID = projectFieldRestrictionsEntry.getKey();
                        Map<Integer, Map<Integer, Integer>> projectFieldRestrictions = projectFieldRestrictionsEntry
                                .getValue();
                        Map<Integer, Map<Integer, TFieldConfigBean>> projectFieldConfigs = projectsIssueTypesFieldConfigsMap
                                .get(projectID);
                        if (projectFieldConfigs != null && projectFieldRestrictions != null) {
                            for (Map.Entry<Integer, Map<Integer, Integer>> itemTypeFieldRestrictionsEntry : projectFieldRestrictions
                                    .entrySet()) {
                                Integer itemType = itemTypeFieldRestrictionsEntry.getKey();
                                Map<Integer, Integer> itemTypeFieldRestrictions = itemTypeFieldRestrictionsEntry
                                        .getValue();
                                Map<Integer, TFieldConfigBean> itemTypeFieldConfigs = projectFieldConfigs
                                        .get(itemType);
                                if (itemTypeFieldConfigs != null && itemTypeFieldRestrictions != null) {
                                    for (Integer fieldID : itemTypeFieldRestrictions.keySet()) {
                                        TFieldConfigBean fieldConfigBean = itemTypeFieldConfigs.get(fieldID);
                                        if (fieldConfigBean != null && !fieldConfigBean.isHistoryString()) {
                                            LOGGER.debug("The restricted field " + fieldConfigBean.getLabel() + " ("
                                                    + fieldID
                                                    + ") has no explict history so the Commons history field is also disabled");
                                            itemTypeFieldRestrictions.put(TFieldChangeBean.COMPOUND_HISTORY_FIELD,
                                                    TRoleFieldBean.ACCESSFLAG.NOACCESS);
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        Map<Integer, TFieldBean> fieldsMap = new HashMap<Integer, TFieldBean>(1);
        Map<Integer, String> fieldConfigLabelsMap = new HashMap<Integer, String>(1);
        if (isISO) {
            fieldsMap = GeneralUtils.createMapFromList(FieldBL.loadAll());
        } else {
            fieldConfigLabelsMap = LocalizeUtil.getLocalizedFieldConfigLables(FieldConfigBL.loadDefault(), locale);
        }
        /*List<THistoryTransactionBean> historyTransactionBeanList = HistoryTransactionBL.getByWorkItemsAndFields(
        workItemIDs, filterFieldIDs, includeField, filterHistoryPersonIDs, fromDate, toDate);*/
        addPersonNamesToHistoryTransactionBeans(historyTransactionBeanList);
        Map<Integer, THistoryTransactionBean> historyTransactionBeanMap = GeneralUtils
                .createMapFromList(historyTransactionBeanList);
        /*List<TFieldChangeBean> fieldChangeBeanList = 
        FieldChangeBL.getByWorkItemsAndFields(workItemIDs, filterFieldIDs, includeField, filterHistoryPersonIDs, fromDate, toDate);*/
        if (LOGGER.isDebugEnabled() && start != null) {
            loadingHistoryData = new Date();
            LOGGER.debug("Loading the history values from database for " + " ReportBeanWithHistory lasted "
                    + Long.toString(loadingHistoryData.getTime() - start.getTime()) + " ms");
        }
        Map<Integer, Map<Integer, Map<String, Object>>> workItemsFieldChangeBeansMap = getFieldChangeBeansByWorkItemAndTransactionAndMergedField(
                historyTransactionBeanMap, fieldChangeBeanList);
        Map<Integer, SortedMap<Integer, Map<Integer, HistoryValues>>> workItemsHistoryValuesMap = new HashMap<Integer, SortedMap<Integer, Map<Integer, HistoryValues>>>();
        Set<Integer> pseudoHistoryFields = getPseudoHistoryFields();
        for (Map.Entry<Integer, Map<Integer, Map<String, Object>>> entry : workItemsFieldChangeBeansMap
                .entrySet()) {
            Integer workItemID = entry.getKey();
            Map<Integer, Map<String, Object>> workItemFieldChangeBeansMap = entry.getValue();
            Map<Integer, Integer> issueTypeFieldRestrictions = null;
            Integer projectID = null;
            Integer issueTypeID = null;
            if (fieldRestrictions != null && workItemBeansMap != null) {
                TWorkItemBean workItemBean = workItemBeansMap.get(workItemID);
                if (workItemBean != null) {
                    projectID = workItemBean.getProjectID();
                    issueTypeID = workItemBean.getListTypeID();
                    Map<Integer, Map<Integer, Integer>> projectFieldRestrictions = fieldRestrictions.get(projectID);
                    if (projectFieldRestrictions != null) {
                        issueTypeFieldRestrictions = projectFieldRestrictions.get(issueTypeID);
                    }
                }
            }
            for (Map.Entry<Integer, Map<String, Object>> itemEntry : workItemFieldChangeBeansMap.entrySet()) {
                Integer historyTransactionID = itemEntry.getKey();
                Map<String, Object> transactionFieldChangeBeansMap = itemEntry.getValue();
                Iterator<String> mergeKeysIterator = transactionFieldChangeBeansMap.keySet().iterator();
                Map<String, Object> newValuesHistoryMap = new HashMap<String, Object>();
                Map<String, Object> oldValuesHistoryMap = new HashMap<String, Object>();
                while (mergeKeysIterator.hasNext()) {
                    String mergeKey = mergeKeysIterator.next();
                    Integer[] parts = MergeUtil.getParts(mergeKey);
                    Integer fieldKey = parts[0];
                    if (issueTypeFieldRestrictions != null && issueTypeFieldRestrictions.containsKey(fieldKey)) {
                        LOGGER.debug("Field " + fieldKey + " is hidden for person " + loggedPersonID
                                + " in project " + projectID + " and issueType " + issueTypeID);
                        continue;
                    }
                    Integer parameterCode = parts[1];
                    if (parameterCode == null || parameterCode.intValue() == 1) {
                        //execute for simple fields and one times for the composite fields (not for each part)
                        IFieldTypeRT fieldTypeRT = null;
                        if (pseudoHistoryFields.contains(fieldKey)) {
                            //load the Compound and Attachment fields the same as Description (longText)  
                            fieldTypeRT = FieldTypeManager.getFieldTypeRT(SystemFields.INTEGER_DESCRIPTION);
                        } else {
                            fieldTypeRT = FieldTypeManager.getFieldTypeRT(fieldKey);
                        }
                        if (fieldTypeRT == null) {
                            LOGGER.warn("Fieldtype unknown for field " + fieldKey);
                            continue;
                        }
                        fieldTypeRT.processHistoryLoad(fieldKey, null, transactionFieldChangeBeansMap,
                                newValuesHistoryMap, oldValuesHistoryMap);
                        SortedMap<Integer, Map<Integer, HistoryValues>> workItemHistoryValuesMap = workItemsHistoryValuesMap
                                .get(workItemID);
                        if (workItemHistoryValuesMap == null) {
                            workItemHistoryValuesMap = new TreeMap<Integer, Map<Integer, HistoryValues>>();
                            workItemsHistoryValuesMap.put(workItemID, workItemHistoryValuesMap);
                        }
                        Map<Integer, HistoryValues> historyTransactionMap = workItemHistoryValuesMap
                                .get(historyTransactionID);
                        if (historyTransactionMap == null) {
                            historyTransactionMap = new TreeMap<Integer, HistoryValues>();
                            workItemHistoryValuesMap.put(historyTransactionID, historyTransactionMap);
                        }
                        HistoryValues historyValues = initHistoryValues(
                                historyTransactionBeanMap.get(historyTransactionID));
                        historyValues.setDate(fieldTypeRT.getValueType() == ValueType.DATE
                                || fieldTypeRT.getValueType() == ValueType.DATETIME);
                        historyValues.setObjectID(getFirstFieldChangeID(fieldKey, parameterCode,
                                transactionFieldChangeBeansMap.get(mergeKey)));
                        historyValues.setTimesEdited(
                                getTimesEdited(fieldKey, transactionFieldChangeBeansMap.get(mergeKey)));
                        historyValues.setTransactionID(historyTransactionID);
                        historyValues.setFieldID(fieldKey);
                        historyValues.setLongField(fieldTypeRT.isLong());
                        if (isISO) {
                            TFieldBean fieldBean = fieldsMap.get(fieldKey);
                            if (fieldBean != null) {
                                historyValues.setFieldName(fieldBean.getName());
                            } else {
                                if (pseudoHistoryFields.contains(fieldKey)) {
                                    historyValues
                                            .setFieldName(LocalizeUtil.getLocalizedTextFromApplicationResources(
                                                    getHistoryFieldKey(fieldKey), Locale.ENGLISH));
                                }
                            }
                        } else {
                            String fieldConfigLabel = fieldConfigLabelsMap.get(fieldKey);
                            if (fieldConfigLabel != null) {
                                historyValues.setFieldName(fieldConfigLabel);
                            } else {
                                if (pseudoHistoryFields.contains(fieldKey)) {
                                    historyValues
                                            .setFieldName(LocalizeUtil.getLocalizedTextFromApplicationResources(
                                                    getHistoryFieldKey(fieldKey), locale));
                                }
                            }
                        }
                        Object newValue = getAttribute(newValuesHistoryMap, fieldKey, fieldTypeRT);
                        Object oldValue = getAttribute(oldValuesHistoryMap, fieldKey, fieldTypeRT);
                        historyValues.setNewValue(newValue);
                        historyValues.setOldValue(oldValue);
                        if (fieldTypeRT.isLong() && (longTextIsPlain == LONG_TEXT_TYPE.ISPLAIN
                                || longTextIsPlain == LONG_TEXT_TYPE.ISFULLHTML)) {
                            if (longTextIsPlain == LONG_TEXT_TYPE.ISPLAIN) {
                                try {
                                    historyValues
                                            .setNewShowValue(Html2Text.getNewInstance().convert((String) newValue));
                                } catch (Exception e) {
                                    LOGGER.info("Transforming the new HTML value to plain text for workItemID "
                                            + workItemID + " and field " + fieldKey + " failed with " + e);
                                }
                                try {
                                    historyValues
                                            .setOldShowValue(Html2Text.getNewInstance().convert((String) oldValue));
                                } catch (Exception e) {
                                    LOGGER.info("Transforming the old HTML value to plain text for workItemID "
                                            + workItemID + " and field " + fieldKey + " failed with " + e);
                                }
                            } else {
                                //leave the full HTML as it is
                                historyValues.setNewShowValue((String) newValue);
                                historyValues.setOldShowValue((String) oldValue);
                            }
                            //historyValues.setFieldName("");
                        } else {
                            String newShowValue;
                            String oldShowValue;
                            if (isISO) {
                                newShowValue = fieldTypeRT.getShowISOValue(fieldKey, null, newValue, workItemID,
                                        localLookupContainer, locale);
                                oldShowValue = fieldTypeRT.getShowISOValue(fieldKey, null, oldValue, workItemID,
                                        localLookupContainer, locale);
                            } else {
                                //item hierarchy container may be null because changing the
                                //fields depending on that (wbs or project specific ID) is not historized
                                newShowValue = fieldTypeRT.getShowValue(fieldKey, null, newValue, workItemID, null,
                                        locale);
                                oldShowValue = fieldTypeRT.getShowValue(fieldKey, null, oldValue, workItemID, null,
                                        locale);
                            }
                            historyValues.setNewShowValue(newShowValue);
                            historyValues.setOldShowValue(oldShowValue);
                        }
                        historyTransactionMap.put(fieldKey, historyValues);
                    }
                }
            }
        }
        if (LOGGER.isDebugEnabled() && loadingHistoryData != null) {
            populatingHistoryData = new Date();
            LOGGER.debug("Populating the history values for " + " ReportBeanWithHistory lasted "
                    + Long.toString(populatingHistoryData.getTime() - loadingHistoryData.getTime()) + " ms");
        }
        return workItemsHistoryValuesMap;
    }

    /**
     * Gets the raw TFieldChangeBean by workItemID, transactionID and fieldID_parametercode
     * Object can be a  TFieldChangeBean or a  List of TFieldChangeBean (for multiple values)
     * @param workItemIDs
     * @param filterFieldIDs
     * @param includeField
     * @param personID
     * @param fromDate
     * @param toDate
     * @return
     */
    private static Map<Integer, Map<Integer, Map<String, Object>>> getFieldChangeBeansByWorkItemAndTransactionAndMergedField(
            Map<Integer, THistoryTransactionBean> historyTransactionBeansMap,
            List<TFieldChangeBean> fieldChangeBeansList) {
        Map<Integer, Map<Integer, Map<String, Object>>> workItemsFieldChangeBeansMap = new HashMap<Integer, Map<Integer, Map<String, Object>>>();
        Iterator<TFieldChangeBean> fieldChangeBeanItr = fieldChangeBeansList.iterator();
        //field changes map: key -> fieldID_parameterCode value -> TFieldChangeBean or list of fieldChange beans (multiple value)
        Map<String, Object> transactionFieldChangeBeansMap;
        //first load the workItemFieldChangeBeansMap entirely with TFieldChangeBeans (or list of TFieldChangeBeans)
        //to be sure that by processHistoryLoad (the second loop) the transactionFieldChangeBeansMap
        //is fully loaded (because of the composite and multiple fields)
        while (fieldChangeBeanItr.hasNext()) {
            TFieldChangeBean fieldChangeBean = fieldChangeBeanItr.next();
            Integer historyTransactionID = fieldChangeBean.getHistoryTransaction();
            THistoryTransactionBean historyTransactionBean = historyTransactionBeansMap.get(historyTransactionID);
            if (historyTransactionBean != null) {
                Integer workItemID = historyTransactionBean.getWorkItem();
                Map<Integer, Map<String, Object>> workItemFieldChangeBeansMap = workItemsFieldChangeBeansMap
                        .get(workItemID);
                if (workItemFieldChangeBeansMap == null) {
                    workItemFieldChangeBeansMap = new HashMap<Integer, Map<String, Object>>();
                    workItemsFieldChangeBeansMap.put(workItemID, workItemFieldChangeBeansMap);
                }
                transactionFieldChangeBeansMap = workItemFieldChangeBeansMap.get(historyTransactionID);
                if (transactionFieldChangeBeansMap == null) {
                    transactionFieldChangeBeansMap = new HashMap<String, Object>();
                    workItemFieldChangeBeansMap.put(historyTransactionID, transactionFieldChangeBeansMap);
                }
                loadFieldChangeBean(fieldChangeBean, transactionFieldChangeBeansMap);
            }
        }
        return workItemsFieldChangeBeansMap;
    }

    /**
     * Add the multiple values in the map as a list of fieldChangeBeans, and the single values directly in the map 
     * @param fieldChangeBean
     * @param transactionFieldChangeBeansMap
     */
    private static void loadFieldChangeBean(TFieldChangeBean fieldChangeBean,
            Map<String, Object> transactionFieldChangeBeansMap) {
        Integer fieldKey = fieldChangeBean.getFieldKey();
        Integer parameterCode = fieldChangeBean.getParameterCode();
        String mergeKey = MergeUtil.mergeKey(fieldKey, parameterCode);
        IFieldTypeRT fieldTypeRT = null;
        Set<Integer> pseudoHistoryFields = HistoryLoaderBL.getPseudoHistoryFields();
        if (!pseudoHistoryFields.contains(fieldKey)) {
            //do not take compound field types bust just simple field types or compound components field types!
            fieldTypeRT = FieldTypeManager.getFieldTypeRT(fieldKey, parameterCode);
        }
        if (fieldTypeRT != null && fieldTypeRT.isMultipleValues()) {
            List multipleValues = (List) transactionFieldChangeBeansMap.get(mergeKey);
            if (multipleValues == null) {
                multipleValues = new ArrayList();
                transactionFieldChangeBeansMap.put(mergeKey, multipleValues);
            }
            multipleValues.add(fieldChangeBean);
        } else {
            transactionFieldChangeBeansMap.put(mergeKey, fieldChangeBean);
        }
    }

    /**
     * Get the object id of the TFieldChangeBean
     * @param fieldKey
     * @param parameterCode
     * @param fieldChangeObject TFieldChangeBean or list of TFieldChangeBeans
     * @return
     */
    private static Integer getFirstFieldChangeID(Integer fieldKey, Integer parameterCode,
            Object fieldChangeObject) {
        if (fieldChangeObject == null) {
            return null;
        }
        Set<Integer> pseudoHistoryFields = HistoryLoaderBL.getPseudoHistoryFields();
        TFieldChangeBean fieldChangeBean;
        IFieldTypeRT fieldTypeRT = null;
        if (!pseudoHistoryFields.contains(fieldKey)) {
            //if no system or custom field do not try to get the fieldType because it is a costly operation  
            //do not take compound field types bust just simple field types or compound components field types!
            fieldTypeRT = FieldTypeManager.getFieldTypeRT(fieldKey, parameterCode);
        }
        try {
            if (fieldTypeRT != null && fieldTypeRT.isMultipleValues()) {
                List fieldChangeBeanList = (List) fieldChangeObject;
                if (!fieldChangeBeanList.isEmpty()) {
                    fieldChangeBean = (TFieldChangeBean) fieldChangeBeanList.get(0);
                    return fieldChangeBean.getObjectID();
                }
            } else {
                //single fields or the common history
                fieldChangeBean = (TFieldChangeBean) fieldChangeObject;
                return fieldChangeBean.getObjectID();
            }
        } catch (Exception e) {
            LOGGER.warn("Getting the objectID of the history entry failed with " + e.getMessage());
        }
        return null;
    }

    /**
     * Get the object id of the TFieldChangeBean
     * @param fieldKey   
     * @param fieldChangeObject TFieldChangeBean or list of TFieldChangeBeans
     * @return
     */
    private static Integer getTimesEdited(Integer fieldKey, Object fieldChangeObject) {
        TFieldChangeBean fieldChangeBean;
        if (SystemFields.INTEGER_COMMENT.equals(fieldKey) && fieldChangeObject != null) {
            try {
                fieldChangeBean = (TFieldChangeBean) fieldChangeObject;
                return fieldChangeBean.getTimesEdited();
            } catch (Exception e) {
                LOGGER.debug("FieldChangeObject for comment is " + fieldChangeObject.getClass().getName() + " "
                        + e.getMessage(), e);
            }
        }
        return null;
    }

    /**
     * Get the history for a field for an array of workItemIDs
     * @param workItemIDs is null or empty get for all
     * @param fieldID 
     * @param longTextIsPlain
     * @return
     */
    public static Map<Integer, StringBuffer> getByWorkItemsLongTextField(List<Integer> workItemIDs, Integer fieldID,
            LONG_TEXT_TYPE longTextIsPlain) {
        Map<Integer, StringBuffer> commentsMap = new HashMap<Integer, StringBuffer>();
        List<IntegerStringBean> commentList = HistoryTransactionBL.getByWorkItemsLongTextField(workItemIDs,
                fieldID);
        if (commentList != null) {
            Iterator<IntegerStringBean> iterator = commentList.iterator();
            while (iterator.hasNext()) {
                IntegerStringBean integerStringBean = iterator.next();
                Integer workItemID = integerStringBean.getValue();
                String newComment = integerStringBean.getLabel();
                if (newComment != null && !"".equals(newComment)) {
                    StringBuffer commentsBuffer = commentsMap.get(workItemID);
                    if (commentsBuffer == null) {
                        commentsBuffer = new StringBuffer();
                        commentsMap.put(workItemID, commentsBuffer);
                    }
                    String processedText;
                    switch (longTextIsPlain) {
                    case ISPLAIN:
                        try {
                            processedText = Html2Text.getNewInstance().convert(newComment);
                        } catch (Exception e) {
                            processedText = newComment;
                        }
                        break;
                    case ISSIMPLIFIEDHTML:
                        try {
                            processedText = Html2Text.getCustomInstance().convert(newComment);
                        } catch (Exception e) {
                            processedText = newComment;
                        }
                        break;
                    default:
                        processedText = newComment;
                        break;
                    }
                    commentsBuffer.append(processedText);
                }
            }
        }
        return commentsMap;
    }

    /**
     * Get the history for an array of workItemIDs
     * @param workItemIDs
     * @param filterFieldID if null do not filter by it
     * @param includeField not null: if true include this field, if false exclude this field
     * @param personBean if null or not external do not filter by it
     * @param loaderResourceBundleMessages
     * @param longTextIsPlain
     * @return
     */
    public static String getLongTextField(Integer objectID, boolean newValue, LONG_TEXT_TYPE longTextIsPlain) {
        TFieldChangeBean fieldChangeBean = FieldChangeBL.loadByPrimaryKey(objectID);
        String processedText = null;
        String value = null;
        if (newValue) {
            value = fieldChangeBean.getNewLongTextValue();
        } else {
            value = fieldChangeBean.getOldLongTextValue();
        }
        if (fieldChangeBean != null && value != null) {
            switch (longTextIsPlain) {
            case ISPLAIN:
                try {
                    processedText = Html2Text.getNewInstance().convert(value);
                } catch (Exception e) {
                    processedText = value;
                }
                break;
            case ISSIMPLIFIEDHTML:
                try {
                    processedText = Html2Text.getCustomInstance().convert(value);
                } catch (Exception e) {
                    processedText = value;
                }
                break;
            default:
                processedText = value;
                break;
            }
        }
        return processedText;
    }

    /**
     * Get the list of HistoryValues from the map of Map<Integer, Map<Integer, HistoryValues>>
     * @param allHistoryMap
     * @param showCommentsInHistory whether the comment fields should be melt together with the most specific field 
     * from the transaction in this case allHistoryMap contains all history values (history and comment) 
     * @return
     */
    public static List<HistoryValues> getHistoryValuesList(Map<Integer, Map<Integer, HistoryValues>> allHistoryMap,
            boolean showCommentsInHistory) {
        List<HistoryValues> historyList = new ArrayList<HistoryValues>();
        if (allHistoryMap != null) {
            Iterator<Integer> itrHistoryMap = allHistoryMap.keySet().iterator();
            while (itrHistoryMap.hasNext()) {
                Integer transactionID = itrHistoryMap.next();
                Map<Integer, HistoryValues> changesMap = allHistoryMap.get(transactionID);
                Set<Integer> changedFieldIDs = changesMap.keySet();
                Integer mostSpecificField = null;
                String transactionComment = null;
                if (changedFieldIDs.contains(SystemFields.INTEGER_COMMENT)) {
                    //the transaction comments are shown in the comment column of the most specific fields's row
                    //single comments, like any other explicit field are shown in a separate row but only if showCommentsInHistory = true
                    if (changedFieldIDs.size() > 1) {
                        //a transaction which contains both a comment and other field changes: 
                        //save the comment to the transaction's most specific fields comment column
                        //but remove it as a row
                        HistoryValues commentHistoryValue = changesMap.remove(SystemFields.INTEGER_COMMENT);
                        transactionComment = (String) commentHistoryValue.getNewValue();
                        mostSpecificField = getMostSpecificFieldForComment(changedFieldIDs);
                    }
                    if (!showCommentsInHistory) {
                        //this comment is single one: remove if the comments are not shown in the history 
                        changesMap.remove(SystemFields.INTEGER_COMMENT);
                    }
                }
                Iterator<Integer> itrChanges = changedFieldIDs.iterator();
                while (itrChanges.hasNext()) {
                    Integer fieldID = itrChanges.next();
                    HistoryValues histValues = changesMap.get(fieldID);
                    if (mostSpecificField != null && fieldID.equals(mostSpecificField)) {
                        histValues.setTransactionComment(transactionComment);
                    }
                    historyList.add(histValues);
                }
            }
        }
        if (historyList.size() > 0) {
            // sort all history beans chronologically
            Collections.sort(historyList, new HistoryComparator());
        }
        return historyList;
    }

    /**
     * Get the list of HistoryValues from the map of Map<Integer, Map<Integer, HistoryValues>>
     * @param allHistoryMap
     * @return
     */
    public static List<FlatHistoryBean> getFlatHistoryValuesListForWorkItems(
            Map<Integer, SortedMap<Integer, Map<Integer, HistoryValues>>> allHistoryForWorkItemsMap,
            Map<Integer, TWorkItemBean> workItemsMap, Locale locale) {
        List<FlatHistoryBean> workItemsHistoryList = new ArrayList<FlatHistoryBean>();
        Iterator<Integer> workItemsIterartor = workItemsMap.keySet().iterator();
        while (workItemsIterartor.hasNext()) {
            Integer workItemID = workItemsIterartor.next();
            SortedMap<Integer, Map<Integer, HistoryValues>> allHistoryForWorkItemMap = allHistoryForWorkItemsMap
                    .get(workItemID);
            if (allHistoryForWorkItemMap != null) {
                List<FlatHistoryBean> workItemHistoryList = getFlatHistoryValuesList(allHistoryForWorkItemMap,
                        workItemsMap, workItemID, locale, true, true);
                workItemsHistoryList.addAll(workItemHistoryList);
            }
        }
        return workItemsHistoryList;
    }

    private static boolean isLong(Integer fieldID) {
        if (fieldID == null) {
            return false;
        } else {
            int intFieldID = fieldID.intValue();
            return (intFieldID == TFieldChangeBean.COMPOUND_HISTORY_FIELD || intFieldID == SystemFields.COMMENT
                    || intFieldID == SystemFields.COMMENT_MODIFY_HISTORY_FIELD
                    || intFieldID == SystemFields.COMMENT_DELETE_HISTORY_FIELD
                    || intFieldID == SystemFields.DESCRIPTION);
        }

    }

    /**
     * Get the list of HistoryValues from the map of Map<Integer, Map<Integer, HistoryValues>>
     * @param allHistoryMap
     * @param workItemBeansMap
     * @param workItemID
     * @param locale
     * @param longEntriesSeparate
     * @return
     */
    public static List<FlatHistoryBean> getFlatHistoryValuesList(
            SortedMap<Integer, Map<Integer, HistoryValues>> allHistoryMap,
            Map<Integer, TWorkItemBean> workItemBeansMap, Integer workItemID, Locale locale,
            boolean longEntriesSeparate, boolean withChildren) {
        List<FlatHistoryBean> historyList = new ArrayList<FlatHistoryBean>();
        if (allHistoryMap != null) {
            Iterator<Integer> itrHistoryMap = allHistoryMap.keySet().iterator();
            while (itrHistoryMap.hasNext()) {
                Integer transactionID = itrHistoryMap.next();
                Map<Integer, HistoryValues> changesMap = allHistoryMap.get(transactionID);
                Set<Integer> changedFieldIDs = changesMap.keySet();
                int type = getType(changedFieldIDs);
                HistoryValues histValues = null;
                List<HistoryEntry> historyEntries = new ArrayList<HistoryEntry>();
                List<HistoryEntry> historyLongEntries = new ArrayList<HistoryEntry>();
                Iterator<Integer> itrChanges = changedFieldIDs.iterator();
                while (itrChanges.hasNext()) {
                    Integer fieldID = itrChanges.next();
                    histValues = changesMap.get(fieldID);
                    HistoryEntry historyEntry = new HistoryEntry();
                    historyEntry.setFieldLabel(histValues.getFieldName());
                    String newValue = histValues.getNewShowValue();
                    String oldValue = histValues.getOldShowValue();
                    //for flat history with children only the new values are shown but for date change
                    //the old value will also be included in a parenthesis
                    if (withChildren && (SystemFields.INTEGER_STARTDATE.equals(fieldID)
                            || SystemFields.INTEGER_ENDDATE.equals(fieldID))) {
                        if (oldValue != null && !"".equals(oldValue.trim())) {
                            newValue = newValue + " (" + oldValue + ")";
                        }
                    }
                    boolean isLongField = isLong(fieldID);
                    if (isLongField) {
                        //if(!SystemFields.INTEGER_COMMENT.equals(fieldID)){
                        String diff = null;
                        try {
                            String newValueFormatted = ItemDetailBL.formatDescription(newValue, locale);
                            String oldValueFormatted = ItemDetailBL.formatDescription(oldValue, locale);
                            diff = HTMLDiff.makeDiff(newValueFormatted, oldValueFormatted, locale);
                            diff = ItemDetailBL.formatDescription(diff, locale);
                        } catch (Exception ex) {
                            LOGGER.error(" can't create diff: " + ex.getMessage());
                            if (LOGGER.isDebugEnabled()) {
                                LOGGER.error(ExceptionUtils.getStackTrace(ex));
                            }
                        }
                        historyEntry.setDiff(diff);
                        //}
                    }
                    String changedText = null;
                    if (type == HistoryBean.HISTORY_TYPE.ATTACHMENT) {
                        changedText = "<strong>" + histValues.getFieldName() + "</strong> ";
                        if (newValue != null && newValue.length() > 0) {
                            changedText += "<span class=\"histNewValue\">" + histValues.getNewShowValue()
                                    + "</span>";
                        } else {
                            changedText += "<span class=\"histOldValue\">" + histValues.getOldShowValue()
                                    + "</span>";
                        }
                    } else {
                        String tkey = "item.history.changeTextSimpleField";
                        if (isLongField) {
                            tkey = "item.history.changeTextLongField";
                        }
                        if (newValue != null && newValue.length() > 0 && oldValue != null
                                && oldValue.length() > 0) {
                            changedText = LocalizeUtil
                                    .getParametrizedString(
                                            tkey, new Object[] { histValues.getFieldName(),
                                                    histValues.getOldShowValue(), histValues.getNewShowValue() },
                                            locale);
                        } else if (newValue != null && newValue.length() > 0) {
                            tkey = tkey + ".oldValueNull";
                            changedText = LocalizeUtil.getParametrizedString(tkey,
                                    new Object[] { histValues.getFieldName(), histValues.getNewShowValue() },
                                    locale);
                        } else {
                            tkey = tkey + ".newValueNull";
                            changedText = LocalizeUtil.getParametrizedString(tkey,
                                    new Object[] { histValues.getFieldName(), histValues.getOldShowValue() },
                                    locale);
                        }
                    }
                    historyEntry.setNewValue(newValue);
                    historyEntry.setChangedText(changedText);
                    historyEntry.setOldValue(oldValue);
                    if (longEntriesSeparate && fieldID.intValue() == TFieldChangeBean.COMPOUND_HISTORY_FIELD) {
                        historyEntry.setFieldLabel(null);
                    }
                    boolean fieldIsLong = isLong(fieldID);
                    if (fieldIsLong) {
                        historyEntry
                                .setNewValue(ItemDetailBL.formatDescription(historyEntry.getNewValue(), locale));
                    }
                    if (longEntriesSeparate && fieldIsLong) {
                        //TODO whether the not pseudo field is of type long 
                        historyLongEntries.add(historyEntry);
                    } else {
                        //long fields and short fields are treated same: format the long field content already here (not in the jsp) 
                        historyEntries.add(historyEntry);
                    }
                }
                if (histValues != null) {
                    FlatHistoryBean flatHistoryBean = new FlatHistoryBean();
                    flatHistoryBean.setChangedByName(histValues.getChangedByName());
                    flatHistoryBean.setPersonID(histValues.getChangedByID());
                    flatHistoryBean.setLastEdit(histValues.getLastEdit());
                    flatHistoryBean.setHistoryEntries(historyEntries);
                    flatHistoryBean.setHistoryLongEntries(historyLongEntries);
                    flatHistoryBean.setType(type);
                    flatHistoryBean.setIconName(getIconByType(type));
                    addWorkItemToFlatHistoryBean(flatHistoryBean, workItemBeansMap, workItemID,
                            FlatHistoryBean.RENDER_TYPE.HISTORY_VALUES);
                    historyList.add(flatHistoryBean);
                }
            }
        }
        if (historyList.size() > 0) {
            // sort all history beans chronologically
            Collections.sort(historyList, new HistoryComparator());
        }
        return historyList;
    }

    public static int getType(Set<Integer> fieldIDs) {
        if (fieldIDs == null) {
            return HistoryBean.HISTORY_TYPE.COMMON_HISTORYVALUES;
        }
        if (fieldIDs.contains(SystemFields.INTEGER_STATE)) {
            return HistoryBean.HISTORY_TYPE.STATE_CHANGE;
        }
        if (fieldIDs.contains(SystemFields.INTEGER_STARTDATE) || fieldIDs.contains(SystemFields.INTEGER_ENDDATE)) {
            return HistoryBean.HISTORY_TYPE.BASELINE_CHANGE;
        }
        if (fieldIDs.contains(SystemFields.ATTACHMENT_ADD_HISTORY_FIELD)
                || fieldIDs.contains(SystemFields.ATTACHMENT_MODIFY_HISTORY_FIELD)
                || fieldIDs.contains(SystemFields.ATTACHMENT_DELETE_HISTORY_FIELD)) {
            return HistoryBean.HISTORY_TYPE.ATTACHMENT;
        }
        Iterator<Integer> iterator = fieldIDs.iterator();
        while (iterator.hasNext()) {
            Integer fieldID = iterator.next();
            //not common and not comment but probably explicit history field
            if (!fieldID.equals(TFieldChangeBean.COMPOUND_HISTORY_FIELD)
                    && !fieldID.equals(SystemFields.INTEGER_COMMENT)
                    && !fieldIDs.contains(SystemFields.COMMENT_MODIFY_HISTORY_FIELD)
                    && !fieldIDs.contains(SystemFields.COMMENT_DELETE_HISTORY_FIELD)) {
                return HistoryBean.HISTORY_TYPE.OTHER_EXPLICIT_HISTORY;
            }
        }
        if (fieldIDs.contains(SystemFields.INTEGER_COMMENT)
                || fieldIDs.contains(SystemFields.COMMENT_MODIFY_HISTORY_FIELD)
                || fieldIDs.contains(SystemFields.COMMENT_DELETE_HISTORY_FIELD)) {
            //comment
            return HistoryBean.HISTORY_TYPE.COMMENT;
        }
        //common
        return HistoryBean.HISTORY_TYPE.COMMON_HISTORYVALUES;
    }

    public static String getIconByType(int type) {
        switch (type) {
        case HistoryBean.HISTORY_TYPE.COMMON_HISTORYVALUES:
            return "trail.gif";
        case HistoryBean.HISTORY_TYPE.STATE_CHANGE:
            return "stateChange.gif";
        case HistoryBean.HISTORY_TYPE.COMMENT:
            return "comment.png";
        /*case HistoryBean.HISTORY_TYPE.BUDGET_CHANGE:
           return "budget.gif";*/
        case HistoryBean.HISTORY_TYPE.BASELINE_CHANGE:
            return "calendar.png";
        case HistoryBean.HISTORY_TYPE.COST:
            return "cost.gif";
        case HistoryBean.HISTORY_TYPE.ATTACHMENT:
            return "attachment.png";
        case HistoryBean.HISTORY_TYPE.OTHER_EXPLICIT_HISTORY:
            return "customField.gif";
        default:
            return "";
        }
    }

    /**
     * Try to guess the most important field the comment will be linked with 
     * @param fieldIDsSet
     * @return
     */
    private static Integer getMostSpecificFieldForComment(Set<Integer> fieldIDsSet) {
        List<Integer> fieldPriorityList = new ArrayList<Integer>();
        fieldPriorityList.add(SystemFields.INTEGER_STATE);
        fieldPriorityList.add(SystemFields.INTEGER_PROJECT);
        fieldPriorityList.add(SystemFields.INTEGER_ISSUETYPE);
        fieldPriorityList.add(SystemFields.INTEGER_ENDDATE);
        fieldPriorityList.add(SystemFields.INTEGER_STARTDATE);
        fieldPriorityList.add(SystemFields.INTEGER_MANAGER);
        fieldPriorityList.add(SystemFields.INTEGER_RESPONSIBLE);
        Iterator<Integer> itrFieldPriorityList = fieldPriorityList.iterator();
        while (itrFieldPriorityList.hasNext()) {
            Integer fieldID = itrFieldPriorityList.next();
            if (fieldIDsSet.contains(fieldID)) {
                return fieldID;
            }
        }
        Iterator<Integer> itrFieldIDsSet = fieldIDsSet.iterator();
        return itrFieldIDsSet.next();
    }

    public static List<HistoryValues> setTransactionLimits(List<HistoryValues> historyList) {
        Integer oldTransactionID = null;
        Integer newTransactionID;
        if (historyList != null) {
            Iterator<HistoryValues> iterator = historyList.iterator();
            while (iterator.hasNext()) {
                HistoryValues historyValues = iterator.next();
                newTransactionID = historyValues.getTransactionID();
                historyValues.setNewTransction(EqualUtils.notEqual(newTransactionID, oldTransactionID));
                oldTransactionID = newTransactionID;
            }
        }
        return historyList;
    }

    /**
     * Get the history for a workItemID
     * @param workItemID
     * @param filterFieldID if null do not filter by it
     * @param includeField: if filterFieldID not null: if true include this field, if false exclude this field
     * @param personID personBean if null or not external do not filter by it
     * @param locale
     * @param longTextIsPlain
     * @return
     */
    public static SortedMap<Integer, Map<Integer, HistoryValues>> getWorkItemHistory(Integer workItemID,
            Integer filterFieldID, boolean includeField, Integer personID, Locale locale, boolean isISO,
            LONG_TEXT_TYPE longTextIsPlain) {
        Integer[] filterFieldIDs = null;
        if (filterFieldID != null) {
            filterFieldIDs = new Integer[] { filterFieldID };
        }
        Map<Integer, SortedMap<Integer, Map<Integer, HistoryValues>>> historyValues = getWorkItemsHistory(
                new int[] { workItemID }, filterFieldIDs, includeField, null, null, null, locale, isISO,
                longTextIsPlain, true, personID);
        return historyValues.get(workItemID);
    }

    /**
     * Gets the comments form the history
     * @param personID
     * @param workItemBean
     * @param locale
     * @param isISO
     * @param longTextIsPlain
     * @return
     */
    public static List<HistoryValues> getRestrictedWorkItemComments(Integer personID, Integer workItemID,
            Locale locale, boolean isISO, LONG_TEXT_TYPE longTextIsPlain) {
        List<HistoryValues> comments = null;
        try {
            TWorkItemBean workItemBean = ItemBL.loadWorkItem(workItemID);
            if (workItemBean != null) {
                return getRestrictedWorkItemComments(personID, workItemBean, locale, isISO, longTextIsPlain);
            }
        } catch (ItemLoaderException e) {
        }
        return comments;
    }

    private static List<HistoryValues> getRestrictedWorkItemComments(Integer personID, TWorkItemBean workItemBean,
            Locale locale, boolean isISO, LONG_TEXT_TYPE longTextIsPlain) {
        List<HistoryValues> comments = null;
        Map<Integer, Integer> fieldRestrictions = AccessBeans.getFieldRestrictions(personID,
                workItemBean.getProjectID(), workItemBean.getListTypeID(), false);
        if (fieldRestrictions == null || !fieldRestrictions.containsKey(SystemFields.INTEGER_COMMENT)) {
            return getWorkItemComments(personID, workItemBean, locale, isISO, longTextIsPlain);
        }
        return comments;
    }

    private static List<HistoryValues> getWorkItemComments(Integer personID, TWorkItemBean workItemBean,
            Locale locale, boolean isISO, LONG_TEXT_TYPE longTextIsPlain) {
        List<HistoryValues> comments = HistoryLoaderBL
                .getHistoryValuesList(HistoryLoaderBL.getWorkItemHistory(workItemBean.getObjectID(),
                        SystemFields.INTEGER_COMMENT, true, personID, locale, isISO, longTextIsPlain), true);
        return comments;
    }

    /**
     * Get the history for a workItemID
     * @param workItemID
     * @param filterFieldIDs if null do not filter by it
     * @param includeField: if filterFieldID not null: if true include this field, if false exclude this field
     * @param personID personBean if null or not external do not filter by it
     * @param locale
     * @param longTextIsPlain
     * @return
     */
    public static SortedMap<Integer, Map<Integer, HistoryValues>> getRestrictedWorkItemHistory(Integer personID,
            TWorkItemBean workItemBean, Locale locale, boolean isISO, LONG_TEXT_TYPE longTextIsPlain) {
        Map<Integer, SortedMap<Integer, Map<Integer, HistoryValues>>> historyValues = null;
        if (workItemBean != null) {
            historyValues = getWorkItemsHistory(new int[] { workItemBean.getObjectID() }, null, false, null, null,
                    null, locale, isISO, longTextIsPlain, true, personID);
            return historyValues.get(workItemBean.getObjectID());
        } else {
            return null;
        }
    }

    private static HistoryValues initHistoryValues(THistoryTransactionBean historyTransactionBean) {
        HistoryValues historyValues = new HistoryValues();
        if (historyTransactionBean != null) {
            historyValues.setChangedByID(historyTransactionBean.getChangedByID());
            historyValues.setChangedByName(historyTransactionBean.getChangedByName());
            historyValues.setWorkItemID(historyTransactionBean.getWorkItem());
            historyValues.setLastEdit(historyTransactionBean.getLastEdit());
            historyValues.setTransactionUuid(historyTransactionBean.getUuid());
        }
        return historyValues;
    }

    /**
     * Get the attribute value(s) for a fieldID
     * If composite gather the parts in a list (@see TWorkItemBean.getAttribute(Integer fieldID)})
     * 
     * @param historyValuesMap key: fieldID_parameterCode, value: Object or Object[]
     * @param fieldID
     * @return
     */
    private static Object getAttribute(Map<String, Object> historyValuesMap, Integer fieldID,
            IFieldTypeRT fieldTypeRT) {
        if (historyValuesMap == null || historyValuesMap.isEmpty()) {
            return null;
        }
        if (fieldTypeRT.isComposite()) {
            //composite custom field
            int parameterCode = 1;
            String mergeKey = MergeUtil.mergeKey(fieldID, Integer.valueOf(parameterCode));
            Map<Integer, Object> compositeValueMap = new HashMap<Integer, Object>();
            //get the parts till keys are found (no problem if the value for key is null)
            while (historyValuesMap.containsKey(mergeKey)) {
                compositeValueMap.put(Integer.valueOf(parameterCode), historyValuesMap.get(mergeKey));
                parameterCode++;
                mergeKey = MergeUtil.mergeKey(fieldID, Integer.valueOf(parameterCode));
            }
            if (!compositeValueMap.isEmpty()) {
                return compositeValueMap;
            }
        } else {
            //single custom field
            return historyValuesMap.get(MergeUtil.mergeKey(fieldID, null));
        }
        return null;
    }

    /**
     * Get the resource key for attachmentHistoryField 
     * @param historyFieldKey
     * @return
     */
    public static String getHistoryFieldKey(Integer historyFieldKey) {
        if (historyFieldKey == null) {
            return COMMON_FIELD_KEY;
        }
        switch (historyFieldKey.intValue()) {
        case SystemFields.ATTACHMENT_ADD_HISTORY_FIELD:
            return ATTACHMENT_ADDED_FIELD_KEY;
        case SystemFields.ATTACHMENT_MODIFY_HISTORY_FIELD:
            return ATTACHMENT_MODIFIED_FIELD_KEY;
        case SystemFields.ATTACHMENT_DELETE_HISTORY_FIELD:
            return ATTACHMENT_DELETED_FIELD_KEY;
        case SystemFields.COMMENT_MODIFY_HISTORY_FIELD:
            return COMMENT_MODIFIED_FIELD_KEY;
        case SystemFields.COMMENT_DELETE_HISTORY_FIELD:
            return COMMENT_DELETED_FIELD_KEY;
        /**
         * !!!Explicit for Revenue Solutions!!!
         */
        /*case SystemFields.MIGRATION_ADD_HISTORY_FIELD:
           return MigrateBL.MIGRATION_HISTORY_ADDED;
        case SystemFields.MIGRATION_MODIFY_HISTORY_FIELD:
           return MigrateBL.MIGRATION_HISTORY_MODIFIED;
        case SystemFields.MIGRATION_DELETE_HISTORY_FIELD:
           return MigrateBL.MIGRATION_HISTORY_DELETED;*/
        /**
         * !!!Explicit for Revenue Solutions!!!
         */
        default:
            return COMMON_FIELD_KEY;
        }
    }

    /**
     * Only for history with children (in the history for a single issue no extra issue identifier is needed )
     * @param flatHistoryBean
     * @param workItemBeansMap
     * @param workItemID
     * @param renderType
     */
    public static void addWorkItemToFlatHistoryBean(FlatHistoryBean flatHistoryBean,
            Map<Integer, TWorkItemBean> workItemBeansMap, Integer workItemID, int renderType) {
        if (workItemBeansMap != null) {
            TWorkItemBean workItemBean = workItemBeansMap.get(workItemID);
            if (workItemBean != null) {
                flatHistoryBean.setWorkItemID(workItemBean.getObjectID());
                flatHistoryBean.setTitle(workItemBean.getSynopsis());
                flatHistoryBean.setRenderType(renderType);
            }
        }
    }

    public static String formatEffort(TBudgetBean bugetBean, Locale locale) {
        DoubleNumberFormatUtil doubleNumberFormatUtil = DoubleNumberFormatUtil.getInstance();
        String effortString = "";
        if (bugetBean != null && bugetBean.getEstimatedHours() != null) {
            effortString = doubleNumberFormatUtil.formatGUI(bugetBean.getEstimatedHours(), locale);
            String timeUnitString = AccountingBL.getTimeUnitOption(bugetBean.getTimeUnit(), locale);
            if (timeUnitString != null) {
                effortString += " " + timeUnitString;
            }
        }
        return effortString;
    }

    public static String formatCost(TBudgetBean budgetBean, Locale locale) {
        DoubleNumberFormatUtil doubleNumberFormatUtil = DoubleNumberFormatUtil.getInstance();
        String costString = "";
        if (budgetBean != null && budgetBean.getEstimatedCost() != null) {
            costString = doubleNumberFormatUtil.formatGUI(budgetBean.getEstimatedCost(), locale);
            if (budgetBean.getCurrency() != null) {
                costString += " " + budgetBean.getCurrency();
            }
        }
        return costString;
    }

    public static String formatEffort(TCostBean costBean, Locale locale) {
        DoubleNumberFormatUtil doubleNumberFormatUtil = DoubleNumberFormatUtil.getInstance();
        String effortString = "";
        if (costBean != null && costBean.getHours() != null) {
            effortString = doubleNumberFormatUtil.formatGUI(costBean.getHours(), locale);
            String timeUnitString = AccountingBL.getTimeUnitOption(AccountingBL.TIMEUNITS.HOURS, locale);
            if (timeUnitString != null) {
                effortString += " " + timeUnitString;
            }
        }
        return effortString;
    }

    public static String formatCost(TCostBean costBean, Locale locale) {
        DoubleNumberFormatUtil doubleNumberFormatUtil = DoubleNumberFormatUtil.getInstance();
        String costString = "";
        if (costBean != null && costBean.getCost() != null) {
            costString = doubleNumberFormatUtil.formatGUI(costBean.getCost(), locale);
            if (costBean.getCurrency() != null) {
                costString += " " + costBean.getCurrency();
            }
        }
        return costString;
    }

    public static String formatEffort(TActualEstimatedBudgetBean actualEstimatedBudgetBean, Locale locale) {
        DoubleNumberFormatUtil doubleNumberFormatUtil = DoubleNumberFormatUtil.getInstance();
        String effortString = "";
        if (actualEstimatedBudgetBean != null && actualEstimatedBudgetBean.getEstimatedHours() != null) {
            effortString = doubleNumberFormatUtil.formatGUI(actualEstimatedBudgetBean.getEstimatedHours(), locale);
            String timeUnitString = AccountingBL.getTimeUnitOption(actualEstimatedBudgetBean.getTimeUnit(), locale);
            if (timeUnitString != null) {
                effortString += " " + timeUnitString;
            }
        }
        return effortString;
    }

    public static String formatCost(TActualEstimatedBudgetBean actualEstimatedBudgetBean, Locale locale) {
        DoubleNumberFormatUtil doubleNumberFormatUtil = DoubleNumberFormatUtil.getInstance();
        String costString = "";
        if (actualEstimatedBudgetBean != null && actualEstimatedBudgetBean.getEstimatedCost() != null) {
            costString = doubleNumberFormatUtil.formatGUI(actualEstimatedBudgetBean.getEstimatedCost(), locale);
            if (actualEstimatedBudgetBean.getCurrency() != null) {
                costString += " " + actualEstimatedBudgetBean.getCurrency();
            }
        }
        return costString;
    }

    public static Set<Integer> getAttachmentHistoryFields() {
        //we force all the attachment changes to have explicit history
        Set<Integer> attachmentHistoryFields = new HashSet<Integer>();
        attachmentHistoryFields.add(SystemFields.INTEGER_ATTACHMENT_ADD_HISTORY_FIELD);
        attachmentHistoryFields.add(SystemFields.INTEGER_ATTACHMENT_MODIFY_HISTORY_FIELD);
        attachmentHistoryFields.add(SystemFields.INTEGER_ATTACHMENT_DELETE_HISTORY_FIELD);
        /**
         * !!!Explicit for Revenue Solutions!!!
         */
        //attachmentHistoryFields.add(SystemFields.MIGRATION_ADD_HISTORY_FIELD);
        //attachmentHistoryFields.add(SystemFields.MIGRATION_MODIFY_HISTORY_FIELD);
        //attachmentHistoryFields.add(SystemFields.MIGRATION_DELETE_HISTORY_FIELD);
        /**
         * !!!Explicit for Revenue Solutions!!!
         */
        return attachmentHistoryFields;
    }

    public static Set<Integer> getCommentHistoryFields() {
        //we force all the attachment changes to have explicit history
        Set<Integer> commentHistoryFields = new HashSet<Integer>();
        commentHistoryFields.add(SystemFields.INTEGER_COMMENT_MODIFY_HISTORY_FIELD);
        commentHistoryFields.add(SystemFields.INTEGER_COMMENT_DELETE_HISTORY_FIELD);
        return commentHistoryFields;
    }

    public static Set<Integer> getPseudoHistoryFields() {
        Set<Integer> pseudoHistoryFields = getAttachmentHistoryFields();
        pseudoHistoryFields.addAll(getCommentHistoryFields());
        pseudoHistoryFields.add(TFieldChangeBean.COMPOUND_HISTORY_FIELD);
        return pseudoHistoryFields;
    }
}