/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.program.api.util.sql.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import com.fluidbpm.program.api.util.IFormAction;
import com.fluidbpm.program.api.util.cache.CacheUtil;
import com.fluidbpm.program.api.util.sql.ABaseSQLUtil;
import com.fluidbpm.program.api.util.sql.exception.FluidSQLException;
import com.fluidbpm.program.api.util.sql.syntax.ISyntax;
import com.fluidbpm.program.api.util.sql.syntax.SyntaxFactory;
import com.fluidbpm.program.api.vo.field.Field;
import com.fluidbpm.program.api.vo.form.Form;
import com.fluidbpm.program.api.vo.item.FluidItem;
import com.fluidbpm.program.api.vo.user.User;

/**
 * SQL Utility class used for {@code Form} related actions.
 *
 * @author jasonbruwer
 * @since v1.0
 *
 * @see ABaseSQLUtil
 * @see Form
 * @see IFormAction
 */
public class SQLFormUtil extends ABaseSQLUtil implements IFormAction {

	private SQLFormDefinitionUtil formDefUtil = null;
	private SQLFormFieldUtil fieldUtil = null;

	/**
	 * Contains the SQL column indexes for {@code Form}.
	 */
	public static class SQLColumnIndex {
		public static final int _01_FORM_ID = 1;
		public static final int _02_FORM_TYPE = 2;
		public static final int _03_TITLE = 3;
		public static final int _04_CREATED = 4;
		public static final int _05_LAST_UPDATED = 5;
		public static final int _06_CURRENT_USER_ID = 6;

		//additional...
		public static final int _07_FORM_CONTAINER_STATE = 7;
		public static final int _08_FORM_CONTAINER_FLOW_STATE = 8;
	}

	/**
	 * New instance using provided {@code connectionParam}.
	 *
	 * @param connectionParam SQL Connection to use.
	 * @param cacheUtilParam The Cache Util for better performance.
	 */
	public SQLFormUtil(Connection connectionParam, CacheUtil cacheUtilParam) {
		super(connectionParam, cacheUtilParam);

		this.formDefUtil = new SQLFormDefinitionUtil(connectionParam);
		this.fieldUtil = new SQLFormFieldUtil(
				connectionParam, cacheUtilParam, this.formDefUtil);
	}

	/**
	 * New instance using provided {@code connectionParam}.
	 *
	 * @param connectionParam SQL Connection to use.
	 */
	public SQLFormUtil(Connection connectionParam) {
		this(connectionParam, null);
	}

	/**
	 * Retrieves the Table field records as {@code List<Form>}.
	 *
	 * @param electronicFormIdParam The Form Identifier.
	 * @param includeFieldDataParam Whether to populate the return {@code List<Form>} fields.
	 * @return {@code List<Form>} records.
	 */
	public List<Form> getFormTableForms(
			Long electronicFormIdParam,
			boolean includeFieldDataParam
	) {
		List<Form> returnVal = new ArrayList();

		if(electronicFormIdParam == null) {
			return returnVal;
		}

		Map<Long,String> definitionAndTitle =
				this.formDefUtil.getFormDefinitionIdAndTitle();

		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			ISyntax syntax = SyntaxFactory.getInstance().getSyntaxFor(
					this.getSQLTypeFromConnection(),
					ISyntax.ProcedureMapping.Form.GetFormContainersTableFieldFormContainers);

			preparedStatement = this.getConnection().prepareStatement(
					syntax.getPreparedStatement());

			preparedStatement.setLong(1, electronicFormIdParam);

			resultSet = preparedStatement.executeQuery();

			//Iterate each of the form containers...
			while (resultSet.next())
			{
				returnVal.add(this.mapFormContainerTo(
						definitionAndTitle,
						resultSet));
			}

			//When field data must also be included...
			if(includeFieldDataParam)
			{
				for(Form form : returnVal)
				{
					List<Field> formFields = this.fieldUtil.getFormFields(
									form.getId(),
									false,
							false);

					form.setFormFields(formFields);
				}
			}
		} catch (SQLException sqlError) {
			throw new FluidSQLException(sqlError);
		} finally {
			this.closeStatement(preparedStatement,resultSet);
		}

