org.nuclos.server.masterdata.ejb3.MasterDataFacadeHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.nuclos.server.masterdata.ejb3.MasterDataFacadeHelper.java

Source

//Copyright (C) 2010  Novabit Informationssysteme GmbH
//
//This file is part of Nuclos.
//
//Nuclos is free software: you can redistribute it and/or modify
//it under the terms of the GNU Affero General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//Nuclos 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 Affero General Public License for more details.
//
//You should have received a copy of the GNU Affero General Public License
//along with Nuclos.  If not, see <http://www.gnu.org/licenses/>.
package org.nuclos.server.masterdata.ejb3;

import java.io.File;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.NullArgumentException;
import org.apache.commons.lang.ObjectUtils;
import org.apache.log4j.Logger;
import org.nuclos.common.JMSConstants;
import org.nuclos.common.NuclosBusinessException;
import org.nuclos.common.NuclosEntity;
import org.nuclos.common.NuclosFatalException;
import org.nuclos.common.ParameterProvider;
import org.nuclos.common.SearchConditionUtils;
import org.nuclos.common.SpringApplicationContextHolder;
import org.nuclos.common.collect.collectable.CollectableEntityField;
import org.nuclos.common.collect.collectable.CollectableValueIdField;
import org.nuclos.common.collect.collectable.searchcondition.CollectableComparison;
import org.nuclos.common.collect.collectable.searchcondition.CollectableIdCondition;
import org.nuclos.common.collect.collectable.searchcondition.CollectableSearchCondition;
import org.nuclos.common.collect.collectable.searchcondition.ComparisonOperator;
import org.nuclos.common.collect.collectable.searchcondition.CompositeCollectableSearchCondition;
import org.nuclos.common.collect.collectable.searchcondition.LogicalOperator;
import org.nuclos.common.collection.CollectionUtils;
import org.nuclos.common.collection.MasterDataToEntityObjectTransformer;
import org.nuclos.common.collection.Transformer;
import org.nuclos.common.dal.DalSupportForMD;
import org.nuclos.common.dal.vo.EntityFieldMetaDataVO;
import org.nuclos.common.dal.vo.EntityMetaDataVO;
import org.nuclos.common.dal.vo.EntityObjectVO;
import org.nuclos.common.dal.vo.SystemFields;
import org.nuclos.common.querybuilder.NuclosDatasourceException;
import org.nuclos.common.transport.GzipList;
import org.nuclos.common2.EntityAndFieldName;
import org.nuclos.common2.IOUtils;
import org.nuclos.common2.IdUtils;
import org.nuclos.common2.StringUtils;
import org.nuclos.common2.TruncatableCollection;
import org.nuclos.common2.TruncatableCollectionDecorator;
import org.nuclos.common2.exception.CommonCreateException;
import org.nuclos.common2.exception.CommonFatalException;
import org.nuclos.common2.exception.CommonFinderException;
import org.nuclos.common2.exception.CommonPermissionException;
import org.nuclos.common2.exception.CommonRemoveException;
import org.nuclos.common2.exception.CommonStaleVersionException;
import org.nuclos.common2.exception.CommonValidationException;
import org.nuclos.server.attribute.ejb3.AttributeFacadeLocal;
import org.nuclos.server.attribute.ejb3.LayoutFacadeBean;
import org.nuclos.server.autosync.XMLEntities;
import org.nuclos.server.common.LocalCachesUtil;
import org.nuclos.server.common.MasterDataMetaCache;
import org.nuclos.server.common.MetaDataServerProvider;
import org.nuclos.server.common.ModuleConstants;
import org.nuclos.server.common.NuclosPerformanceLogger;
import org.nuclos.server.common.NuclosSystemParameters;
import org.nuclos.server.common.RecordGrantUtils;
import org.nuclos.server.common.SecurityCache;
import org.nuclos.server.common.ServerParameterProvider;
import org.nuclos.server.common.ServerServiceLocator;
import org.nuclos.server.dal.DalSupportForGO;
import org.nuclos.server.dal.DalUtils;
import org.nuclos.server.dal.processor.nuclet.JdbcEntityObjectProcessor;
import org.nuclos.server.dal.provider.NucletDalProvider;
import org.nuclos.server.database.SpringDataBaseHelper;
import org.nuclos.server.dblayer.DbAccess;
import org.nuclos.server.dblayer.DbException;
import org.nuclos.server.dblayer.DbObjectHelper;
import org.nuclos.server.dblayer.DbObjectHelper.DbObjectType;
import org.nuclos.server.dblayer.DbStatementUtils;
import org.nuclos.server.dblayer.DbTuple;
import org.nuclos.server.dblayer.DbType;
import org.nuclos.server.dblayer.incubator.DbExecutor.ResultSetRunner;
import org.nuclos.server.dblayer.query.DbColumnExpression;
import org.nuclos.server.dblayer.query.DbFrom;
import org.nuclos.server.dblayer.query.DbQuery;
import org.nuclos.server.dblayer.query.DbQueryBuilder;
import org.nuclos.server.dblayer.query.DbSelection;
import org.nuclos.server.dblayer.statements.DbStatement;
import org.nuclos.server.dblayer.statements.DbStructureChange;
import org.nuclos.server.dblayer.statements.DbStructureChange.Type;
import org.nuclos.server.dblayer.structure.DbSimpleView;
import org.nuclos.server.dblayer.structure.DbSimpleView.DbSimpleViewColumn;
import org.nuclos.server.genericobject.ejb3.GenericObjectFacadeLocal;
import org.nuclos.server.genericobject.searchcondition.CollectableSearchExpression;
import org.nuclos.server.genericobject.valueobject.GenericObjectDocumentFile;
import org.nuclos.server.genericobject.valueobject.GenericObjectVO;
import org.nuclos.server.genericobject.valueobject.GenericObjectWithDependantsVO;
import org.nuclos.server.jms.NuclosJMSUtils;
import org.nuclos.server.masterdata.valueobject.DependantMasterDataMap;
import org.nuclos.server.masterdata.valueobject.DependantMasterDataMapImpl;
import org.nuclos.server.masterdata.valueobject.MasterDataMetaFieldVO;
import org.nuclos.server.masterdata.valueobject.MasterDataMetaVO;
import org.nuclos.server.masterdata.valueobject.MasterDataVO;
import org.nuclos.server.masterdata.valueobject.MasterDataWithDependantsVO;
import org.nuclos.server.report.ejb3.DatasourceFacadeLocal;
import org.nuclos.server.report.valueobject.DatasourceVO;
import org.nuclos.server.resource.ResourceCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Helper class for the MasterDataFacade.
 * <br>
 * <br>Created by Novabit Informationssysteme GmbH
 * <br>Please visit <a href="http://www.novabit.de">www.novabit.de</a>
 *
 * @author   <a href="mailto:christoph.radig@novabit.de">christoph.radig</a>
 * @version 01.00.00
 */
@Component
public class MasterDataFacadeHelper {

    private static final Logger LOG = Logger.getLogger(MasterDataFacadeHelper.class);

    private static final List<String> lstSystemDbFieldNames = Arrays.asList("intid", "datcreated", "strcreated",
            "datchanged", "strchanged", "intversion");

    //final ClientNotifier clientnotifier = new ClientNotifier(JMSConstants.TOPICNAME_MASTERDATACACHE);

    static final int MAXROWS = 100;

    public static enum RoleDependant {

        ROLE_ACTION(NuclosEntity.ROLEACTION, "22292", "action", null), ROLE_MODULE(NuclosEntity.ROLEMODULE, "22293",
                "module", "group"), ROLE_MASTERDATA(NuclosEntity.ROLEMASTERDATA, "22294", "entity",
                        null), ROLE_USER(NuclosEntity.ROLEUSER, "22290", "user",
                                null), ROLE_REPORT(NuclosEntity.ROLEREPORT, "22295", "report", null);

        private final NuclosEntity entity;
        private final String resourceId;
        private final String entityFieldName;
        private final String subFieldName;

