/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package com.queplix.core.integrator.entity;

import com.queplix.core.client.app.vo.EntityData;
import com.queplix.core.client.app.vo.EntityElement;
import com.queplix.core.client.app.vo.EntityLinkFieldData;
import com.queplix.core.client.app.vo.FieldData;
import com.queplix.core.client.app.vo.FieldMeta;
import com.queplix.core.client.app.vo.GridData;
import com.queplix.core.client.app.vo.InFormGridFieldData;
import com.queplix.core.client.app.vo.MultiselectFieldData;
import com.queplix.core.client.app.vo.RowData;
import com.queplix.core.client.app.vo.SortField;
import com.queplix.core.client.app.vo.SubsetData;
import com.queplix.core.client.app.vo.TextboxFieldData;
import com.queplix.core.client.common.CollectionsHelper;
import com.queplix.core.client.common.StringUtil;
import com.queplix.core.integrator.ActionContext;
import com.queplix.core.integrator.security.LogonSession;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eqlext.GetRecordsRes;
import com.queplix.core.modules.eqlext.ejb.SetRecordsLocal;
import com.queplix.core.modules.eqlext.jxb.gr.Req;
import com.queplix.core.modules.eqlext.jxb.gr.ReqEntity;
import com.queplix.core.modules.eqlext.jxb.gr.ReqField;
import com.queplix.core.modules.eqlext.jxb.gr.ReqFilter;
import com.queplix.core.modules.eqlext.jxb.gr.ReqFilters;
import com.queplix.core.modules.eqlext.jxb.gr.ReqFiltersTypeItem;
import com.queplix.core.modules.eqlext.jxb.gr.Reqs;
import com.queplix.core.modules.eqlext.jxb.gr.ResField;
import com.queplix.core.modules.eqlext.jxb.gr.ResRecord;
import com.queplix.core.modules.eqlext.jxb.gr.Ress;
import com.queplix.core.modules.eqlext.jxb.gr.types.ConditionSType;
import com.queplix.core.modules.eqlext.jxb.gr.types.OrderDirectionSType;
import com.queplix.core.modules.eqlext.jxb.sr.Sreq;
import com.queplix.core.modules.eqlext.jxb.sr.SreqDataset;
import com.queplix.core.modules.eqlext.jxb.sr.SreqField;
import com.queplix.core.modules.eqlext.jxb.sr.SreqRecord;
import com.queplix.core.modules.eqlext.jxb.sr.types.TodoSType;
import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.log.AbstractLogger;
import com.queplix.core.utils.log.Log;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Class is facade for the core2.6 entity described in xml files.
 * This class incapsulate from us details of it's interface representation.
 *
 * @author Sergey Kozmin
 * @since 21.11.2006, 18:26:44
 */
public class EntityFacade {
    private static final AbstractLogger logger = Log.getLog(EntityFacade.class);

    public static IntegratedRecordSet createEmptyEntityRequest(
            String entityName, LogonSession ls, ActionContext ctx)
            throws EQLException, CouldntGetEJBException,
            IncorrectEntityDescriptionException, IllegalControlTypeException {
        Reqs castorRequest = createRequest(entityName, new RequestProperties(),
                true);
        return performRequest(castorRequest, entityName, ls, ctx,
                EntityViewHelper.FieldsModificator.CUSTOMIZED_GRID);
    }

    public static IntegratedRecordSet getEntityByIDRequest(String entityName,
                                                           Long rowID,
                                                           LogonSession ls,
                                                           ActionContext ctx)
            throws EQLException, CouldntGetEJBException,
            IncorrectEntityDescriptionException, IllegalControlTypeException {

        return getEntityByIDRequest(entityName, entityName, rowID, ls, ctx);
    }

    /**
     * Search requiredEntity with id filter for the filteringEntity. Entities should be bound. See dataschema binging.
     *
     * @param requiredEntity    entity which will be searched
     * @param filteringEntity   entity for filters
     * @param filteringEntityID id for the filteringEntity
     * @param ls                logon session
     * @param ctx               ActionContext
     * @return integrated result set with for the requiredEntity
     * @throws EQLException                thrown if eql excetpion occured
     * @throws CouldntGetEJBException      thrown if error occured during a ejb instantiation
     * @throws IncorrectEntityDescriptionException
     *                                     throw if entities have incorrect description
     * @throws IllegalControlTypeException if illegal control type was found.
     */
    public static IntegratedRecordSet getEntityByIDRequest(
            String requiredEntity, String filteringEntity,
            Long filteringEntityID,
            LogonSession ls, ActionContext ctx)
            throws EQLException, CouldntGetEJBException,
            IncorrectEntityDescriptionException, IllegalControlTypeException {

        FieldData pkeyDataField = getPkeyFilter(filteringEntity,
                filteringEntityID, ctx);
        FieldData[] filters = new FieldData[]{pkeyDataField};
        ArrayList<EntityData> filtersEntity = new ArrayList<EntityData>(1);
        EntityData entity = new EntityData(filteringEntity, filteringEntityID,
                filters);
        filtersEntity.add(entity);
        Reqs request = buildReqsFromEntities(filtersEntity, null,
                requiredEntity, new RequestProperties(), ls, ctx);
        return performRequest(request, requiredEntity, ls, ctx,
                EntityViewHelper.FieldsModificator.CUSTOMIZED_GRID);
    }

    /**
     * Method returns found records for the given entity. Result is filtered by entityFilters
     *
     * @param entityFilters filters for request
     * @param entityName    requested entity
     * @param props         request property
     * @param requestType   type of the request
     * @param ls            logon session
     * @param ctx           servlet context
     * @return found records for selected entity
     * @throws EQLException
     * @throws IncorrectEntityDescriptionException
     *
     * @throws CouldntGetEJBException
     */
    public static IntegratedRecordSet getResultByEntitiesFilters(
            List<EntityData> entityFilters, String entityName,
            RequestProperties props,
            EntityViewHelper.FieldsModificator requestType,
            LogonSession ls, ActionContext ctx)
            throws EQLException {
        Reqs castorRequest = buildReqsFromEntities(entityFilters, null,
                entityName, props, ls, ctx);
        return performRequest(castorRequest, entityName, ls, ctx, requestType);
    }

    public static IntegratedRecordSet getCombinedEntitiesResult(
            List<EntityElement> requestingEntities,
            Collection<EntityData> entityFilters,
            RequestProperties props,
            LogonSession ls, ActionContext ctx)
            throws EQLException {

        Reqs reqs = EntityFacade.createRequest(props);
        reqs.getReq().setReqField(getReqFields(requestingEntities, props));
        reqs.setReqFilters(EntityFacade.getReqFilters(entityFilters, props, ls,
                ctx));

        GetRecordsRes recordsRes = ctx.getRecordsManager().process(reqs, ls);

        return parseMultipleEntityResult(recordsRes, ls, ctx);
    }

    public static ReqField[] getReqFields(List<EntityElement> fields,
                                          RequestProperties props) {
        ReqField[] rf = new ReqField[fields.size()];
        SortField sortField = props.getSortField();
        for(int i = 0; i < fields.size(); i++) {
            EntityElement t = fields.get(i);
            rf[i] = new ReqField();
            rf[i].setEntity(EntityOperationsHelper.getEntityNameFromFormID(
                    t.getFormId()));
            rf[i].setName(t.getElementId());
            rf[i].setCaption(t.getElementCaption());
            if(sortField != null && t.getElementKey().equalsIgnoreCase(
                    sortField.getFieldID())
                    && sortField.getSortOrder() != null) {
                rf[i].setSortdir(sortField.getSortOrder()
                        ? OrderDirectionSType.ASC:OrderDirectionSType.DESC);
                rf[i].setSort(true);
            }
        }
        return rf;
    }

