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

Source: bop/js/api/operation/OperationInput.js

define([
    'core/js/api/utils/StringUtils',
    'data/js/api/OperationInput',
    'entity/js/api/Property',
    'operation/js/api/Operation',
    'operation/js/api/Operator',
    'operation/js/query/QueryDescription',
    'operation/js/query/QueryDescriptionFactory',
    'operation/js/query/QueryParameterDescription'
], function (
        StringUtils,
        OperationInputImpl,
        Property,
        Operation,
        Operator,
        QueryDescription,
        QueryDescriptionFactory,
        QueryParameterDescription
    ) {

    'use strict';

    /**
     * Builds operation input instances..
     *
     * <p>
     * What's being build here needs to be passed into {@link bop/js/api/operation/OperationBuilder#takes OperationBuilder.takes(..)} method
     * creating the final {@link operation/js/api/Operation Operation} instance which is the base building block for creating custom BOP.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     * @exports bop/js/api/operation/OperationInput
     * @constructor
     *
     * @param {Object} params - Object literal with all possible parameters.
     * @param {String} params.entity - {@link entity/js/api/Entity Entity} whose {@link entity/js/api/Property Properties} the build {@link operation/js/api/Operation operation} accepts on it's input.
     *
     * @see {@link entity/js/api/Entity Entity}
     * @see {@link module:operation/js/api/Operator Operator}
     * @see {@link operation/js/api/Operation Operation}
     * @see {@link bop/js/api/operation/OperationBuilder OperationBuilder}
     * @see {@link bop/js/api/operation/OperationBuilder#takes OperationBuilder.takes(..)}
     *
     * @example
     * <caption>
     *  Creates minimalistic instance of {@link bop/js/api/operation/OperationInput OperationInput} with only input {@link entity/js/api/Entity Entity} specified.
     * </caption>
     *
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var input = new OperationInput({
     *     entity: employee
     * });
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} with two explicit parameters specified.
     * </caption>
     *
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var firstname = employee.getProperty('Firstname');
     * var lastname = employee.getProperty('Lastname');
     * var input = new OperationInput({
     *     entity: employee
     * }).parameter(firstname).
     *     parameter(lastname);
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} accepting parameters for all {@link entity/js/api/Property properties}
     *  defined inside of the passed {@link entity/js/api/Entity entity}.
     * </caption>
     *
     * // Assuming three properties: "Firstname", "Lastname" and "Age"
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var input = new OperationInput({
     *     entity: employee
     * }).parametersAll();
     */
    var OperationInput = function(params) {
        AbcsLib.checkThis(this);
        AbcsLib.checkDefined(params, 'params', 'You need to pass object literal with "entity" attribute set');
        AbcsLib.checkParameterCount(arguments, 1);
        AbcsLib.checkObjectLiteral(params, ['entity'], ['entity']);

        this._entity = params.entity;
        this._explicitParams = [];
        this._operatorsMap = {};
        this._defaultOperators = [Operator.EQUALS];
    };

    /**
     * Sets the default {@link bop/js/api/operation/OperationInput.Type OperationInput.Type}. It will be automatically used for
     * all parameters that don't have explicitely specified any other {@link bop/js/api/operation/OperationInput.Type parameter type}.
     *
     * @param {bop/js/api/operation/OperationInput.Type} parameterType
     * @returns {bop/js/api/operation/OperationInput} a reference to this object to allow method chaining
     *
     * @deprecated Please use parameter with "additionalInfo" attribute, to pass custom definition values, instead
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} with two parameters specified.
     *  Both of them inherits default parameter type value set by {@link bop/js/api/operation/OperationInput#parameterType parameterType(..)} method.
     * </caption>
     *
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var firstname = employee.getProperty('Firstname');
     * var lastname = employee.getProperty('Lastname');
     * var input = new OperationInput({
     *     entity: employee
     * }).parameterType(OperationInput.Type.PATH).  // Sets default parameter type
     *     parameter(firstname).                    // Nothing explicitly set, default PATH parameter type is used
     *     parameter(lastname);                     // Nothing explicitly set, default PATH parameter type is used
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} with two parameters specified.
     *  One of them is explicitly specified, the second one inherits default value set by {@link bop/js/api/operation/OperationInput#parameterType parameterType(..)} method.
     * </caption>
     *
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var firstname = employee.getProperty('Firstname');
     * var lastname = employee.getProperty('Lastname');
     * var input = new OperationInput({
     *     entity: employee
     * }).parameterType(OperationInput.Type.PATH).        // Sets default parameter type
     *     parameter({
     *         property: firstname,
     *         parameterType: OperationInput.Type.QUERY)  // Override default parameter type with explicitly set value (QUERY)
     *     }).
     *     parameter(lastname);                           // Nothing explicitly set, default PATH parameter type is used
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} for all entity {@link entity/js/api/Property properties}.
     *  All of those {@link entity/js/api/Property properties} inherits default parameter type value set by {@link bop/js/api/operation/OperationInput#parameterType parameterType(..)} method.
     * </caption>
     *
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var input = new OperationInput({
     *     entity: employee
     * }).parametersAll().                           // Nothing explicitly set, default PATH parameter type will be used for all properties
     *     parameterType(OperationInput.Type.PATH);  // Sets default parameter type
     */
    OperationInput.prototype.parameterType = function(parameterType) {
        AbcsLib.checkDefined(parameterType, 'parameterType');
        AbcsLib.checkParameterCount(arguments, 1);
        AbcsLib.checkDataType(parameterType, AbcsLib.Type.STRING);

        if (parameterType !== OperationInput.Type.PATH &&
            parameterType !== OperationInput.Type.QUERY &&
            parameterType !== OperationInput.Type.HEADER) {
            throw new Error('Given value (= ' + parameterType + ') doesn\'t correspond to any know OperationInput.Type constant and can\'t be handled by ABCS.');
        }

        this._parameterType = parameterType;
        return this;
    };

    /**
     * Includes single parameter into the {@link bop/js/api/operation/OperationInput OperationInput} configuration.
     *
     * <p>
     * You can either pass {@link entity/js/api/Property Property} instance directly or object literal which allows you to customize parameter description more
     * precisely. Few examples:
     * <ul>
     *  <li>
     *      If your parameter can handle different set of {@link module:operation/js/api/Operator Operator}s, you can include "operators" parameter into your object literal listing all available values.
     *  </li>
     *  <li>
     *      If your parameter is required, you can specify that by passing "required" attribute with value <code>true</code>.
     *  </li>
     *  <li>
     *      If you want to mark your parameter with "PATH" or "QUERY" constant which allow you later to process them easily in your {@link bop/js/spi/operation/ConditionVisitor ConditionVisitor} implementation,
     *      you can use "additionalInfo" parameter and place all custom values there.
     *  </li>
     * <ul>
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @see {@link entity/js/api/Property Property}
     * @see {@link module:operation/js/api/Operator Operator}
     * @see {@link bop/js/api/operation/OperationInput#operators OperationInput.operators(..)}
     * @see {@link bop/js/api/operation/OperationInput#operatorsForType OperationInput.operatorsForType(..)}
     *
     * @param {Property | Object} params - Either {@link entity/js/api/Property Property} instance directly or an Object literal with customized parameters.
     * @param {entity/js/api/Property} params.property - {@link entity/js/api/Property Property} this parameter is working with.
     * @param {Boolean} [params.required] - <code>true</code> if this parameter is required, <code>false</code> otherwise.
     * @param {module:operation/js/api/Operator[]} [params.operators] - Array of {@link module:operation/js/api/Operator Operator}s supported by this parameter.
     * @param {Object} [params.additionalInfo] - Additional information required for processing this parameter further.
     * @returns {bop/js/api/operation/OperationInput} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} with two explicit parameters specified.
     * </caption>
     *
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var firstname = employee.getProperty('Firstname');
     * var lastname = employee.getProperty('Lastname');
     * var input = new OperationInput({
     *     entity: employee
     * }).parameter(firstname).
     *     parameter(lastname);
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} with two parameters and their supported
     *  {@link module:operation/js/api/Operator operator}s explicitly specified. Also includes additional information which marks
     *  this parameters with 'PATH' constant. That allows us to process such parameter correctly in {@link bop/js/spi/operation/ConditionVisitor ConditionVisitor} implementation.
     * </caption>
     *
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var firstname = employee.getProperty('Firstname');
     * var lastname = employee.getProperty('Lastname');
     * var input = new OperationInput({
     *     entity: employee
     * }).parameter({
     *     property: firstname,
     *     operators: [Operator.EQUALS, Operator.NOT_EQUALS],
     *     additionalInfo: {
     *         type: 'PATH'
     *     }
     * }).parameter({
     *     property: lastname,
     *     operators: [Operator.EQUALS, Operator.NOT_EQUALS],
     *     additionalInfo: {
     *         type: 'PATH'
     *     }
     * });
     */
    OperationInput.prototype.parameter = function(params) {
        AbcsLib.checkDefined(params, 'params', 'You need to pass either Property instance of Object literal with at least "property" attribute set.');
        AbcsLib.checkParameterCount(arguments, 1);

        // If no special configuration is needed, client is allowed to simply pass property instance directly
        var self = this;
        var property;
        if (AbcsLib.isInstanceof(params, Property)) {
            property = params;
        } else {
            AbcsLib.checkObjectLiteral(params, ['property', 'operators', 'parameterType', 'additionalInfo', 'required'], ['property']);

            property = params.property;
        }

        // Check if the given Property really exist in the configured input Entity
        var exists = false;
        var propertyID = property.getId();
        var properties = self._entity.getProperties();
        for (var i = 0; i < properties.length; i++) {
            var existingProperty = properties[i];
            if (existingProperty.getId() === propertyID) {
                exists = true;
                break;
            }
        }

        // Accepting parameter which is not inside of the given Entity is not allowed
        if (!exists) {
            throw new Error('Property (id = ' + propertyID + ') passed into the parameter(..) method doesn\'t exist in the configured input Entity (id = ' + self._entity.getId() + ' )');
        }

        this._explicitParams.push(new OperationInput.Parameter(
            property,
            params.operators,
            params.parameterType,
            params.additionalInfo,
            params.required
        ));
        return this;
    };

    /**
     * Configure this {@link bop/js/api/operation/OperationInput OperationInput} so it accepts all {@link entity/js/api/Entity entity}
     * {@link entity/js/api/Property properties} on the resulted {@link operation/js/api/Operation operation} input.
     *
     * <p>
     * Same effect can be accomplised by calling {@link bop/js/api/operation/OperationInput#parameter OperationInput.parameter(..)}
     * multiple times for each {@link entity/js/api/Property Property}. But if your {@link entity/js/api/Entity entity} is more complex
     * and your {@link operation/js/api/Operation operation} accepts all of it's {@link entity/js/api/Property properties} on input, this
     * method might be handy.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @see {@link bop/js/api/operation/OperationInput#parameter OperationInput.parameter(..)}
     * @see {@link bop/js/api/operation/OperationInput#operators OperationInput.operators(..)}
     * @see {@link bop/js/api/operation/OperationInput#operatorsForType OperationInput.operatorsForType(..)}
     *
     * @returns {bop/js/api/operation/OperationInput} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} accepting all {@link entity/js/api/Property properties}
     *  defined inside of the passed {@link entity/js/api/Entity entity}.
     * </caption>
     *
     * // Assuming Employee entity contains several different properties: Firstname, lastname, age, ..
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var input = new OperationInput({
     *     entity: employee
     * }).parametersAll();
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} accepting all {@link entity/js/api/Property properties}
     *  defined inside of the passed {@link entity/js/api/Entity entity}.
     *  Setting different sets of {@link module:operation/js/api/Operator operators} with respect to the actual {@link entity/js/api/PropertyType PropertyType}s.
     * </caption>
     *
     * // Assuming Employee entity contains several different properties: Firstname, lastname, age, ..
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var textOperators = [
     *     Operator.EQUALS,
     *     Operator.NOT_EQUALS,
     *     Operator.CONTAINS,
     *     Operator.NOT_CONTAINS,
     *     Operator.STARTS_WITH,
     *     Operator.ENDS_WITH
     * ];
     * var numberOperators = [
     *     Operator.EQUALS,
     *     Operator.NOT_EQUALS,
     *     Operator.LESS,
     *     Operator.LESS_OR_EQUAL,
     *     Operator.MORE,
     *     Operator.MORE_OR_EQUAL
     * ];
     * var input = new OperationInput({
     *     entity: employee
     * }).parametersAll().
     *     operatorsForType(PropertyType.TEXT, textOperators).     // Setting Operators set for all TEXT-based properties
     *     operatorsForType(PropertyType.NUMBER, numberOperators); // Setting Operators set for all NUMBER-based properties
     */
    OperationInput.prototype.parametersAll = function() {
        AbcsLib.checkParameterCount(arguments, 0);

        this._allParameters = true;
        return this;
    };

    /**
     * Sets default set of {@link module:operation/js/api/Operator Operators} applied to every {@link entity/js/api/Property Property} of all
     * {@link entity/js/api/PropertyType PropertyType}s.
     *
     * <p>
     * This will be used if neither explicit configuration nor {@link entity/js/api/PropertyType PropertyType}-sensitive operators configuration
     * had been set to override these defaults.
     * </p>
     *
     * <p>
     * Abcs UI is automatically adapted with respect to the available {@link module:operation/js/api/Operator operators}. For example if only Equals
     * and Contains operators are available, only those are going to be shown to the end-user in Advanced Search UI.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @see {@link entity/js/api/Property Property}
     * @see {@link entity/js/api/PropertyType PropertyType}
     * @see {@link module:operation/js/api/Operator Operator}
     * @see {@link bop/js/api/operation/OperationInput#operatorsForType OperationInput.operatorsForType(..)}
     *
     * @param {module:operation/js/api/Operator[]} operators
     * @returns {bop/js/api/operation/OperationInput} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} accepting all {@link entity/js/api/Property properties}
     *  defined inside of the passed {@link entity/js/api/Entity entity}.<br/>
     *  All parameters are capable to process the same set of {@link module:operation/js/api/Operator operators} (Equals,
     *  Contains, Starts-with, Ends-with) with no difference based on actual {@link entity/js/api/PropertyType PropertyType}.
     * </caption>
     *
     * // Assuming three properties: Firstname (PropertyType.TEXT), Lastname (PropertyType.TEXT) and Age (PropertyType.NUMBER)
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var operators = [Operator.EQUALS, Operator.NOT_EQUALS];
     * var input = new OperationInput({
     *     entity: employee
     * }).parametersAll().
     *     operators(operators);
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} accepting all {@link entity/js/api/Property properties}
     *  defined inside of the passed {@link entity/js/api/Entity entity}.<br/>
     *  This time all parameters of {@link module:entity/js/api/PropertyType.TEXT PropertyType.TEXT} are capable to process extended set of {@link module:operation/js/api/Operator operators}
     *  than the default one which is used for all other {@link entity/js/api/PropertyType PropertyType}.
     * </caption>
     *
     * // Assuming three properties: Firstname (PropertyType.TEXT), Lastname (PropertyType.TEXT) and Age (PropertyType.NUMBER)
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var operators = [Operator.EQUALS, Operator.NOT_EQUALS];
     * var textOperators = [
     *     Operator.EQUALS,
     *     Operator.NOT_EQUALS,
     *     Operator.CONTAINS,
     *     Operator.NOT_CONTAINS
     * ];
     * var input = new OperationInput({
     *     entity: employee
     * }).parametersAll().
     *     operators(operators).                                // General operators are used for "Age" property as it had no closer specification
     *     operatorsForType(PropertyType.TEXT, textOperators);  // TEXT-based operators are used for "Firstname" and "Lastname" properties
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} accepting all {@link entity/js/api/Property properties}
     *  defined inside of the passed {@link entity/js/api/Entity entity}.<br/>
     *  This time all parameters of {@link module:entity/js/api/PropertyType.TEXT PropertyType.TEXT} are capable to process extended set of {@link module:operation/js/api/Operator operators}
     *  than the default one. Plus "Firstname" {@link entity/js/api/Property Property} supports completely customized {@link module:operation/js/api/Operator Operator} set.
     * </caption>
     *
     * // Assuming three properties: Firstname (PropertyType.TEXT), Lastname (PropertyType.TEXT) and Age (PropertyType.NUMBER)
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var firstname = employee.getProperty('Firstname');
     * var operators = [Operator.EQUALS, Operator.NOT_EQUALS];
     * var textOperators = [
     *     Operator.EQUALS,
     *     Operator.NOT_EQUALS,
     *     Operator.CONTAINS,
     *     Operator.NOT_CONTAINS
     * ];
     * var firstnameOperators = [
     *     Operator.EQUALS,
     *     Operator.NOT_EQUALS,
     *     Operator.CONTAINS,
     *     Operator.NOT_CONTAINS,
     *     Operator.STARTS_WITH,
     *     Operator.ENDS_WITH
     * ];
     * var input = new OperationInput({
     *     entity: employee
     * }).parametersAll().
     *     parameter({
     *         property: firstname,
     *         operators: firstnameOperators                    // Concrete operator set is used for "Firstname" property
     *     })
     *     operators(operators).                                // General operator set is used for "Age" property as it had no closer specification
     *     operatorsForType(PropertyType.TEXT, textOperators);  // TEXT-based operator set is used for "Lastname" property as it's most specifc configuration
     */
    OperationInput.prototype.operators = function(operators) {
        AbcsLib.checkDefined(operators, 'operators');
        AbcsLib.checkParameterCount(arguments, 1);
        AbcsLib.checkDataType(operators, AbcsLib.Type.ARRAY);

        this._defaultOperators = operators;
        return this;
    };

    /**
     * Sets default set of {@link module:operation/js/api/Operator operators} applied to every {@link entity/js/api/Property property}
     * of the given {@link entity/js/api/PropertyType property type} if no explicit configuration had been set to override these defaults.
     *
     * <p>
     * Abcs UI is automatically adapted with respect to the available {@link module:operation/js/api/Operator operators}. For example if only Equals
     * and Contains operators are available, only those are going to be shows to the end-user in Advanced Search UI.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @see {@link entity/js/api/Property Property}
     * @see {@link entity/js/api/PropertyType PropertyType}
     * @see {@link module:operation/js/api/Operator Operator}
     * @see {@link bop/js/api/operation/OperationInput#operators OperationInput.operators(..)}
     *
     * @param {module:entity/js/api/PropertyType} propertyType
     * @param {module:operation/js/api/Operator[]} operators
     * @returns {bop/js/api/operation/OperationInput} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} accepting all {@link entity/js/api/Property properties}
     *  defined inside of the passed {@link entity/js/api/Entity entity}.<br/>
     *  Both parameters are capable to process the same set of TEXT-based {@link module:operation/js/api/Operator operators} (Equals,
     *  Contains, Starts-with, Ends-with).
     * </caption>
     *
     * // Assuming three properties: Firstname (PropertyType.TEXT), Lastname (PropertyType.TEXT) and Age (PropertyType.NUMBER)
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var textOperators = [
     *     Operator.EQUALS,
     *     Operator.NOT_EQUALS,
     *     Operator.CONTAINS,
     *     Operator.NOT_CONTAINS
     * ];
     * var input = new OperationInput({
     *     entity: employee
     * }).parametersAll().
     *     operatorsForType(PropertyType.TEXT, textOperators);  // Sets textOperators only for "Firstname" and "Lastname" properties
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationInput OperationInput} with two explicit parameters specified.<br/>
     *  One parameter is inheriting default set of {@link module:operation/js/api/Operator operators}, the other one override this
     *  configuration with custom set.
     * </caption>
     *
     * // Assuming three properties: Firstname (PropertyType.TEXT), Lastname (PropertyType.TEXT) and Age (PropertyType.NUMBER)
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var firstname = employee.getProperty('Firstname');
     * var lastname = employee.getProperty('Lastname');
     * var textOperators = [
     *     Operator.EQUALS,
     *     Operator.NOT_EQUALS,
     *     Operator.CONTAINS,
     *     Operator.NOT_CONTAINS
     * ];
     * var firstnameOperators = [
     *     Operator.EQUALS,
     *     Operator.NOT_EQUALS,
     *     Operator.CONTAINS,
     *     Operator.NOT_CONTAINS,
     *     Operator.STARTS_WITH,
     *     Operator.ENDS_WITH
     * ];
     * var input = new OperationInput({
     *     entity: employee
     * }).parameter(lastname).                                      // Default operators (textOperators) are used
     *     parameter({
     *         property: firstname,
     *         operators: [Operator.EQUALS, Operator.NOT_EQUALS]    // Overriding default TEXT-based operators set with custom configuration
     *     })
     *     operatorsForType(PropertyType.TEXT, textOperators);      // Sets operators for all TEXT-based properties with no explicit configuration
     */
    OperationInput.prototype.operatorsForType = function(propertyType, operators) {
        AbcsLib.checkDefined(propertyType, 'propertyType');
        AbcsLib.checkDefined(operators, 'operators');
        AbcsLib.checkParameterCount(arguments, 2);

        AbcsLib.checkDataType(propertyType, AbcsLib.Type.STRING);
        AbcsLib.checkDataType(operators, AbcsLib.Type.ARRAY);

        this._operatorsMap[propertyType] = operators;
        return this;
    };

    /**
     * Builds an {@link data/js/api/OperationInput OperationInput} instance based on the current builder configuration.
     *
     * @see {@link bop/js/api/operation/OperationInput#parameter OperationInput.parameter(..)}
     * @see {@link bop/js/api/operation/OperationInput#parametersAll OperationInput.parametersAll(..)}
     * @see {@link bop/js/api/operation/OperationInput#parameterType OperationInput.parameterType(..)}
     * @see {@link bop/js/api/operation/OperationInput#operatorsForType OperationInput.operatorsForType(..)}
     *
     * @param {operation/js/api/Operation.SpecialType} specialType
     * @param {bop/js/api/operation/Pagination} pagination
     * @param {Property[]} sortableFields
     * @returns {data/js/api/OperationInput}
     */
    OperationInput.prototype.build = function(specialType, pagination, sortableFields) {
        var self = this;
        var params = self._createExplicitParams();

        // If all-parameters flag had been defined, take all not-explicitely defined properties and create ParameterDescription for them
        var entity = self._entity;
        if (self._allParameters) {
            var properties = entity.getProperties();
            for (var i = 0; i < properties.length; i++) {
                var property = properties[i];
                var propertyType = property.getType();

                // Skipping explicitly specified parameters
                if (!self._isExplicitlySpecified(params, property)) {
                    var operatorsForType = self._operatorsMap[propertyType];
                    var operators = operatorsForType ? operatorsForType : self._defaultOperators;
                    var paramDesc = new QueryParameterDescription(property, operators, self._parameterType);

                    params.push(paramDesc);
                }
            }
        }

        var queryDescription;
        if (specialType) {
            switch (specialType) {
                case Operation.SpecialType.QUERY_BY_ID:
                case Operation.SpecialType.QUERY_BY_IDS:
                    if (params.length === 0) {
                        // Usually no explicit parameter should be set if special type is used
                        queryDescription = self._createSpecialInput(entity, specialType);
                    } else if (params.length === 1 && params[0].isIDParameter() && params[0].getProperty().getEntity().getId() === entity.getId()) {
                        queryDescription = self._createQueryDescription(entity, params, specialType);
                    } else {
                        throw self._createNoOtherThanIDParamError(specialType);
                    }
                    break;
                case Operation.SpecialType.QUERY_BY_ANYTHING:
                    if (params.length > 0) {
                        throw self._createNoAdditionalParamsError(specialType);
                    }
                    queryDescription = self._createSpecialInput(entity, specialType);
                    break;
                default:
                    // No-op
                    break;
            }
        } else {
            queryDescription = self._createQueryDescription(entity, params, specialType);
        }

        return new OperationInputImpl(entity, queryDescription, pagination, sortableFields);
    };

    OperationInput.prototype._createQueryDescription = function(entity, params, specialType) {
        var id = this._generateUniqueID(entity, params, specialType);
        return new QueryDescription(id, id, params, specialType);
    };

    OperationInput.prototype._generateUniqueID = function(entity, params, specialType) {
        var paramsJSON = [];
        if (params) {
            params.forEach(function(param) {
                paramsJSON = paramsJSON.concat(param.getDefinition());
            });
        }
        var hash = StringUtils.hash(JSON.stringify(paramsJSON));
        if (specialType) {
            return entity.getId() + '_' + specialType + '_' + hash;
        } else {
            return entity.getId() + '_' + hash;
        }
    };

    OperationInput.prototype._createExplicitParams = function() {
        var self = this;
        var result = [];
        var params = this._explicitParams;
        for (var i = 0; i < params.length; i++) {
            var param = params[i];
            var property = param.property;

            /*
             * Three step process:
             *
             *  1) If explicit operators had been set, use these
             *  2) If no-explicit operators are available, check if operators-set for this PropertyType had been set and use these
             *  3) If no-explicit operators and no PropertyType sensitive operators are available, use defaults
             */
            var operatorsForType = self._operatorsMap[property.getType()];
            var defaultOperators = operatorsForType ? operatorsForType : self._defaultOperators;
            var operators = param.supportedOperators ? param.supportedOperators : defaultOperators;
            var parameterType = param.parameterType ? param.parameterType : self._parameterType;

            result.push(new QueryParameterDescription(property, operators, parameterType, param.isRequired, param.additionalInfo));
        }
        return result;
    };

    /**
     * Checks if the given propertyID had been explicitly specified using parameter() function or not.
     *
     * @param {QueryParameterDescription[]} explicitParams
     * @param {Property} property
     * @returns {Boolean}
     */
    OperationInput.prototype._isExplicitlySpecified = function(explicitParams, property) {
        var propertyID = property.getId();
        for (var i = 0; i < explicitParams.length; i++) {
            if (explicitParams[i].getPropertyID() === propertyID) {
                return true;
            }
        }
        return false;
    };

    OperationInput.prototype._createSpecialInput = function(entity, specialType) {
        switch (specialType) {
            case Operation.SpecialType.QUERY_BY_ID:
                return QueryDescriptionFactory.createQueryByID(entity);
            case Operation.SpecialType.QUERY_BY_IDS:
                return QueryDescriptionFactory.createQueryByIDs(entity);
            case Operation.SpecialType.QUERY_BY_ANYTHING:
                return QueryDescriptionFactory.createQueryByAnything(entity);
            default:
                throw OperationInput._createUnrecognizedSpecialKindError(specialType);
        }
    };

    /**
     * Creates an exception message that the operation can't be extended if special kind QUERY_BY_ANYTHING were already set.
     *
     * @param {operation/js/api/Operation.SpecialType} specialType
     * @returns {String}
     */
    OperationInput.prototype._createNoOtherThanIDParamError = function(specialType) {
        return 'Special type (' + specialType + ') had been already defined for this operation. When this special kind is explicitely\n\
                defined for certain method, you are not allowed to include any other additional parameters than ID parameter itself.';
    };

    /**
     * Creates an exception message that the operation can't be extended if special kind QUERY_BY_ANYTHING were already set.
     *
     * @returns {String}
     */
    OperationInput.prototype._createNoAdditionalParamsError = function() {
        return 'Operation.SpecialType.QUERY_BY_ANYTHING had been already defined for this operation. When this special kind is explicitely\n\
                defined for certain method, you are not allowed to include any additional parameters using OperationInput.parameter(..)\n\
                method into such operation. In case of Operation with Special Type QUERY_BY_ANYTHING set, all parameters are automatically\n\
                pre-registered by ABCS itself from the input entity definition.';
    };

    /**
     * Creates an exception message that the given special kind in unrecognized.
     *
     * @param {operation/js/api/Operation.SpecialType} specialType
     * @returns {String}
     */
    OperationInput.prototype._createUnrecognizedSpecialKindError = function(specialType) {
        return 'The given special kind (' + specialType + ') is undefined and can\'t be passed';
    };

    /**
     * Represents the type of the input parameter corresponding to the real REST call.
     *
     * <p>
     * Setting certain type can help you to handle input parameters better in your implementation of {@link bop/js/spi/operation/ConditionVisitor ConditionVisitor}.
     * For example PATH parameters are probably going to be processed differently (by changing the base URL) than QUERY (by including suffix at the end of the URL).
     * </p>
     *
     * @enum {String}
     *
     * @see {@link bop/js/api/operation/OperationInput#parameterType OperationInput.parameterType(..)} which is the method where you use parameter type.
     * @see {@link bop/js/spi/operation/ConditionVisitor ConditionVisitor} which is the typical consumer of parameter type information.
     */
    OperationInput.Type = Object.freeze({
        /**
         * Path parameter type.
         */
        PATH: 'path',
        /**
         * Query parameter type.
         */
        QUERY: 'query',
        /**
         * Header parameter type.
         */
        HEADER: 'header'

    });

    /**
     * Holder object later used for construction of appropriate QueryParameterDescriptor.
     *
     * <p>
     * Without this holder, I would need to create parameter descriptor immediatelly within parameter(..)
     * method and later change that instance if some defaults (type or operators) should be included there.
     * </p>
     *
     * @param {entity/js/api/Property} property
     * @param {operation/js/api/Operator[]} supportedOperators
     * @param {bop/js/api/operation/OperationInput.Type} parameterType
     * @param {Object} additionalInfo
     * @param {Boolean} isRequired
     */
    OperationInput.Parameter = function(property, supportedOperators, parameterType, additionalInfo, isRequired) {
        this.property = property;
        this.supportedOperators = supportedOperators;
        this.parameterType = parameterType;
        this.additionalInfo = additionalInfo;
        this.isRequired = isRequired;
    };

    return OperationInput;
});