JavaScript Extension Development API for Oracle Visual Builder Cloud Service - Classic Applications

Source: operation/js/api/Operation.js

define([
    'module',
    'bop/js/EmptyCondition',
    'bop/js/api/operation/Pagination',
    'core/js/api/Listenable',
    'core/js/api/utils/ArrayUtils',
    'operation/js/api/OperationData',
    'operation/js/api/OperationResult'
], function(
        module,
        EmptyCondition,
        Pagination,
        Listenable,
        ArrayUtils,
        OperationData,
        OperationResult
    ) {

    'use strict';

    /**
     * An API object representing a single data operation.
     *
     * <p>
     * This object is useful for both roles, ABCS Business User and ABCS Developer.
     * <ul>
     *  <li>
     *      ABCS Developer needs to produce Operation instances to make his custom custom Business Object Provider (BOP) useful.
     *  </li>
     *  <li>
     *      ABCS Business User consumes these Operation instances and performs the operation using {@link operation/js/api/Operation#perform Operation.perform(..)} method.
     *  </li>
     * </p>
     *
     * @AbcsAPI stable
     * @version 17.1.1
     * @exports operation/js/api/Operation
     *
     * @constructor
     * @private
     *
     * @param {operation/js/api/Operation.Type} type - Type of the operation
     * @param {OperationInput} input - Description of what kind of input the operation accepts
     * @param {OperationOutput} output - Description of the output this operation returns
     * @param {function} functionToRun - Function performing the real REST call
     * @returns {Operation}
     *
     * @see {@link module:api/js/Operations Operations} to understand how to get certain registered operation and perform it.
     * @see {@link module:api/js/Operations.read Operations.read(..)}
     * @see {@link module:api/js/Operations.create Operations.create(..)}
     * @see {@link module:api/js/Operations.update Operations.update(..)}
     * @see {@link module:api/js/Operations.delete Operations.delete(..)}
     */
    var Operation = function(type, input, output, functionToRun) {
        AbcsLib.checkThis(this);
        Listenable.apply(this, arguments);

        this._type = type;
        this._functionToRun = functionToRun;
        this._input = input;
        this._output = output;
        this._operationProvider = undefined;
    };
    AbcsLib.extend(Operation, Listenable);

    Operation._LOGGER = Logger.get(module.id);
    Operation._MISSING_FUNCTION_TO_RUN = 'FunctionToRun is undefined. This operation can\'t be performed.';

    Operation.DATA_HAS_CHANGED = 'dataChanged';
    Operation.DATA_FULLY_LOADED = 'dataFullyLoaded';
    Operation.DATA_LOADING_FAILED = 'dataLoadingFailed';

    Operation.prototype.getId = function() {};

    Operation.prototype.getName = function() {};

    Operation.prototype.getDescription = function() {};

    Operation.prototype.getType = function() {
        return this._type;
    };

    Operation.prototype.getInput = function() {
        return this._input;
    };

    Operation.prototype.getOutput = function() {
        return this._output;
    };

    /**
     * Sets the owning {@link OperationProvider} for this Operation instance.
     *
     * <p>
     * BE AWARE:
     * This is an infrastructure method which is supposed to be called only by OperationProvider itself. The reason why
     * it's here is to avoid each Operation instance accepting OP instance within it's constructor. Since every method
     * that should possibly live inside Abcs DataModel should be registered using OP, this should be ensured. But if you
     * think about calling this method from somewhere else, it's very suspicious and you should consult such change first.
     * </p>
     *
     * @param {OperationProvider} provider
     */
    Operation.prototype.setOperationProvider = function(provider) {
        this._operationProvider = provider;
    };

    /**
     * Gets an instance of OperationProvider associated with this Operation.
     *
     * <p>
     * Allows client to access e.g. Authentication mechanism which is provided directly by OperationProvider itself.
     * </p>
     *
     * @returns {OperationProvider}
     */
    Operation.prototype.getOperationProvider = function() {
        return this._operationProvider;
    };

    Operation.prototype.setInputData = function(operationData) {
        this._operationData = operationData;
    };

    /**
     * Gets explicitly set {@link operation/js/api/OperationData OperationData} set to this <code>Operation</code>.
     *
     * @returns {operation/js/api/OperationData}
     */
    Operation.prototype.getInputData = function() {
        return this._operationData;
    };

    /**
     * Performs this operation.
     *
     * <p>
     * Each {@link operation/js/api/Operation#perform Operation.perform(..)} method call returns an instance of {@link operation/js/api/OperationResult OperationResult}.
     * It either:
     * <ul>
     *  <li>
     *      Provides returned data in case {@link operation/js/api/Operation Operation} perform correctly. <br/>
     *      It also can contain any other metadata which Business Object Provider decides to return. For example {@link operation/js/api/PaginationCursor PaginationCursor} can be available if the perfomed
     *      {@link operation/js/api/Operation Operation} has type {@link operation/js/api/Operation.Type.READ_MANY Operation.Type.READ_MANY} set and provider implemented capability to paginate through
     *      the resulted records.
     *  </li>
     *  <li>
     *      Provides error code, message and possibly any other additional error information in case {@link operation/js/api/Operation Operation} performed incorrectly.
     *  </li>
     * </ul>
     *
     * To check the result of {@link operation/js/api/Operation#perform Operation.perform(..)} call, you can use:
     * <ul>
     *  <li>
     *      {@link operation/js/api/OperationResult#isSuccess OperationResult.isSuccess()} to check if the call was performed correctly and resulted in a {@link operation/js/api/OperationResult.Success OperationResult.Success}.
     *  </li>
     *  <li>
     *      {@link operation/js/api/OperationResult#isFailure OperationResult.isFailure()} to check if the call was performed incorrectly and resulted in an {@link operation/js/api/OperationResult.Failure OperationResult.Failure}.
     *  </li>
     * </ul>
     * </p>
     *
     * @AbcsAPI stable
     * @version 15.4.5
     *
     * @see {@link operation/js/api/OperationResult OperationResult}
     * @see {@link operation/js/api/OperationResult.Failure OperationResult.Failure}
     * @see {@link operation/js/api/OperationResult.Success OperationResult.Success}
     * @see {@link operation/js/api/OperationResult#isFailure OperationResult.isFailure()}
     * @see {@link operation/js/api/OperationResult#isSuccess OperationResult.isSuccess()}
     *
     * @return {Promise<operation/js/api/OperationResult>} - Promise of the {@link operation/js/api/OperationResult OperationResult} for this operation.
     */
    Operation.prototype.perform = function(operationData) {
        var self = this;

        var inputData = self._operationData ? self._operationData : (operationData ? operationData : new OperationData());
        var query = inputData.getQuery();

        if (query) {
            return query.replaceLookups().then(function() {
                return self._perform(inputData);
            }, function() {
                // If replaceLookups() is rejected, it means that there is a string value which doesn't correspond
                // to any of the existing Lookup values and thus we can be sure that the operation will not return
                // any record
                return Promise.resolve(OperationResult.success([]));
            });
        } else {
            return self._perform(inputData);
        }
    };

    Operation.prototype._perform = function(inputData) {
        var self = this;
        if (self._functionToRun) {

            // This is a user function so might thrown an exception rather
            // than correctly return a promise.
            try {
                return self._functionToRun(inputData).then(function(result) {
                    self.fireEvent(Operation.DATA_FULLY_LOADED);
                    self._operationData = undefined;
                    return result;
                }).catch(self._onErrorHandler.bind(self));
            } catch (error) {
                return self._onErrorHandler(error);
            }
        } else {
            return Promise.reject(OperationResult.failure(Operation._MISSING_FUNCTION_TO_RUN));
        }
    };

    /**
     * Handle and log the given error object.
     *
     * @param {Error | String | OperationResult.Failure} error
     * @returns {Promise} A Promise that returns an OperationResult object
     *      representation of the input
     */
    Operation.prototype._onErrorHandler = function(error) {
        var self = this;

        self.fireEvent(Operation.DATA_LOADING_FAILED);
        self._operationData = undefined;

        Operation._LOGGER.error('Performing operation with ID = \'{0}\' failed.', self.getId());
        if (OperationResult.isFailureResult(error)) {
            var code = error.getCode();
            if (code) {
                Operation._LOGGER.error('Error code: ' + code);
            }

            var message = error.getMessage();
            if (message) {
                Operation._LOGGER.error('Error message: ' + message);
            }

            var additionalInfo = error.getAdditionalInfo();
            if (additionalInfo) {
                Operation._LOGGER.error('Additional info: ' + JSON.stringify(additionalInfo));
            }

            return Promise.reject(error);
        } else if (error && error.stack) {
            Operation._LOGGER.error('Stack trace: ' + error.stack);
            // Intentionally expose failure to the user to aid diagnostics
            return Promise.reject(OperationResult.failure(
                    AbcsLib.i18n('operation.js.api.errorInPerform', error.stack)));
        } else {
            return Promise.reject(OperationResult.failure(
                    error ? AbcsLib.i18n('operation.js.api.errorInPerform', error)
                        : AbcsLib.i18n('operation.js.api.unidentifiableErrorInPerform')));
        }
    };

    /**
     * Gets all fields that are available for simple filtering.
     *
     * <p>
     * These are all fields which are optional in addition to mandatory fields and supports Operator.CONTAINS required by the UI logic.
     * </p>
     *
     * @returns {entity/js/api/Property[]}
     */
    Operation.prototype.getFilterableFields = function() {
        var result = [];
        if (this.getType() === Operation.Type.READ_MANY) {
            var operationInput = this.getInput();
            if (operationInput) {
                var queryDesc = operationInput.getQuery();
                if (queryDesc) {
                    var queryParams = queryDesc.getQueryParameters();
                    if (queryParams && queryParams.length > 0) {
                        queryParams.forEach(function(queryParam) {
                            if (queryParam.isSimpleFilterable()) {
                                result.push(queryParam.getProperty());
                            }
                        });
                    }
                }
            }
        }
        return result;
    };

    /**
     * Checks whether this {@link Operation} is working on the given entity or not.
     *
     * <p>
     * That means, if the operation is either returning data of the entity or accepting
     * an entity as a parameter, then this method return true.
     * </p>
     *
     * @param {Entity} entity
     * @returns {Boolean}
     */
    Operation.prototype.isWorkingOn = function(entity) {
        return this.hasOutputData(entity) || this.hasInputData(entity);
    };

    Operation.prototype.hasInputData = function(entity) {
        if (entity === undefined) {
            return this._input.getData().length > 0;
        }
        var entityID = entity.getId();
        return this._input && entityID.length > 0 && entityID === this._input.getData();
    };

    Operation.prototype.hasQuery = function() {
        return (this._input && this._input.getQuery());
    };

    /**
     * Checks whether this operation supports the given {@link operation/js/api/OperationData OperationData} or not.
     *
     * @param {operation/js/api/OperationData} operationData
     * @returns {Boolean} true if this operation supports the given {@link operation/js/api/OperationData OperationData}, false otherwise.
     */
    Operation.prototype.supports = function(operationData) {
        switch (this._type) {
            case Operation.Type.READ_MANY:
                return this.supportsQuery(operationData.getQuery());
            case Operation.Type.CREATE:
            case Operation.Type.UPDATE:
            case Operation.Type.DELETE:
                var missingValues = this.getMissingValues(operationData.getData());
                if (missingValues.length > 0) {
                    return false;
                }
                return true;

            // Read-one operation can possibly have some required parameters as well. This is not exposed to Dave at the moment but when it's gonna be,
            // should these be an instance of Condition as for READ_MANY or is it just record object as in case of CREATE/UPDATE/DELETE ??
            case Operation.Type.READ_ONE:
            default:
                return true;
        }
    };

    Operation.prototype.getMissingValues = function(record) {
        // This is sort of messy as Query object actually holds an instances of QueryDescription and QueryParameterDescription and these are holding information
        // about whether certain property is required or not. It would make sense to split implementation of {@link data/js/api/OperationInput OperationInput}
        // at some point and create two child implemenations of OperationInput. That way we could let only "ReadOperationInput.js" to hold Query object. And
        // similarly something like "NonReadOperation.js" could hold some array of mandatory values which would be much more simplier than QueryParameterDescription
        // itself which holds much more information that are in fact required only for READ-type of Operations.
        var missingValues = [];

        var input = this.getInput();
        var queryDesc = input ? input.getQuery() : undefined;
        if (queryDesc) {
            var requiredParams = queryDesc.getRequiredQueryParameters();
            for (var i = 0; i < requiredParams.length; i++) {
                var queryParamDesc = requiredParams[i];
                var propertyID = queryParamDesc.getPropertyID();

                // If at least one required parameter is missing value in the given "record", this is unsupported operation
                if (record[propertyID] === undefined) {
                    missingValues.push(propertyID);
                }
            }
        }
        return missingValues;
    };

    /**
     * Checks whether this operation works over the given {@link Query}.
     *
     * <p>
     * If the given Condition contains
     * </p>
     *
     * @param {Query} query
     * @returns {Boolean}
     */
    Operation.prototype.supportsQuery = function(query) {
        var input = this.getInput();
        var currentQueryDesc = input ? input.getQuery() : undefined;

        var condition = query && query.getCondition();
        if (condition && !(condition instanceof EmptyCondition)) {
            var conditions = query.getQueryParameters();
            var conditionCounts = {}; // Map of <PropertyID, Number> where number is the count of all SimpleCondition's working over the PropertyID

            // Some Condition were passed but this Operation don't support any input
            if (!currentQueryDesc) {
                return false;
            }

            for (var i = 0; i < conditions.length; i++) {
                var simpleCondition = conditions[i];
                var fieldPath = simpleCondition.getFieldPath();
                var fieldPathId = fieldPath.getId();
                var operator = simpleCondition.getOperator();

                // Increase counter of condition numbers
                if (conditionCounts[fieldPathId]) {
                    conditionCounts[fieldPathId]++;
                } else {
                    conditionCounts[fieldPathId] = 1;
                }

                var queryParameterExists = currentQueryDesc.getQueryParameter(fieldPath);

                // If there is no description parameter for this Condition, this Operation can't support such input
                if (!queryParameterExists) {
                    // BUFP-12626:
                    // Remove this hack and code it properly if the direction change happened again and we get back to this code
                    if (fieldPathId.indexOf('.') > 0) {
                        // short term workaround to allow custom code to take advantage of "Framework Version 2" and search by properties of children objects
                        // where child property ID consist of child BO and property separated by dot, for example: "types.PartnerTypeCode" see. "Framework Version 2" at
                        // <http://docs.oracle.com/middleware/12212/adf/develop/GUID-8F85F6FA-1A13-4111-BBDB-1195445CB630.htm#ADFFD-GUID-9F6BC67D-453E-4E97-B397-DC662A4D4325>
                    } else {
                        return false;
                    }
                } else {
                    // Or even if the parameter description exists but the given Operator is unsupported
                    var supportedOperators = queryParameterExists.getSupportedOperators();
                    if (!ArrayUtils.contains(supportedOperators, operator)) {
                        return false;
                    }
                }
            }

            if (!currentQueryDesc.isQueryByIDs() && !currentQueryDesc.isQueryByAnything()) {
                // Map of <PropertyID, Number> where number is the count of all QueryParameters defined in this Operation for the corresponding PropertyID
                var queryParameterCounts = {};
                var queryParameterProperties = currentQueryDesc.getProperties();
                for (var k = 0; k < queryParameterProperties.length; k++) {
                    var queryParamProperty = queryParameterProperties[k];
                    var queryParamID = queryParamProperty.getId();

                    // Increase counter of query parameter numbers
                    if (queryParameterCounts[queryParamID]) {
                        queryParameterCounts[queryParamID]++;
                    } else {
                        queryParameterCounts[queryParamID] = 1;
                    }
                }

                // Let's check whether more SimpleConditions working over the same property were passed but the Operation can handle only one
                for (var key in conditionCounts) {
                    if (conditionCounts.hasOwnProperty(key)) {
                        var conditionNumber = conditionCounts[key];
                        var queryParameterCount = queryParameterCounts[key];

                        // If there is at least one occurence where number of Conditions is higher than
                        // what's defined in the description this operation can't handle the given input
                        if (conditionNumber > queryParameterCount) {
                            return false;
                        }
                    }
                }
            }
            return true;
        } else {
            if (currentQueryDesc && currentQueryDesc.hasRequiredQueryParameter()) {
                // If current Query has some required parameter, it's obviously missing
                return false;
            } else {
                // But otherwise all are optional, so we definitely support Condition.EMPTY
                return true;
            }
        }
    };

    Operation.prototype.hasOutputData = function(entity) {
        if (entity === undefined) {
            var output = this._output;
            var outputEntityID = output.getData();
            return outputEntityID.length > 0 && !output.isBlank();
        }
        var entityID = entity.getId();
        return this._output && entityID.length > 0 && entityID === this._output.getData();
    };

    /**
     * Gets the {@link bop/js/api/operation/Pagination Pagination} metadata information set for this <code>Operation</code>.
     *
     * @returns {bop/js/api/operation/Pagination}
     */
    Operation.prototype.getPagination = function() {
        return this._input ? this._input.getPagination() : Pagination.NONE;
    };

    /**
     * Represents behavior classification of single {@link operation/js/api/Operation Operation} instance.
     *
     * <p>
     * Type of the {@link operation/js/api/Operation Operation} is the most important information that Abcs needs to understand
     * in order to see the logical purpose of the operation itself. The information is used to make the action accessible on
     * appropriate places within Abcs UI.
     * </p>
     *
     * <p>
     * <ul>
     *  <li>
     *    If your operation is expected to be available for retrieving collection data, it needs to have {@link operation/js/api/Operation.Type.READ_MANY Operation.Type.READ_MANY}
     *    set. That makes it available for example when client drops the Table into an empty page.
     *  </li>
     *  <li>
     *    If you would also like to have Create page available for such table, you need to provide operation with {@link operation/js/api/Operation.Type.CREATE Operation.Type.CREATE}
     *    type which gives Abcs an understanding of what to perform when "Create" button is being clicked.
     *  </li>
     *  <li>
     *    If you would also like to have Delete action available for each Collection record, you need to provide operation with
     *    {@link operation/js/api/Operation.Type.DELETE Operation.Type.DELETE} type which gives Abcs an understanding of what to perform when "Delete" item is being clicked.
     *  </li>
     *  <li>
     *    If you would like to have also Edit/Detail pages available for the selected Collection record, you will need to implement
     *    operation with {@link operation/js/api/Operation.Type.READ_ONE Operation.Type.READ_ONE} type and with special type set to {@link operation/js/api/Operation.SpecialType.QUERY_BY_ID QUERY_BY_ID}
     *    which gives Abcs a possibility to read whole record. It also allows the infrastructure to restore bookmarked Edit/Detail page.<br/>
     *
     *    To make Edit page working correctly, you also need to provide operation with {@link operation/js/api/Operation.Type.UPDATE Operation.Type.UPDATE} type which gives Abcs an
     *    understanding of what to perform when either Edit or Detail button is being clicked.
     *  </li>
     *  <li>
     *    If you would also like to provide an Action-like operation which performs certain business rule (e.g. Fire an employee, Raise Salary etc.),
     *    {@link operation/js/api/Operation.Type.PERFORM Operation.Type.PERFORM} should be used. Although, please be aware that these are unsupported by ABCS UI at the moment and because of that, no
     *    functionality is guaranteed by ABCS itself.
     *  </li>
     * <ul>
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     * @enum {String}
     */
    Operation.Type = {

        /**
         * Operation that reads a single record.
         */
        READ_ONE: 'RETRIEVE_ONE',
        /**
         * Operation that reads multiple records.
         */
        READ_MANY: 'RETRIEVE_MANY',
        /**
         * Operation that creates a single record.
         */
        CREATE: 'CREATE_ONE',
        /**
         * Operation that updates a single record.
         */
        UPDATE: 'UPDATE_ONE',
        /**
         * Operation that deletes a single record.
         */
        DELETE: 'DELETE_ONE',
        /**
         * Operation that performs non-CRUD action.
         */
        PERFORM: 'PERFORM'
    };

    /**
     * Represents a special kind of an {@link operation/js/api/Operation Operation}.
     *
     * <p>
     * Special type is relevant only in cases of {@link operation/js/api/Operation Operation} instance with standard type set to
     * {@link operation/js/api/Operation.Type.READ_MANY Operation.Type.READ_MANY} or {@link operation/js/api/Operation.Type.READ_ONE Operation.Type.READ_ONE}.
     * For all other {@link operation/js/api/Operation.Type Operation.Type}s, the value is ignored and resulted behavior is not guaranteed by ABCS.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     * @enum {String}
     *
     * @see {@link entity/js/api/Entity Entity}
     */
    Operation.SpecialType = {

        /**
         * Marks an {@link operation/js/api/Operation Operation} capable to retrieve single record by ID.
         *
         * <p>
         * Having an {@link operation/js/api/Operation Operation} instance marked with this special type has strong impact
         * on the resulted capabilities of the {@link entity/js/api/Entity Entity} which supports such operation:
         * <ul>
         *  <li>
         *      If an {@link entity/js/api/Entity Entity} provides {@link operation/js/api/Operation Operation} of type {@link operation/js/api/Operation.SpecialType.QUERY_BY_ID QUERY_BY_ID},
         *      it allows ABCS UI to make Edit/Detail pages available for that {@link entity/js/api/Entity Entity}. This is because ABCS needs to be able to fetch only particular record when
         *      you click on Edit/Detail action in the table itself.
         *  </li>
         *  <li>
         *      Also Edit/Detail pages of that {@link entity/js/api/Entity Entity} will become automatically bookmarkable. This is caused by the fact that later ABCS needs to be able to
         *      retrieve opened record from bookmarked URL. Since the URL contains ID of the record, ABCS reads the record automatically by using the {@link operation/js/api/Operation Operation}
         *      marked with special type {@link operation/js/api/Operation.SpecialType.QUERY_BY_ID QUERY_BY_ID}.
         *  </li>
         * </ul>
         * </p>
         *
         * <p>
         * Please be aware, that only one {@link operation/js/api/Operation Operation} of type {@link operation/js/api/Operation.SpecialType.QUERY_BY_ID QUERY_BY_ID}
         * is expected for each {@link entity/js/api/Entity Entity}. If your BOP provides more {@link operation/js/api/Operation Operation}s of this type, the
         * behavior is not guaranteed by ABCS.
         * </p>
         *
         * @AbcsExtension stable
         * @version 17.1.1
         * @type {String}
         */
        QUERY_BY_ID: 'queryByID',

        /**
         * Marks an {@link operation/js/api/Operation Operation} capable to retrieve many records using the given list of IDs.
         *
         * <p>
         * Includes a capability for ABCS to fetch many records given their IDs. Such {@link operation/js/api/Operation Operation} is used when
         * {@link entity/js/api/Entity Entity} which provides this {@link operation/js/api/Operation Operation} is being referenced from other
         * {@link entity/js/api/Entity Entity}. If there is a table of referencing {@link entity/js/api/Entity Entity} and the table has visible
         * column which correspond to the referenced {@link entity/js/api/Entity Entity}, operation of type {@link operation/js/api/Operation.SpecialType.QUERY_BY_IDS QUERY_BY_IDS}
         * will be used to fetch all referenced values needed for the table view. And it will be done effectively in one call.
         * </p>
         *
         * <p>
         * To illustrate on an example:
         * <ul>
         *  <li>
         *      ABCS application consists of two Entities: Employee and Department
         *  </li>
         *  <li>
         *      Department contains few {@link entity/js/api/Property Properties}: Id (KEY), Name (TEXT)<br/>
         *      Name could be string like 'Sales', 'Department' etc.
         *  </li>
         *  <li>
         *      Employee contains few {@link entity/js/api/Property Properties}: Id (KEY), Firstname (TEXT), Lastname (TEXT), Age (NUMBER)
         *  </li>
         *  <li>
         *      Each Employee also has it's Department assigned: refToEmployee (REFERENCE)
         *  </li>
         * </ul>
         *
         * Now assume that ABCS client want to have an Employee table on the Home page and the table is configured to show Firstname, Lastname and
         * Department-Name columns. If client opens Home page, we need to fetch all Employees and for each record fetch assigned Department to be
         * able show Department name instead of just ID. To make this effective, ABCS looks for {@link operation/js/api/Operation Operation} of type
         * {@link operation/js/api/Operation.SpecialType.QUERY_BY_IDS QUERY_BY_IDS} and use that one if available. If it's not provided by the referred
         * {@link entity/js/api/Entity Entity} (Department in this case), ABCS attempts to do the same using {@link operation/js/api/Operation Operation}
         * of type {@link operation/js/api/Operation.SpecialType.QUERY_BY_ID QUERY_BY_ID} which is obviously much less effective as one call needs to be
         * done per each visible row.
         * </p>
         *
         * <p>
         * Please be aware, that only one {@link operation/js/api/Operation Operation} of type {@link operation/js/api/Operation.SpecialType.QUERY_BY_IDS QUERY_BY_IDS}
         * is expected for each {@link entity/js/api/Entity Entity}. If your BOP provides more {@link operation/js/api/Operation Operation}s of this type, the behavior
         * is not guaranteed by ABCS.
         * </p>
         *
         * @AbcsExtension stable
         * @version 17.1.1
         * @type {String}
         */
        QUERY_BY_IDS: 'queryByIDs',

        /**
         * Marks an {@link operation/js/api/Operation Operation} capable of handling any kind of input {@link operation/js/api/Condition Condition}.
         *
         * <p>
         * Having an {@link operation/js/api/Operation Operation} of this special type available gives ABCS an opportunity to query any kind of data.
         * This is used for ABCS UIs like "Advanced Search" or "Default Query" which, thanks to having this {@link operation/js/api/Operation Operation}
         * available, can give ABCS user much more flexibility on how to visually build the query.
         * </p>
         *
         * <p>
         * Please be aware that by registering the {@link operation/js/api/Operation Operation} of this type, you're obligating that the operation function
         * will handle all possible combinations of AND/OR {@link module:bop/js/api/operation/RelationOperator RelationOperator}s between unlimited number of parameters.
         * If that's not true, ABCS UI might allow final client to setup query which is actually unsupported by your BOP.
         * </p>
         *
         * <p>
         * To illustrate on an example:
         *
         * <ul>
         *  <li>
         *      Assume ABCS application contains Employee {@link entity/js/api/Entity Entity}.
         *  </li>
         *  <li>
         *      Employee contains few {@link entity/js/api/Property Properties}: Id (KEY), Firstname (TEXT), Lastname (TEXT), Age (NUMBER)
         *  </li>
         *  <li>
         *      Employee also provides an {@link operation/js/api/Operation Operation} of type {@link operation/js/api/Operation.SpecialType.QUERY_BY_ANYTHING QUERY_BY_ANYTHING}.
         *  </li>
         * </ul>
         *
         * Now assume that ABCS client wants to have an Employee table on the Home page and opens "Default Query" editor. Since {@link operation/js/api/Operation Operation} of type
         * {@link operation/js/api/Operation.SpecialType.QUERY_BY_ANYTHING QUERY_BY_ANYTHING} is available, ABCS allows client to Add/Remove any number of query rows. It also allows
         * client to set any {@link module:operation/js/api/Operator Operator}s which is by default supported by ABCS. Because the client is capable to make very various configurations
         * and because he always expects from ABCS that his application is going to work, the underlying function making the real call has to be capable to manage all these possible
         * configurations. Otherwise client app will became broken.
         * </p>
         *
         * <p>
         * Please be aware, that only one {@link operation/js/api/Operation Operation} of type {@link operation/js/api/Operation.SpecialType.QUERY_BY_ANYTHING QUERY_BY_ANYTHING}
         * is expected for each {@link entity/js/api/Entity Entity}. If your BOP provides more {@link operation/js/api/Operation Operation}s of this type, the behavior is not
         * guaranteed by ABCS.
         * </p>
         *
         * @AbcsExtension stable
         * @version 17.1.1
         * @type {String}
         */
        QUERY_BY_ANYTHING: 'queryByAnything'
    };

    return Operation;

});