        private RoleDependant(NuclosEntity entity, String resourceId, String entityFieldName, String subFieldName) {
            this.entity = entity;
            this.resourceId = resourceId;
            this.entityFieldName = entityFieldName;
            this.subFieldName = subFieldName;
        }

        public NuclosEntity getEntity() {
            return entity;
        }

        public String getResourceId() {
            return resourceId;
        }

        public String getEntityFieldName() {
            return entityFieldName;
        }

        public String getSubFieldName() {
            return subFieldName;
        }

        public static RoleDependant getByEntityName(String entityName) {
            for (RoleDependant u : RoleDependant.class.getEnumConstants())
                if (u.getEntity().checkEntityName(entityName))
                    return u;
            return null;
        }
    }

    //

    private AttributeFacadeLocal attributeFacade;

    private RecordGrantUtils grantUtils;

    private MasterDataMetaCache masterDataMetaCache;

    private SpringDataBaseHelper dataBaseHelper;

    private NucletDalProvider nucletDalProvider;

    private DatasourceFacadeLocal datasourceFacade;

    public MasterDataFacadeHelper() {
    }

    private DatasourceFacadeLocal getDatasourceFacade() {
        if (this.datasourceFacade == null)
            this.datasourceFacade = ServerServiceLocator.getInstance().getFacade(DatasourceFacadeLocal.class);
        return this.datasourceFacade;
    }

    @Autowired
    void setRecordGrantUtils(RecordGrantUtils grantUtils) {
        this.grantUtils = grantUtils;
    }

    @Autowired
    void setMasterDataMetaCache(MasterDataMetaCache masterDataMetaCache) {
        this.masterDataMetaCache = masterDataMetaCache;
    }

    @Autowired
    void setDataBaseHelper(SpringDataBaseHelper dataBaseHelper) {
        this.dataBaseHelper = dataBaseHelper;
    }

    @Autowired
    void setNucletDalProvider(NucletDalProvider nucletDalProvider) {
        this.nucletDalProvider = nucletDalProvider;
    }

    public void notifyClients(String sCachedEntityName) {
        if (sCachedEntityName == null) {
            throw new NullArgumentException("sCachedEntityName");
        }
        LOG.info("JMS send: notify clients that master data changed:" + this);
        LocalCachesUtil.getInstance().updateLocalCacheRevalidation(JMSConstants.TOPICNAME_MASTERDATACACHE);
        NuclosJMSUtils.sendOnceAfterCommitDelayed(sCachedEntityName, JMSConstants.TOPICNAME_MASTERDATACACHE);
    }

    public MasterDataVO getMasterDataCVOById(final MasterDataMetaVO mdmetavo, final Object oId)
            throws CommonFinderException {
        return getMasterDataCVOById(mdmetavo, oId, true);
    }

    public MasterDataVO getMasterDataCVOById(final MasterDataMetaVO mdmetavo, final Object oId,
            boolean checkRecordGrant) throws CommonFinderException {
        MasterDataVO mdVO = XMLEntities.getSystemObjectById(mdmetavo.getEntityName(), oId);
        if (mdVO != null) {
            return mdVO;
        }

        JdbcEntityObjectProcessor eoProcessor = nucletDalProvider
                .getEntityObjectProcessor(mdmetavo.getEntityName());
        EntityObjectVO eoResult = eoProcessor.getByPrimaryKey(IdUtils.toLongId(oId));
        if (checkRecordGrant) {
            try {
                grantUtils.checkInternal(mdmetavo.getEntityName(), IdUtils.toLongId(oId));
            } catch (CommonPermissionException e) {
                throw new CommonFinderException(e);
            }
        }
        if (eoResult != null) {
            mdVO = DalSupportForMD.wrapEntityObjectVO(eoResult);
        }

        if (mdVO == null) {
            throw new CommonFinderException("Can't find " + mdmetavo.getEntityName() + " with id " + oId);
        }

        return mdVO;
    }

    /**
     * gets the dependant master data records for the given entity, using the given foreign key field and the given id as foreign key.
     * @param sEntityName name of the entity to get all dependant master data records for
     * @param sForeignKeyField name of the field relating to the foreign entity
     * @param oRelatedId id by which sEntityName and sParentEntity are related
     * @return
     * @precondition oRelatedId != null
     * @todo restrict permissions by entity name
     */
    public Collection<EntityObjectVO> getDependantMasterData(String sEntityName, String sForeignKeyField,
            Object oRelatedId, String username) {
        return this.getDependantMasterData(sEntityName, sForeignKeyField, oRelatedId, username,
                new HashMap<String, Object>());
    }

    /**
     * gets the dependant master data records for the given entity, using the given foreign key field and the given id as foreign key.
     * @param sEntityName name of the entity to get all dependant master data records for
     * @param sForeignKeyField name of the field relating to the foreign entity
     * @param oRelatedId id by which sEntityName and sParentEntity are related
     * @return
     * @precondition oRelatedId != null
     * @todo restrict permissions by entity name
     */
    public Collection<EntityObjectVO> getDependantMasterData(String sEntityName, String sForeignKeyField,
            Object oRelatedId, String username, Map<String, Object> mpParams) {
        if (oRelatedId == null) {
            throw new NullArgumentException("oRelatedId");
        }
        LOG.debug("Getting dependant masterdata for entity " + sEntityName + " with foreign key field "
                + sForeignKeyField + " and related id " + oRelatedId);

        final MasterDataMetaVO mdmetavo = masterDataMetaCache.getMetaData(sEntityName);
        Date startDate = new Date();

        Collection<MasterDataVO> result = new ArrayList<MasterDataVO>();
        if (mdmetavo.isDynamic()) {
            if (sEntityName.startsWith(MasterDataMetaVO.DYNAMIC_ENTITY_PREFIX)) {
                String sDataSource = mdmetavo.getDBEntity()
                        .substring(MasterDataMetaVO.DYNAMIC_ENTITY_VIEW_PREFIX.length()).toLowerCase();

                try {
                    DatasourceVO datasourceVO = getDatasourceFacade().getDynamicEntity(sDataSource);
                    // @see NUCLOS-654
                    boolean bIntidCaseInsensitive = true;
                    boolean bIntidGenericObjectCaseInsensitive = true;
                    String sql = getDatasourceFacade().createSQL(datasourceVO.getSource(),
                            new HashMap<String, Object>());
                    if (sql.toUpperCase().indexOf(" \"INTID\"") != -1)
                        bIntidCaseInsensitive = false;
                    if (sql.toUpperCase().indexOf(" \"INTID_T_UD_GENERICOBJECT\"") != -1)
                        bIntidGenericObjectCaseInsensitive = false;

                    result = getDependantMasterDataForDatasource(oRelatedId, mdmetavo, bIntidCaseInsensitive,
                            bIntidGenericObjectCaseInsensitive);
                } catch (Exception e) {
                    LOG.warn("getDependantMasterDataForDatasource failed for datasource " + sDataSource, e);
                }
            } else if (sEntityName.startsWith(MasterDataMetaVO.CHART_ENTITY_PREFIX)) {
                String sDataSource = mdmetavo.getDBEntity()
                        .substring(MasterDataMetaVO.CHART_ENTITY_VIEW_PREFIX.length()).toLowerCase();

                try {
                    DatasourceVO datasourceVO = getDatasourceFacade().getChart(sDataSource);
                    Map<String, Object> mpTempParams = new HashMap<String, Object>(mpParams);
                    mpTempParams.put("genericObject", oRelatedId);
                    result = getDependantMasterDataForDatasource(oRelatedId, mdmetavo, datasourceVO, mpTempParams);
                } catch (Exception e) {
                    LOG.warn("getDependantMasterDataForDatasource failed for datasource " + sDataSource, e);
                }
            }
        } else {
            result = getDependantMasterDataByBean(sEntityName, sForeignKeyField, oRelatedId);
        }

        Collection<EntityObjectVO> colEntityObject = CollectionUtils.transform(result,
                new MasterDataToEntityObjectTransformer(sEntityName));

        Date endate = new Date();
        NuclosPerformanceLogger.performanceLog(startDate.getTime(), endate.getTime(), username, oRelatedId,
                sEntityName, "Reading the master data entity for an objekt if type " + sEntityName + " ("
                        + (mdmetavo.isDynamic() ? " dynamic " : " static ") + ")",
                "", "");

        return colEntityObject;
    }