    /**
     * Perform request to DB, integrated result will include datasets in it. It simply wrap result set with FieldData objects.
     * Initial eql result is attached to integrated result set.
     *
     * @param castorRequest 2.6 core request
     * @param entityName    request entity name
     * @param ls            logon session
     * @param ctx           servlet context
     * @param entityType
     * @return integrated result set
     * @throws EQLException           if eql exception occured
     * @throws CouldntGetEJBException thrown if could not get ejb exception
     * @throws IncorrectEntityDescriptionException
     *                                thrown if entity has incorrect description
     */
    public static IntegratedRecordSet performRequest(Reqs castorRequest,
                                                     String entityName,
                                                     LogonSession ls,
                                                     ActionContext ctx,
                                                     EntityViewHelper.FieldsModificator entityType)
            throws EQLException {
        GetRecordsRes recordsRes = ctx.getRecordsManager().process(
                castorRequest, ls);
        return parseSingleEntityResult(recordsRes, entityName, entityType, ls,
                ctx);
    }

    /**
     * Parse resultset and perform additional requests if needed for datasets. Returned metadata is defined by the #entityType parameter
     *
     * @param recordsRes
     * @param entityName
     * @param entityType
     * @param ls
     * @param ctx
     * @return
     * @throws CouldntGetEJBException
     * @throws IncorrectEntityDescriptionException
     *
     */
    private static IntegratedRecordSet parseSingleEntityResult(
            GetRecordsRes recordsRes, String entityName,
            EntityViewHelper.FieldsModificator entityType,
            LogonSession ls, ActionContext ctx)
            throws CouldntGetEJBException, IncorrectEntityDescriptionException {
        Ress result = recordsRes.getRess();

        Map<String, FieldMeta> metas = EntityViewHelper.getMetaForEntity(
                entityName, EntityViewHelper.FieldsModificator.FORM, false,
                ls, ctx);
        String pkeyFieldName = EntityViewHelper.getPkeyID(entityName, ctx);

        //parse result and fill up empty values with non-empty incoming data
        int rows = result.getRes().getResRecordCount();

        List<IntegratedRecord> list = new ArrayList<IntegratedRecord>(rows);
        List<DatasetDescriptor> datasetsInEntity = getDatasetDescriptors(metas,
                entityName, ctx);

        for(int r = 0; r < rows; r++) {
            //datasets result doesn't included into standart eql resultset
            ResRecord rec = result.getRes().getResRecord(r);
            Long recordID = getRowId(rec, pkeyFieldName);

            int cols = rec.getResFieldCount();
            Map<String, FieldData> fieldSet
                    = new LinkedHashMap<String, FieldData>(cols);
            for(int c = 0; c < cols; c++) {
                ResField fld = rec.getResField(c);
                String key = fld.getName();
                if(metas.containsKey(key)) {
                    try {
                        fieldSet.put(key,
                                EntitySerializeHelper.createFieldDataFromString(
                                        fld, metas.get(key), recordID,
                                        ls.getUser(), ctx));
                    } catch (Exception e) {
                        logger.ERROR("Could not retrieve data for entity ["
                                + entityName + "], field name [" + key + "]. ",
                                e);
                    }
                }
            }

            for(DatasetDescriptor descriptor : datasetsInEntity) {
                String fieldName = descriptor.getLocationEntityFieldName();
                try {
                    fieldSet.put(fieldName, createFieldDataForDataset(
                            descriptor, recordID, ls, ctx));
                } catch (Exception e) {
                    logger.ERROR("Could not retrieve data for entity ["
                            + entityName + "], field name [" + fieldName
                            + "]. ", e);
                }
            }

            list.add(new IntegratedRecord(fieldSet));
        }

        return new IntegratedRecordSet(pkeyFieldName,
                EntityViewHelper.getListFieldNameForEntity(entityName, ctx),
                list, metas, recordsRes, EntityViewHelper.getGridFields(
                entityName, entityType, false, ls, ctx));
    }

    private static Long getRowId(ResRecord rec, String pkeyFieldId) {
        Long ret = -1L;
        int pkeyIndex = -1;
        for(int i = 0; i < rec.getResFieldCount(); i++) {
            ResField field = rec.getResField(i);
            if(field.getName().equalsIgnoreCase(pkeyFieldId)) {
                pkeyIndex = i;
            }
        }
        if(pkeyIndex >= 0) {
            String rowID = rec.getResField(pkeyIndex).getResFieldValue();
            if(rowID != null) {
                ret = Long.parseLong(rowID);
            }
        }
        return ret;
    }

    private static IntegratedRecordSet parseMultipleEntityResult(
            GetRecordsRes recordsRes, LogonSession ls, ActionContext ctx)
            throws CouldntGetEJBException, IncorrectEntityDescriptionException {
        Ress result = recordsRes.getRess();

        //parse result and fill up empty values with non-empty incoming data
        int rows = result.getRes().getResRecordCount();

        Map<String, Map<String, FieldMeta>> entitiesMap
                = new HashMap<String, Map<String, FieldMeta>>();

        List<IntegratedRecord> list = new ArrayList<IntegratedRecord>(rows);
        //create combined meta. Since we can have names collision we use key as "entityName" + "fieldName".
        Set<String> combinedGridFields = new LinkedHashSet<String>();
        Map<String, FieldMeta> combinedRecordMeta
                = new LinkedHashMap<String, FieldMeta>();

        //iterate result fields
        for(int r = 0; r < rows; r++) {
            ResRecord rec = result.getRes().getResRecord(
                    r);//datasets result doesn't included into standart eql resultset

            int cols = rec.getResFieldCount();
            Map<String, FieldData> fieldSet
                    = new LinkedHashMap<String, FieldData>(cols);
            for(int c = 0; c < cols; c++) {
                ResField fld = rec.getResField(c);
                String fieldName = fld.getName();
                String entityName = fld.getEntity();

                Map<String, FieldMeta> metas;
                if(entitiesMap.containsKey(entityName)) {
                    metas = entitiesMap.get(entityName);
                } else {
                    metas = EntityViewHelper.getMetaForEntity(entityName,
                            EntityViewHelper.FieldsModificator.FORM, false, ls,
                            ctx);
                    entitiesMap.put(entityName, metas);
                }
                if(metas.containsKey(fieldName)) {
                    try {
                        String key = EntityElement.getElementKey(entityName,
                                fieldName);

                        FieldMeta fieldMeta = metas.get(fieldName);
                        fieldSet.put(key,
                                EntitySerializeHelper.createFieldDataFromString(
                                        fld, fieldMeta, -1L, ls.getUser(),
                                        ctx));

                        combinedGridFields.add(key);
                        combinedRecordMeta.put(key, fieldMeta);
                    } catch (Exception e) {
                        logger.ERROR("Could not retrieve data for entity ["
                                + entityName + "], field name [" + fieldName
                                + "]. ", e);
                    }
                }
            }

            list.add(new IntegratedRecord(fieldSet));
        }

        return new IntegratedRecordSet("", "", list, combinedRecordMeta,
                recordsRes, combinedGridFields);
    }