		return returnVal;
	}

	/**
	 * Gets the descendants for the {@code electronicFormIdsParam} Forms.
	 *
	 * @param electronicFormIdsParam Identifiers for the Forms to retrieve.
	 * @param includeFieldDataParam Whether to populate the return {@code List<Form>} fields.
	 * @param includeTableFieldsParam Whether to populate the return {@code List<Form>} table fields.
	 * @param includeTableFieldFormRecordInfoParam Does table record form data need to be included.
	 *
	 * @return {@code List<Form>} descendants.
	 *
	 * @see Form
	 */
	@Override
	public List<Form> getFormDescendants(
			List<Long> electronicFormIdsParam,
			boolean includeFieldDataParam,
			boolean includeTableFieldsParam,
			boolean includeTableFieldFormRecordInfoParam) {

		if(electronicFormIdsParam == null || electronicFormIdsParam.isEmpty()) {
			return null;
		}

		List<Form> returnVal = new ArrayList();

		for(Long electronicFormId : electronicFormIdsParam) {
			List<Form> forTheCycle = this.getFormDescendants(
					electronicFormId,
					includeFieldDataParam,
					includeTableFieldsParam,
					includeTableFieldFormRecordInfoParam);

			if(forTheCycle == null) {
				continue;
			}

			returnVal.addAll(forTheCycle);
		}

		return returnVal;
	}

	/**
	 * Gets the descendants for the {@code electronicFormIdParam} Form.
	 *
	 * @param electronicFormIdParam Identifier for the Form.
	 * @param includeFieldDataParam Whether to populate the return {@code List<Form>} fields.
	 * @param includeTableFieldsParam Whether to populate the return {@code List<Form>} table fields.
	 * @param includeTableFieldFormRecordInfoParam Does table record form data need to be included.
	 *
	 * @return {@code List<Form>} descendants.
	 *
	 * @see Form
	 */
	public List<Form> getFormDescendants(
			Long electronicFormIdParam,
			boolean includeFieldDataParam,
			boolean includeTableFieldsParam,
			boolean includeTableFieldFormRecordInfoParam
	) {
		List<Form> returnVal = new ArrayList();

		if(electronicFormIdParam == null)
		{
			return returnVal;
		}

		Map<Long,String> definitionAndTitle =
				this.formDefUtil.getFormDefinitionIdAndTitle();

		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			ISyntax syntax = SyntaxFactory.getInstance().getSyntaxFor(
					this.getSQLTypeFromConnection(),
					ISyntax.ProcedureMapping.Form.GetFormContainersChildFormContainers);

			preparedStatement = this.getConnection().prepareStatement(
					syntax.getPreparedStatement());

			preparedStatement.setLong(1,electronicFormIdParam);

			resultSet = preparedStatement.executeQuery();

			//Iterate each of the form containers...
			while (resultSet.next())
			{
				Form mappedForm = this.mapFormContainerTo(
						definitionAndTitle,
						resultSet);

				if(mappedForm == null)
				{
					continue;
				}

				//Ancestor...
				mappedForm.setAncestorId(electronicFormIdParam);

				returnVal.add(mappedForm);
			}

			//When field data must also be included...
			if(includeFieldDataParam)
			{
				for(Form form : returnVal)
				{
					List<Field> formFields =
							this.fieldUtil.getFormFields(
									form.getId(),
									includeTableFieldsParam,
									includeTableFieldFormRecordInfoParam);

					form.setFormFields(formFields);
				}
			}
		} catch (SQLException sqlError) {
			throw new FluidSQLException(sqlError);
		} finally {
			this.closeStatement(preparedStatement,resultSet);
		}

		return returnVal;
	}

	/**
	 * Gets the descendants for the {@code electronicFormIdParam} Form with the
	 * FormContainer and FormContainerFlow state.
	 *
	 * @param electronicFormIdParam Identifier for the Form.
	 * @param includeFieldDataParam Whether to populate the return {@code List<Form>} fields.
	 * @param includeTableFieldsParam Whether to populate the return {@code List<Form>} table fields.
	 * @return {@code List<Form>} descendants.
	 *
	 * @see Form
	 */
	@Deprecated
	public List<Form> getFormDescendantsWithStates(
			Long electronicFormIdParam,
			boolean includeFieldDataParam,
			boolean includeTableFieldsParam
	) {
		List<Form> returnVal = new ArrayList();

		if(electronicFormIdParam == null) {
			return returnVal;
		}

		Map<Long,String> definitionAndTitle =
				this.formDefUtil.getFormDefinitionIdAndTitle();

		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			ISyntax syntax = SyntaxFactory.getInstance().getSyntaxFor(
					this.getSQLTypeFromConnection(),
					ISyntax.ProcedureMapping.Form.GetFormContainersChildFormContainersWithStates);

			preparedStatement = this.getConnection().prepareStatement(
					syntax.getPreparedStatement());

			preparedStatement.setLong(1, electronicFormIdParam);

			resultSet = preparedStatement.executeQuery();

			//Iterate each of the form containers...
			while (resultSet.next()) {
				Form mappedForm = this.mapFormContainerTo(
						definitionAndTitle,
						resultSet);

				if(mappedForm == null) {
					continue;
				}

				//Map the states...
				this.mapFormContainerStatesTo(mappedForm, resultSet);

				//Ancestor...
				mappedForm.setAncestorId(electronicFormIdParam);

				returnVal.add(mappedForm);
			}

			//When field data must also be included...
			if(includeFieldDataParam) {
				for(Form form : returnVal) {
					List<Field> formFields =
							this.fieldUtil.getFormFields(
									form.getId(),
									includeTableFieldsParam,
									false);
					form.setFormFields(formFields);
				}
			}
		} catch (SQLException sqlError) {
			throw new FluidSQLException(sqlError);
		} finally {
			this.closeStatement(preparedStatement,resultSet);
		}

		return returnVal;
	}

	/**
	 * Gets the ancestor for the {@code electronicFormIdParam} Form.
	 *
	 * @param electronicFormIdParam Identifier for the Form.
	 * @param includeFieldDataParam Whether to populate the return {@code Form} fields.
	 * @param includeTableFieldsParam Whether to populate the return {@code Form} table fields.
	 * @return {@code Form} descendants.
	 *
	 * @see Form
	 */
	public Form getFormAncestor(
			Long electronicFormIdParam,
			boolean includeFieldDataParam,
			boolean includeTableFieldsParam
	) {
		if(electronicFormIdParam == null) {
			return null;
		}

		Form returnVal = null;
		Map<Long,String> definitionAndTitle =
				this.formDefUtil.getFormDefinitionIdAndTitle();

		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			ISyntax syntax = SyntaxFactory.getInstance().getSyntaxFor(
					this.getSQLTypeFromConnection(),
					ISyntax.ProcedureMapping.Form.GetFormContainersParentFormContainer);

			preparedStatement = this.getConnection().prepareStatement(
					syntax.getPreparedStatement());

			preparedStatement.setLong(1,electronicFormIdParam);

			resultSet = preparedStatement.executeQuery();

			//Iterate each of the form containers...
			if (resultSet.next()) {
				returnVal = this.mapFormContainerTo(
						definitionAndTitle,
						resultSet);
			}

			//When field data must also be included...
			if(includeFieldDataParam && returnVal != null) {
				returnVal.setFormFields(
						this.fieldUtil.getFormFields(
								returnVal.getId(),
								includeTableFieldsParam,
								false));
			}

			return returnVal;
		} catch (SQLException sqlError) {
			throw new FluidSQLException(sqlError);
		} finally {
			this.closeStatement(preparedStatement,resultSet);
		}
	}

	/**
	 * Maps the Form to the provided Definition-Id and Title.
	 *
	 * @param definitionAndTitleParam Form Definition Id and Title mapping.
	 * @param resultSetParam Result Set used to populate {@code Form} with.
	 *
	 * @return Form
	 *
	 * @throws SQLException If no mapping exists for Form Type.
	 *
	 * @see ResultSet
	 */
	private Form mapFormContainerTo(
			Map<Long,String> definitionAndTitleParam,
			ResultSet resultSetParam
	) throws SQLException {
		Long formId = resultSetParam.getLong(SQLColumnIndex._01_FORM_ID);
		String formType = definitionAndTitleParam.get(
				resultSetParam.getLong(SQLColumnIndex._02_FORM_TYPE));

		String title = resultSetParam.getString(SQLColumnIndex._03_TITLE);
		Date created = resultSetParam.getDate(SQLColumnIndex._04_CREATED);
		Date lastUpdated = resultSetParam.getDate(SQLColumnIndex._05_LAST_UPDATED);
		Long currentUserId = resultSetParam.getLong(SQLColumnIndex._06_CURRENT_USER_ID);

		if(formType == null) {
			throw new SQLException("No mapping found for Form Type '"+
					resultSetParam.getLong(SQLColumnIndex._02_FORM_TYPE)+"'.");
		}

		Form toAdd = new Form(formType);

		toAdd.setId(formId);
		toAdd.setTitle(title);

		//Created...
		if(created != null) {
			toAdd.setDateCreated(new Date(created.getTime()));
		}

		//Last Updated...
		if(lastUpdated != null) {
			toAdd.setDateLastUpdated(new Date(lastUpdated.getTime()));
		}

		//Current User...
		if(currentUserId != null &&
				currentUserId.longValue() > 0) {
			User currentUser = new User();
			currentUser.setId(currentUserId);
			toAdd.setCurrentUser(currentUser);
		}

		return toAdd;
	}

	/**
	 * Maps the Form states with the {@code resultSetParam}.
	 *
	 * @param resultSetParam Result Set used to populate {@code Form} with.
	 *
	 * @return Form
	 *
	 * @throws SQLException If no mapping exists for Form Type.
	 *
	 * @see ResultSet
	 */
	private void mapFormContainerStatesTo(
			Form previousMappedForm,
			ResultSet resultSetParam
	) throws SQLException {
		if(previousMappedForm == null) {
			return;
		}

		//Form Container State...
		Long formContainerState = resultSetParam.getLong(
				SQLColumnIndex._07_FORM_CONTAINER_STATE);
		long formContStateId = (formContainerState == null) ?
				0: formContainerState.longValue();
		if(formContStateId > 0) {
			if(formContStateId == 1)
			{
				previousMappedForm.setState(Form.State.OPEN);
			}
			else if(formContStateId == 2)
			{
				previousMappedForm.setState(Form.State.LOCKED);
			}
		}

		Long formContainerFlowState = resultSetParam.getLong(
				SQLColumnIndex._08_FORM_CONTAINER_FLOW_STATE);
		long formContFlowStateId = (formContainerFlowState == null) ?
				0: formContainerFlowState.longValue();
		if(formContFlowStateId > 0) {
			if(formContFlowStateId == 1) {
				previousMappedForm.setFlowState(
						FluidItem.FlowState.NotInFlow.name());
			}
			else if(formContFlowStateId == 2) {
				previousMappedForm.setFlowState(
						FluidItem.FlowState.WorkInProgress.name());
			}
			else if(formContFlowStateId == 3) {
				previousMappedForm.setFlowState(
						FluidItem.FlowState.UserSend.name());
			}
			else if(formContFlowStateId == 4) {
				previousMappedForm.setFlowState(
						FluidItem.FlowState.UserSendWorkInProgress.name());
			}
			else if(formContFlowStateId == 5) {
				previousMappedForm.setFlowState(FluidItem.FlowState.Archive.name());
			}
		}
	}
}


/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.program.api.vo.role;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlTransient;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.fluidbpm.program.api.util.UtilGlobal;
import com.fluidbpm.program.api.vo.ABaseFluidJSONObject;
import com.fluidbpm.program.api.vo.user.User;
import com.fluidbpm.program.api.vo.userquery.UserQuery;

/**
 * <p>
 *     Represents the Fluid framework user Role.
 * </p>
 *
 * The following fields are mandatory for creating or updating a Role;
 *
 * Name (create and update)
 * Description (create and update)
 *
 * @author jasonbruwer
 * @since v1.1
 *
 * @see User
 * @see RoleToFormDefinition
 * @see RoleToJobView
 * @see UserQuery
 */
public class Role extends ABaseFluidJSONObject {

    public static final long serialVersionUID = 1L;

    private String name;
    private String description;

    private List<String> adminPermissions;
    private List<RoleToFormDefinition> roleToFormDefinitions;
    private List<RoleToFormFieldToFormDefinition> roleToFormFieldToFormDefinitions;

    private List<RoleToJobView> roleToJobViews;
    private List<RoleToUserQuery> roleToUserQueries;

    /**
     * The JSON mapping for the {@code Role} object.
     */
    public static class JSONMapping {
        public static final String NAME = "name";
        public static final String DESCRIPTION = "description";