    Collection<MasterDataVO> getDependantMasterDataByBean(String sEntityName, String sForeignKeyFieldName,
            Object oRelatedId) {
        final CollectableEntityField clctef = SearchConditionUtils
                .newMasterDataEntityField(masterDataMetaCache.getMetaData(sEntityName), sForeignKeyFieldName);
        final CollectableSearchCondition cond = new CollectableComparison(clctef, ComparisonOperator.EQUAL,
                new CollectableValueIdField(oRelatedId, null));
        return getGenericMasterData(sEntityName, cond, true);
    }

    Collection<MasterDataVO> getDependantMasterDataForDatasource(Object oRelatedId, final MasterDataMetaVO mdmetavo,
            boolean bIntidCaseInsensitive, boolean bIntidGenericObjectCaseInsensitive) {
        final List<MasterDataMetaFieldVO> collFields = mdmetavo.getFields();
        final int fieldCount = collFields.size();

        DbQueryBuilder builder = dataBaseHelper.getDbAccess().getQueryBuilder();
        DbQuery<DbTuple> query = builder.createTupleQuery();
        DbFrom t = query.from(mdmetavo.getDBEntity()).alias(SystemFields.BASE_ALIAS);

        DbColumnExpression<Integer> goColumn = bIntidGenericObjectCaseInsensitive
                ? t.baseColumn("INTID_T_UD_GENERICOBJECT", Integer.class)
                : t.baseColumnCaseSensitive("INTID_T_UD_GENERICOBJECT", Integer.class, false);
        List<DbSelection<?>> selection = new ArrayList<DbSelection<?>>();
        for (MasterDataMetaFieldVO field : collFields) {
            String fieldName = field.getFieldName();
            if (fieldName.equals(ModuleConstants.DEFAULT_FOREIGNKEYFIELDNAME)) {
                selection.add(goColumn);
            } else {
                Class<?> javaType = DalUtils.getDbType(field.getJavaClass());
                selection.add(t.baseColumnCaseSensitive(fieldName, javaType, false));
            }
        }
        selection.add(bIntidCaseInsensitive ? t.baseColumn("INTID", Integer.class)
                : t.baseColumnCaseSensitive("INTID", Integer.class, false));
        query.multiselect(selection);
        query.where(builder.equal(goColumn, oRelatedId));

        return dataBaseHelper.getDbAccess().executeQuery(query, new Transformer<DbTuple, MasterDataVO>() {
            @Override
            public MasterDataVO transform(DbTuple tuple) {
                MasterDataVO result = new MasterDataVO(mdmetavo, false);
                result.setId(tuple.get(fieldCount, Integer.class));
                for (int i = 0; i < fieldCount; i++) {
                    MasterDataMetaFieldVO field = collFields.get(i);
                    String fieldName = field.getFieldName();
                    Object value = tuple.get(i);
                    if (fieldName.equals(ModuleConstants.DEFAULT_FOREIGNKEYFIELDNAME)) {
                        result.setField(fieldName + "Id", value);
                    } else {
                        result.setField(fieldName, value);
                    }
                }
                return result;
            };
        });
    }

    Collection<MasterDataVO> getDependantMasterDataForDatasource(Object oRelatedId, final MasterDataMetaVO mdmetavo,
            final DatasourceVO datasourceVO, final Map<String, Object> mpParams) throws NuclosDatasourceException {
        String sql = getDatasourceFacade().createSQL(datasourceVO.getSource(), mpParams);
        return dataBaseHelper.getDbAccess().executePlainQuery(sql, -1,
                new ResultSetRunner<Collection<MasterDataVO>>() {
                    @Override
                    public java.util.Collection<MasterDataVO> perform(ResultSet result) throws SQLException {
                        Collection<MasterDataVO> values = new ArrayList<MasterDataVO>();

                        MasterDataVO mdvo = null;
                        HashMap<String, Object> mpFields = null;
                        HashMap<Integer, String> columnames = new HashMap<Integer, String>();
                        ResultSetMetaData metadata = result.getMetaData();

                        for (int i = 1; i <= metadata.getColumnCount(); i++) {
                            String columName = metadata.getColumnName(i);
                            columnames.put(i, columName);
                        }

                        while (result.next()) {
                            mpFields = new HashMap<String, Object>();
                            for (Integer columnNumber : columnames.keySet()) {
                                Object value = result.getObject(columnNumber);
                                mpFields.put(columnames.get(columnNumber), value);
                            }

                            mdvo = new MasterDataVO(mdmetavo, false);
                            mdvo.setFields(mpFields);
                            values.add(mdvo);
                        }

                        return values;
                    }
                });
    }

    static String getProtocolValue(Object oValue) {
        String result = "";
        if (oValue != null) {
            if (oValue instanceof Date) {
                result = new SimpleDateFormat("dd.MM.yyyy").format(((Date) oValue));
            } else {
                result = oValue.toString();
            }
        }
        return result;
    }

    static void invalidateCaches(String sEntityName, MasterDataVO mdvo) {
        NuclosEntity nuclosEntity = NuclosEntity.getByName(sEntityName);
        LOG.info("invalidateCaches(" + sEntityName + ", " + mdvo + ")");
        if (nuclosEntity != null) {
            switch (nuclosEntity) {
            case ROLE:
            case ACTION:
            case REPORT:
            case TASKLIST:
                SecurityCache.getInstance().invalidate();
                break;
            case USER:
                SecurityCache.getInstance().invalidate(mdvo.getField("name", String.class));
                break;
            case LAYOUT:
                MetaDataServerProvider.getInstance().revalidate(true);
                SpringApplicationContextHolder.getBean(LayoutFacadeBean.class).evictCaches();
                break;
            case LAYOUTUSAGE:
                MetaDataServerProvider.getInstance().revalidate(true);
                break;
            case RESOURCE:
                ResourceCache.getInstance().invalidate();
                break;
            case PARAMETER:
                ServerParameterProvider.getInstance().revalidate();
                break;
            case DYNAMICENTITY:
            case DYNAMICENTITYUSAGE:
            case CHART:
            case CHARTUSAGE:
                MetaDataServerProvider.getInstance().revalidate(true);
                break;
            case ENTITYLAFPARAMETER:
                MetaDataServerProvider.getInstance().revalidate(false);
                break;
            default:
                LOG.info("invalidateCaches: Nothing to do for " + sEntityName);
            }
        }
    }

    /**
     * Called after an entity was changed - that is, a row was inserted, updated or deleted.
     * @param mdmetavo the entity that was changed.
     * @param mdvoChanged the object that was inserted, updated or deleted.
     */
    private void entityChanged(MasterDataMetaVO mdmetavo, MasterDataVO mdvoChanged) {
        NuclosEntity nuclosEntity = NuclosEntity.getByName(mdmetavo.getEntityName());

        if (nuclosEntity == NuclosEntity.ROLEUSER) {
            // Rights are reloaded the next time the user logs in - we don't need to do anything here
            // @todo To make these changes visible immediately, however, we could notify the client and change the roles dynamically in the server.
            // But can we do that in a J2EE conformant way?
            //         NucleusSecurityProxy.invalidateMethodRightsForUser(mdvoChanged.getField("user", String.class));
        } else if (nuclosEntity == NuclosEntity.ROLEACTION) {
            //         NucleusSecurityProxy.invalidateMethodRightsForAllUsers();
        }

        if (mdmetavo.isCacheable()) {
            this.notifyClients(mdmetavo.getEntityName());
        } else {
            if (nuclosEntity == NuclosEntity.RULETRANSITION || nuclosEntity == NuclosEntity.RULEGENERATION
                    || nuclosEntity == NuclosEntity.RULEUSAGE || nuclosEntity == NuclosEntity.RULE
                    || nuclosEntity == NuclosEntity.CODE || nuclosEntity == NuclosEntity.TIMELIMITRULE)
                this.notifyClients(mdmetavo.getEntityName());

        }
    }