    /**
     * Creates {@link com.queplix.core.client.app.vo.FieldData} element for the 3 types of datasets : multiselect, entitylink and
     * inform grid.
     *
     * @param descriptor dataset descriptor, that contains all the data, that describs dataset.
     * @param recordID   record id, for which this dataset should be get. (in old terms pkey value)
     * @param ls         logon session.
     * @param ctx        servlet context @return created field data object. Cannot be a null object.
     * @throws IllegalControlTypeException thrown if dataset descriptor points to not-dataset field.
     * @throws EQLException                thrown if some eql exception occured during procedure performing.
     * @throws CouldntGetEJBException      thrown if some ejb could not be get.
     * @throws IncorrectEntityDescriptionException
     *                                     thrown if some entity has incorrect description
     */
    private static FieldData createFieldDataForDataset(
            DatasetDescriptor descriptor,
            Long recordID,
            LogonSession ls,
            ActionContext ctx)
            throws EQLException {
        FieldData retData;
        DatasetType datasetType = descriptor.getType();
        switch(datasetType) {
            case ENYTITYLINK: {
                FieldData filter = new TextboxFieldData(
                        descriptor.getBindingEntityFkeyFieldNameToLocation(),
                        Long.toString(recordID));
                FieldData[] filters = new FieldData[]{filter};
                ArrayList<EntityData> filtersEntity = new ArrayList<EntityData>(
                        1);
                EntityData entity = new EntityData(
                        descriptor.getBindingEntity(), -1L, filters);
                filtersEntity.add(entity);
                Reqs request = buildReqsFromEntities(filtersEntity, null,
                        descriptor.getBindingEntity(),
                        new RequestProperties(), ls, ctx);
                IntegratedRecordSet result = performRequest(request,
                        descriptor.getBindingEntity(), ls, ctx,
                        EntityViewHelper.FieldsModificator.CUSTOMIZED_GRID);
                String linkedPkeyValueFieldName = descriptor
                        .getBindingEntityFkeyFieldNameToLinked();
                FieldMeta idFieldMeta = result.getRecordMeta().get(
                        linkedPkeyValueFieldName);
                if(idFieldMeta
                        == null) {//entity result doesn't contain required foreign key value.
                    throw new IncorrectEntityDescriptionException(
                            descriptor.getBindingEntity(),
                            "Could not find field [" + linkedPkeyValueFieldName
                                    + "] in entity ["
                                    + descriptor.getBindingEntity()
                                    + "]. Check your entity description. ");
                } else {
                    ArrayList<Long> linkedPkeyValues = new ArrayList<Long>(
                            result.getRowsCount());
                    for(IntegratedRecord record : result.getRecordSet()) {
                        FieldData idData = record.getFieldSet().get(
                                linkedPkeyValueFieldName);
                        try {
                            Long linkedRecordID = Long.parseLong(
                                    EntitySerializeHelper.getValueStringRepresentation(
                                            idData, idFieldMeta));
                            linkedPkeyValues.add(linkedRecordID);
                        } catch (NumberFormatException e) {
                            logger.ERROR("Field [" + linkedPkeyValueFieldName
                                    + "] in entity ["
                                    + descriptor.getBindingEntity()
                                    + "] should be numeric and should not contain null or empty values. Because it is used as a foreign key to ["
                                    +
                                    descriptor.getLinkedEntity() + "] entity. ",
                                    e);
                        } catch (IllegalControlTypeException e) {
                            logger.ERROR("Field [" + linkedPkeyValueFieldName
                                    + "] in entity ["
                                    + descriptor.getBindingEntity()
                                    + "] could not be represented as a string. ",
                                    e);
                        }
                    }
                    retData = new EntityLinkFieldData(
                            descriptor.getLocationEntityFieldName(), recordID,
                            new HashSet<Long>(
                                    linkedPkeyValues));
                }
                break;
            }
            case MULTISELECT: {
                //get selected entities in the binding table
                IntegratedRecordSet resp = getEntityByIDRequest(
                        descriptor.getLinkedEntity(),
                        descriptor.getLocationEntity(), recordID, ls,
                        ctx);

                String pkeyFieldName = EntityViewHelper.getPkeyID(
                        descriptor.getLinkedEntity(), ctx);

                /* Core 2.6 implementation.
            // Getting records.
            Ress ress = resp.getInitialResultSet().getRess();

            String pkeyFieldName = null;
            ResHeaderField[] headers = ress.getResHeader().getResHeaderField();
            for(ResHeaderField header : headers) {
                *//* in 2.6 version it was necessary to have both (header.getGrid() && header.getPkey()) on for linked entity (see see m2mform.xsl)
                     * but here due to the fact we only one pkey field in entity we change logic to header.getPkey() is enaught to indicate field
                     *//*
                    if(header.getPkey()) {
                        pkeyFieldName = header.getName();
                        break;
                    }
                }*/
                if(pkeyFieldName != null) {
                    //Retrieving the selected items.
                    ArrayList<Long> selectedItems = new ArrayList<Long>();

                    if(resp.getRowsCount() > 0) {
                        Map<String, FieldMeta> metaData = resp.getRecordMeta();
                        for(IntegratedRecord record : resp.getRecordSet()) {
                            FieldData recData = record.getFieldSet().get(
                                    pkeyFieldName);
                            try {
                                long l = Long.parseLong(
                                        EntitySerializeHelper.getValueStringRepresentation(
                                                recData, metaData.get(
                                                pkeyFieldName)));
                                selectedItems.add(l);
                            } catch (Exception e) {
                                logger.ERROR("Could not add selected value. ",
                                        e);
                            }
                        }
                    }
                    //convert ArrayLong to long[]
                    long[] ds = new long[selectedItems.size()];
                    for(int i = 0; i < selectedItems.size(); i++) {
                        ds[i] = selectedItems.get(i);
                    }
                    SubsetData selected = new SubsetData(ds);
                    retData = new MultiselectFieldData(
                            descriptor.getLocationEntityFieldName(), selected);
                } else {
                    throw new IncorrectEntityDescriptionException(
                            descriptor.getBindingEntity(),
                            "Could not find field pkey=\"true\" and grid=\"true\" in entity ["
                                    + descriptor.getLinkedEntity() + "]. ");
                }
                break;
            }
            case IN_FORM_GRID: {
                //check if all needed data is available
                String fieldName = descriptor.getLocationEntityFieldName();

                //build filters for current entity, and select linked entity with it.
                List<FieldData> filterFields = new LinkedList<FieldData>();
                filterFields.add(new TextboxFieldData(
                        descriptor.getLocationPkeyFieldName(), String.valueOf(
                        recordID)));

                Reqs request = buildReqsFromFields(filterFields,
                        descriptor.getLinkedEntity(),
                        descriptor.getLocationEntity(), null,
                        new RequestProperties(),
                        ls, ctx);

                IntegratedRecordSet resp = performRequest(request,
                        descriptor.getLinkedEntity(), ls, ctx,
                        EntityViewHelper.FieldsModificator.GRID);

                RowData[] rows = EntityOperationsHelper
                        .retrieveGridRowsDataFromResult(resp, ls, ctx);

                retData = new InFormGridFieldData(fieldName, new GridData(rows,
                        descriptor.getLinkedEntity()));
                break;
            }
            default: {
                throw new IllegalControlTypeException(
                        "Couldn't create data for dataset of type ["
                                + datasetType.toString() + "].");
            }
        }

        return retData;
    }

    private static List<DatasetDescriptor> getDatasetDescriptors(
            Map<String, FieldMeta> metas, String entityName,
            ActionContext ctx) throws CouldntGetEJBException {
        List<DatasetDescriptor> ret = new LinkedList<DatasetDescriptor>();
        Set<String> keys = metas.keySet();
        for(String key : keys) {
            int type = metas.get(key).getDataType();
            if(type == FieldMeta.MULTISELECT || type == FieldMeta.IN_FORM_GRID
                    || type == FieldMeta.ENTITYLINK) {
                ret.add(EntityViewHelper.getDatasetMetadata(entityName, key,
                        ctx));
            }
        }
        return ret;
    }

