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

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

define([
    'bop/js/api/operation/OperationInput',
    'bop/js/api/operation/OperationOutput',
    'bop/js/api/operation/Pagination',
    'data/js/api/OperationFactory',
    'data/js/api/OperationInput',
    'data/js/api/OperationOutput',
    'entity/js/api/Entity',
    'entity/js/api/Property',
    'operation/js/api/FieldPathDescription',
    'operation/js/api/Operation'
], function (
        OperationInput,
        OperationOutput,
        Pagination,
        OperationFactory,
        OperationInputImpl,
        OperationOutputImpl,
        Entity,
        Property,
        FieldPathDescription,
        Operation
    ) {

    'use strict';

    /**
     * Builds {@link operation/js/api/Operation Operation} instance which can be registered into custom BOP.
     *
     * <p>
     * Building {@link operation/js/api/Operation Operation} means describing <b><i>exactly</i></b> what the underlying REST call is capable to do.
     * The whole ABCS UI is going to be set based on the configuration done here.
     * </p>
     *
     * <p>
     * The real REST call is being made by {@link operation/js/api/Operation#perform Operation.perform(..)} method which is the function you pass into
     * constructors "performs" object literal attribute. This REST-call function is expected to be able of doing everything described by the
     * {@link operation/js/api/Operation Operation} configuration which you're building here.
     * </p>
     *
     * <p>
     * Few examples of how different configurations can affect resulted Abcs UI and how that affects real REST call.
     * <br/>
     * All {@link operation/js/api/Operation Operation}s with {@link operation/js/api/Operation.Type type} set to {@link operation/js/api/Operation.Type.READ_MANY READ_MANY}
     * will be available when ABCS Business User is dropping table onto an empty page. Furthermore:
     *
     * <ul>
     *  <li>
     *      Each {@link operation/js/api/Operation Operation} of type {@link operation/js/api/Operation.Type.READ_MANY READ_MANY} also has to define for which {@link entity/js/api/Entity Entity} it
     *      returns actual records. And that can be done using {@link bop/js/api/operation/OperationBuilder#returns OperationBuilder.returns(..)}. From that point when ABCS client drops Collection
     *      component into the page and select that {@link entity/js/api/Entity Entity}, your {@link operation/js/api/Operation Operation} will be listed and become available for selection.
     *  </li>
     *  <li>
     *      If such {@link operation/js/api/Operation Operation} is also described using {@link bop/js/api/operation/OperationBuilder#paginates
     *      OperationBuilder.paginates(..)}, the Collection itself will have pagination enabled.
     *  </li>
     *  <li>
     *      If such {@link operation/js/api/Operation Operation} is also described using {@link bop/js/api/operation/OperationBuilder#takes OperationBuilder.takes(..)}
     *      method and the given {@link bop/js/api/operation/OperationInput OperationInput} object is configured so that the {@link operation/js/api/Operation Operation}
     *      suppports sorting on certain fields, then table Wizard and PI will adapt accordingly and allow ABCS Business User to set sorting to all fields that were configured as sortable.
     *  </li>
     *  <li>
     *      If such {@link operation/js/api/Operation Operation} is also described using {@link bop/js/api/operation/OperationBuilder#takes OperationBuilder.takes(..)}
     *      method and the given {@link bop/js/api/operation/OperationInput OperationInput} object is configured so that some parameters supports multiple type of
     *      {@link module:operation/js/api/Operator Operator}s, all related UIs like Default Query, Advanced Search etc. will all contain all supported
     *      {@link module:operation/js/api/Operator Operator}s.
     *  </li>
     * <ul>
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     * @exports bop/js/api/operation/OperationBuilder
     * @constructor
     *
     * @param {Object} params - Object literal with all possible parameters.
     * @param {String} params.name - Name of the build {@link operation/js/api/Operation Operation}. Name is used across all Abcs UI features. For example when ABCS Business User
     *                               will drop a table for the entity this operation is returning, combo-box showing potential operations will appear and values in that combo-box
     *                               correspond to the operation name defined here.
     * @param {function} params.performs - JS function representing the real REST call which build {@link operation/js/api/Operation Operation} performs. Always has to return Promise of the {@link operation/js/api/OperationResult Result}.
     * @param {operation/js/api/Operation.Type} params.type - {@link operation/js/api/Operation.Type Type} of the build {@link operation/js/api/Operation Operation}.
     *
     * @see {@link entity/js/api/Entity Entity}
     * @see {@link operation/js/api/Operation Operation}
     * @see {@link operation/js/api/Operation.Type Operation.Type}
     * @see {@link bop/js/api/operation/OperationInput OperationInput}
     *
     * @example
     * <caption>
     *  Creates an instance of {@link operation/js/api/Operation Operation} (type = {@link operation/js/api/Operation.Type.CREATE CREATE}) with minimal set of
     *  parameters specified.
     * </caption>
     *
     * var functionToRun = function() {
     *     // Make any REST call here which performs the real operation and creates new Employee record
     *     return new Promise(function(fulfil, reject) {
     *         $.ajax({
     *             url: 'http://example.com/includeYourEmployee',
     *             type: 'PUT',
     *             data: 'ID=1&Firstname=Martin&Lastname=Janicek&Age=29'
     *         }).done(function(response) {
     *             fulfil(OperationResult.success(response));
     *         }).fail(function(error) {
     *             var message = "Whoops, something went wrong! " + error.message;
     *             reject(OperationResult.failure(message, error.code));
     *         });
     *     });
     * };
     * var operation = new OperationBuilder({
     *     name: 'Create Employee',
     *     type: Operation.Type.CREATE,
     *     performs: functionToRun
     * }).build();
     */
    var OperationBuilder = function(params) {
        var mandatoryKeys = [
            'name',
            'type',
            'performs'
        ];
        AbcsLib.checkThis(this);
        AbcsLib.checkDefined(params, 'params', 'You need to pass object literal with at least "type", "name" and "performs" attributes being set');
        AbcsLib.checkParameterCount(arguments, 1);
        AbcsLib.checkObjectLiteral(params, mandatoryKeys, mandatoryKeys);

        this._type = params.type;
        this._name = params.name;
        this._functionToRun = params.performs;
        this._sortableFields = [];
    };

    /**
     * Sets the description of the build {@link operation/js/api/Operation Operation}.
     *
     * <p>
     * The description is used as more detailed information (in tooltips etc.) of the {@link operation/js/api/Operation Operation} across ABCS UI.
     * It should help ABCS client to understand what's the business meaning of the {@link operation/js/api/Operation Operation} itself.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @param {String} description - More detailed text information about the build {@link operation/js/api/Operation Operation}.
     * @returns {bop/js/api/operation/OperationBuilder} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Using {@link bop/js/api/operation/OperationBuilder#description OperationBuilder.description(..)} method to specify detailed description of the constructed
     *  {@link operation/js/api/Operation Operation}.
     * </caption>
     *
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var functionToRun = function(operationData) {
     *     var baseURL = 'http://example.com/employees';
     *     var resultURL = new MyConditionVisitor(baseURL).visit(operationData.getCondition());
     *
     *     // Include REST call here which performs the real operation retrieving your Employee data
     *     return new Promise(function(fulfil, reject) {
     *         $.ajax({
     *             url: resultURL,
     *             type: 'GET'
     *         }).done(function(response) {
     *             fulfil(OperationResult.success(response));
     *         }).fail(function(error) {
     *             reject(OperationResult.failure(error.message, error.code));
     *         });
     *     });
     * };
     * var operation = new OperationBuilder({
     *     name: 'Read Employees',
     *     type: Operation.Type.READ_MANY,
     *     performs: functionToRun
     * }).description('Some long description of what the operation does in detail. It will be showed in some ABCS UI elements (e.g. in tooltips)').
     *     returns(employee).
     *     build();
     */
    OperationBuilder.prototype.description = function(description) {
        AbcsLib.checkDefined(description, 'description');
        AbcsLib.checkParameterCount(arguments, 1);
        AbcsLib.checkDataType(description, AbcsLib.Type.STRING);

        this._description = description;
        return this;
    };

    /**
     * Sets the input configuration of the build {@link operation/js/api/Operation Operation}.
     *
     * <p>
     * Creating comprehensive {@link bop/js/api/operation/OperationInput OperationInput} is absolutely necessary to make resulted
     * {@link operation/js/api/Operation Operation} behaving correctly inside ABCS UI. Usually this is complex mainly for {@link operation/js/api/Operation operations}
     * of type {@link operation/js/api/Operation.Type.READ_MANY READ_MANY} as those need to describe querying, sorting and pagination capabilities. For
     * other {@link operation/js/api/Operation.Type Operation.Type}s, it's fine for the given {@link bop/js/api/operation/OperationInput OperationInput} instance
     * to have only input {@link entity/js/api/Entity Entity} configured.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @see {@link bop/js/api/operation/OperationInput OperationInput}
     *
     * @param {bop/js/api/operation/OperationInput} operationInput
     * @returns {bop/js/api/operation/OperationBuilder} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Using {@link bop/js/api/operation/OperationBuilder#takes OperationBuilder.takes(..)} method to specify what input parameters the build {@link operation/js/api/Operation Operation}
     *  accepts. In this case, input {@link entity/js/api/Entity Entity} is defined 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 functionToRun = function(operationData) {
     *     // Include REST call here which performs the real operation retrieving your Employee data
     *     // In this case your REST call:
     *     //  --> Has to be capable to manage Firstname and Lastname input parameters with EQUALS operator only
     *     var baseURL = 'http://example.com/employees';
     *     var resultURL = new MyConditionVisitor(baseURL).visit(operationData.getCondition());
     *
     *     // Include REST call here which performs the real operation retrieving your Employee data
     *     return new Promise(function(fulfil, reject) {
     *         $.ajax({
     *             url: resultURL,
     *             type: 'GET'
     *         }).done(function(response) {
     *             fulfil(OperationResult.success(response));
     *         }).fail(function(error) {
     *             reject(OperationResult.failure(error.message, error.code));
     *         });
     *     });
     * };
     * var inputBuilder = new OperationInput({
     *     entity: employee
     * }).parameter(firstname).
           parameter(lastname);
     * var operation = new OperationBuilder({
     *     name: 'Read Employees',
     *     type: Operation.Type.READ_MANY,
     *     performs: functionToRun
     * }).returns(employee).
     *     takes(inputBuilder).
     *     build();
     *
     * @example
     * <caption>
     *  Using {@link bop/js/api/operation/OperationBuilder#takes OperationBuilder.takes(..)} method to specify what input parameters the build {@link operation/js/api/Operation Operation}
     *  accepts. In this case, input {@link entity/js/api/Entity Entity} is defined with three explicit parameters specified. And also supported {@link module:operation/js/api/Operator Operator}s
     *  are configured based on the actual {@link entity/js/api/PropertyType PropertyType}.
     * </caption>
     *
     * 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 employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var firstname = employee.getProperty('Firstname');   // Let's assume this is PropertyType.TEXT
     * var lastname = employee.getProperty('Lastname');     // Let's assume this is PropertyType.TEXT
     * var age = employee.getProperty('Age');               // Let's assume this is PropertyType.NUMBER
     * var functionToRun = function(operationData) {
     *     // Include REST call here which performs the real operation retrieving your Employee data
     *     // In this case your REST call:
     *     //  --> Has to be capable to manage Firstname, Lastname and Age input parameters
     *     //  --> Has to be capable to manage all "textOperators" for Firstname and Lastname input parameters
     *     //  --> Has to be capable to manage all "numberOperators" for Age input parameter
     *     var baseURL = 'http://example.com/employees';
     *     var resultURL = new MyConditionVisitor(baseURL).visit(operationData.getCondition());
     *
     *     // Include REST call here which performs the real operation retrieving your Employee data
     *     return new Promise(function(fulfil, reject) {
     *         $.ajax({
     *             url: resultURL,
     *             type: 'GET'
     *         }).done(function(response) {
     *             fulfil(OperationResult.success(response));
     *         }).fail(function(error) {
     *             reject(OperationResult.failure(error.message, error.code));
     *         });
     *     });
     * };
     * var inputBuilder = new OperationInput({
     *     entity: employee
     * }).parameter(age).
     *     parameter(firstname).
     *     parameter(lastname).
     *     operatorsForType(PropertyType.TEXT, textOperators).
     *     operatorsForType(PropertyType.NUMBER, numberOperators);
     * var operation = new OperationBuilder({
     *     name: 'Read Employees',
     *     type: Operation.Type.READ_MANY,
     *     performs: functionToRun
     * }).returns(employee).
     *     takes(inputBuilder).
     *     build();
     */
    OperationBuilder.prototype.takes = function(operationInput) {
        AbcsLib.checkDefined(operationInput, 'operationInput');
        AbcsLib.checkParameterCount(arguments, 1);
        AbcsLib.checkDataType(operationInput, OperationInput);

        this._inputBuilder = operationInput;
        return this;
    };

    /**
     * Sets either {@link entity/js/api/Entity Entity} or just a sub-set of it by passing {@link bop/js/api/operation/OperationOutput OperationOutput}
     * instance, whose records the built {@link operation/js/api/Operation Operation} returns.
     *
     * <p>
     * Check {@link bop/js/api/operation/OperationBuilder OperationBuilder} documentation for more details of how this impacts on ABCS UI capabilities.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @param {entity/js/api/Entity | bop/js/api/operation/OperationOutput} param - If an {@link entity/js/api/Entity Entity} instance is passed, all its {@link entity/js/api/Property Properties}
     *          are automatically included into operation output description. If you need to restrict these somehow (e.g. your {@link operation/js/api/Operation Operation} returns only certain sub-set of
     *          {@link entity/js/api/Property Properties}), instance of {@link bop/js/api/operation/OperationOutput OperationOutput} can be configured and passed instead.
     * @returns {bop/js/api/operation/OperationBuilder} a reference to this object to allow method chaining
     *
     * @see {@link entity/js/api/Entity Entity}
     * @see {@link bop/js/api/operation/OperationOutput OperationOutput}
     * @see {@link bop/js/api/operation/OperationOutput#parameter OperationOutput.parameter(..)}
     *
     * @example
     * <caption>
     *  Using {@link bop/js/api/operation/OperationBuilder#returns OperationBuilder.returns(..)} method to specify that the build {@link operation/js/api/Operation Operation}
     *  returns records with values for all {@link entity/js/api/Property Properties} from the given {@link entity/js/api/Entity Entity}.
     * </caption>
     *
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var functionToRun = function(operationData) {
     *     var baseURL = 'http://example.com/employees';
     *     var resultURL = new MyConditionVisitor(baseURL).visit(operationData.getCondition());
     *
     *     // Include REST call here which performs the real operation retrieving your Employee data
     *     return new Promise(function(fulfil, reject) {
     *         $.ajax({
     *             url: resultURL,
     *             type: 'GET'
     *         }).done(function(response) {
     *             fulfil(OperationResult.success(response));
     *         }).fail(function(error) {
     *             reject(OperationResult.failure(error.message, error.code));
     *         });
     *     });
     * };
     * var operation = new OperationBuilder({
     *     name: 'Read Employees',
     *     type: Operation.Type.READ_MANY,
     *     performs: functionToRun
     * }).returns(employee).build();
     *
     * @example
     * <caption>
     *  Using {@link bop/js/api/operation/OperationBuilder#returns OperationBuilder.returns(..)} method to specify that the build {@link operation/js/api/Operation Operation}
     *  returns only a smaller sub-set of these {@link entity/js/api/Property Properties} defined using the given {@link bop/js/api/operation/OperationOutput OperationOutput}.
     * </caption>
     *
     * // Let's assume, Employee entity has a lot of properties but our operation returns just two of them ("Firstname", "Lastname")
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var outputBuilder = new OperationOutput({
     *     entity: employee
     * }).property(firstname).
     *     property(lastname);
     *
     * var functionToRun = function(operationData) {
     *     var baseURL = 'http://example.com/employees';
     *     var resultURL = new MyConditionVisitor(baseURL).visit(operationData.getCondition());
     *
     *     // Include REST call here which performs the real operation retrieving your Employee data
     *     return new Promise(function(fulfil, reject) {
     *         $.ajax({
     *             url: resultURL,
     *             type: 'GET'
     *         }).done(function(response) {
     *             fulfil(OperationResult.success(response));
     *         }).fail(function(error) {
     *             reject(OperationResult.failure(error.message, error.code));
     *         });
     *     });
     * };
     * var operation = new OperationBuilder({
     *     name: 'Read only basic information about Employees',
     *     type: Operation.Type.READ_MANY,
     *     performs: functionToRun
     * }).returns(outputBuilder).build();
     */
    OperationBuilder.prototype.returns = function(param) {
        AbcsLib.checkDefined(param, 'param', 'Either an instance of Entity or instance of OperationOutput needs to be passed into OperationBuilder.returns(..) method');
        AbcsLib.checkParameterCount(arguments, 1);
        AbcsLib.checkDataType(param, [Entity, OperationOutput]);

        // Allowing to pass Entity to make this easy for Carrie, but internally we want to encapsulate it into the same OperationOutput instance
        if (param instanceof Entity) {
            this._outputBuilder = new OperationOutput({
                entity: param
            }).propertiesAll();
        } else {
            this._outputBuilder = param;
        }

        return this;
    };

    /**
     * Sets the type of the {@link bop/js/api/operation/Pagination Pagination} the build {@link operation/js/api/Operation Operation}
     * supports.
     *
     * <p>
     * This method is relevant only in case of {@link operation/js/api/Operation.Type Operation.Type} being set to
     * {@link operation/js/api/Operation.Type.READ_MANY Operation.Type.READ_MANY}, otherwise pagination configuration
     * is meaningless and the {@link bop/js/api/operation/OperationBuilder OperationBuilder} throws an exception.
     * </p>
     *
     * <p>
     * If {@link bop/js/api/operation/OperationBuilder#paginates OperationBuilder.paginates(..)} method is not explicitely called,
     * resulted operation is by default configured with pagination being unsupported.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @param {bop/js/api/operation/Pagination} pagination - Type of the {@link bop/js/api/operation/Pagination Pagination} the built {@link operation/js/api/Operation Operation} supports.
     * @returns {bop/js/api/operation/OperationBuilder} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Using {@link bop/js/api/operation/OperationBuilder#paginates OperationBuilder.paginates(..)} method to specify that the build {@link operation/js/api/Operation Operation}
     *  is capable to manage pagination.
     * </caption>
     *
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var functionToRun = function(operationData) {
     *     var baseURL = 'http://example.com/employees';
     *     var resultURL = new MyConditionVisitor(baseURL).visit(operationData.getCondition());
     *
     *     // Include REST call here which performs the real operation retrieving your Employee data
     *     return new Promise(function(fulfil, reject) {
     *         $.ajax({
     *             url: resultURL,
     *             type: 'GET'
     *         }).done(function(response) {
     *             fulfil(OperationResult.success(response));
     *         }).fail(function(error) {
     *             reject(OperationResult.failure(error.message, error.code));
     *         });
     *     });
     * };
     * var operation = new OperationBuilder({
     *     name: 'Read Employees',
     *     type: Operation.Type.READ_MANY,
     *     performs: functionToRun
     * }).returns(employee).
     *     paginates(Pagination.STANDARD).
     *     build();
     */
    OperationBuilder.prototype.paginates = function(pagination) {
        AbcsLib.checkDefined(pagination, 'pagination');
        AbcsLib.checkParameterCount(arguments, 1);
        AbcsLib.checkDataType(pagination, Pagination);

        if (this._type !== Operation.Type.READ_MANY) {
            throw new Error('Calling paginates(..) method on method with other type than Operation.Type.READ_MANY is unsupported.');
        }

        this._paginates = pagination;
        return this;
    };

    /**
     * Defines the build {@link operation/js/api/Operation Operation} to be sortable by all {@link entity/js/api/Property Properties} available in the configured {@link entity/js/api/Entity Entity}.
     *
     * <p>
     * Please be aware that the value given here is tightly connected to what you specified within the {@link bop/js/api/operation/OperationBuilder#returns OperationBuilder.returns(..)} method.
     * All {@link entity/js/api/Property Properties} that were defined as an ouput of the {@link operation/js/api/Operation Operation} (= all {@link entity/js/api/Property Properties} assigned
     * to the {@link entity/js/api/Entity Entity} given into the {@link bop/js/api/operation/OperationBuilder#returns OperationBuilder.returns(..) method}) will be available for sorting.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @see {@link entity/js/api/Entity Entity}
     * @see {@link entity/js/api/Property Property}
     *
     * @returns {bop/js/api/operation/OperationBuilder} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationBuilder OperationBuilder} sortable by all {@link entity/js/api/Property properties} defined inside of the configured {@link entity/js/api/Entity entity}.
     * </caption>
     *
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var operation = new OperationBuilder({
     *     name: 'Read Employees',
     *     type: Operation.Type.READ_MANY,
     *     performs: function() {
     *         // This performs the REST call
     *     }
     * }).returns(employee).
     *     sortableByAll().
     *     build();
     */
    OperationBuilder.prototype.sortableByAll = function() {
        AbcsLib.checkParameterCount(arguments, 0);

        this._sortableAll = true;
        return this;
    };

    /**
     * Defines the given {@link entity/js/api/Property Property}(or {@link entity/js/api/Property Properties}) as sortable option(s) supported by the build {@link operation/js/api/Operation Operation}.
     *
     * <p>
     * Argument is allowed to be passed in two different forms:
     *  <ul>
     *      <li>
     *          Single {@link entity/js/api/Property Property} instance which is automatically assumed to be sortable option.
     *      </li>
     *      <li>
     *          Array of {@link entity/js/api/Property Properties} making all of them automatically assumed to be sortable options.
     *      </li>
     *  </ul>
     * </p>
     *
     * <p>
     * Please be aware that the value given here is tightly connected to what you specified within the {@link bop/js/api/operation/OperationBuilder#returns OperationBuilder.returns(..)} method.
     * Only {@link entity/js/api/Property Properties} that are defined as an ouput of the {@link operation/js/api/Operation Operation} can be specified for sorting.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @see {@link entity/js/api/Property Property}
     *
     * @param {entity/js/api/Property | entity/js/api/Property[]} value - sortable options for the build {@link operation/js/api/Operation Operation}.
     * @returns {bop/js/api/operation/OperationBuilder} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Creates an instance of {@link bop/js/api/operation/OperationBuilder OperationBuilder} sortable by few explicitly specified {@link entity/js/api/Property properties}.
     * </caption>
     *
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var firstname = employee.getProperty('Firstname');
     * var lastname = employee.getProperty('Lastname');
     *
     * // Assuming Employee entity contains three properties: Firstname, Lastname and Age
     * // Using this description only two of them will be available for sorting
     * var operation = new OperationBuilder({
     *     name: 'Read Employees',
     *     type: Operation.Type.READ_MANY,
     *     performs: function() {
     *         // This performs the REST call
     *     }
     * }).returns(employee).
     *     sortableBy(firstname).
     *     sortableBy(lastname).
     *     build();
     */
    OperationBuilder.prototype.sortableBy = function(value) {
        AbcsLib.checkDefined(value, 'value');
        AbcsLib.checkParameterCount(arguments, 1);
        AbcsLib.checkDataType(value, [Property, AbcsLib.Type.ARRAY]);

        // Check also if all array values are Property instances
        if (AbcsLib.isArray(value)) {
            this._addSortableProperties(value);
        }

        if (AbcsLib.isInstanceof(value, Property)) {
            this._sortableFields.push(value);
        }

        return this;
    };

    OperationBuilder.prototype._addSortableProperties = function(properties) {
        for (var i = 0; i < properties.length; i++) {
            AbcsLib.checkDataType(properties[i], Property);
            this._sortableFields.push(properties[i]);
        }
    };

    /**
     * Sets the {@link operation/js/api/Operation.SpecialType SpecialType} of the build {@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 either
     * {@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, this information is unsupported and resulted behavior is not guaranteed by ABCS.
     * </p>
     *
     * <p>
     * Only one {@link operation/js/api/Operation Operation} of each {@link operation/js/api/Operation.SpecialType Operation.SpecialType}
     * is expected for a single {@link entity/js/api/Entity Entity}. If your BOP will provide more {@link operation/js/api/Operation Operation}s of this type for
     * the same {@link entity/js/api/Entity Entity}, the behavior is not guaranteed by ABCS.
     * </p>
     *
     * <p>
     * For detailed information of how different {@link operation/js/api/Operation.SpecialType SpecialType}s affects ABCS capabilities please check
     * {@link operation/js/api/Operation.SpecialType SpecialType} documentation.
     * </p>
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @param {operation/js/api/Operation.SpecialType} specialType
     * @returns {bop/js/api/operation/OperationBuilder} a reference to this object to allow method chaining
     *
     * @example
     * <caption>
     *  Using {@link bop/js/api/operation/OperationBuilder#specialType OperationBuilder.specialType(..)} method to specify that the build {@link operation/js/api/Operation Operation}
     *  is capable to manage every possible combination of input parameters corresponding to the given input {@link entity/js/api/Entity Entity}.
     * </caption>
     *
     * // Let's just assume Employee has four properties (ID, Firstname, Lastname, Age)
     * var employee = Abcs.Entities().findById('my.custom.bop.Employee');
     * var functionToRun = function(operationData) {
     *     // Include REST call here which performs the real operation retrieving your Employee data
     *     // In this case your REST call:
     *     //  --> Has to be capable to manage ID, Firstname, Lastname, Age input parameters
     *     //  --> Has to be capable to manage all possible combinations AND/OR of these input parameters
     *     var baseURL = 'http://example.com/employees';
     *     var resultURL = new MyConditionVisitor(baseURL).visit(operationData.getCondition());
     *
     *     // Include REST call here which performs the real operation retrieving your Employee data
     *     return new Promise(function(fulfil, reject) {
     *         $.ajax({
     *             url: resultURL,
     *             type: 'GET'
     *         }).done(function(response) {
     *             fulfil(OperationResult.success(response));
     *         }).fail(function(error) {
     *             reject(OperationResult.failure(error.message, error.code));
     *         });
     *     });
     * };
     * var inputBuilder = new OperationInput({
     *     entity: employee
     * })
     * var operation = new OperationBuilder({
     *     name: 'Read Employees',
     *     type: Operation.Type.READ_MANY,
     *     performs: functionToRun
     * }).returns(employee).
     *     takes(inputBuilder).
     *     specialType(Operation.SpecialType.QUERY_BY_ANYTHING).
     *     build();
     */
    OperationBuilder.prototype.specialType = function(specialType) {
        AbcsLib.checkDefined(specialType, 'specialType');
        AbcsLib.checkParameterCount(arguments, 1);
        AbcsLib.checkDataType(specialType, AbcsLib.Type.STRING);

        switch (this._type) {
            case Operation.Type.CREATE:
            case Operation.Type.UPDATE:
            case Operation.Type.DELETE:
            case Operation.Type.PERFORM:
                throw new Error('Calling specialType(..) is unsupported for the given specialType ( =' + specialType + ' )');
            case Operation.Type.READ_ONE:
                if (specialType === Operation.SpecialType.QUERY_BY_IDS || specialType === Operation.SpecialType.QUERY_BY_ANYTHING) {
                    throw new Error('Only special type supported by READ_ONE operation is: QUERY_BY_ID.');
                }
                break;
            case Operation.Type.READ_MANY:
                if (specialType === Operation.SpecialType.QUERY_BY_ID) {
                    throw new Error('Only special types supported by READ_MANY operation\'s are: QUERY_BY_IDS and QUERY_BY_ANYTHING.');
                }
                break;
            default:
                throw new Error('Calling specialType(..) with unknown specialType ( =' + specialType + ' )');
        }

        this._specialType = specialType;
        return this;
    };

    /**
     * Builds an {@link operation/js/api/Operation Operation} instance based on the current {@link bop/js/api/operation/OperationBuilder builder} configuration.
     *
     * @AbcsExtension stable
     * @version 17.1.1
     *
     * @returns {operation/js/api/Operation}
     */
    OperationBuilder.prototype.build = function() {
        AbcsLib.checkParameterCount(arguments, 0);

        var self = this;
        var type = self._type;
        var specialType = self._specialType;
        var outputBuilder = self._outputBuilder;
        var output = outputBuilder ? outputBuilder.build() : new OperationOutputImpl();
        var pagination = self._paginates;
        var sortableFields = self._getSortableFields(output);

        if (type !== Operation.Type.READ_MANY && (self._sortableAll || self._sortableFields.length > 0)) {
            throw new Error('Sortable fields can be specified only on Operations with Operation.Type equal to READ_MANY.');
        }

        // build an array of FieldPathDescriptions from the array of sortable
        // fields
        var sortablePropDescs = [];
        if (sortableFields && sortableFields.length > 0) {
            var ent = sortableFields[0].getEntity();
            if (ent) {
                sortablePropDescs = FieldPathDescription.forEntity(ent, false, function(fpd){
                    return (sortableFields.indexOf(fpd.getProperty()) !== -1);
                });
            }
        }

        var input;
        var inputBuilder = self._inputBuilder;
        if (inputBuilder) {
            input = inputBuilder.build(specialType, pagination, sortablePropDescs);
        } else {
            // It's possible to have Operation without any parameters with pagination set. Ideally, isPagination information should be
            // moved from OperationInput into Operation itself which would solve this weirdness
            input = new OperationInputImpl(undefined, undefined, pagination, sortablePropDescs);

            // If client didn't call OperationBuilder.takes(..) but set OperationBuilder.specialType(..), we don't know what's the input
            // entity and as such we won't be able to construct operation correctly
            if (specialType) {
                throw self._createNoInputBuilderError(specialType);
            }
        }

        if (type === Operation.Type.READ_MANY || type === Operation.Type.READ_ONE) {
            AbcsLib.checkDefined(outputBuilder, 'outputEntity', 'In case of READ operation, output entity needs to be defined. You need to use operationBuilder.returns(...) method before calling operationBuilder.build() function');
        }

        return OperationFactory.createExternal(
            type,
            input,
            output,
            self._functionToRun,
            self._name,
            self._name,
            self._description
        );
    };

    OperationBuilder.prototype._getSortableFields = function(output) {
        var outputProperties = output.getProperties();
        var sortableFields;
        if (this._sortableAll) {
            sortableFields = outputProperties;
        } else {
            sortableFields = this._sortableFields;

            // Check if all defined fields are also part of the output
            if (sortableFields) {
                for (var i = 0; i < sortableFields.length; i++) {
                    var sortableField = sortableFields[i];

                    // If at least one field explicitly set as sortable is not part of the output, the operation is defined incorrectly
                    if (outputProperties.indexOf(sortableField) < 0) {
                        throw new Error('You can\'t declare field as sortable (using OperationBuilder.sortableBy(..) method) for fields that are not part of the operation output.\n\
                                         Please either remove calling of sortableBy() on Property with ID: \'' + sortableField.getId() + '\' or you need to include this Property into\n\
                                         the OperationOutput defined within your OperationBuilder.returns(..) method.');
                    }
                }
            }
        }
        return sortableFields;
    };

    OperationBuilder.prototype._createNoInputBuilderError = function(specialType) {
        return 'The operation is configured to use special type (' + specialType + '), but there is no input entity defined. You need to pass at least minimalistic\n\
                OperationInput instance into OperationBuilder.takes(..) method so we can generate input parameters automatically for the ' + specialType + ' \n\
                special type. See also Operation.SpecialType documentation for more details.';
    };

    return OperationBuilder;
});