    /**
     * Returns the name of the user-writable
     * @param mdmetavo
     * @return
     */
    public static String getUserWritableDbEntityName(MasterDataMetaVO mdmetavo) {
        String table = mdmetavo.getDBEntity();
        return table.startsWith("V_") ? "T_" + table.substring(2) : table;
    }

    /**
     * @param mdmetavo
     * @return the names of all user writable database fields.
     */
    public static List<String> getUserWritableDbFieldNames(MasterDataMetaVO mdmetavo) {
        final List<String> result = CollectionUtils.transform(mdmetavo.getFields(),
                new Transformer<MasterDataMetaFieldVO, String>() {
                    @Override
                    public String transform(MasterDataMetaFieldVO mdmetafieldvo) {
                        return (mdmetafieldvo.getForeignEntity() == null) ? mdmetafieldvo.getDBFieldName()
                                : mdmetafieldvo.getDBIdFieldName();
                    }
                });
        // remove all system fields from the result:
        for (Iterator<String> iter = result.iterator(); iter.hasNext();) {
            if (lstSystemDbFieldNames.contains(iter.next().toLowerCase())) {
                iter.remove();
            }
        }
        return result;
    }

    /**
     * performs a stale version check.
     * @param mdvo
     * @throws CommonStaleVersionException
     */
    MasterDataVO checkForStaleVersion(MasterDataMetaVO mdMetaVO, MasterDataVO mdvo)
            throws CommonStaleVersionException, CommonPermissionException, CommonFinderException {
        final MasterDataVO mdvoInDataBase = getMasterDataCVOById(mdMetaVO, mdvo.getIntId());
        if (mdvo.getVersion() != mdvoInDataBase.getVersion()) {
            throw new CommonStaleVersionException("master data", mdvo.toDescription(),
                    mdvoInDataBase.toDescription());
        }
        if (mdvo.isSystemRecord()) {
            throw new CommonPermissionException();
        }

        return mdvoInDataBase;
    }

    static void checkInvariantFields(MasterDataMetaVO mdMetaVO, MasterDataVO mdvo, MasterDataVO mdvoInDataBase)
            throws CommonValidationException {
        for (MasterDataMetaFieldVO mdMetaFieldVO : mdMetaVO.getInvariantFields()) {
            String fieldName = mdMetaFieldVO.getFieldName();
            if (!ObjectUtils.equals(mdvo.getField(fieldName), mdvoInDataBase.getField(fieldName))) {
                // TODO_AUTOSYNC: translation
                throw new CommonValidationException(MessageFormat
                        .format("Field \"{0}\" cannot be changed because it is declared as invariant", fieldName));
            }
        }
    }

    /**
     * removes a single masterdata row.
     * @param sEntityName
     * @param mdvo
     * @throws CommonFinderException
     * @throws CommonRemoveException
     * @throws CommonStaleVersionException
     * @precondition sEntityName != null
     */
    void removeSingleRow(String sEntityName, final MasterDataVO mdvo) throws CommonFinderException,
            CommonRemoveException, CommonStaleVersionException, CommonPermissionException {

        if (sEntityName == null) {
            throw new NullArgumentException("sEntityName");
        }

        final MasterDataMetaVO mdmetavo = masterDataMetaCache.getMetaData(sEntityName);

        // prevent removal if dependant dynamic attributes exist:
        final Object oExternalId = mdvo.getId();
        if (oExternalId == null) {
            throw new NuclosFatalException("mdhelper.error.invalid.id");//"Der Datensatz hat eine leere Id.");
        }

        checkForStaleVersion(mdmetavo, mdvo);

        // @todo refactor: make this easier to write:
        try {
            dataBaseHelper.execute(
                    DbStatementUtils.deleteFrom(getUserWritableDbEntityName(mdmetavo), "INTID", mdvo.getIntId()));
        } catch (CommonFatalException ex) {
            throw new CommonRemoveException(ex);
        }
        //remove documents

        for (MasterDataMetaFieldVO field : masterDataMetaCache.getMetaData(sEntityName).getFields()) {
            if (field.getJavaClass().equals(GenericObjectDocumentFile.class)) {
                String sExtendedPath = "";
                if (sEntityName.equals("nuclos_" + NuclosEntity.GENERALSEARCHDOCUMENT)) {
                    GenericObjectDocumentFile docFile = (GenericObjectDocumentFile) mdvo.getField("file");
                    sExtendedPath = StringUtils.emptyIfNull(docFile.getDirectoryPath());
                }
                File file = new File(NuclosSystemParameters.getString(NuclosSystemParameters.DOCUMENT_PATH) + "/"
                        + sExtendedPath);
                remove(mdvo.getIntId(), null,
                        NuclosSystemParameters.getDirectory(NuclosSystemParameters.DOCUMENT_PATH));
            }
        }

        NuclosEntity nucEntity = NuclosEntity.getByName(sEntityName);
        if (nucEntity != null)
            switch (nucEntity) {
            case DBSOURCE:
                try {
                    updateDbObject(DalSupportForMD.getEntityObjectVO(sEntityName, mdvo), null, false);
                } catch (NuclosBusinessException e) {
                    throw new CommonRemoveException(e.getMessage(), e);
                }
                break;
            case DBOBJECT:
                for (EntityObjectVO source : nucletDalProvider.getEntityObjectProcessor(NuclosEntity.DBSOURCE)
                        .getBySearchExpression(appendRecordGrants(
                                new CollectableSearchExpression(SearchConditionUtils.newEOComparison(
                                        NuclosEntity.DBSOURCE.getEntityName(), "dbobject", ComparisonOperator.EQUAL,
                                        mdvo.getField("name"), MetaDataServerProvider.getInstance())),
                                sEntityName)))
                    this.removeSingleRow(NuclosEntity.DBSOURCE.getEntityName(),
                            DalSupportForMD.wrapEntityObjectVO(source));
                break;
            }

        this.entityChanged(mdmetavo, mdvo);
    }

    /**
     * modifies a single masterdata row.
     * @param sEntityName
     * @param mdvo
     * @param sUserName
     * @param bValidate
     * @return
     * @throws CommonStaleVersionException
     * @throws CommonValidationException
     */
    Object modifySingleRow(String sEntityName, MasterDataVO mdvo, String sUserName, boolean bValidate)
            throws CommonCreateException, CommonFinderException, CommonStaleVersionException,
            CommonValidationException, CommonPermissionException {

        final MasterDataMetaVO mdmetavo = masterDataMetaCache.getMetaData(sEntityName);

        final MasterDataVO mdvoInDB = checkForStaleVersion(mdmetavo, mdvo);

        checkInvariantFields(mdmetavo, mdvo, mdvoInDB);

        validateUniqueConstraintWithJson(mdmetavo, mdvo);

        if (NuclosEntity.USER.getEntityName().equals(sEntityName)
                && sUserName.equalsIgnoreCase(mdvoInDB.getField("name", String.class))
                && !mdvoInDB.getField("name", String.class).equalsIgnoreCase(mdvo.getField("name", String.class))) {
            throw new CommonPermissionException("masterdata.error.change.own.user.name");
        }

        JdbcEntityObjectProcessor eoProcessor = nucletDalProvider
                .getEntityObjectProcessor(mdmetavo.getEntityName());
        EntityObjectVO eoVO = DalSupportForMD.getEntityObjectVO(sEntityName, mdvo);
        DalUtils.updateVersionInformation(eoVO, sUserName);
        eoVO.flagUpdate();

        try {
            eoProcessor.insertOrUpdate(eoVO);
        } catch (DbException e) {
            throw new CommonCreateException(e.getMessage(), e);
        }

        storeFiles(sEntityName, eoVO);

        NuclosEntity nucEntity = NuclosEntity.getByName(sEntityName);
        if (nucEntity != null)
            switch (nucEntity) {
            case DBSOURCE:
                try {
                    updateDbObject(DalSupportForMD.getEntityObjectVO(sEntityName, mdvoInDB), eoVO, false);
                } catch (NuclosBusinessException e) {
                    throw new CommonCreateException(e.getMessage(), e);
                }
                break;
            }

        this.entityChanged(mdmetavo, mdvo);

        return mdvo.getId();
    }