    /**
     * Delete records of the single entity.
     * This method doesn't take any filters from entity data, but just take its id.
     *
     * @param entityData data array of EntityData. All the entity datas should correspond to the single entity
     * @param ctx        action context
     * @param ls         logon session
     * @throws EQLException if some eql exception occured.
     */
    public static void deleteRecord(EntityData[] entityData, ActionContext ctx,
                                    LogonSession ls)
            throws EQLException {
        if(entityData.length == 0) {
            throw new IllegalArgumentException("No record(s) to delete");
        }

        // Let's assume that all entityData's corresponds to the single entity
        String entityName = entityData[0].getEntityID();
        String pkeyName = EntityViewHelper.getPkeyID(entityName, ctx);

        Sreq sreq = new Sreq();
        sreq.setChecklock(true);
        sreq.setGetresponse(false);
        sreq.setName(entityName);

        for(EntityData data : entityData) {
            SreqRecord sreqRecord = new SreqRecord();
            sreq.addSreqRecord(sreqRecord);
            sreqRecord.setTodo(TodoSType.DELETE);

            SreqField sreqField = new SreqField();
            sreqRecord.addSreqField(sreqField);
            sreqField.setName(pkeyName);
            sreqField.setSreqFieldValue(data.getRowID().toString());
        }

        SetRecordsLocal setRecordsManager = ctx.getSetRecordsManager();
        setRecordsManager.process(sreq, ls);
    }

    /**
     * Delete the single entity with the given id.
     *
     * @param entityName entity name to delete
     * @param entityId   record id in DB.
     * @param ctx        action context
     * @param ls         logon session
     * @throws EQLException if some eql exception occured.
     */
    public static void deleteRecord(String entityName, Long entityId,
                                    ActionContext ctx, LogonSession ls)
            throws EQLException {

        String pkeyName = EntityViewHelper.getPkeyID(entityName, ctx);

        Sreq sreq = new Sreq();
        sreq.setChecklock(true);
        sreq.setGetresponse(false);
        sreq.setName(entityName);

        SreqRecord sreqRecord = new SreqRecord();
        sreq.addSreqRecord(sreqRecord);
        sreqRecord.setTodo(TodoSType.DELETE);

        SreqField sreqField = new SreqField();
        sreqRecord.addSreqField(sreqField);
        sreqField.setName(pkeyName);
        sreqField.setSreqFieldValue(entityId.toString());

        SetRecordsLocal setRecordsManager = ctx.getSetRecordsManager();
        setRecordsManager.process(sreq, ls);
    }

    public static void insertRecord(EntityData entityToUpdate,
                                    ActionContext ctx, LogonSession ls)
            throws EQLException, RecordDoesntExistsException {
        changeRecord(entityToUpdate, true, ctx, ls);
    }

    public static void updateRecord(EntityData entityToUpdate,
                                    ActionContext ctx, LogonSession ls)
            throws EQLException, RecordDoesntExistsException {
        changeRecord(entityToUpdate, false, ctx, ls);
    }

    private static void changeRecord(EntityData entityToUpdate,
                                     boolean toBeInserted, ActionContext ctx,
                                     LogonSession ls)
            throws RecordDoesntExistsException, EQLException {
        String entityName = entityToUpdate.getEntityID();
        Long rowID = entityToUpdate.getRowID();

        Collection<FieldData> oldData;
        if(toBeInserted) {//old data is empty
            oldData = Arrays.asList(entityToUpdate.getFields());
        } else {//get old data from DB
            IntegratedRecordSet dbFieldsData = getEntityByIDRequest(entityName,
                    rowID, ls, ctx);
            if(dbFieldsData.getRowsCount() > 0) {//record still exists
                oldData = dbFieldsData.getRecordSet().get(0).getFieldSet()
                        .values();
            } else {
                throw new RecordDoesntExistsException(
                        "Could not update data. Record does not exist at the DB. ");
            }
        }

        //update base entity
        performInitialEntityUpdate(entityName, toBeInserted, ls, ctx, oldData,
                entityToUpdate);
    }

    /**
     * Performing initial entity update. Non entity datasets is updated after this call.
     *
     * @param entityName     entity to update
     * @param toBeInserted   to be inserted or to be updated
     * @param ls             logon session
     * @param ctx            servlet context
     * @param dbFieldsData   old data, that is stored in db (old data), or in case of new entity that is initial data,
     *                       that was sent from user (copy of another record for example, or empty collection)
     * @param entityToUpdate entity data to be inserted to db (new data)
     * @throws EQLException thrown if eql exception occured
     */
    private static void performInitialEntityUpdate(String entityName,
                                                   boolean toBeInserted,
                                                   LogonSession ls,
                                                   ActionContext ctx,
                                                   Collection<FieldData> dbFieldsData,
                                                   EntityData entityToUpdate)
            throws EQLException {
        //create update records castor request object
        Sreq setRecords = new Sreq();
        setRecords.setChecklock(true);
        setRecords.setName(entityName);
        setRecords.setGetresponse(
                false);//will get result record manualy. not performance decreasing.

        //fill up entities
        SreqRecord record = new SreqRecord();

        if(toBeInserted) {
            record.setTodo(TodoSType.INSERT);
        } else {
            record.setTodo(TodoSType.UPDATE);
        }

        //fill up fields to be updated
        Map<String, FieldMeta> metas = EntityViewHelper.getMetaForEntity(
                entityName, EntityViewHelper.FieldsModificator.FORM,
                false, ls, ctx);

        HashMap<String, FieldData> oldDatas = new HashMap<String, FieldData>();
        for(FieldData data : dbFieldsData) {//could not be null, because it comes here only if 1 rec exists
            oldDatas.put(data.getFieldID(), data);
        }

        FieldData[] fieldsList = entityToUpdate.getFields();
        Long rowID = entityToUpdate.getRowID();
        LinkedList<SreqField> updateFields = new LinkedList<SreqField>();
        LinkedList<SreqDataset> updateDataset = new LinkedList<SreqDataset>();
        fillUpdatingStructures(entityName, rowID, fieldsList, oldDatas, metas,
                updateDataset, updateFields, toBeInserted, ls, ctx);

        //perform update
        if(updateFields.size() != 0 || updateDataset.size() != 0) {
            record.setSreqField(updateFields.toArray(
                    new SreqField[updateFields.size()]));
            record.setSreqDataset(updateDataset.toArray(
                    new SreqDataset[updateDataset.size()]));
            setRecords.setSreqRecord(new SreqRecord[]{record});
            SetRecordsLocal setter = ctx.getSetRecordsManager();
            setter.process(setRecords, ls);
        }
    }

    private static void fillUpdatingStructures(String entityName, Long rowID,
                                               FieldData[] fieldsList,
                                               HashMap<String, FieldData> oldDatas,
                                               Map<String, FieldMeta> metas,
                                               LinkedList<SreqDataset> updateDataset,
                                               LinkedList<SreqField> updateFields,
                                               boolean toBeInserted,
                                               LogonSession ls,
                                               ActionContext ctx)
            throws EQLException {
        for(FieldData data : fieldsList) {
            FieldData oldData = oldDatas.get(data.getFieldID());
            FieldMeta meta = metas.get(data.getFieldID());
            if(meta != null && oldData
                    != null) {//metadata should not be a null and old data field should not be a null
                try {
                    int fieldType = meta.getDataType();
                    if(fieldType == FieldMeta.MULTISELECT
                            || fieldType == FieldMeta.ENTITYLINK
                            || fieldType == FieldMeta.IN_FORM_GRID) {
                        DatasetDescriptor descriptor = EntityViewHelper
                                .getDatasetMetadata(entityName,
                                        meta.getFieldID(), ctx);
                        if(toBeInserted) {//because it merges it, and doesn't add if it's the same.
                            oldData = EntitySerializeHelper
                                    .createEmptyFieldData(meta, ls.getUser());
                        }
                        addUpdatingDataset(data, meta, oldData, updateDataset,
                                descriptor, rowID, ls, ctx);
                    } else {
                        addUpdatingField(data, meta, toBeInserted, oldData,
                                updateFields);
                    }
                } catch (IllegalControlTypeException e) {
                    logger.ERROR("Field [" + data.getFieldID() + "], entity ["
                            + entityName +
                            "] was been tried to update. See caused error for details. Field excluded from update. ",
                            e);
                } catch (RecordDoesntExistsException e) {
                    logger.ERROR("Could not update dataset[" +
                            data.getFieldID() +
                            "], entity ["
                            + entityName +
                            "] + , it won't be included to updating structure.", e);
                }
            }
        }
    }