        public static final String ADMIN_PERMISSIONS = "adminPermissions";
        public static final String ROLE_TO_FORM_DEFINITIONS = "roleToFormDefinitions";
        public static final String ROLE_TO_FORM_FIELD_TO_FORM_DEFINITIONS =
                "roleToFormFieldToFormDefinitions";
        public static final String ROLE_TO_JOB_VIEWS = "roleToJobViews";
        public static final String ROLE_TO_USER_QUERIES = "roleToUserQueries";
    }

    /**
     * Default constructor.
     */
    public Role() {
        super();
    }

    /**
     * Sets the Id associated with a 'Role'.
     *
     * @param roleIdParam Role Id.
     */
    public Role(Long roleIdParam) {
        super();
        this.setId(roleIdParam);
    }

    /**
     * Sets the Name associated with a 'Role'.
     *
     * @param roleNameParam Role Name.
     */
    public Role(String roleNameParam) {
        super();
        this.setName(roleNameParam);
    }

    /**
     * Populates local variables with {@code jsonObjectParam}.
     *
     * @param jsonObjectParam The JSON Object.
     */
    public Role(JSONObject jsonObjectParam){
        super(jsonObjectParam);

        if(this.jsonObject == null)
        {
            return;
        }

        //Name...
        if (!this.jsonObject.isNull(JSONMapping.NAME)) {
            this.setName(this.jsonObject.getString(JSONMapping.NAME));
        }

        //Description...
        if (!this.jsonObject.isNull(JSONMapping.DESCRIPTION)) {
            this.setDescription(this.jsonObject.getString(JSONMapping.DESCRIPTION));
        }

        //Admin Permissions...
        if (!this.jsonObject.isNull(JSONMapping.ADMIN_PERMISSIONS)) {

            JSONArray adminPermissionListing =
                    this.jsonObject.getJSONArray(JSONMapping.ADMIN_PERMISSIONS);

            List<String> adminPermissionList = new ArrayList();

            for(int index = 0;index < adminPermissionListing.length();index++)
            {
                adminPermissionList.add(adminPermissionListing.getString(index));
            }

            this.setAdminPermissions(adminPermissionList);
        }

        //Role to Form Definitions...
        if (!this.jsonObject.isNull(JSONMapping.ROLE_TO_FORM_DEFINITIONS)) {

            JSONArray roleToFormDefArray = this.jsonObject.getJSONArray(
                    JSONMapping.ROLE_TO_FORM_DEFINITIONS);

            List<RoleToFormDefinition> roleToFormDefListing = new ArrayList();

            for(int index = 0;index < roleToFormDefArray.length();index++)
            {
                roleToFormDefListing.add(
                        new RoleToFormDefinition(roleToFormDefArray.getJSONObject(index)));
            }

            this.setRoleToFormDefinitions(roleToFormDefListing);
        }

        //Role to Form Field to Form Definitions...
        if (!this.jsonObject.isNull(JSONMapping.ROLE_TO_FORM_FIELD_TO_FORM_DEFINITIONS)) {

            JSONArray roleToFormDefArray = this.jsonObject.getJSONArray(
                    JSONMapping.ROLE_TO_FORM_FIELD_TO_FORM_DEFINITIONS);

            List<RoleToFormFieldToFormDefinition> roleToFormDefListing = new ArrayList();

            for(int index = 0;index < roleToFormDefArray.length();index++)
            {
                roleToFormDefListing.add(
                        new RoleToFormFieldToFormDefinition(roleToFormDefArray.getJSONObject(index)));
            }

            this.setRoleToFormFieldToFormDefinitions(roleToFormDefListing);
        }

        //Role to Job Views...
        if (!this.jsonObject.isNull(JSONMapping.ROLE_TO_JOB_VIEWS)) {

            JSONArray roleToJobViewDefArray = this.jsonObject.getJSONArray(
                    JSONMapping.ROLE_TO_JOB_VIEWS);

            List<RoleToJobView> roleToFormDefListing = new ArrayList();

            for(int index = 0;index < roleToJobViewDefArray.length();index++)
            {
                roleToFormDefListing.add(
                        new RoleToJobView(roleToJobViewDefArray.getJSONObject(index)));
            }

            this.setRoleToJobViews(roleToFormDefListing);
        }

        //Role to User Queries...
        if (!this.jsonObject.isNull(JSONMapping.ROLE_TO_USER_QUERIES)) {

            JSONArray userQueryArray = this.jsonObject.getJSONArray(
                    JSONMapping.ROLE_TO_USER_QUERIES);

            List<RoleToUserQuery> userQueryListing = new ArrayList();

            for(int index = 0;index < userQueryArray.length();index++)
            {
                userQueryListing.add(
                        new RoleToUserQuery(userQueryArray.getJSONObject(index)));
            }

            this.setRoleToUserQueries(userQueryListing);
        }
    }

    /**
     * Convert the comma separated list of roles as objects.
     *
     * @param roleListingParam The comma separated role listing.
     *                         Example; {@code "Admin, Super Admin, HR, Finance"}
     *
     * @return The {@code roleListingParam} as {@code Role} objects.
     */
    @XmlTransient
    public static List<Role> convertToObjects(String roleListingParam)
    {
        if(roleListingParam == null || roleListingParam.trim().isEmpty())
        {
            return null;
        }

        String[] listOfRoles = roleListingParam.split(UtilGlobal.REG_EX_COMMA);

        List<Role> returnVal = new ArrayList<>();

        for(String roleName : listOfRoles)
        {
            Role roleToAdd = new Role();
            roleToAdd.setName(roleName.trim());

            returnVal.add(roleToAdd);
        }

        return returnVal;
    }

    /**
     * Gets {@code Role} name.
     *
     * @return A {@code Role}s name.
     */
    public String getName() {
        return this.name;
    }

    /**
     * Sets {@code Role} name.
     *
     * @param nameParam A {@code Role} name.
     */
    public void setName(String nameParam) {
        this.name = nameParam;
    }

    /**
     * Gets {@code Role} description.
     *
     * @return A {@code Role}s description.
     */
    public String getDescription() {
        return this.description;
    }

    /**
     * Sets {@code Role} description.
     *
     * @param descriptionParam A {@code Role}s description.
     */
    public void setDescription(String descriptionParam) {
        this.description = descriptionParam;
    }

    /**
     * Gets {@code Role} associated {@code RoleToFormDefinition}s.
     *
     * @return A {@code Role}s {@code RoleToFormDefinition}s.
     *
     * @see RoleToFormDefinition
     */
    public List<RoleToFormDefinition> getRoleToFormDefinitions() {
        return this.roleToFormDefinitions;
    }

    /**
     * Sets {@code Role} associated {@code RoleToFormDefinition}s.
     *
     * @param roleToFormDefinitionsParam A {@code Role}s {@code RoleToFormDefinition}s.
     *
     * @see RoleToFormDefinition
     */
    public void setRoleToFormDefinitions(List<RoleToFormDefinition> roleToFormDefinitionsParam) {
        this.roleToFormDefinitions = roleToFormDefinitionsParam;
    }

    /**
     * Gets {@code Role} associated {@code RoleToFormFieldToFormDefinition}s.
     *
     * @return A {@code Role}s {@code RoleToFormFieldToFormDefinition}s.
     *
     * @see RoleToFormFieldToFormDefinition
     */
    public List<RoleToFormFieldToFormDefinition> getRoleToFormFieldToFormDefinitions() {
        return this.roleToFormFieldToFormDefinitions;
    }

    /**
     * Gets {@code Role} associated {@code RoleToFormFieldToFormDefinition}s.
     *
     * @param roleToFormFieldToFormDefinitionsParam
     * A {@code Role}s {@code RoleToFormFieldToFormDefinition}s.
     *
     * @see RoleToFormFieldToFormDefinition
     */
    public void setRoleToFormFieldToFormDefinitions(
            List<RoleToFormFieldToFormDefinition> roleToFormFieldToFormDefinitionsParam) {
        this.roleToFormFieldToFormDefinitions = roleToFormFieldToFormDefinitionsParam;
    }

    /**
     * Gets {@code Role} Administration Permissions.
     *
     * @return A {@code Role}s Administration Permissions.
     */
    public List<String> getAdminPermissions() {
        return this.adminPermissions;
    }

    /**
     * Sets {@code Role} Administration Permissions.
     *
     * @param adminPermissionsParam A {@code Role}s Administration Permissions.
     */
    public void setAdminPermissions(List<String> adminPermissionsParam) {
        this.adminPermissions = adminPermissionsParam;
    }

    /**
     * Gets {@code Role} Job Views.
     *
     * @return A {@code Role}s Job Views.
     */
    public List<RoleToJobView> getRoleToJobViews() {
        return this.roleToJobViews;
    }