    /**
     * creates a single masterdata row.
     * @param sEntityName
     * @param mdvoToCreate
     * @param sUserName
     * @param bValidate
     * @return the new id of the created row
     * @precondition mdvo.getId() == null
     */
    Integer createSingleRow(String sEntityName, MasterDataVO mdvoToCreate, String sUserName, boolean bValidate,
            Integer intid) throws CommonCreateException, CommonValidationException {
        if (mdvoToCreate.getId() != null) {
            throw new IllegalArgumentException("mdvoToCreate.getId()");
        }

        final MasterDataMetaVO mdmetavo = masterDataMetaCache.getMetaData(sEntityName);
        final EntityMetaDataVO entityMeta = MetaDataServerProvider.getInstance().getEntity(sEntityName);

        validateUniqueConstraintWithJson(mdmetavo, mdvoToCreate);

        // @todo optimize: use idfactory.nextval for insert

        final Integer result;
        if (intid != null) {
            result = intid;
        } else {
            final String idFactory = entityMeta.getIdFactory();
            if (idFactory == null) {
                result = dataBaseHelper.getNextIdAsInteger(SpringDataBaseHelper.DEFAULT_SEQUENCE);
            } else {
                result = dataBaseHelper.getDbAccess().executeFunction(idFactory, Integer.class);
            }
        }
        mdvoToCreate.setId(result);

        JdbcEntityObjectProcessor eoProcessor = nucletDalProvider
                .getEntityObjectProcessor(mdmetavo.getEntityName());
        EntityObjectVO eoVO = DalSupportForMD.getEntityObjectVO(sEntityName, mdvoToCreate);
        eoVO.setVersion(null);
        eoVO.setCreatedBy(null);
        eoVO.setCreatedAt(null);
        DalUtils.updateVersionInformation(eoVO, sUserName);
        eoVO.flagNew();

        try {
            eoProcessor.insertOrUpdate(eoVO);
        } catch (DbException e) {
            throw new CommonCreateException(e.toString());
        }

        storeFiles(sEntityName, eoVO);

        NuclosEntity nucEntity = NuclosEntity.getByName(sEntityName);
        if (nucEntity != null)
            switch (nucEntity) {
            case DBSOURCE:
                try {
                    updateDbObject(null, eoVO, false);
                } catch (NuclosBusinessException e) {
                    throw new CommonCreateException(e.getMessage(), e);
                }
                break;
            }

        this.entityChanged(mdmetavo, mdvoToCreate);

        return result;
    }

    private void validateUniqueConstraintWithJson(MasterDataMetaVO mdmetavo, MasterDataVO mdvoToCreate)
            throws CommonValidationException {
        if (!XMLEntities.hasSystemData(mdmetavo.getEntityName())) {
            return;
        }
        CompositeCollectableSearchCondition cond = new CompositeCollectableSearchCondition(LogicalOperator.AND);
        for (MasterDataMetaFieldVO field : mdmetavo.getFields()) {
            if (field.isUnique()) {
                String fieldname = field.getFieldName();
                if (mdvoToCreate.getField(fieldname) != null) {
                    if (field.getForeignEntity() != null) {
                        cond.addOperand(SearchConditionUtils.newMDReferenceComparison(mdmetavo, fieldname,
                                mdvoToCreate.getField(fieldname + "Id", Integer.class)));
                    } else {
                        cond.addOperand(SearchConditionUtils.newMDComparison(mdmetavo, field.getFieldName(),
                                ComparisonOperator.EQUAL, mdvoToCreate.getField(field.getFieldName())));
                    }
                } else {
                    cond.addOperand(SearchConditionUtils.newMDIsNullCondition(mdmetavo, fieldname));
                }
            }
        }

        if (cond.getOperandCount() > 0) {
            final Collection<MasterDataVO> systemObjects = XMLEntities.getSystemObjects(mdmetavo.getEntityName(),
                    cond);
            if (!systemObjects.isEmpty()) {
                throw new CommonValidationException("nuclos.validation.systementity.unique");
            }
        }
    }

    /**
     *
     * @param eoVO
     */
    public static void storeFiles(String sEntity, EntityObjectVO eoVO) {
        for (EntityFieldMetaDataVO efMeta : MetaDataServerProvider.getInstance().getAllEntityFieldsByEntity(sEntity)
                .values()) {
            final Object oValue = eoVO.getFields().get(efMeta.getField());
            final String sClzz = efMeta.getDataType();

            if (GenericObjectDocumentFile.class.getName().equals(sClzz)) {
                GenericObjectDocumentFile documentFile = (GenericObjectDocumentFile) oValue;
                if (documentFile != null) {
                    if (documentFile.getContents() != null) {
                        storeFile(
                                new GenericObjectDocumentFile(documentFile.getFilename(),
                                        IdUtils.unsafeToId(eoVO.getId()), documentFile.getContents(),
                                        documentFile.getDirectoryPath()),
                                NuclosSystemParameters.getDirectory(NuclosSystemParameters.DOCUMENT_PATH));
                    }
                }
            }
        }
    }

    /**
     * removes the given dependants.
     * @param mpDependants
     * @throws CommonFinderException
     * @throws CommonRemoveException
     * @throws CommonStaleVersionException
     */
    public void removeDependants(DependantMasterDataMap mpDependants, String customUsage)
            throws CommonFinderException, CommonRemoveException, CommonStaleVersionException,
            CommonPermissionException {
        for (String sDependantEntityName : mpDependants.getEntityNames()) {
            EntityMetaDataVO eMeta = MetaDataServerProvider.getInstance().getEntity(sDependantEntityName);
            if (!eMeta.isEditable()) {
                continue;
            }
            if (!masterDataMetaCache.getMetaData(sDependantEntityName).isDynamic() && !eMeta.isStateModel()) {
                for (EntityObjectVO mdvoDependant : mpDependants.getData(sDependantEntityName)) {

                    removeDependants(mdvoDependant.getDependants(), customUsage);
                    if (MetaDataServerProvider.getInstance().getEntity(sDependantEntityName).isStateModel()) {
                        try {
                            mdvoDependant.setEntity(sDependantEntityName);
                            GenericObjectVO govo = DalSupportForGO.getGenericObjectVO(mdvoDependant);
                            GenericObjectFacadeLocal goLocal = ServerServiceLocator.getInstance()
                                    .getFacade(GenericObjectFacadeLocal.class);
                            goLocal.remove(new GenericObjectWithDependantsVO(govo, mdvoDependant.getDependants()),
                                    true, customUsage);
                        } catch (CommonCreateException ex) {
                            throw new NuclosFatalException(ex);
                        } catch (NuclosBusinessException ex) {
                            throw new NuclosFatalException(ex);
                        }
                    } else {
                        if (masterDataMetaCache.getMetaData(sDependantEntityName).isEditable()
                                && mdvoDependant.isFlagRemoved() && mdvoDependant.getId() != null) {
                            // remove the row:
                            MasterDataVO voDependant = DalSupportForMD.wrapEntityObjectVO(mdvoDependant);
                            removeSingleRow(sDependantEntityName, voDependant);
                        }
                    }
                }
            }
        }
    }