    /**
     * This method creates entity prototype. It means, that nothing is inserted into the DB, but all the custom server scripts will be
     * applied and all def-src attributes performed.
     *
     * @param entityName        creating entity name
     * @param prototypeDataList data list, that is to be used when no data was created by custom scripts.
     * @param ls                logon session
     * @param ctx               servlet context
     * @return entity prototype object
     * @throws EQLException                thrown if some eql exc occured during the method.
     * @throws RecordDoesntExistsException thrown if record wasn't created.
     */
    public static EntityData createEntityPrototype(String entityName,
                                                   Collection<FieldData> prototypeDataList,
                                                   LogonSession ls,
                                                   ActionContext ctx)
            throws EQLException, RecordDoesntExistsException {
        IntegratedRecordSet recordset = EntityFacade.createEmptyEntityRequest(
                entityName, ls, ctx);
        EntityData entityData;
        if(recordset.getRowsCount() > 0) {
            //find what exactly rows should be included to resultset
            Map<String, FieldMeta> metas = EntityViewHelper.getMetaForEntity(
                    entityName, EntityViewHelper.FieldsModificator.FORM,
                    false, ls, ctx);

            //fill prototype data values
            HashMap<String, FieldData> prototypeDatas
                    = new HashMap<String, FieldData>(prototypeDataList.size());
            for(FieldData fieldData : prototypeDataList) {
                prototypeDatas.put(fieldData.getFieldID(), fieldData);
            }

            List<FieldData> data = new LinkedList<FieldData>();

            IntegratedRecord firstRecord = recordset.getRecordSet().get(0);
            Map<String, FieldData> dbValuesMap = firstRecord.getFieldSet();
            Map<String, FieldMeta> dbMetaMap = recordset.getRecordMeta();
            Set<String> keys = dbValuesMap.keySet();

            //get row id
            FieldData rowIDField = firstRecord.getFieldSet().get(
                    recordset.getPkeyFieldName());
            Long rowID = null;
            try {
                rowID = Long.parseLong(
                    EntitySerializeHelper.getValueStringRepresentation(
                            rowIDField, metas.get(
                                recordset.getPkeyFieldName())));
            } catch (NumberFormatException e) {
                //do nothing, if it happens, then it could mean entity doesn't have pkey field.
            }

            //choose what should pass as new record 
            for(String key : keys) {
                FieldData dataToAdd;

                FieldMeta fieldMeta = dbMetaMap.get(key);
                FieldData fieldData = dbValuesMap.get(key);
                if(!fieldData .isEmpty()) {
                    //core returned filled value, don't edit it
                    dataToAdd = fieldData;
                } else {
                    if(!fieldMeta.hasDefSrcAttribute() && !fieldMeta
                            .hasClearAttribute()) {//can be filled up with client values
                        dataToAdd = prototypeDatas.get(key);
                    } else {//was filled up with empty value
                        dataToAdd = fieldData;
                        if(fieldMeta.hasDefSrcAttribute()) {
                            logger.INFO("Don't fill field: " + key
                                    + ", entity :" + entityName
                                    + ", because it has eql-defsrc value: ["
                                    + EntitySerializeHelper
                                    .getValueStringRepresentation(fieldData,
                                            fieldMeta) + "]");
                        }
                    }
                }
                if(dataToAdd != null) {
                    EntitySerializeHelper.initializeEmptyFieldData(fieldData,
                            fieldMeta, rowID);//fill up with values
                    data.add(dataToAdd);
                }
            }

            entityData = new EntityData(entityName, rowID, data.toArray(
                    new FieldData[data.size()]));
        } else {
            throw new RecordDoesntExistsException(
                    "Could not create initial entity prototype. ");
        }
        return entityData;
    }

    private static void addUpdatingDataset(FieldData data, FieldMeta meta,
                                           FieldData oldData,
                                           LinkedList<SreqDataset> updateDatasets,
                                           DatasetDescriptor descriptor,
                                           Long recordID, LogonSession ls,
                                           ActionContext ctx)
            throws IllegalControlTypeException, EQLException,
            CouldntGetEJBException,
            IncorrectEntityDescriptionException, RecordDoesntExistsException {

        SreqDataset dataSet = new SreqDataset();
        dataSet.setName(descriptor.getLocationEntityFieldName());
        dataSet.setEntity(descriptor.getBindingEntity());
        dataSet.setExternal(descriptor.getLocationPkeyFieldName());
        dataSet.setInternal(
                descriptor.getBindingEntityFkeyFieldNameToLocation());

        switch(meta.getDataType()) {
            case FieldMeta.MULTISELECT: {
                MultiselectFieldData mfData = (MultiselectFieldData) data;
                MultiselectFieldData mfOldData = (MultiselectFieldData) oldData;

                long[] selectedIDsMultiselect = mfData.getItemsSelected()
                        .getSelectedIDs();
                long[] oldIDsMultiselect = mfOldData.getItemsSelected()
                        .getSelectedIDs();

                Set<Long> convSelectedIDs = new HashSet<Long>(
                        selectedIDsMultiselect.length);
                Set<Long> convOldIDs = new HashSet<Long>(
                        oldIDsMultiselect.length);

                CollectionsHelper.copyArrayToCollection(selectedIDsMultiselect,
                        convSelectedIDs);
                CollectionsHelper.copyArrayToCollection(oldIDsMultiselect,
                        convOldIDs);

                constructDatasetUpdateStructure(dataSet, descriptor,
                        convSelectedIDs, convOldIDs, recordID, ls, ctx);
                break;
            }
            case FieldMeta.ENTITYLINK: {
                EntityLinkFieldData elData = (EntityLinkFieldData) data;
                EntityLinkFieldData elOldData = (EntityLinkFieldData) oldData;

                constructDatasetUpdateStructure(dataSet, descriptor,
                        elData.getLinkedEntityIDs(),
                        elOldData.getLinkedEntityIDs(),
                        elData.getCurrentEntityID(), ls, ctx);
                break;
            }
            case FieldMeta.IN_FORM_GRID: {
                InFormGridFieldData inGridData = (InFormGridFieldData) data;
                InFormGridFieldData inGridOldData
                        = (InFormGridFieldData) oldData;

                RowData[] rows = inGridData.getGridData().getRows();
                RowData[] oldRows = inGridOldData.getGridData().getRows();

                Set<Long> linkedIDs = new HashSet<Long>(rows.length);
                Set<Long> oldIDs = new HashSet<Long>(oldRows.length);

                for(RowData row : rows) {
                    Long id = row.getId();
                    if(id != null) {
                        linkedIDs.add(id);
                    }
                }

                for(RowData row : oldRows) {
                    Long id = row.getId();
                    if(id != null) {
                        oldIDs.add(id);
                    }
                }

                constructDatasetUpdateStructure(dataSet, descriptor, linkedIDs,
                        oldIDs, recordID, ls, ctx);
                break;
            }
            default:
                throw new IllegalControlTypeException(
                        "Unsupported control type [" + meta.getDataType()
                                + "]");
        }
        if(dataSet.getSreqRecordCount() > 0) {
            updateDatasets.add(dataSet);
        }
    }