    /**
     * Sets {@code Role} Job Views.
     *
     * @param roleToJobViewsParam A {@code Role}s Job Views.
     */
    public void setRoleToJobViews(List<RoleToJobView> roleToJobViewsParam) {
        this.roleToJobViews = roleToJobViewsParam;
    }

    /**
     * Gets {@code RoleToUserQuery}s.
     *
     * @return A {@code Role}s associated {@code RoleToUserQuery}s.
     *
     * @see UserQuery
     */
    public List<RoleToUserQuery> getRoleToUserQueries() {
        return this.roleToUserQueries;
    }

    /**
     * Sets {@code RoleToUserQuery}s.
     *
     * @param userQueriesParam A {@code Role}s associated {@code RoleToUserQuery}s.
     *
     * @see UserQuery
     */
    public void setRoleToUserQueries(List<RoleToUserQuery> userQueriesParam) {
        this.roleToUserQueries = userQueriesParam;
    }

    /**
     * Conversion to {@code JSONObject} from Java Object.
     *
     * @return {@code JSONObject} representation of {@code Role}
     * @throws JSONException If there is a problem with the JSON Body.
     *
     * @see ABaseFluidJSONObject#toJsonObject()
     */
    @Override
    public JSONObject toJsonObject() throws JSONException {

        JSONObject returnVal = super.toJsonObject();

        //Name...
        if(this.getName() != null)
        {
            returnVal.put(JSONMapping.NAME,this.getName());
        }

        //Description...
        if(this.getDescription() != null)
        {
            returnVal.put(JSONMapping.DESCRIPTION,this.getDescription());
        }

        //Admin Permissions...
        if(this.getAdminPermissions() != null &&
                !this.getAdminPermissions().isEmpty())
        {
            JSONArray adminPerArr = new JSONArray();

            for(String toAdd : this.getAdminPermissions())
            {
                adminPerArr.put(toAdd);
            }

            returnVal.put(JSONMapping.ADMIN_PERMISSIONS, adminPerArr);
        }

        //Role to Form Definitions...
        if(this.getRoleToFormDefinitions() != null &&
                !this.getRoleToFormDefinitions().isEmpty())
        {
            JSONArray roleToFormDefArr = new JSONArray();
            for(RoleToFormDefinition toAdd :this.getRoleToFormDefinitions())
            {
                roleToFormDefArr.put(toAdd.toJsonObject());
            }

            returnVal.put(JSONMapping.ROLE_TO_FORM_DEFINITIONS,roleToFormDefArr);
        }

        //Role To Form Field To Form Definition...
        if(this.getRoleToFormFieldToFormDefinitions() != null &&
                !this.getRoleToFormFieldToFormDefinitions().isEmpty())
        {
            JSONArray roleToJobViewArr = new JSONArray();
            for(RoleToFormFieldToFormDefinition toAdd :this.getRoleToFormFieldToFormDefinitions())
            {
                roleToJobViewArr.put(toAdd.toJsonObject());
            }

            returnVal.put(JSONMapping.ROLE_TO_FORM_FIELD_TO_FORM_DEFINITIONS, roleToJobViewArr);
        }

        //Role to Job Views...
        if(this.getRoleToJobViews() != null &&
                !this.getRoleToJobViews().isEmpty())
        {
            JSONArray roleToJobViewArr = new JSONArray();
            for(RoleToJobView toAdd :this.getRoleToJobViews())
            {
                roleToJobViewArr.put(toAdd.toJsonObject());
            }

            returnVal.put(JSONMapping.ROLE_TO_JOB_VIEWS, roleToJobViewArr);
        }

        //Role to User Queries...
        if(this.getRoleToUserQueries() != null &&
                !this.getRoleToUserQueries().isEmpty())
        {
            JSONArray userQueriesArr = new JSONArray();
            for(RoleToUserQuery toAdd :this.getRoleToUserQueries())
            {
                userQueriesArr.put(toAdd.toJsonObject());
            }

            returnVal.put(JSONMapping.ROLE_TO_USER_QUERIES, userQueriesArr);
        }

        return returnVal;
    }
}


/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.ws.client.v1.form;

import java.util.List;

import org.json.JSONObject;

import com.fluidbpm.program.api.vo.field.Field;
import com.fluidbpm.program.api.vo.form.Form;
import com.fluidbpm.program.api.vo.form.FormListing;
import com.fluidbpm.program.api.vo.ws.WS;
import com.fluidbpm.ws.client.v1.ABaseClientWS;

/**
 * Java Web Service Client for Form Definition related actions.
 *
 * @author jasonbruwer
 * @since v1.0
 *
 * @see JSONObject
 * @see com.fluidbpm.program.api.vo.ws.WS.Path.FormDefinition
 * @see Form
 */
public class FormDefinitionClient extends ABaseClientWS {

    /**
     * Constructor that sets the Service Ticket from authentication.
     *
     * @param endpointBaseUrlParam URL to base endpoint.
     * @param serviceTicketParam The Server issued Service Ticket.
     */
    public FormDefinitionClient(
            String endpointBaseUrlParam,
            String serviceTicketParam) {
        super(endpointBaseUrlParam);

        this.setServiceTicket(serviceTicketParam);
    }

    /**
     * Creates a new Form Definition with the Fields inside the definition.
     *
     * @param formDefinitionParam The Definition to create.
     * @return The Created Form Definition.
     *
     * @see Field
     * @see Form
     */
    public Form createFormDefinition(Form formDefinitionParam)
    {
        if(formDefinitionParam != null && this.serviceTicket != null)
        {
            formDefinitionParam.setServiceTicket(this.serviceTicket);
        }

        return new Form(this.putJson(
                formDefinitionParam, WS.Path.FormDefinition.Version1.formDefinitionCreate()));
    }

    /**
     * Updates an existing Form Definition with the Fields inside the definition.
     *
     * @param formDefinitionParam The Definition to update.
     * @return The Updated Form Definition.
     *
     * @see Field
     * @see Form
     */
    public Form updateFormDefinition(Form formDefinitionParam)
    {
        if(formDefinitionParam != null && this.serviceTicket != null)
        {
            formDefinitionParam.setServiceTicket(this.serviceTicket);
        }

        return new Form(this.postJson(
                formDefinitionParam,
                WS.Path.FormDefinition.Version1.formDefinitionUpdate()));
    }

    /**
     * Retrieves the Form Definition by Primary key.
     *
     * @param formDefinitionIdParam The Form Definition primary key.
     * @return Form by Primary key.
     */
    public Form getFormDefinitionById(Long formDefinitionIdParam)
    {
        Form form = new Form(formDefinitionIdParam);

        if(this.serviceTicket != null)
        {
            form.setServiceTicket(this.serviceTicket);
        }

        return new Form(this.postJson(
                form, WS.Path.FormDefinition.Version1.getById()));
    }

    /**
     * Retrieves the Form Definition by Name.
     *
     * @param formDefinitionNameParam The Form Definition name.
     * @return Form by Name.
     */
    public Form getFormDefinitionByName(String formDefinitionNameParam)
    {
        Form form = new Form(formDefinitionNameParam);

        if(this.serviceTicket != null)
        {
            form.setServiceTicket(this.serviceTicket);
        }

        return new Form(this.postJson(
                form, WS.Path.FormDefinition.Version1.getByName()));
    }

    /**
     * Retrieves all Form Definitions by logged in user.
     *
     * @param includeTableRecordTypesParam Include the Table Record form definitions.
     *
     * @return List of Form Definitions.
     */
    public List<Form> getAllByLoggedInUser(boolean includeTableRecordTypesParam)
    {
        Form form = new Form();

        if(this.serviceTicket != null)
        {
            form.setServiceTicket(this.serviceTicket);
        }

        if(includeTableRecordTypesParam)
        {
            return new FormListing(this.postJson(
                    form, WS.Path.FormDefinition.Version1.getAllByLoggedInUserIncludeTableTypes())).getListing();
        }
        else
        {
            return new FormListing(this.postJson(
                    form, WS.Path.FormDefinition.Version1.getAllByLoggedInUser())).getListing();
        }
    }

    /**
     * Retrieves all Form Definitions where the logged in user can
     * create a new instance of the {@code Form}.
     *
     * @return List of Form Definitions.
     */
    public List<Form> getAllByLoggedInUserWhereCanCreateInstanceOf()
    {
        Form form = new Form();

        if(this.serviceTicket != null)
        {
            form.setServiceTicket(this.serviceTicket);
        }

        return new FormListing(this.postJson(
                form, WS.Path.FormDefinition.Version1.getAllByLoggedInAndCanCreateInstanceOf())).
                getListing();
    }