    /**
     * creates/modifies the given dependants.
     * @param mpDependants
     * @param sEntityName
     * @param sUserName
     * @param bValidate
     * @throws CommonCreateException
     * @throws CommonValidationException
     * @throws CommonFinderException
     * @throws CommonStaleVersionException
     */
    void createOrModifyDependants(DependantMasterDataMap mpDependants, String sEntityName, String sUserName,
            boolean bValidate, Map<MasterDataVO, Integer> mpDependantsWithId,
            Map<EntityAndFieldName, String> mpEntityAndParentEntityName, String customUsage)
            throws CommonCreateException, CommonValidationException, CommonFinderException,
            CommonStaleVersionException, CommonPermissionException {

        for (String sDependantEntityName : mpDependants.getEntityNames()) {
            for (EntityObjectVO mdvoDependant : mpDependants.getData(sDependantEntityName)) {
                EntityMetaDataVO eMeta = MetaDataServerProvider.getInstance().getEntity(sDependantEntityName);
                if (!eMeta.isEditable()) {
                    continue;
                }
                // create/modify the row:
                Integer intid = null;
                if (mpDependantsWithId != null && !mpDependantsWithId.isEmpty()) {
                    intid = mpDependantsWithId.get(mdvoDependant);
                }
                if (eMeta.isStateModel()) {
                    try {
                        mdvoDependant.setEntity(sDependantEntityName);
                        GenericObjectVO govo = DalSupportForGO.getGenericObjectVO(mdvoDependant);
                        GenericObjectFacadeLocal goLocal = ServerServiceLocator.getInstance()
                                .getFacade(GenericObjectFacadeLocal.class);
                        final DependantMasterDataMap deps = mdvoDependant.getDependants();
                        if (mdvoDependant.isFlagNew()) {
                            goLocal.create(new GenericObjectWithDependantsVO(govo, deps), customUsage);
                        } else if (mdvoDependant.isFlagRemoved()) {
                            goLocal.remove(new GenericObjectWithDependantsVO(govo, deps), true, customUsage);
                        } else if (mdvoDependant.isFlagUpdated() || deps.getPendingChanges()) {
                            goLocal.modify(govo, deps, false, customUsage);
                        }
                    } catch (NuclosBusinessException ex) {
                        throw new NuclosFatalException(ex);
                    } catch (CommonRemoveException ex) {
                        throw new NuclosFatalException(ex);
                    }
                } else {
                    MasterDataVO voDependant = DalSupportForMD.wrapEntityObjectVO(mdvoDependant);
                    Integer id = createOrModify(sDependantEntityName, voDependant, sEntityName, sUserName,
                            bValidate, intid, mpEntityAndParentEntityName, customUsage);
                    mdvoDependant.setId(IdUtils.toLongId(id));
                }
            }
        }
    }

    /**
     * creates the given dependant row, if it is new or updates it, if it has changed.
     * @param sDependantEntityName
     * @param mdvoDependant
     * @param sEntityName
     * @param sUserName
     * @param bValidate
     * @throws CommonCreateException
     * @throws CommonValidationException
     * @throws CommonStaleVersionException
     */
    private Integer createOrModify(String sDependantEntityName, MasterDataVO mdvoDependant, String sEntityName,
            String sUserName, boolean bValidate, Integer intid,
            Map<EntityAndFieldName, String> mpEntityAndParentEntityName, String customUsage)
            throws CommonCreateException, CommonValidationException, CommonFinderException,
            CommonStaleVersionException, CommonPermissionException {

        final String sIdFieldName = getForeignKeyFieldName(sEntityName, sDependantEntityName,
                mpEntityAndParentEntityName) + "Id";
        if (!mdvoDependant.isRemoved() && !mdvoDependant.isEmpty(sIdFieldName)
                && !masterDataMetaCache.getMetaData(sDependantEntityName).isDynamic()) {
            // validate the row
            if (ServerParameterProvider.getInstance()
                    .getValue(ParameterProvider.KEY_SERVER_VALIDATES_MASTERDATAVALUES).equals("1")) {
                mdvoDependant.validate(masterDataMetaCache.getMetaData(sDependantEntityName));
            }

            Integer iReferenceId;

            if (mdvoDependant.getId() == null) {
                iReferenceId = this.createSingleRow(sDependantEntityName, mdvoDependant, sUserName, bValidate,
                        intid);
            } else {
                iReferenceId = (Integer) mdvoDependant.getId();
                if (masterDataMetaCache.getMetaData(sDependantEntityName).isEditable()
                        && mdvoDependant.isChanged()) {
                    this.modifySingleRow(sDependantEntityName, mdvoDependant, sUserName, bValidate);
                } else {
                    LOG.debug("Dependant row " + mdvoDependant.getId() + " has not changed. Will not be updated.");
                }
            }

            for (String sDependantMasterDataEntityName : mdvoDependant.getDependants().getEntityNames()) {
                String sForeignKeyFieldName = getForeignKeyFieldName(sDependantEntityName,
                        sDependantMasterDataEntityName, mpEntityAndParentEntityName);

                if (sForeignKeyFieldName != null) {
                    for (EntityObjectVO mdvo : mdvoDependant.getDependants()
                            .getData(sDependantMasterDataEntityName)) {
                        if (mdvo.getFieldIds().get(sForeignKeyFieldName) == null)
                            mdvo.getFieldIds().put(sForeignKeyFieldName, new Long(iReferenceId));
                    }
                } else {
                    final String sMessage = StringUtils.getParameterizedExceptionMessage(
                            "mdhelper.error.missing.foreignkey.field", sDependantMasterDataEntityName,
                            sDependantEntityName);
                    //"Es existiert kein Fremdschl\u00fcsselfeld der Entit\u00e4t "+sDependantMasterDataEntityName+", das auf die \u00fcbergeordnete Entit\u00e4t "+sDependantEntityName+" referenziert";
                    throw new NuclosFatalException(sMessage);
                }
            }

            //create or modify dependant data
            DependantMasterDataMap otherDependants = mdvoDependant.getDependants();
            for (String sOtherDependantEntityName : otherDependants.getEntityNames()) {
                for (EntityObjectVO mdvoOtherDependant : otherDependants.getData(sOtherDependantEntityName)) {
                    String sRefField = MetaDataServerProvider.getInstance()
                            .getRefField(sDependantEntityName, sOtherDependantEntityName).getField();
                    mdvoOtherDependant.getFieldIds().put(sRefField, IdUtils.toLongId(iReferenceId));
                }
            }
            createOrModifyDependants(mdvoDependant.getDependants(), sDependantEntityName, sUserName, bValidate,
                    null, mpEntityAndParentEntityName, customUsage);
            return iReferenceId;
        } else {
            return null;
        }
    }

    public String getForeignKeyFieldName(String sEntityName, String sDependantEntityName,
            Map<EntityAndFieldName, String> mpEntityAndParentEntityName) {
        String result = null;

        /**
         * search in layout...
         */
        for (EntityAndFieldName eafn : mpEntityAndParentEntityName.keySet()) {
            if (eafn.getEntityName().equals(sDependantEntityName)) {
                final String sFieldName = eafn.getFieldName();
                if (result == null) {
                    // this is the foreign key field:
                    result = sFieldName;
                } else {
                    final String sMessage = StringUtils.getParameterizedExceptionMessage(
                            "mdhelper.error.more.foreignkey.field", result, sFieldName);
                    //"Es gibt mehr als ein Fremdschl\u00fcsselfeld, das die \u00fcbergeordnete Entit\u00e4t referenziert:\n" + "\t" + result + "\n" + "\t" + sFieldName;
                    throw new NuclosFatalException(sMessage);
                }
            }
        }

        /**
         * if no information from layout is accessible try to get it from meta data...
         */
        if (result == null) {
            final MasterDataMetaVO mdmetavo = masterDataMetaCache.getMetaData(sDependantEntityName);

            // Old Nucleus instance namend the foreign key field "genericObject"
            // and it could be that more than column refers on the parent entity, so
            // the underlying search could not find the referencing column.
            for (MasterDataMetaFieldVO mdmetafieldvo : mdmetavo.getFields()) {
                if ("genericObject".equalsIgnoreCase(mdmetafieldvo.getFieldName())) {
                    return mdmetafieldvo.getFieldName();
                }
            }

            // Default: field referencing the parent entity has the same name as the parent entity
            if (mdmetavo.getFieldNames().contains(sEntityName)) {
                return sEntityName;
            }

            // If no such field is present, it must be a (the) field referencing the parent entity
            for (MasterDataMetaFieldVO mdmetafieldvo : mdmetavo.getFields()) {
                if (sEntityName.equals(mdmetafieldvo.getForeignEntity())) {
                    final String sFieldName = mdmetafieldvo.getFieldName();
                    if (result == null) {
                        // this is the foreign key field:
                        result = sFieldName;
                    } else {
                        final String sMessage = StringUtils.getParameterizedExceptionMessage(
                                "mdhelper.error.more.foreignkey.field", result, sFieldName);
                        //"Es gibt mehr als ein Fremdschl\u00fcsselfeld, das die \u00fcbergeordnete Entit\u00e4t referenziert:\n" + "\t" + result + "\n" + "\t" + sFieldName;
                        throw new NuclosFatalException(sMessage);
                    }
                }
            }
        }

        return result;
    }