    /**
     * This method add appropriate parameters to dataSet update object. It compares old and selected id's and insert
     * appropriate fields to update object.
     * DB doesn't allow to insert incorrect keys, because it uses foreign keys for that purpose. (it is checking by tools).
     *
     * @param dataSet              dataset object that should be filled up with insert and delete object.
     * @param descriptor           dataset descriptor
     * @param selectedLinkedIDs    id's that was selected by user (id for the linked entity)
     * @param oldLinkedIDs         id's that stored in db (id for the linked entity)
     * @param locationReferencedID current (id for the location entity)
     */
    private static void constructDatasetUpdateStructure(SreqDataset dataSet,
                                                        DatasetDescriptor descriptor,
                                                        Set<Long> selectedLinkedIDs,
                                                        Set<Long> oldLinkedIDs,
                                                        Long locationReferencedID,
                                                        LogonSession ls,
                                                        ActionContext ctx)
            throws EQLException, RecordDoesntExistsException {
        //parse what should be deleted, and what should be inserted
        List<Long> toDelete = new ArrayList<Long>();
        List<Long> toInsert = new ArrayList<Long>();
        for(Long oldID : oldLinkedIDs) {
            if(!selectedLinkedIDs.contains(oldID)) {
                toDelete.add(oldID);
            }
        }

        for(Long id : selectedLinkedIDs) {
            if(!oldLinkedIDs.contains(id)) {
                toInsert.add(id);
            }
        }

        if(toInsert.size() > 0 || toDelete.size() > 0) {
            //get changing entity metadata
            Map<String, FieldMeta> bindingEntityMeta = EntityViewHelper
                    .getMetaForEntity(
                            descriptor.getBindingEntity(),
                            EntityViewHelper.FieldsModificator.FORM, false, ls,
                            ctx);

            FieldMeta firstFieldMeta = bindingEntityMeta.get(
                    descriptor.getBindingEntityFkeyFieldNameToLinked());
            FieldMeta secondFieldMeta = bindingEntityMeta.get(
                    descriptor.getBindingEntityFkeyFieldNameToLocation());

            FieldData secondFieldData = EntitySerializeHelper.createFieldData(
                    descriptor.getBindingEntityFkeyFieldNameToLocation(),
                    Long.toString(locationReferencedID), "", secondFieldMeta,
                    -1L, null, ctx, false);

            String bindingPkeyName = EntityViewHelper.getPkeyID(
                    descriptor.getBindingEntity(), ctx);

            //build update request object for dataset. can add filters only
            for(long idToDelete : toDelete) {
                SreqRecord record = new SreqRecord();
                record.setTodo(TodoSType.DELETE);

                if(bindingPkeyName
                        != null) {//if pkey field is presented we need to get record Id in order to delete it
                    FieldData firstFieldData = EntitySerializeHelper
                            .createFieldData(
                                    descriptor.getBindingEntityFkeyFieldNameToLinked(),
                                    Long.toString(idToDelete), "",
                                    firstFieldMeta, -1L, null, ctx, false);

                    //build structure to retrieve record ids
                    List<FieldData> fieldsList = new ArrayList<FieldData>(2);
                    fieldsList.add(firstFieldData);
                    fieldsList.add(secondFieldData);

                    //todo optimize. could build one eql query for all deleting records.
                    Reqs req = buildReqsFromThisEntityFields(fieldsList,
                            descriptor.getBindingEntity(), "",
                            new RequestProperties(), ls, ctx);
                    IntegratedRecordSet resp = performRequest(req,
                            descriptor.getBindingEntity(), ls, ctx,
                            EntityViewHelper.FieldsModificator.FORM);

                    for(IntegratedRecord integratedRecord : resp
                            .getRecordSet()) {
                        SreqField field = new SreqField();
                        field.setName(bindingPkeyName);

                        FieldData fieldData = integratedRecord.getFieldSet()
                                .get(bindingPkeyName);
                        Long rowId = EntitySerializeHelper.retrieveRowId(
                                bindingEntityMeta.get(bindingPkeyName),
                                fieldData);
                        field.setSreqFieldValue(Long.toString(rowId));
                        record.addSreqField(field);
                    }
                } else {
                    SreqField field = new SreqField();
                    field.setName(descriptor.getBindingEntityFkeyFieldNameToLinked());
                    field.setSreqFieldValue(Long.toString(idToDelete));
                    record.addSreqField(field);

                    SreqField field2 = new SreqField();
                    field2.setName(
                            descriptor.getBindingEntityFkeyFieldNameToLocation());
                    field2.setSreqFieldValue(Long.toString(locationReferencedID));
                    record.addSreqField(field2);
                }

                dataSet.addSreqRecord(record);
            }

            for(long idToInsert : toInsert) {
                SreqRecord record = new SreqRecord();
                record.setTodo(TodoSType.INSERT);

                //retrieve two actiual inserting fields
                FieldData firstFieldData = EntitySerializeHelper
                        .createFieldData(
                                descriptor.getBindingEntityFkeyFieldNameToLinked(),
                                Long.toString(idToInsert), "", firstFieldMeta,
                                -1L, null, ctx, false);

                //build structure to create record prototype
                List<FieldData> fieldsList = new ArrayList<FieldData>(2);
                fieldsList.add(firstFieldData);
                fieldsList.add(secondFieldData);

                EntityData entityData = createEntityPrototype(descriptor.getBindingEntity(),
                        fieldsList, ls, ctx);

                //create 2.6 inserting structure
                LinkedList<SreqField> updateFields = new LinkedList<SreqField>();
                LinkedList<SreqDataset> updateDatasets = new LinkedList<SreqDataset>();
                List<FieldData> oldData = Arrays.asList(entityData.getFields());
                HashMap<String, FieldData> oldDatas = new HashMap<String, FieldData>();
                for(FieldData data : oldData) {
                    oldDatas.put(data.getFieldID(), data);
                }

                //fill structure
                fillUpdatingStructures(descriptor.getBindingEntity(), -1L,
                        entityData.getFields(), oldDatas, bindingEntityMeta,
                        updateDatasets, updateFields, true, ls, ctx);

                for(SreqField updateField : updateFields) {
                    record.addSreqField(updateField);
                }
                for(SreqDataset updateDataset : updateDatasets) {
                    record.addSreqDataset(updateDataset);
                }
                dataSet.addSreqRecord(record);
            }
        }
    }

    private static void addUpdatingField(FieldData data, FieldMeta meta,
                                         boolean toBeInserted,
                                         FieldData oldData,
                                         LinkedList<SreqField> updateFields)
            throws IllegalControlTypeException {
        SreqField field = new SreqField();
        field.setName(data.getFieldID());
        String newValue = getValue(data, meta);
        String newText = getText(data, meta);
        field.setSreqFieldValue(newValue);
        field.setSreqFieldText(newText);
        if(!toBeInserted) {
            String oldValue = getValue(oldData, meta);
            String oldText = getText(oldData, meta);
            field.setSreqFieldOldValue(oldValue);
            field.setSreqFieldOldText(oldText);
            field.setHasChanged(!StringUtil.isStringsEquals(newValue,
                    oldValue));
        } else {
            field.setHasChanged(true);
        }
        updateFields.add(field);
    }