    /**
     * Deletes the Form Definition provided.
     * Id must be set on the Form Definition.
     *
     * @param formDefinitionParam The Form Definition to Delete.
     * @return The deleted Form Definition.
     */
    public Form deleteFormDefinition(Form formDefinitionParam)
    {
        if(formDefinitionParam != null && this.serviceTicket != null)
        {
            formDefinitionParam.setServiceTicket(this.serviceTicket);
        }

        return new Form(this.postJson(formDefinitionParam,
                WS.Path.FormDefinition.Version1.formDefinitionDelete()));
    }

}


/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.program.api.util.cache;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeoutException;

import com.fluidbpm.program.api.util.ABaseUtil;
import com.fluidbpm.program.api.util.cache.exception.FluidCacheException;
import com.fluidbpm.program.api.vo.field.Field;
import com.fluidbpm.program.api.vo.field.MultiChoice;

import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.XMemcachedClient;
import net.rubyeye.xmemcached.exception.MemcachedException;

/**
 * Cache Utility class used for {@code Field} value retrieval actions.
 *
 * @author jasonbruwer on 2016/02/29.
 * @since 1.0
 *
 * @see ABaseUtil
 */
public class CacheUtil extends ABaseUtil {

	private static final String NULL = "null";
	private static final String DASH = "-";

	private transient MemcachedClient memcachedClient;

	private String cacheHost = null;
	private int cachePort = -1;

	/**
	 * Aliases for properties used by the {@code CacheUtil}.
	 */
	private static class PropName
	{
		public static final String MEMORY_CACHE_TYPE = "MemoryCacheType";
		public static final String MEMORY_CACHE_HOSTNAME = "MemoryCacheHostname";
		public static final String MEMORY_CACHE_PORT_NUMBER = "MemoryCachePortNumber";
	}

	/**
	 * The FlowJob data type description mappings.
	 * See Fluid configuration.
	 */
	private static class FlowJobType
	{
		public static final String DATE_TIME = "Date Time";
		public static final String DECIMAL = "Decimal";
		public static final String MULTIPLE_CHOICE = "Multiple Choice";
		public static final String PARAGRAPH_TEXT = "Paragraph Text";
		public static final String TABLE_FIELD = "Table Field";
		public static final String TEXT = "Text";
		public static final String TEXT_ENCRYPTED = "Text Encrypted";
		public static final String TRUE_FALSE = "True / False";
		public static final String LABEL = "Label";
	}

	/**
	 * Enum for mapping the Fluid data types to Flow-Job.
	 */
	private enum FlowJobTypeMapping
	{
		DateTime(FlowJobType.DATE_TIME, Field.Type.DateTime),
		Decimal(FlowJobType.DECIMAL, Field.Type.Decimal),
		MultiChoice(FlowJobType.MULTIPLE_CHOICE, Field.Type.MultipleChoice),
		ParagraphText(FlowJobType.PARAGRAPH_TEXT, Field.Type.ParagraphText),
		TableField(FlowJobType.TABLE_FIELD, Field.Type.Table),
		Text(FlowJobType.TEXT, Field.Type.Text),
		TextEncrypted(FlowJobType.TEXT_ENCRYPTED, Field.Type.TextEncrypted),
		TrueFalse(FlowJobType.TRUE_FALSE, Field.Type.TrueFalse),
		Label(FlowJobType.LABEL, Field.Type.Label),
		;

		private String flowJobDataTypeDesc;
		private Field.Type fluidType;

		/**
		 * Maps the Fluid to the Flow-Job data types.
		 *
		 * @param flowJobDataTypeDescParam The Flow-Job text data type.
		 * @param fluidTypeParam The Fluid enum type.
		 */
		FlowJobTypeMapping(
				String flowJobDataTypeDescParam,
				Field.Type fluidTypeParam)
		{
			this.flowJobDataTypeDesc = flowJobDataTypeDescParam;
			this.fluidType = fluidTypeParam;
		}

		/**
		 * Retrieves the Fluid data type from the Flow-Hob data
		 * type description.
		 *
		 * @param flowJobTypeParam The Flow-Job type.
		 * @return Fluid Field Type from Flow-Job type.
		 */
		public static Field.Type getFluidTypeFromFlowJobType(
				String flowJobTypeParam)
		{
			if(flowJobTypeParam == null || flowJobTypeParam.trim().isEmpty())
			{
				return null;
			}

			for(FlowJobTypeMapping mapping : values())
			{
				if(flowJobTypeParam.equals(mapping.flowJobDataTypeDesc))
				{
					return mapping.fluidType;
				}
			}

			return null;
		}
	}

	/**
	 * Fluid API cached field value.
	 */
	public static class CachedFieldValue implements Serializable
	{
		private Object cachedFieldValue;
		private String dataType;

		/**
		 * Converts the cached value to Fluid Field.
		 *
		 * @return Fluid Field.
		 *
		 * @see Field
		 */
		public Field getCachedFieldValueAsField()
		{
			Field returnVal = new Field();

			//No table field...
			if(FlowJobType.TABLE_FIELD.equals(this.dataType))
			{
				return null;
			}
			else
			{
				returnVal.setFieldValue(this.cachedFieldValue);
			}

			//Set the Type as Enum...
			returnVal.setTypeAsEnum(
					FlowJobTypeMapping.getFluidTypeFromFlowJobType(dataType));

			return returnVal;
		}
	}

	/**
	 * New instance of cache util using the
	 * provided {@code propertiesParam}.
	 *
	 * @param propertiesParam The Properties to extract the cache configs from.
	 */
	public CacheUtil(Properties propertiesParam) {
		this(
				getStringPropertyFromProperties(
						propertiesParam, PropName.MEMORY_CACHE_HOSTNAME),
				getIntPropertyFromProperties(
						propertiesParam, PropName.MEMORY_CACHE_PORT_NUMBER));
	}

	/**
	 * New instance of cache util using the
	 * provided Host {@code cacheHostParam} and
	 * Port {@code cachePortParam}.
	 *
	 * @param cacheHostParam The MemCache Host IP or hostname.
	 * @param cachePortParam The MemCache Port.
	 */
	public CacheUtil(
			String cacheHostParam,
			int cachePortParam) {

		this.cacheHost = cacheHostParam;
		this.cachePort = cachePortParam;

		if(this.cacheHost == null || this.cacheHost.trim().isEmpty())
		{
			throw new FluidCacheException("Cache Host cannot be empty.");
		}

		if(this.cachePort < 1 || this.cachePort > 65535)
		{
			throw new FluidCacheException("Cache Port number '"+this.cachePort+"' is invalid.");
		}

		this.initXMemcachedClient();
	}

	/**
	 * Retrieves the {@code CachedFieldValue} value stored under
	 * the params.
	 *
	 * @param formDefIdParam The Form Definition Id.
	 * @param formContIdParam The Form Container Id.
	 * @param formFieldIdParam The Form Field Id.
	 *
	 * @return Storage Key
	 */
	public CachedFieldValue getCachedFieldValueFrom(
			Long formDefIdParam,
			Long formContIdParam,
			Long formFieldIdParam)
	{
		if((formDefIdParam == null || formContIdParam == null) ||
				formFieldIdParam == null)
		{
			return null;
		}

		String storageKey = this.getStorageKeyFrom(
				formDefIdParam,
				formContIdParam,
				formFieldIdParam);

		Object objWithKey;
		try {
			objWithKey = this.memcachedClient.get(storageKey);
		}
		//Changed for Java 1.6 compatibility...
		catch (MemcachedException e) {

			throw new FluidCacheException("Unable to get Field value for '"+storageKey+"'." +
					"Contact administrator. "+e.getMessage(),e);
		} catch (TimeoutException e) {

			throw new FluidCacheException("Unable to get Field value for '"+storageKey+"'." +
					"Contact administrator. "+e.getMessage(),e);
		} catch (InterruptedException e) {

			throw new FluidCacheException("Unable to get Field value for '"+storageKey+"'." +
					"Contact administrator. "+e.getMessage(),e);
		}

		return this.getCacheFieldValueFromObject(objWithKey);
	}

	/**
	 * Retrieves the MemCached server descriptions from the MemCached client.
	 * Performs a connection test.
	 *
	 * @return Servers descriptions from MemCached client.
	 *
	 * @see MemcachedClient#getServersDescription()
	 */
	public List<String> getMemcacheServersDescription()
	{
		if(this.memcachedClient == null)
		{
			throw new FluidCacheException(
					"MemCached client is not set.");
		}

		return this.memcachedClient.getServersDescription();
	}