    /**
     * creates the given dependants.
     * @param mpDependants
     * @param sEntityName
     * @param iParentId
     * @param sUserName
     * @param bValidate
     * @throws CommonCreateException
     * @throws CommonValidationException
     * @precondition mpDependants != null
     * @precondition sForeignIdFieldName != null
     * @precondition iParentId != null
     */
    public void createDependants(DependantMasterDataMap mpDependants, String sEntityName, Integer iParentId,
            String sUserName, boolean bValidate, Map<MasterDataVO, Integer> mpDependantsWithId,
            Map<EntityAndFieldName, String> mpEntityAndParentEntityName)
            throws CommonCreateException, CommonValidationException {

        for (String sDependantEntityName : mpDependants.getEntityNames()) {
            for (EntityObjectVO mdvoDependant : mpDependants.getData(sDependantEntityName)) {
                final String sForeignIdFieldName = getForeignKeyFieldName(sEntityName, sDependantEntityName,
                        mpEntityAndParentEntityName) + "Id";
                if (!mdvoDependant.isFlagRemoved()/* && !mdvoDependant.isEmpty(sForeignIdFieldName)*/) {
                    // @todo eliminate this workaround:
                    // set the id of the foreign key field to the id of the parent:
                    //mdvoDependant.setField(sForeignIdFieldName, iParentId);
                    mdvoDependant.getFields().put(sForeignIdFieldName, iParentId);
                    mdvoDependant.getFieldIds().put(
                            sForeignIdFieldName.substring(0, sForeignIdFieldName.length() - 2),
                            IdUtils.toLongId(iParentId));

                    // create dependant row:
                    Integer iId;
                    MasterDataVO voDependant = DalSupportForMD.wrapEntityObjectVO(mdvoDependant);
                    if (mpDependantsWithId != null && !mpDependantsWithId.isEmpty())
                        iId = createSingleRow(sDependantEntityName, voDependant, sUserName, bValidate,
                                mpDependantsWithId.get(mdvoDependant));
                    else
                        iId = createSingleRow(sDependantEntityName, voDependant, sUserName, bValidate, null);

                    createDependants(mdvoDependant.getDependants(), sDependantEntityName, iId, sUserName, bValidate,
                            null, mpEntityAndParentEntityName);
                }
            }
        }
    }

    /**
     * gets master data records for a given entity and search condition (generic mechanism)
     * @param sEntityName name of the entity to get master data records for
     * @param cond search condition value object
     * @return TruncatableCollection<MasterDataVO> collection of master data value objects
     * @postcondition result != null
     */
    public TruncatableCollection<MasterDataVO> getGenericMasterData(String sEntityName,
            final CollectableSearchCondition cond, final boolean bAll) {
        JdbcEntityObjectProcessor eoProcessor = nucletDalProvider.getEntityObjectProcessor(sEntityName);

        CollectableSearchExpression clctexpr = new CollectableSearchExpression(cond);
        List<EntityObjectVO> eoResult = eoProcessor.getBySearchExpression(appendRecordGrants(clctexpr, sEntityName),
                bAll ? null : MAXROWS + 1, false);
        if (sEntityName.equals(NuclosEntity.GENERALSEARCHDOCUMENT.getEntityName())) {
            for (EntityObjectVO voEntity : eoResult) {
                String sPath = voEntity.getField("path", String.class);
                voEntity.getField("file", GenericObjectDocumentFile.class).setDirectoryPath(sPath);
            }
        }

        boolean truncated = false;
        int recordCount = eoResult.size();
        if (!bAll && recordCount >= MAXROWS) {
            //         recordCount = eoProcessor.count(clctexpr);
            eoResult.subList(MAXROWS, recordCount).clear();
            truncated = true;
        }

        List<MasterDataVO> result = CollectionUtils.transform(eoResult,
                new Transformer<EntityObjectVO, MasterDataVO>() {
                    @Override
                    public MasterDataVO transform(EntityObjectVO eo) {
                        return DalSupportForMD.wrapEntityObjectVO(eo);
                    }
                });
        result = new GzipList<MasterDataVO>(result);

        final Collection<MasterDataVO> systemObjects = XMLEntities.getSystemObjects(sEntityName, cond);
        if (!systemObjects.isEmpty()) {
            recordCount += systemObjects.size();
            result.addAll(systemObjects);
        }

        return new TruncatableCollectionDecorator<MasterDataVO>(result, truncated, recordCount);
    }

    public List<MasterDataVO> getMasterDataChunk(String sEntityName, final CollectableSearchExpression clctexpr,
            final int istart, final int iend) {
        JdbcEntityObjectProcessor eoProcessor = nucletDalProvider.getEntityObjectProcessor(sEntityName);
        List<EntityObjectVO> eoResult = eoProcessor.getChunkBySearchExpression(clctexpr, istart, iend);
        List<MasterDataVO> result = CollectionUtils.transform(eoResult,
                new Transformer<EntityObjectVO, MasterDataVO>() {
                    @Override
                    public MasterDataVO transform(EntityObjectVO eo) {
                        return DalSupportForMD.wrapEntityObjectVO(eo);
                    }
                });
        return result;
    }

    public Integer countMasterDataRows(String sEntityName, final CollectableSearchExpression clctexpr) {
        JdbcEntityObjectProcessor eoProcessor = nucletDalProvider.getEntityObjectProcessor(sEntityName);
        return eoProcessor.count(clctexpr);
    }

    /**
     * create or replace a file attachement in the file system
     * @param documentFile
     */
    private static void storeFile(GenericObjectDocumentFile documentFile, java.io.File dir) {
        File directory = dir;
        if (documentFile.getDirectoryPath() != null && documentFile.getDirectoryPath().length() > 0) {

            directory = new File(dir.getAbsolutePath() + "/" + documentFile.getDirectoryPath());
            directory.mkdirs();
        }

        remove(documentFile.getDocumentFileId(), documentFile.getFilename(), directory);

        try {
            String sPath = getPathName(documentFile);
            //sPath += "/" + StringUtils.emptyIfNull(documentFile.getDirectoryPath()) + "/";

            IOUtils.writeToBinaryFile(new java.io.File(sPath), documentFile.getContents());
        } catch (java.io.IOException e) {
            // logger.error("File content cannot be updated for new file", e);
            throw new NuclosFatalException("File content cannot be updated for new file (" + e.getMessage() + ").");
        }
    }

    /**
     * deletes the file with the given id
     * @param iFileId
     */
    public static void remove(Integer iFileId, String sFilename, java.io.File dir) {
        if (dir.isDirectory()) {
            for (String sFileName : dir.list()) {
                if (sFileName.startsWith(iFileId + "." + (sFilename != null ? sFilename : ""))) {
                    new java.io.File(dir.getAbsolutePath() + java.io.File.separator + sFileName).delete();
                }
            }
        }
    }