    /**
     * Retrieve text string representation. If representation is empty string, then returns null.
     * That method might be needed for the controls, where text make sense too.
     *
     * @param data field data
     * @param meta field meta
     * @return field text string representation
     */
    private static String getText(FieldData data, FieldMeta meta) {
        String newText = EntitySerializeHelper.getTextStringRepresentation(data,
                meta);
        if("".equalsIgnoreCase(newText.trim())) {
            newText = null;
        }
        return newText;
    }

    /**
     * Retrieve value string representation. If representation is empty string, then returns null.
     *
     * @param data field data
     * @param meta field meta
     * @return field value string representation
     */
    private static String getValue(FieldData data, FieldMeta meta) {
        String newValue = EntitySerializeHelper.getValueStringRepresentation(
                data, meta);
        if("".equalsIgnoreCase(newValue.trim())) {
            newValue = null;
        }
        return newValue;
    }

    public static IntegratedRecordSet searchEntity(
            List<EntityData> filterEntities, String requestedEntityName,
            RequestProperties props,
            LogonSession ls, ActionContext ctx)
            throws EQLException, IncorrectEntityDescriptionException,
            CouldntGetEJBException {

        Reqs request = buildReqsFromEntities(filterEntities, null,
                requestedEntityName, props, ls, ctx);
        return performRequest(request, requestedEntityName, ls, ctx,
                EntityViewHelper.FieldsModificator.CUSTOMIZED_GRID);
    }

    /**
     * Build request for the given entity with filters for this entity.
     *
     * @param filterFields        filter fields for entity
     * @param requestedEntityName requested entity
     * @param eqlFilters
     * @param props
     * @param ls
     * @param ctx
     * @return
     * @throws EQLException
     */
    public static Reqs buildReqsFromThisEntityFields(
            List<FieldData> filterFields, String requestedEntityName,
            String eqlFilters, RequestProperties props, LogonSession ls,
            ActionContext ctx) throws EQLException {
        return buildReqsFromFields(filterFields, requestedEntityName,
                requestedEntityName,
                eqlFilters, props, ls, ctx);
    }

    /**
     * Build request for the given requested entity with the filter for the another entity. Note this two entities should be linked with each other.
     *
     * @param filterFields        filtering field for the filtering entity
     * @param requestedEntityName requested entity
     * @param filteringEntityName filtering entity
     * @param eqlFilters
     * @param props
     * @param ls
     * @param ctx
     * @return
     * @throws EQLException
     */
    public static Reqs buildReqsFromFields(Collection<FieldData> filterFields,
                                           String requestedEntityName,
                                           String filteringEntityName,
                                           String eqlFilters,
                                           RequestProperties props,
                                           LogonSession ls, ActionContext ctx)
            throws EQLException {
        FieldData[] data = (filterFields != null ? filterFields.toArray(
                new FieldData[filterFields.size()]) : null);
        return buildReqsFromEntity(new EntityData(filteringEntityName, -1L,
                data), requestedEntityName, eqlFilters, props, ls, ctx);
    }

    /**
     * Build the request for the #requestedEntityName entity with the filters for the #filterEntity.
     *
     * @param filterEntity        entity and filters for this entity.
     * @param requestedEntityName requested entity name
     * @param props
     * @param ls
     * @param ctx
     * @return
     * @throws EQLException
     */
    public static Reqs buildReqsFromEntity(EntityData filterEntity,
                                           String requestedEntityName,
                                           String eqlFilters,
                                           RequestProperties props,
                                           LogonSession ls,
                                           ActionContext ctx)
            throws EQLException {
        ArrayList<EntityData> datas = new ArrayList<EntityData>();
        datas.add(filterEntity);
        return buildReqsFromEntities(datas, eqlFilters, requestedEntityName,
                props, ls, ctx);
    }

    /**
     * Create request for #requestedEntityName entity, with the given constraints for another linked to requested entity entities.
     *
     * @param filterEntities      constrainst
     * @param eqlFilters          additional eql filter
     * @param requestedEntityName requested entity name
     * @param props
     * @param ls
     * @param ctx
     * @return castor request object
     * @throws EQLException
     */
    public static Reqs buildReqsFromEntities(List<EntityData> filterEntities,
                                             String eqlFilters,
                                             String requestedEntityName,
                                             RequestProperties props,
                                             LogonSession ls, ActionContext ctx)
            throws EQLException {

        Reqs reqs = createRequest(requestedEntityName, props, false);

        try {
            ReqFilters reqFilters = getReqFilters(filterEntities, props, ls,
                    ctx);
            if(!StringHelper.isEmpty(eqlFilters)) {
                reqs.setEqlFilters(eqlFilters);
            }
            reqs.setReqFilters(reqFilters);
        } catch (Exception e) {
            logger.ERROR("Couldn't create filter, see caused by error", e);
        }
        return reqs;
    }

    /**
     * Create request object for 2.6 core, with given properties with requested entity definition.
     *
     * @param entityName
     * @param props
     * @param isNew
     * @return
     */
    public static Reqs createRequest(String entityName, RequestProperties props,
                                     boolean isNew) {
        Reqs reqs = createRequest(props);
        ReqEntity reqEntity = new ReqEntity();
        reqEntity.setName(entityName);
        reqEntity.setNew(isNew);

        if(props.getSortField() != null
                && props.getSortField().getSortOrder() != null) {
            reqEntity.setSortfield(props.getSortField().getFieldID());
            reqEntity.setSortdir(props.getSortField().getSortOrder()
                    ? OrderDirectionSType.ASC:OrderDirectionSType.DESC);
        }
        reqs.getReq().setReqEntity(reqEntity);
        return reqs;
    }

    /**
     * Create request object with given properties, without requested entity object defenition.
     *
     * @param props
     * @return
     */
    public static Reqs createRequest(RequestProperties props) {
        Reqs reqs = new Reqs();
        reqs.setDocount(props.isDoCount());
        reqs.setDoheader(props.isDoHeader());
        reqs.setGetrequest(props.isGetRequest());
        reqs.setIgnoreSendOnRequest(props.isIgnoreSendOnRequest());
        reqs.setPage(props.getPage());
        reqs.setPagesize(props.getPagesize());
        Req req = new Req();
        reqs.setReq(req);
        return reqs;
    }

    public static Reqs buildRequestByID(String entityID, Long rowID,
                                        RequestProperties props,
                                        LogonSession ls, ActionContext ctx)
            throws EQLException, CouldntGetEJBException,
            IncorrectEntityDescriptionException {
        FieldData pkeyDataField = getPkeyFilter(entityID, rowID, ctx);

        FieldData[] filters = new FieldData[]{pkeyDataField};
        ArrayList<EntityData> filtersEntity = new ArrayList<EntityData>(1);
        EntityData entity = new EntityData(entityID, rowID, filters);
        filtersEntity.add(entity);

        return buildReqsFromEntities(filtersEntity, null, entityID, props, ls,
                ctx);
    }

    public static Reqs buildRequestByIDs(String entityID,
                                         Collection<Long> rowIDs,
                                         RequestProperties props,
                                         LogonSession ls, ActionContext ctx)
            throws EQLException, CouldntGetEJBException,
            IncorrectEntityDescriptionException {
        ArrayList<EntityData> filtersEntity = new ArrayList<EntityData>(
                rowIDs.size());

        for(Long rowID : rowIDs) {
            FieldData pkeyDataField = getPkeyFilter(entityID, rowID, ctx);

            FieldData[] filters = new FieldData[]{pkeyDataField};

            EntityData entity = new EntityData(entityID, rowID, filters);
            filtersEntity.add(entity);
        }

        return buildReqsFromEntities(filtersEntity, null, entityID, props, ls,
                ctx);
    }