	/**
	 * Converts the {@code objWithKeyParam} Object to {@code CachedFieldValue}.
	 *
	 * @param objWithKeyParam The retrieved cached object.
	 *
	 * @return CachedFieldValue from {@code objWithKeyParam}.
	 */
	@SuppressWarnings("unchecked")
	private CachedFieldValue getCacheFieldValueFromObject(Object objWithKeyParam)
	{
		if(objWithKeyParam == null)
		{
			return null;
		}

		//Get Word...
		Method methodGetWord = CacheUtil.getMethod(
				objWithKeyParam.getClass(),
				CustomCode.IWord.METHOD_getWord);

		//Get Value...
		Method methodGetValue = CacheUtil.getMethod(
				objWithKeyParam.getClass(),
				CustomCode.ADataType.METHOD_getValue);

		//Word...
		Object getWordObj = CacheUtil.invoke(methodGetWord, objWithKeyParam);
		String getWordVal = null;
		if(getWordObj instanceof String)
		{
			getWordVal = (String)getWordObj;
		}

		//Value...
		Object getValueObj;
		if(FlowJobType.MULTIPLE_CHOICE.equals(getWordVal))
		{
			MultiChoice multiChoice = new MultiChoice();

			//Available Choices...
			Method methodAvailableChoices = getMethod(
					objWithKeyParam.getClass(),
					CustomCode.MultipleChoice.METHOD_getAvailableChoices);

			Object availChoicesObj =
					CacheUtil.invoke(methodAvailableChoices, objWithKeyParam);

			if(availChoicesObj instanceof List)
			{
				multiChoice.setAvailableMultiChoices((List)availChoicesObj);
			}

			//Selected...
			Method methodSelectedChoices = getMethod(
					objWithKeyParam.getClass(),
					CustomCode.MultipleChoice.METHOD_getSelectedChoices);

			Object selectedChoicesObj =
					invoke(methodSelectedChoices, objWithKeyParam);

			if(selectedChoicesObj instanceof List)
			{
				multiChoice.setSelectedMultiChoices((List)selectedChoicesObj);
			}

			getValueObj = multiChoice;
		}
		else
		{
			getValueObj = CacheUtil.invoke(methodGetValue, objWithKeyParam);
		}

		if(getValueObj == null)
		{
			return null;
		}

		if(getWordVal == null)
		{
			throw new FluidCacheException(
					"Get Word value is 'null'. Not allowed.");
		}

		CachedFieldValue returnVal = new CachedFieldValue();

		returnVal.dataType = getWordVal;
		returnVal.cachedFieldValue = getValueObj;

		return returnVal;
	}

	/**
	 * Retrieves the java method from class {@code clazzParam}.
	 *
	 * @param clazzParam The class.
	 * @param nameParam The class name.
	 *
	 * @return Method from {@code clazzParam} and {@code nameParam}.
	 */
	@SuppressWarnings("unchecked")
	private static Method getMethod(Class clazzParam, String nameParam)
	{
		try {
			if(clazzParam == null || nameParam == null)
			{
				return null;
			}

			Method returnVal = clazzParam.getDeclaredMethod(nameParam);
			returnVal.setAccessible(true);

			return returnVal;
		}
		//
		catch (NoSuchMethodException e) {

			throw new FluidCacheException(
					"Unable to get method '"+
							nameParam +"'. "+e.getMessage(),e);
		}
	}

	/**
	 * Invokes the {@code methodParam} method on {@code objParam}.
	 *
	 * @param methodParam The method to invoke.
	 * @param objParam The object to invoke the method on.
	 *
	 * @return The result of the invoked object.
	 */
	private static Object invoke(Method methodParam, Object objParam)
	{
		try {
			return methodParam.invoke(objParam);
		}
		//Changed for Java 1.6 compatibility...
		catch (InvocationTargetException e) {

			throw new FluidCacheException(
					"Unable to invoke method '"+
							methodParam.getName() +"'. "+e.getMessage(),e);
		} catch (IllegalAccessException e) {

			throw new FluidCacheException(
					"Unable to invoke method '"+
							methodParam.getName() +"'. "+e.getMessage(),e);
		} catch (IllegalArgumentException e) {

			throw new FluidCacheException(
					"Unable to invoke method '"+
							methodParam.getName() +"'. "+e.getMessage(),e);
		}
	}

	/**
	 * Generates the storage key the provided parameters.
	 *
	 * @param formDefIdParam The Form Definition Id.
	 * @param formContIdParam The Form Container Id.
	 * @param formFieldIdParam The Form Field Id.
	 *
	 * @return Storage Key
	 */
	private String getStorageKeyFrom(
			Long formDefIdParam,
			Long formContIdParam,
			Long formFieldIdParam)
	{
		StringBuilder stringBuff = new StringBuilder();

		//Form Definition...
		if(formDefIdParam == null)
		{
			stringBuff.append(NULL);
		}
		else
		{
			stringBuff.append(formDefIdParam.toString());
		}

		stringBuff.append(DASH);

		//Form Container...
		if(formContIdParam == null)
		{
			stringBuff.append(NULL);
		}
		else
		{
			stringBuff.append(formContIdParam.toString());
		}

		stringBuff.append(DASH);

		//Form Field...
		if(formFieldIdParam == null)
		{
			stringBuff.append(NULL);
		}
		else {
			stringBuff.append(formFieldIdParam.toString());
		}

		return stringBuff.toString();
	}

	/**
	 * Creates an instance of MemcachedClient.
	 *
	 * @return MemcachedClient
	 */
	private MemcachedClient initXMemcachedClient()
	{
		if(this.memcachedClient != null && !this.memcachedClient.isShutdown())
		{
			return this.memcachedClient;
		}

		try{
			this.memcachedClient = new XMemcachedClient(
					this.cacheHost,this.cachePort);

			return this.memcachedClient;
		}
		//Unable to create client with connection.
		catch (IOException e) {

			throw new FluidCacheException(
					"Unable to create MemCache client. "+e.getMessage(), e);
		}
	}

	/**
	 * Closes the Memcached client connection.
	 */
	public void shutdown()
	{
		if(this.memcachedClient != null &&
				!this.memcachedClient.isShutdown())
		{
			try {
				this.memcachedClient.shutdown();
			}
			//
			catch (IOException eParam) {

				throw new FluidCacheException(
						"Unable to create shutdown MemCache client. "+eParam.getMessage(), eParam);
			}
		}
	}
}


/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.program.api.vo.flow;

import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.fluidbpm.program.api.vo.ABaseFluidJSONObject;

/**
 * Fluid workflow Step Rule that belongs to,
 * Entry Step, Exit Step and View.
 *
 * @author jasonbruwer
 * @since v1.0
 *
 * @see Flow
 * @see FlowStep
 * @see ABaseFluidJSONObject
 */
public class FlowStepRule extends ABaseFluidJSONObject {

    public static final long serialVersionUID = 1L;

    private Long order;
    private String rule;

    private Flow flow;
    private FlowStep flowStep;

    private List<String> nextValidSyntaxWords;
    private String currentTypedSyntax;

    /**
     * The JSON mapping for the {@code FlowStepRule} object.
     */
    public static class JSONMapping
    {
        public static final String ORDER = "order";
        public static final String RULE = "rule";
        public static final String FLOW = "flow";
        public static final String FLOW_STEP = "flowStep";

        public static final String NEXT_VALID_SYNTAX_WORDS = "nextValidSyntaxWords";
        public static final String CURRENT_TYPED_SYNTAX = "currentTypedSyntax";
    }

    /**
     * Default constructor.
     */
    public FlowStepRule() {
        super();
    }

    /**
     * The unique identifier of the Step Rule.
     *
     * @param flowStepRuleIdParam Rule Primary Key.
     */
    public FlowStepRule(Long flowStepRuleIdParam) {
        super();

        this.setId(flowStepRuleIdParam);
    }