    private static String getPathName(GenericObjectDocumentFile documentFile) {
        try {
            if (documentFile == null) {
                throw new CommonFatalException("godocumentfile.invalid.file");//"Der Parameter documentFile darf nicht null sein.");
            }
            // @todo introduce symbolic constant
            if (documentFile.getDocumentFileId() == null) {
                throw new NuclosFatalException("godocumentfile.invalid.id");//"Die Id des Dokumentanhangs darf nicht null sein");
            }
            java.io.File documentDir;
            if (documentFile.getDirectoryPath() != null && documentFile.getDirectoryPath().length() > 0) {
                documentDir = new File(NuclosSystemParameters.getString(NuclosSystemParameters.DOCUMENT_PATH) + "/"
                        + documentFile.getDirectoryPath());
            } else {
                documentDir = NuclosSystemParameters.getDirectory(NuclosSystemParameters.DOCUMENT_PATH);
            }

            java.io.File file = new java.io.File(documentDir,
                    documentFile.getDocumentFileId() + "." + documentFile.getFilename());
            LOG.debug("Calculated path for document attachment: " + file.getCanonicalPath());
            return file.getCanonicalPath();
        } catch (java.io.IOException e) {
            throw new NuclosFatalException(e);
        }
    }

    public static void validateRoleDependants(DependantMasterDataMap mpDependants)
            throws CommonValidationException {

        for (String entityName : mpDependants.getEntityNames()) {
            RoleDependant dependant = RoleDependant.getByEntityName(entityName);
            if (dependant != null) {
                List<String> names = CollectionUtils.transform(
                        mpDependants.getData(dependant.getEntity().getEntityName()),
                        new EntityObjectVO.GetTypedField<String>(dependant.getEntityFieldName(), String.class));

                for (String name : names) {
                    if (Collections.frequency(names, name) > 1) {
                        if (dependant.getSubFieldName() != null) {
                            List<String> subFieldNames = new ArrayList<String>();

                            for (EntityObjectVO mdVO : mpDependants.getData(dependant.getEntity().getEntityName()))
                                if (mdVO.getField(dependant.getEntityFieldName(), String.class).equals(name))
                                    subFieldNames.add(mdVO.getField(dependant.getSubFieldName(), String.class));

                            for (String subName : subFieldNames) {
                                if (Collections.frequency(subFieldNames, subName) > 1) {
                                    throw new CommonValidationException(StringUtils
                                            .getParameterizedExceptionMessage("role.error.validation.dependant.sub",
                                                    name, subName, dependant.getResourceId()));
                                }
                            }
                        } else {
                            throw new CommonValidationException(StringUtils.getParameterizedExceptionMessage(
                                    "role.error.validation.dependant", name, dependant.getResourceId()));
                        }
                    }
                }
            }
        }
    }

    private AttributeFacadeLocal getAttributeFacade() {
        if (attributeFacade == null) {
            attributeFacade = ServerServiceLocator.getInstance().getFacade(AttributeFacadeLocal.class);
        }
        return attributeFacade;
    }

    private void updateDbObject(EntityObjectVO oldSource, EntityObjectVO newSource, boolean isRollback)
            throws NuclosBusinessException {
        if (oldSource == null && newSource == null) {
            throw new NuclosFatalException("oldSource and newSource must not be null.");
        } else if (oldSource != null && newSource != null && !oldSource.getField("dbobject", String.class)
                .equals(newSource.getField("dbobject", String.class))) {
            throw new NuclosFatalException("oldSource and newSource not from same object.");
        } else if (oldSource != null && newSource != null
                && !oldSource.getField("dbtype", String.class).equals(newSource.getField("dbtype", String.class))) {
            throw new NuclosFatalException("Dbtype of oldSource and dbtype of newSource have to be equal.");
        }

        String dbtype = (oldSource != null) ? oldSource.getField("dbtype", String.class)
                : newSource.getField("dbtype", String.class);

        final DbAccess dbAccess = dataBaseHelper.getDbAccess();

        if (!dbAccess.getDbType().equals(DbType.getFromName(dbtype))) {
            return;
        }

        final DbObjectHelper dboHelper = new DbObjectHelper(dbAccess);

        boolean isUsedAsCalculatedAttribute = false;
        EntityMetaDataVO eMetaUsingThisView = null;

        final String objectName = oldSource != null ? oldSource.getField("dbobject", String.class)
                : newSource.getField("dbobject", String.class);
        final List<EntityObjectVO> dbObject = nucletDalProvider.getEntityObjectProcessor(NuclosEntity.DBOBJECT)
                .getBySearchExpression(new CollectableSearchExpression(
                        SearchConditionUtils.newEOComparison(NuclosEntity.DBOBJECT.getEntityName(), "name",
                                ComparisonOperator.EQUAL, objectName, MetaDataServerProvider.getInstance())));
        if (dbObject.isEmpty())
            throw new NuclosFatalException("Database object with name \"" + objectName + "\" does not exists");

        DbObjectType type = DbObjectType.getByName(dbObject.get(0).getField("dbobjecttype", String.class));
        switch (type) {
        case FUNCTION:
            /**
             * look if function is used as calculated attribute
             */
            isUsedAsCalculatedAttribute = DbObjectHelper.isUsedAsCalculatedAttribute(objectName,
                    MetaDataServerProvider.getInstance());
            break;
        case VIEW:
            /**
             * look if view is replacing an entity object view
             */
            eMetaUsingThisView = dboHelper.getEntityMetaForView(objectName);
        }

        /**
         * check before any DML is executed,
         * otherwise oracle commits the transaction and it doesn't matter if throw an exception ot not.
         */
        if (newSource == null || !newSource.getField("active", Boolean.class)) {
            if (isUsedAsCalculatedAttribute) {
                /**
                 * if in use no deactivation/delete allowed
                 */
                throw new NuclosBusinessException("masterdata.error.dbobject.isinuse.calcattr");
            }
        }

        try {
            if (oldSource != null && oldSource.getField("active", Boolean.class)) {
                /** drop '.y' old object */
                dbAccess.execute(dboHelper.getStatements(oldSource, type.getName()).y);
            } else {
                if (eMetaUsingThisView != null && !isRollback) {
                    /**
                     * drop generic entity object view
                     */
                    dbAccess.execute(new DbStructureChange(Type.DROP,
                            new DbSimpleView(null, objectName, new ArrayList<DbSimpleViewColumn>())));
                }
            }
        } catch (DbException dbex) {
            // ignore and try to create new object. maybe the object does not exists any more.
        }

        try {
            if (newSource != null && newSource.getField("active", Boolean.class)) {
                try {
                    /** create '.x' new object */
                    dbAccess.execute(dboHelper.getStatements(newSource, type.getName()).x);
                } catch (DbException dbex) {
                    /**
                     * back to previous version
                     */
                    if (oldSource != null) {
                        this.updateDbObject(null, oldSource, true);
                    }
                }
            } else {
                if (eMetaUsingThisView != null) {
                    /**
                     * back to generic entity object view
                     */
                    try {
                        dbAccess.execute(dboHelper.getCreateEntityView(eMetaUsingThisView));
                    } catch (DbException dbex) {
                        throw new NuclosBusinessException(StringUtils.getParameterizedExceptionMessage(
                                "masterdata.error.dbobject.restore.generic.view", dbex.getMessage()));
                    }
                }
            }
        } catch (Exception ex) {
            throw new NuclosBusinessException(ex.getMessage(), ex);
        }
    }

    /**
     * append record grant(s) to expr for given entity.
     * @param expr
     * @param entity
     * @return new AND 'condition' if any record grant(s) found, otherwise expr is returned.
     * 
     * @deprecated Use Spring injection instead.
     */
    protected CollectableSearchExpression appendRecordGrants(CollectableSearchExpression expr, String entity) {
        return grantUtils.append(expr, entity);
    }

    /**
     * @deprecated Use Spring injection instead.
     */
    protected CollectableSearchExpression getRecordGrantExpression(Long id, String entity) {
        return appendRecordGrants(new CollectableSearchExpression(new CollectableIdCondition(id)), entity);
    }

    public void removeDependantTaskObjects(Integer entityId) {
        DbStatement stmt = DbStatementUtils.deleteFrom("T_UD_TODO_OBJECT", "INTID_T_UD_GENERICOBJECT", entityId);
        dataBaseHelper.getDbAccess().execute(stmt);
    }

} // class MasterDataFacadeHelper