    /**
     * Creates filters for multiple entities.
     *
     * @param filterEntities
     * @param props
     * @param ls
     * @param ctx            @return filters.
     */
    public static ReqFilters getReqFilters(
            Collection<EntityData> filterEntities, RequestProperties props,
            LogonSession ls, ActionContext ctx) {
        ReqFilters reqFilters = new ReqFilters();
        try {
            for(EntityData filterEntity : filterEntities) {
                Map<String, FieldMeta> metas = EntityViewHelper
                        .getMetaForEntity(filterEntity.getEntityID(),
                                EntityViewHelper.FieldsModificator.FORM, false,
                                ls, ctx);

                for(FieldData filterData : filterEntity.getFields()) {
                    FieldMeta fieldMeta = metas.get(filterData.getFieldID());
                    if(fieldMeta != null) {
                        addRequestFilter(filterEntity.getEntityID(), fieldMeta,
                                filterData, reqFilters, ls, ctx);
                    } else {
                        //for debug purposes only
                        String fieldsInEntity = "";
                        for(String key : metas.keySet()) {
                            fieldsInEntity = fieldsInEntity + ", " + key;
                        }
                        logger.ERROR("Wasn't able to find metadata for field ["
                                + filterData.getFieldID() + "], entity ["
                                + filterEntity.getEntityID()
                                + "]. Filter for field ["
                                + filterData.getFieldID()
                                + "] won't be included to request. \n. Here are entities fields ["
                                + fieldsInEntity + "]. ");
                    }
                }
            }
            switch(props.getOperation()) {
                case AND: {
                    reqFilters.setType(ConditionSType.AND);
                    break;
                }
                case OR: {
                    reqFilters.setType(ConditionSType.OR);
                    break;
                }
            }
        } catch (Exception e) {
            logger.ERROR("Couldn't create filter, see caused by error. ", e);
        }
        return reqFilters;
    }

    /**
     * Creates and adds filter for the given entity filed meta and entity field data
     *
     * @param entityName
     * @param fieldMeta
     * @param filterData
     * @param reqFilters
     * @param ctx
     * @throws CouldntGetEJBException
     */
    private static void addRequestFilter(String entityName, FieldMeta fieldMeta,
                                         FieldData filterData,
                                         ReqFilters reqFilters,
                                         LogonSession ls, ActionContext ctx)
            throws CouldntGetEJBException {


        int type = fieldMeta.getDataType();
        if(type == FieldMeta.MULTISELECT || type == FieldMeta.ENTITYLINK
                || type == FieldMeta.IN_FORM_GRID) {
            Collection<ReqFilter> filters = createDatasetFilters(entityName,
                    fieldMeta, filterData, ls, ctx);
            for(ReqFilter filter : filters) {
                addNotEmptyFilter(reqFilters, filter);
            }
        } else {
            ReqFilter filter = EntitySerializeHelper.createEFieldFilter(
                    entityName, fieldMeta, filterData, ls);
            addNotEmptyFilter(reqFilters, filter);
        }
    }

    private static void addNotEmptyFilter(ReqFilters reqFilters,
                                          ReqFilter filter) {
        if(filter
                != null) {//can be used to determine empty value request, or unapropriate type
            ReqFiltersTypeItem item = new ReqFiltersTypeItem();
            item.setReqFilter(filter);
            reqFilters.addReqFiltersTypeItem(item);
        }
    }

    /**
     * Creates filter for the given dataset meta and dataset data.
     * If data has unsupported type or value is empty method it
     * return null object.
     *
     * @param locationEntityName
     * @param fieldMeta
     * @param filterData
     * @param ls
     * @param ctx
     * @return
     * @throws CouldntGetEJBException
     */
    private static Collection<ReqFilter> createDatasetFilters(
            String locationEntityName, FieldMeta fieldMeta,
            FieldData filterData,
            LogonSession ls, ActionContext ctx) throws CouldntGetEJBException {
        List<ReqFilter> filters = new LinkedList<ReqFilter>();

        switch(fieldMeta.getDataType()) {
            case FieldMeta.MULTISELECT: {
                MultiselectFieldData msfd = (MultiselectFieldData) filterData;
                DatasetDescriptor meta = EntityViewHelper.getDatasetMetadata(
                        locationEntityName, fieldMeta.getFieldID(), ctx);
                ReqFilter filter = new ReqFilter();
                filter.setEntity(meta.getBindingEntity());
                filter.setName(meta.getBindingEntityFkeyFieldNameToLinked());
                //add filtering values
                long[] selectedIndexes = msfd.getItemsSelected()
                        .getSelectedIDs();
                for(long selectedIndex : selectedIndexes) {
                    filter.addReqFilterValue(String.valueOf(
                            selectedIndex));//simply set selected row id, need to call {@link EntitySerializerHelper}
                }
                filter.setConditiontype(ConditionSType.OR);
                filters.add(filter);
                break;
            }
            case FieldMeta.IN_FORM_GRID: {
                InFormGridFieldData inGridData
                        = (InFormGridFieldData) filterData;
                DatasetDescriptor meta = EntityViewHelper.getDatasetMetadata(
                        locationEntityName, fieldMeta.getFieldID(), ctx);
                RowData[] rows = inGridData.getGridData().getRows();
                //initialize filters. We can have 2 different cases: 1 - can search by id when linked, or 2 - by filters.
                //in first case we can filter by binding entity and linked entity, in second only by linked.
                //to be simpler we always would filter by linked.

                if(rows != null && !inGridData.isEmpty()) {
                    String filteringEntity = meta.getLinkedEntity();
                    String filteringField = meta.getLinkedEntityPkeyFieldName();

                    if(rows.length > 0
                            && rows[0].getId() != null) {//record's ids search
                        ReqFilter idFilter = new ReqFilter();
                        idFilter.setEntity(filteringEntity);
                        idFilter.setName(filteringField);
                        idFilter.setConditiontype(ConditionSType.OR);
                        for(RowData row : rows) {
                            //add values with OR, so will be added all
                            // of the parent records which contains any of
                            // those records
                            Long id = row.getId();
                            if(id == null) {
                                continue;
                            }
                            idFilter.addReqFilterValue(String.valueOf(id));
                        }
                        filters.add(idFilter);
                    } else {//fields search
                        Map<String, FieldMeta> metas = EntityViewHelper
                                .getMetaForEntity(filteringEntity,
                                        EntityViewHelper.FieldsModificator.FORM,
                                        false, ls, ctx);
                        Collection<FieldData> fieldFilters = inGridData
                                .getFilters();
                        for(FieldData innerFieldData : fieldFilters) {
                            String innerFieldId = innerFieldData.getFieldID();
                            FieldMeta innerFieldMeta = metas.get(innerFieldId);
                            if(innerFieldMeta != null) {
                                ReqFilter filter = EntitySerializeHelper
                                        .createEFieldFilter(filteringEntity,
                                                innerFieldMeta, innerFieldData,
                                                ls);
                                filters.add(filter);
                            } else {
                                logger.WARN(
                                        "Couldn't find field meta for field [" +
                                                innerFieldId + "] in entity ["
                                                + filteringEntity + "].");
                            }
                        }
                    }
                }
                break;
            }
        }
        return filters;
    }

    public static FieldData getPkeyFilter(String entityName, Long pkeyValue,
                                          ActionContext ctx)
            throws CouldntGetEJBException, IncorrectEntityDescriptionException {
        String recordKeyName = EntityViewHelper.getPkeyID(entityName, ctx);
        return new TextboxFieldData(recordKeyName, String.valueOf(pkeyValue));
    }
}