    /**
     * Populates local variables with {@code jsonObjectParam}.
     *
     * @param jsonObjectParam The JSON Object.
     */
    public FlowStepRule(JSONObject jsonObjectParam) {
        super(jsonObjectParam);

        if(this.jsonObject == null)
        {
            return;
        }

        //Order...
        if (!this.jsonObject.isNull(JSONMapping.ORDER)) {
            this.setOrder(this.jsonObject.getLong(JSONMapping.ORDER));
        }

        //Rule...
        if (!this.jsonObject.isNull(JSONMapping.RULE)) {
            this.setRule(this.jsonObject.getString(JSONMapping.RULE));
        }

        //Current Typed Syntax...
        if (!this.jsonObject.isNull(JSONMapping.CURRENT_TYPED_SYNTAX)) {
            this.setCurrentTypedSyntax(this.jsonObject.getString(JSONMapping.CURRENT_TYPED_SYNTAX));
        }

        //Flow...
        if (!this.jsonObject.isNull(JSONMapping.FLOW)) {
            this.setFlow(new Flow(this.jsonObject.getJSONObject(JSONMapping.FLOW)));
        }

        //Flow Step...
        if (!this.jsonObject.isNull(JSONMapping.FLOW_STEP)) {
            this.setFlowStep(new FlowStep(this.jsonObject.getJSONObject(JSONMapping.FLOW_STEP)));
        }

        //Next Valid Syntax Words...
        if (!this.jsonObject.isNull(JSONMapping.NEXT_VALID_SYNTAX_WORDS)) {

            JSONArray listOfValidWordsArray =
                    this.jsonObject.getJSONArray(JSONMapping.NEXT_VALID_SYNTAX_WORDS);

            List<String> validWordsString = new ArrayList<String>();

            for(int index = 0;index < listOfValidWordsArray.length();index++)
            {
                validWordsString.add(listOfValidWordsArray.getString(index));
            }

            this.setNextValidSyntaxWords(validWordsString);
        }
    }

    /**
     * Conversion to {@code JSONObject} from Java Object.
     *
     * @return {@code JSONObject} representation of {@code FlowStepRule}
     * @throws JSONException If there is a problem with the JSON Body.
     *
     * @see ABaseFluidJSONObject#toJsonObject()
     */
    @Override
    public JSONObject toJsonObject() throws JSONException
    {
        JSONObject returnVal = super.toJsonObject();

        //Order...
        if(this.getOrder() != null)
        {
            returnVal.put(JSONMapping.ORDER, this.getOrder());
        }

        //Rule...
        if(this.getRule() != null)
        {
            returnVal.put(JSONMapping.RULE, this.getRule());
        }

        //Current Typed Syntax...
        if(this.getCurrentTypedSyntax() != null)
        {
            returnVal.put(JSONMapping.CURRENT_TYPED_SYNTAX,
                    this.getCurrentTypedSyntax());
        }

        //Flow...
        if(this.getFlow() != null)
        {
            returnVal.put(JSONMapping.FLOW, this.getFlow().toJsonObject());
        }

        //Flow Step...
        if(this.getFlowStep() != null)
        {
            returnVal.put(JSONMapping.FLOW_STEP, this.getFlowStep().toJsonObject());
        }

        //Next Valid Syntax Words...
        if(this.getNextValidSyntaxWords() != null && !this.getNextValidSyntaxWords().isEmpty())
        {
            JSONArray jsonArrayOfValidWords = new JSONArray();

            for(String validWord : this.getNextValidSyntaxWords())
            {
                jsonArrayOfValidWords.put(validWord);
            }

            returnVal.put(JSONMapping.NEXT_VALID_SYNTAX_WORDS, jsonArrayOfValidWords);
        }

        return returnVal;
    }

    /**
     * The order within the Entry, Exit or View.
     *
     * @return The order of the rule.
     */
    public Long getOrder() {
        return this.order;
    }

    /**
     * The order within the Entry, Exit or View.
     *
     * @param orderParam The order of the rule.
     */
    public void setOrder(Long orderParam) {
        this.order = orderParam;
    }

    /**
     * Gets the Rule as text.
     *
     * @return Text version of the rule.
     */
    public String getRule() {
        return this.rule;
    }

    /**
     * Sets the Rule as text.
     *
     * @param ruleParam Text version of the rule.
     */
    public void setRule(String ruleParam) {
        this.rule = ruleParam;
    }

    /**
     * The {@code Flow} the rule forms part of.
     *
     * @return The Flow of the rule.
     */
    public Flow getFlow() {
        return this.flow;
    }

    /**
     * The {@code Flow} the rule forms part of.
     *
     * @param flowParam The Flow of the rule.
     */
    public void setFlow(Flow flowParam) {
        this.flow = flowParam;
    }

    /**
     * The {@code FlowStep} the rule forms part of.
     *
     * @return The Flow Step of the rule.
     */
    public FlowStep getFlowStep() {
        return this.flowStep;
    }

    /**
     * The {@code FlowStep} the rule forms part of.
     *
     * @param flowStepParam The Flow Step of the rule.
     */
    public void setFlowStep(FlowStep flowStepParam) {
        this.flowStep = flowStepParam;
    }

    /**
     * Typed syntax to provide the {@code getNextValidSyntaxWords}.
     *
     * @return Text entered to get the next valid syntax words.
     */
    public String getCurrentTypedSyntax() {
        return this.currentTypedSyntax;
    }

    /**
     * Typed syntax to provide the {@code getNextValidSyntaxWords}.
     *
     * @param currentTypedSyntaxParam Text entered to get the next valid syntax words.
     */
    public void setCurrentTypedSyntax(String currentTypedSyntaxParam) {
        this.currentTypedSyntax = currentTypedSyntaxParam;
    }

    /**
     * The next valid words from {@code getCurrentTypedSyntax}.
     *
     * @return Next valid syntax.
     */
    public List<String> getNextValidSyntaxWords() {
        return this.nextValidSyntaxWords;
    }

    /**
     * The next valid words from {@code getCurrentTypedSyntax}.
     *
     * @param nextValidSyntaxWordsParam Next valid syntax.
     */
    public void setNextValidSyntaxWords(List<String> nextValidSyntaxWordsParam) {
        this.nextValidSyntaxWords = nextValidSyntaxWordsParam;
    }
}


/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.ws.client.v1.sqlutil;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.json.JSONObject;

import com.fluidbpm.program.api.util.UtilGlobal;
import com.fluidbpm.program.api.vo.form.Form;
import com.fluidbpm.program.api.vo.form.FormListing;
import com.fluidbpm.program.api.vo.item.FluidItem;
import com.fluidbpm.program.api.vo.ws.WS;
import com.fluidbpm.ws.client.FluidClientException;
import com.fluidbpm.ws.client.v1.websocket.ABaseClientWebSocket;
import com.fluidbpm.ws.client.v1.websocket.AGenericListMessageHandler;
import com.fluidbpm.ws.client.v1.websocket.IMessageReceivedCallback;

/**
 * Java Web Socket Client for {@code SQLUtil} related actions.
 *
 * @author jasonbruwer
 * @since v1.0
 *
 * @see JSONObject
 * @see WS.Path.FlowItem
 * @see FluidItem
 */
public class SQLUtilWebSocketGetDescendantsClient extends
		ABaseClientWebSocket<AGenericListMessageHandler<FormListing>> {

	private boolean massFetch;

	/**
	 * Constructor that sets the Service Ticket from authentication.
	 *
	 * @param endpointBaseUrlParam URL to base endpoint.
	 * @param messageReceivedCallbackParam Callback for when a message is received.
	 * @param serviceTicketAsHexParam The Server issued Service Ticket.
	 * @param timeoutInMillisParam The timeout of the request in millis.
	 * @param includeFieldDataParam Should Form Field data be included.
	 * @param includeTableFieldsParam Should Table Fields be included.
	 * @param includeTableFieldFormRecordInfoParam Does table record form data need to be included.
	 * @param massFetchParam Is the fetch a large fetch.
	 * @param compressResponseParam Compress the Descendants result in Base-64.
	 * @param compressResponseCharsetParam Compress response using provided charset.
	 */
	public SQLUtilWebSocketGetDescendantsClient(
			String endpointBaseUrlParam,
			IMessageReceivedCallback<FormListing> messageReceivedCallbackParam,
			String serviceTicketAsHexParam,
			long timeoutInMillisParam,
			boolean includeFieldDataParam,
			boolean includeTableFieldsParam,
			boolean includeTableFieldFormRecordInfoParam,
			boolean massFetchParam,
			boolean compressResponseParam,
			String compressResponseCharsetParam) {
		super(endpointBaseUrlParam,
				messageReceivedCallbackParam,
				timeoutInMillisParam,
				WS.Path.SQLUtil.Version1.getDescendantsWebSocket(
						includeFieldDataParam,
						includeTableFieldsParam,
						includeTableFieldFormRecordInfoParam,
						massFetchParam,
						serviceTicketAsHexParam,
						compressResponseParam,
						compressResponseCharsetParam),
				compressResponseParam);

		this.setServiceTicket(serviceTicketAsHexParam);
		this.massFetch = massFetchParam;
	}

	/**
	 * Constructor that sets the Service Ticket from authentication.
	 *
	 * @param endpointBaseUrlParam URL to base endpoint.
	 * @param messageReceivedCallbackParam Callback for when a message is received.
	 * @param serviceTicketAsHexParam The Server issued Service Ticket.
	 * @param timeoutInMillisParam The timeout of the request in millis.
	 * @param includeFieldDataParam Should Form Field data be included.
	 * @param includeTableFieldsParam Should Table Fields be included.
	 * @param includeTableFieldFormRecordInfoParam Does table record form data need to be included.
	 * @param massFetchParam Is the fetch a large fetch.
	 */
	public SQLUtilWebSocketGetDescendantsClient(
			String endpointBaseUrlParam,
			IMessageReceivedCallback<FormListing> messageReceivedCallbackParam,
			String serviceTicketAsHexParam,
			long timeoutInMillisParam,
			boolean includeFieldDataParam,
			boolean includeTableFieldsParam,
			boolean includeTableFieldFormRecordInfoParam,
			boolean massFetchParam) {
		super(endpointBaseUrlParam,
				messageReceivedCallbackParam,
				timeoutInMillisParam,
				WS.Path.SQLUtil.Version1.getDescendantsWebSocket(
						includeFieldDataParam,
						includeTableFieldsParam,
						includeTableFieldFormRecordInfoParam,
						massFetchParam,
						serviceTicketAsHexParam,
						false,
						UtilGlobal.EMPTY));

		this.setServiceTicket(serviceTicketAsHexParam);
		this.massFetch = massFetchParam;
	}

	/**
	 * Retrieves all the Descendants (Forms) for the {@code formToGetTableFormsForParam}.
	 *
	 * @param formToGetDescendantsForParam The Fluid Form to get Descendants for.
	 *
	 * @return The {@code formToGetDescendantsForParam} Descendants as {@code Form}'s.
	 */
	public List<FormListing> getDescendantsSynchronized(
			Form ... formToGetDescendantsForParam) {

		if(formToGetDescendantsForParam == null ||
				formToGetDescendantsForParam.length == 0) {
			return null;
		}

		//Start a new request...
		String uniqueReqId = this.initNewRequest();

		//Mass data fetch...
		int numberOfSentForms = 0;
		if(this.massFetch) {
			FormListing listingToSend = new FormListing();
			List<Form> listOfValidForms = new ArrayList();
			for(Form formToSend : formToGetDescendantsForParam) {
				if(formToSend == null) {
					throw new FluidClientException(
							"Cannot provide 'null' for Form.",
							FluidClientException.ErrorCode.ILLEGAL_STATE_ERROR);
				}

				listOfValidForms.add(new Form(formToSend.getId()));
			}

			listingToSend.setEcho(UUID.randomUUID().toString());
			listingToSend.setListing(listOfValidForms);

			//Send the actual message...
			this.sendMessage(listingToSend, uniqueReqId);
			numberOfSentForms++;
		} else {
			//Single...
			//Send all the messages...
			for(Form formToSend : formToGetDescendantsForParam) {
				this.setEchoIfNotSet(formToSend);

				//Send the actual message...
				this.sendMessage(formToSend, uniqueReqId);
				numberOfSentForms++;
			}
		}

		try {
			List<FormListing> returnValue =
					this.getHandler(uniqueReqId).getCF().get(
							this.getTimeoutInMillis(), TimeUnit.MILLISECONDS);

			//Connection was closed.. this is a problem....
			if(this.getHandler(uniqueReqId).isConnectionClosed()) {
				throw new FluidClientException(
						"SQLUtil-WebSocket-GetDescendants: " +
								"The connection was closed by the server prior to the response received.",
						FluidClientException.ErrorCode.IO_ERROR);
			}

			return returnValue;
		} catch (InterruptedException exceptParam) {
			//Interrupted...

			throw new FluidClientException(
					"SQLUtil-WebSocket-Interrupted-GetDescendants: " +
							exceptParam.getMessage(),
					exceptParam,
					FluidClientException.ErrorCode.STATEMENT_EXECUTION_ERROR);
		} catch (ExecutionException executeProblem) {
			//Error on the web-socket...

			Throwable cause = executeProblem.getCause();

			//Fluid client exception...
			if(cause instanceof FluidClientException) {
				throw (FluidClientException)cause;
			} else {
				throw new FluidClientException(
						"SQLUtil-WebSocket-GetDescendants: " +
								cause.getMessage(), cause,
						FluidClientException.ErrorCode.STATEMENT_EXECUTION_ERROR);
			}
		} catch (TimeoutException eParam) {
			//Timeout...
			String errMessage = this.getExceptionMessageVerbose(
					"SQLUtil-WebSocket-GetDescendants",
					uniqueReqId,
					numberOfSentForms);
			throw new FluidClientException(
					errMessage, FluidClientException.ErrorCode.IO_ERROR);
		} finally {
			this.removeHandler(uniqueReqId);
		}
	}

	/**
	 * Create a new instance of the handler class for {@code this} client.
	 *
	 * @return new instance of {@code GenericFormListingMessageHandler}
	 */
	@Override
	public GenericFormListingMessageHandler getNewHandlerInstance() {
		return new GenericFormListingMessageHandler(
				this.messageReceivedCallback, this.compressResponse);
	}
}


/*
 * Koekiebox CONFIDENTIAL
 *
 * [2012] - [2017] Koekiebox (Pty) Ltd
 * All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of Koekiebox and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Koekiebox
 * and its suppliers and may be covered by South African and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material is strictly
 * forbidden unless prior written permission is obtained from Koekiebox.
 */

package com.fluidbpm.program.api.util;

import java.io.Serializable;
import java.util.Properties;

import com.fluidbpm.program.api.util.cache.CacheUtil;

/**
 * Base class for any utility classes used in the Fluid framework.
 *
 * @author jasonbruwer on 2016/08/20.
 * @since 1.0
 */
public abstract class ABaseUtil implements Serializable {

	private CacheUtil cacheUtil;

	/**
	 * Create a new instance with a {@link CacheUtil}.
	 *
	 * @param cacheUtilParam The {@link CacheUtil}.
	 *
	 * @see CacheUtil
	 */
	public ABaseUtil(CacheUtil cacheUtilParam) {
		super();
		this.cacheUtil = cacheUtilParam;
	}

	/**
	 * Default constructor.
	 * No use for cache util.
	 */
	public ABaseUtil() {
		super();
		this.cacheUtil = null;
	}

	/**
	 * Returns the {@code CacheUtil} as created
	 * by the constructor.
	 *
	 * @return {@code null} or the {@code CacheUtil} instance.
	 */
	public CacheUtil getCacheUtil() {
		return this.cacheUtil;
	}

	/**
	 * Retrieves a property and returns the value as {@code java.lang.String}.
	 *
	 * @param propertiesParam The origin of the properties.
	 * @param propertyKeyParam The property key.
	 * @return The property value.
	 */
	protected static String getStringPropertyFromProperties(
			Properties propertiesParam,
			String propertyKeyParam
	) {
		if(propertiesParam == null || propertiesParam.isEmpty()) {
			return null;
		}

		return propertiesParam.getProperty(propertyKeyParam);
	}

	/**
	 * Retrieves a property and returns the value as {@code int}.
	 *
	 * @param propertiesParam The origin of the properties.
	 * @param propertyKeyParam The property key.
	 * @return The property value as an {@code int}.
	 */
	protected static int getIntPropertyFromProperties(
			Properties propertiesParam,
			String propertyKeyParam
	) {
		String strProp = getStringPropertyFromProperties(
				propertiesParam, propertyKeyParam);

		if(strProp == null || strProp.trim().isEmpty()) {
			return -1;
		}

		try {
			return Integer.parseInt(strProp);
		} catch(NumberFormatException nfe) {
			return -1;
		}
	}

	/**
	 * Returns -1 if there is a problem with conversion.
	 *
	 * @param toParseParam The {@code String} value to convert to {@code long}.
	 * @return {@code long} primitive version of {@code toParseParam}.
	 */
	protected long toLongSafe(String toParseParam) {
		if (toParseParam == null || toParseParam.trim().isEmpty()) {
			return -1;
		}

		try {
			return Long.parseLong(toParseParam.trim());
		} catch (NumberFormatException e) {
			return -1;
		}
	}
}