/*
 * 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.flow;

import org.json.JSONObject;

import com.fluidbpm.program.api.vo.flow.Flow;
import com.fluidbpm.program.api.vo.ws.WS;
import com.fluidbpm.ws.client.v1.ABaseClientWS;

/**
 * Used to change any of the Flow's / Workflows.
 *
 * This is ideal for doing automated tests against
 * the Fluid platform.
 *
 * @author jasonbruwer
 * @since v1.0
 * @version v1.0
 *
 * @see JSONObject
 * @see com.fluidbpm.program.api.vo.ws.WS.Path.Flow
 * @see Flow
 * @see ABaseClientWS
 */
public class FlowClient extends ABaseClientWS {

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

        this.setServiceTicket(serviceTicketParam);
    }

    /**
     * Creates a new Flow with an Introduction and Exit basic rule.
     *
     * @param flowParam The flow to Create.
     * @return The created flow.
     *
     * @see Flow
     */
    public Flow createFlow(Flow flowParam)
    {
        if(flowParam != null && this.serviceTicket != null)
        {
            flowParam.setServiceTicket(this.serviceTicket);
        }

        return new Flow(this.putJson(
                flowParam, WS.Path.Flow.Version1.flowCreate()));
    }

    /**
     * Updates an existing Flow.
     *
     * @param flowParam The flow to Update.
     * @return The updated flow.
     *
     * @see Flow
     */
    public Flow updateFlow(Flow flowParam)
    {
        if(flowParam != null && this.serviceTicket != null)
        {
            flowParam.setServiceTicket(this.serviceTicket);
        }

        return new Flow(this.postJson(
                flowParam, WS.Path.Flow.Version1.flowUpdate()));
    }

    /**
     * Retrieves a Flow by Primary Key.
     *
     * @param flowIdParam The Flow primary key.
     * @return The Flow.
     */
    public Flow getFlowById(Long flowIdParam)
    {
        Flow flow = new Flow(flowIdParam);

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

        return new Flow(this.postJson(
                flow, WS.Path.Flow.Version1.getById()));
    }

    /**
     * Retrieves a Flow by unique Name.
     *
     * @param flowNameParam The Flow name.
     * @return The Flow.
     */
    public Flow getFlowByName(String flowNameParam)
    {
        Flow flow = new Flow();
        flow.setName(flowNameParam);

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

        return new Flow(this.postJson(
                flow, WS.Path.Flow.Version1.getByName()));
    }

    /**
     * Delete an existing Flow.
     *
     * @param flowParam The Flow to delete.
     * @return The deleted Flow.
     */
    public Flow deleteFlow(Flow flowParam)
    {
        if(flowParam != null && this.serviceTicket != null)
        {
            flowParam.setServiceTicket(this.serviceTicket);
        }

        return new Flow(this.postJson(flowParam, WS.Path.Flow.Version1.flowDelete()));
    }

    /**
     * Forcefully Delete an existing Flow.
     *
     * Only 'admin' can forcefully delete a Flow.
     *
     * @param flowParam The Flow to delete.
     * @return The deleted Flow.
     */
    public Flow forceDeleteFlow(Flow flowParam)
    {
        if(flowParam != null && this.serviceTicket != null)
        {
            flowParam.setServiceTicket(this.serviceTicket);
        }

        return new Flow(this.postJson(flowParam, WS.Path.Flow.Version1.flowDelete(true)));
    }
}


/*
 * 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.config;

import java.util.List;

import org.json.JSONObject;

import com.fluidbpm.program.api.vo.config.GlobalFieldListing;
import com.fluidbpm.program.api.vo.field.Field;
import com.fluidbpm.program.api.vo.field.MultiChoice;
import com.fluidbpm.program.api.vo.ws.WS.Path.GlobalField.Version1;
import com.fluidbpm.ws.client.v1.ABaseFieldClient;

/**
 * Java Web Service Client for Global Field related actions.
 *
 * @author jasonbruwer on 2018-05-05
 * @version v1.8
 *
 * @see JSONObject
 * @see com.fluidbpm.program.api.vo.ws.WS.Path.GlobalField
 * @see Field
 * @see ABaseFieldClient
 */
public class GlobalFieldClient extends ABaseFieldClient {

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

        this.setServiceTicket(serviceTicketParam);
    }

    /**
     * Update an existing Global field value.
     *
     * @param globalFieldValueParam Field to Update.
     * @return Updated Field.
     */
    public Field updateFieldValue(Field globalFieldValueParam)
    {
        if(globalFieldValueParam != null && this.serviceTicket != null)
        {
            globalFieldValueParam.setServiceTicket(this.serviceTicket);
        }

        return new Field(this.postJson(
                globalFieldValueParam,
                Version1.globalFieldUpdateValue()));
    }

    /**
     * Retrieves field value by {@code fieldNameParam}.
     *
     * @param fieldNameParam The field name.
     * @return Field by primary key.
     */
    public Field getFieldValueByName(String fieldNameParam)
    {
        Field field = new Field();
        field.setFieldName(fieldNameParam);

        return this.getFieldValueBy(field);
    }

    /**
     * Retrieves field value by {@code fieldIdParam}.
     *
     * @param fieldIdParam The field id.
     * @return Field value by Global field primary key.
     */
    public Field getFieldValueByFieldId(Long fieldIdParam)
    {
        return this.getFieldValueBy(new Field(fieldIdParam));
    }

    /**
     * Retrieves field value by {@code fieldParam}.
     *
     * @param fieldParam The field name.
     * @return Field by primary key.
     */
    private Field getFieldValueBy(Field fieldParam)
    {
        if(fieldParam != null)
        {
            //Set for Payara server...
            fieldParam.setFieldValue(new MultiChoice());

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

        return new Field(this.postJson(
                fieldParam, Version1.getValueBy()));
    }

    /**
     * Retrieve all the Global field values.
     *
     * @return Global Fields in the destination system.
     *
     * @see GlobalFieldListing
     * @see Field
     */
    public List<Field> getAllGlobalFieldValues()
    {
        Field field = new Field();

        //Set for Payara server...
        field.setFieldValue(new MultiChoice());

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

        return new GlobalFieldListing(this.postJson(
                field, Version1.getAllValues())).getListing();
    }

}


/*
 * 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.elasticsearch;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

import org.elasticsearch.client.Client;
import org.elasticsearch.index.query.QueryBuilders;

import com.fluidbpm.program.api.util.ABaseUtil;
import com.fluidbpm.program.api.util.IFormAction;
import com.fluidbpm.program.api.util.cache.CacheUtil;
import com.fluidbpm.program.api.vo.ABaseFluidJSONObject;
import com.fluidbpm.program.api.vo.form.Form;

/**
 * ElasticSearch Utility class used for {@code Form} related actions.
 * A {@code Form} in Fluid is the equivalent to a Document in ElasticSearch.
 *
 * @author jasonbruwer on 2016/08/19.
 * @since 1.3
 * @see ABaseUtil
 * @see ABaseESUtil
 * @see IFormAction
 */
public class ESFormUtil extends ABaseESUtil implements IFormAction {

	/**
	 * Initialise with the ElasticSearch client.
	 *
	 * @param connectionParam SQL Connection to use.
	 * @param esClientParam The ES Client.
	 * @param cacheUtilParam The Cache Util for better performance.
	 */
	public ESFormUtil(
			Connection connectionParam,
			Client esClientParam,
			CacheUtil cacheUtilParam) {

		super(connectionParam,
				esClientParam,
				cacheUtilParam);
	}

	/**
	 * Initialise with the ElasticSearch client.
	 *
	 * @param esClientParam The ES Client.
	 */
	public ESFormUtil(Client esClientParam) {
		super(esClientParam);
	}

	/**
	 * 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;
		}

		//Query using the descendantId directly...
		StringBuffer ancestorQuery = new StringBuffer(
				Form.JSONMapping.DESCENDANT_IDS);
		ancestorQuery.append(":\"");
		ancestorQuery.append(electronicFormIdParam);
		ancestorQuery.append("\"");

		//Search for the Ancestor...
		List<Form> ancestorForms = null;
		if(includeFieldDataParam)
		{
			ancestorForms = this.searchAndConvertHitsToFormWithAllFields(
					QueryBuilders.queryStringQuery(ancestorQuery.toString()),
					Index.DOCUMENT,
					DEFAULT_OFFSET,
					1,
					new Long[]{});
		}
		else
		{
			ancestorForms = this.searchAndConvertHitsToFormWithNoFields(
					QueryBuilders.queryStringQuery(ancestorQuery.toString()),
					Index.DOCUMENT,
					DEFAULT_OFFSET,
					1,
					new Long[]{});
		}

		Form returnVal = null;
		if(ancestorForms != null && !ancestorForms.isEmpty())
		{
			returnVal = ancestorForms.get(0);
		}

		//No result...
		if(returnVal == null)
		{
			return null;
		}

		//Whether table field data should be included...
		if(!includeTableFieldsParam)
		{
			return returnVal;
		}

		//Populate the Table Fields...
		this.populateTableFields(
				false,
				includeFieldDataParam,
				returnVal.getFormFields());

		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
	 */
	@Override
	public List<Form> getFormDescendants(
			Long electronicFormIdParam,
			boolean includeFieldDataParam,
			boolean includeTableFieldsParam,
			boolean includeTableFieldFormRecordInfoParam) {

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

		List<Long> electronicFormIds = new ArrayList();
		electronicFormIds.add(electronicFormIdParam);

		//Get Form Descendants...
		return this.getFormDescendants(
				electronicFormIds,
				includeFieldDataParam,
				includeTableFieldsParam,
				includeTableFieldFormRecordInfoParam);
	}

	/**
	 * 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
	 */
	public List<Form> getFormDescendants(
			List<Long> electronicFormIdsParam,
			boolean includeFieldDataParam,
			boolean includeTableFieldsParam,
			boolean includeTableFieldFormRecordInfoParam)
	{
		if(electronicFormIdsParam == null ||
				electronicFormIdsParam.isEmpty())
		{
			return null;
		}

		//String stringQuery = "formType:(\"JIT Schedule\" \"SMT Training\") AND flowState:\"Not In Flow\"";

		//Query using the descendantId directly...
		StringBuffer descendantQuery = new StringBuffer(Form.JSONMapping.ANCESTOR_ID);
		descendantQuery.append(":(");

		for(Long electronicFormId : electronicFormIdsParam)
		{
			descendantQuery.append("\"");
			descendantQuery.append(electronicFormId);
			descendantQuery.append("\"");
			descendantQuery.append(" ");
		}

		String fullQueryToExec = descendantQuery.toString();
		fullQueryToExec = fullQueryToExec.substring(
				0, fullQueryToExec.length() - 1);
		fullQueryToExec = fullQueryToExec.concat(")");

		//Search for the Descendants...
		List<Form> returnVal = null;
		if(includeFieldDataParam)
		{
			returnVal = this.searchAndConvertHitsToFormWithAllFields(
					QueryBuilders.queryStringQuery(fullQueryToExec),
					Index.DOCUMENT,
					DEFAULT_OFFSET, MAX_NUMBER_OF_TABLE_RECORDS,
					new Long[]{});
		}
		else
		{
			returnVal = this.searchAndConvertHitsToFormWithNoFields(
					QueryBuilders.queryStringQuery(fullQueryToExec),
					Index.DOCUMENT,
					DEFAULT_OFFSET, MAX_NUMBER_OF_TABLE_RECORDS,
					new Long[]{});
		}

		//Whether table field data should be included...
		if(!includeTableFieldsParam)
		{
			return returnVal;
		}

		//No result...
		if(returnVal == null)
		{
			return returnVal;
		}

		//Populate in order to have table field data...
		for(Form descendantForm : returnVal)
		{
			this.populateTableFields(
					false,
					includeFieldDataParam,
					descendantForm.getFormFields());
		}

		return returnVal;
	}

	/**
	 * 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)
	{
		if(electronicFormIdParam == null)
		{
			return null;
		}

		//Query using the descendantId directly...
		StringBuffer primaryQuery = new StringBuffer(
				ABaseFluidJSONObject.JSONMapping.ID);
		primaryQuery.append(":\"");
		primaryQuery.append(electronicFormIdParam);
		primaryQuery.append("\"");

		//Search for the primary...
		List<Form> formsWithId = null;
		if(includeFieldDataParam)
		{
			formsWithId = this.searchAndConvertHitsToFormWithAllFields(
					QueryBuilders.queryStringQuery(primaryQuery.toString()),
					Index.DOCUMENT,
					DEFAULT_OFFSET,
					1,
					new Long[]{});
		}
		else
		{
			formsWithId = this.searchAndConvertHitsToFormWithNoFields(
					QueryBuilders.queryStringQuery(primaryQuery.toString()),
					Index.DOCUMENT,
					DEFAULT_OFFSET,
					1,
					new Long[]{});
		}

		Form returnVal = null;
		if(formsWithId != null && !formsWithId.isEmpty())
		{
			returnVal = formsWithId.get(0);
		}

		//No result...
		if(returnVal == null) {
			return null;
		}

		//Populate the Table Fields...
		return this.populateTableFields(
				true,
				includeFieldDataParam,
				returnVal.getFormFields());
	}
}


/*
 * 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.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

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.form.Form;

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

	private static final Map<Long,String> LOCAL_MAPPING = new HashMap();
	private static long timeToUpdateAgain = 0;

	/**
	 * New FormDefinition util instance using {@code connectionParam}.
	 *
	 * @param connectionParam SQL Connection to use for Fields.
	 */
	public SQLFormDefinitionUtil(Connection connectionParam) {
		super(connectionParam);
	}

	/**
	 * Retrieves the Form Definition and Title mapping
	 * currently stored in Fluid.
	 *
	 * @return Form Definition Id and Title.
	 */
	public Map<Long,String> getFormDefinitionIdAndTitle()
	{
		//When already cached, use the cached value...
		if(!LOCAL_MAPPING.isEmpty())
		{
			Map<Long,String> returnVal = new HashMap<>(LOCAL_MAPPING);

			//The id's are outdated...
			if(System.currentTimeMillis() > timeToUpdateAgain){

				synchronized (LOCAL_MAPPING)
				{
					LOCAL_MAPPING.clear();
				}
			}

			return returnVal;
		}

		//Only allow one thread to set the local mapping...
		synchronized (LOCAL_MAPPING)
		{
			if(!LOCAL_MAPPING.isEmpty())
			{
				return new HashMap<>(LOCAL_MAPPING);
			}

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

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

				resultSet = preparedStatement.executeQuery();

				//Iterate each of the form containers...
				while (resultSet.next())
				{
					Long id = resultSet.getLong(1);
					String title = resultSet.getString(2);

					LOCAL_MAPPING.put(id,title);
				}

				//Update in 10 mins...
				timeToUpdateAgain =
						(System.currentTimeMillis() +
								TimeUnit.MINUTES.toMillis(10));
			}
			//
			catch (SQLException sqlError) {
				throw new FluidSQLException(sqlError);
			}
			//
			finally {
				this.closeStatement(preparedStatement,resultSet);
			}

			return new HashMap<>(LOCAL_MAPPING);
		}
	}
}


/*
 * 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.ws.auth;

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

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

/**
 * Response to an authorization request.
 *
 * @author jasonbruwer
 * @since v1.0
 *
 * @see ABaseFluidJSONObject
 * @see TokenStatus
 * @see AppRequestToken
 * @see AuthRequest
 * @see AuthEncryptedData
 */
public class AuthResponse extends ABaseFluidJSONObject {

    public static final long serialVersionUID = 1L;

    private String salt;

    //Payload...
    private String encryptedDataBase64;//Contains [TicketExpires], [Roles] and [SessionKey]...
    private String encryptedDataHmacBase64;
    private String ivBase64;
    private String seedBase64;

    //Service Ticket...
    private String serviceTicketBase64;

    /**
     * The JSON mapping for the {@code AuthResponse} object.
     */
    public static class JSONMapping {
        public static final String SALT = "salt";
        public static final String ENCRYPTED_DATA_BASE_64 = "encryptedDataBase64";
        public static final String ENCRYPTED_DATA_HMAC_BASE_64 = "encryptedDataHmacBase64";
        public static final String IV_BASE_64 = "ivBase64";
        public static final String SEED_BASE_64 = "seedBase64";
        public static final String SERVICE_TICKET_BASE_64 = "serviceTicketBase64";
    }

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

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

        //Salt...
        if (!this.jsonObject.isNull(JSONMapping.SALT)) {
            this.setSalt(this.jsonObject.getString(JSONMapping.SALT));
        }

        //Encrypted Data Base64
        if (!this.jsonObject.isNull(JSONMapping.ENCRYPTED_DATA_BASE_64)) {
            this.setEncryptedDataBase64(this.jsonObject.getString(JSONMapping.ENCRYPTED_DATA_BASE_64));
        }

        //Encrypted Data HMAC Base64
        if (!this.jsonObject.isNull(JSONMapping.ENCRYPTED_DATA_HMAC_BASE_64)) {
            this.setEncryptedDataHmacBase64(this.jsonObject.getString(JSONMapping.ENCRYPTED_DATA_HMAC_BASE_64));
        }

        //IV Base 64...
        if (!this.jsonObject.isNull(JSONMapping.IV_BASE_64)) {
            this.setIvBase64(this.jsonObject.getString(JSONMapping.IV_BASE_64));
        }

        //IV Base 64...
        if (!this.jsonObject.isNull(JSONMapping.SEED_BASE_64)) {
            this.setSeedBase64(this.jsonObject.getString(JSONMapping.SEED_BASE_64));
        }

        //Service Ticket 64...
        if (!this.jsonObject.isNull(JSONMapping.SERVICE_TICKET_BASE_64)) {
            this.setServiceTicketBase64(this.jsonObject.getString(JSONMapping.SERVICE_TICKET_BASE_64));
        }
    }

    /**
     * Gets 20 Random ASCII {@code Character}s
     *
     * @return 20 Random ASCII {@code Character}s paired with the {@code User} password.
     */
    public String getSalt() {
        return this.salt;
    }

    /**
     * Sets 20 Random ASCII {@code Character}s
     *
     * @param saltParam 20 Random ASCII {@code Character}s paired with the {@code User} password.
     */
    public void setSalt(String saltParam) {
        this.salt = saltParam;
    }

    /**
     * Gets the Encrypted Data in Base-64 format.
     *
     * @return Encrypted Data in Base-64 format.
     */
    public String getEncryptedDataBase64() {
        return this.encryptedDataBase64;
    }

    /**
     * Sets the Encrypted Data in Base-64 format.
     *
     * @param encryptedDataBase64 Encrypted Data in Base-64 format.
     */
    public void setEncryptedDataBase64(String encryptedDataBase64) {
        this.encryptedDataBase64 = encryptedDataBase64;
    }

    /**
     * Gets the Encrypted Data for HMAC in Base-64 format.
     *
     * @return HMAC Encrypted Data in Base-64 format.
     */
    public String getEncryptedDataHmacBase64() {
        return this.encryptedDataHmacBase64;
    }

    /**
     * Sets the Encrypted Data for HMAC in Base-64 format.
     *
     * @param encryptedDataHmacBase64 HMAC Encrypted Data in Base-64 format.
     */
    public void setEncryptedDataHmacBase64(String encryptedDataHmacBase64) {
        this.encryptedDataHmacBase64 = encryptedDataHmacBase64;
    }

    /**
     * Gets the initialization vector in Base-64 format.
     *
     * @return Initialization Vector in Base-64.
     */
    public String getIvBase64() {
        return this.ivBase64;
    }

    /**
     * Sets the initialization vector in Base-64 format.
     *
     * @param ivBase64 Initialization Vector in Base-64.
     */
    public void setIvBase64(String ivBase64) {
        this.ivBase64 = ivBase64;
    }

    /**
     * Gets the seed in Base-64 format.
     *
     * https://en.wikipedia.org/wiki/SEED
     *
     * @return Seed in Base-64 format.
     */
    public String getSeedBase64() {
        return this.seedBase64;
    }

    /**
     * Sets the seed in Base-64 format.
     *
     * https://en.wikipedia.org/wiki/SEED
     *
     * @param seedBase64 Seed in Base-64 format.
     */
    public void setSeedBase64(String seedBase64) {
        this.seedBase64 = seedBase64;
    }

    /**
     * Gets the Service Ticket in Base-64 format.
     *
     * @return Service Ticket Base-64.
     */
    public String getServiceTicketBase64() {
        return this.serviceTicketBase64;
    }

    /**
     * Sets the Service Ticket in Base-64 format.
     *
     * @param serviceTicketBase64 Service Ticket Base-64.
     */
    public void setServiceTicketBase64(String serviceTicketBase64) {
        this.serviceTicketBase64 = serviceTicketBase64;
    }

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

        JSONObject returnVal = super.toJsonObject();

        //Salt...
        if (this.getSalt() != null) {
            returnVal.put(JSONMapping.SALT, this.getSalt());
        }

        //Encrypted Data Base 64...
        if (this.getEncryptedDataBase64() != null) {
            returnVal.put(JSONMapping.ENCRYPTED_DATA_BASE_64, this.getEncryptedDataBase64());
        }

        //Encrypted Data HMAC Base 64...
        if (this.getEncryptedDataHmacBase64() != null) {
            returnVal.put(JSONMapping.ENCRYPTED_DATA_HMAC_BASE_64, this.getEncryptedDataHmacBase64());
        }

        //IV Base 64...
        if (this.getIvBase64() != null) {
            returnVal.put(JSONMapping.IV_BASE_64, this.getIvBase64());
        }

        //Seed Base 64...
        if (this.getSeedBase64() != null) {
            returnVal.put(JSONMapping.SEED_BASE_64, this.getSeedBase64());
        }

        //Service Ticket Base 64...
        if (this.getServiceTicketBase64() != null) {
            returnVal.put(JSONMapping.SERVICE_TICKET_BASE_64, this.getServiceTicketBase64());
        }

        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.program.api.vo.compress;

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

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

/**
 * Fluid wrapper object for compressed {@code JSON} in {@code Base-64} format.
 *
 * @author jasonbruwer
 * @since v1.8
 */
public class CompressedResponse extends ABaseFluidJSONObject {

    public static final long serialVersionUID = 1L;

    private String dataBase64;

    public static String DEFAULT_ZIP_ENTRY_NAME = "response.json";

    /**
     * The JSON mapping for the {@code CompressedResponse} object.
     */
    public static class JSONMapping
    {
        public static final String DATA_BASE_64 = "dataBase64";
    }

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

    /**
     * Sets compressed data as Base-64.
     *
     * @param dataBase64Param Path to the attachment.
     */
    public CompressedResponse(String dataBase64Param) {
        super();

        this.setDataBase64(dataBase64Param);
    }

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

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

        //Data-64...
        if (!this.jsonObject.isNull(
                JSONMapping.DATA_BASE_64)) {

            this.setDataBase64(this.jsonObject.getString(
                    JSONMapping.DATA_BASE_64));
        }
    }

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

        //Data Base64...
        if(this.getDataBase64() != null)
        {
            returnVal.put(JSONMapping.DATA_BASE_64,
                    this.getDataBase64());
        }

        return returnVal;
    }

    /**
     * Gets the data in {@code Base-64} format.
     *
     * @return Base-64 attachment content.
     */
    public String getDataBase64() {
        return this.dataBase64;
    }

    /**
     * Sets the data in {@code Base-64} format.
     *
     * @param attachmentDataBase64Param Base-64 attachment content.
     */
    public void setDataBase64(String attachmentDataBase64Param) {
        this.dataBase64 = attachmentDataBase64Param;
    }
}


/*
 * 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.field;

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.util.UtilGlobal;
import com.fluidbpm.program.api.vo.ABaseFluidJSONObject;
import com.fluidbpm.program.api.vo.form.Form;

/**
 * <p>
 *     Represents a {@code Form} Table Field.
 *     Table fields have the ability to store other {@code Field}s inside itself.
 *     This allows for a much richer Electronic Form.
 * </p>
 *
 * @author jasonbruwer
 * @since v1.0
 *
 * @see Field
 * @see com.fluidbpm.program.api.util.sql.impl.SQLFormFieldUtil
 */
public class TableField extends ABaseFluidJSONObject {

    public static final long serialVersionUID = 1L;

    private List<Form> tableRecords;
    private Boolean sumDecimals;

    /**
     * The JSON mapping for the {@code Form} object.
     */
    public static class JSONMapping
    {
        public static final String TABLE_RECORDS = "tableRecords";
        public static final String SUM_DECIMALS = "sumDecimals";
    }

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

    /**
     * Constructor to create {@code TableField} with records.
     *
     * @param tableRecordsParam The records to create.
     */
    public TableField(List<Form> tableRecordsParam) {
        super();

        this.setTableRecords(tableRecordsParam);
    }

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

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

        //Sum Decimals...
        if (!this.jsonObject.isNull(JSONMapping.SUM_DECIMALS)) {

            this.setSumDecimals(this.jsonObject.getBoolean(JSONMapping.SUM_DECIMALS));
        }

        //Table Field Records...
        if (!this.jsonObject.isNull(JSONMapping.TABLE_RECORDS)) {

            JSONArray formsArr = this.jsonObject.getJSONArray(
                    JSONMapping.TABLE_RECORDS);

            List<Form> assForms = new ArrayList();
            for(int index = 0;index < formsArr.length();index++)
            {
                assForms.add(new Form(formsArr.getJSONObject(index)));
            }

            this.setTableRecords(assForms);
        }
    }

    /**
     * Gets List of Table Records for {@code TableField}.
     *
     * @return {@code List} of {@code Form}s for {@code TableField}.
     */
    public List<Form> getTableRecords() {
        return this.tableRecords;
    }

    /**
     * Set List of Table Records for {@code TableField}.
     *
     * @param tableRecordsParam {@code List} of {@code Form}s for {@code TableField}.
     */
    public void setTableRecords(List<Form> tableRecordsParam) {
        this.tableRecords = tableRecordsParam;
    }

    /**
     * Determine whether decimal columns should be summed.
     *
     * @return {@code Boolean} for Sum Decimals.
     */
    public Boolean getSumDecimals() {
        return this.sumDecimals;
    }

    /**
     * Set whether decimal columns should be summed.
     *
     * @param sumDecimalsParam Whether table record decimal columns should be summed.
     */
    public void setSumDecimals(Boolean sumDecimalsParam) {
        this.sumDecimals = sumDecimalsParam;
    }

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

        //Sum Decimals...
        if(this.getSumDecimals() != null)
        {
            returnVal.put(JSONMapping.SUM_DECIMALS, this.getSumDecimals());
        }

        //Table Field Records...
        if(this.getTableRecords() != null && !this.getTableRecords().isEmpty())
        {
            JSONArray assoFormsArr = new JSONArray();
            for(Form toAdd :this.getTableRecords())
            {
                assoFormsArr.put(toAdd.toJsonObject());
            }

            returnVal.put(JSONMapping.TABLE_RECORDS, assoFormsArr);
        }

        return returnVal;
    }

    /**
     * String value for a table field.
     *
     * @return JSON text from the table field.
     */
    @Override
    public String toString() {

        JSONObject jsonObject = this.toJsonObject();

        if(jsonObject != null)
        {
            return jsonObject.toString();
        }

        return UtilGlobal.EMPTY;
    }
}


/*
 * 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;

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;

/**
 * <p>
 *     Represents any list returned from the Web Service as {@code T}.
 * </p>
 *
 * @author jasonbruwer
 * @since v1.1
 *
 * @see JSONObject
 *
 */
public abstract class ABaseListing<T extends ABaseFluidJSONObject> extends ABaseFluidJSONObject{

    public static final long serialVersionUID = 1L;

    private List<T> listing;

    private Integer listingCount;
    private Integer listingIndex;
    private Integer listingPage;

    /**
     * The JSON mapping for the {@code ABaseListing} object.
     */
    public static class JSONMapping
    {
        public static final String LISTING = "listing";

        public static final String LISTING_COUNT = "listingCount";
        public static final String LISTING_INDEX = "listingIndex";
        public static final String LISTING_PAGE = "listingPage";
    }

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

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

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

        //Listing...
        int listingArrCount = 0;
        if (!this.jsonObject.isNull(JSONMapping.LISTING)) {

            JSONArray listingArray = this.jsonObject.getJSONArray(
                    JSONMapping.LISTING);
            listingArrCount = listingArray.length();

            List<T> listing = new ArrayList();

            for(int index = 0;index < listingArrCount;index++)
            {
                listing.add(this.getObjectFromJSONObject(
                        listingArray.getJSONObject(index)));
            }

            this.setListing(listing);
        }

        //Listing Count...
        if (this.jsonObject.isNull(JSONMapping.LISTING_COUNT)) {

            this.setListingCount(listingArrCount);
        }
        else
        {
            this.setListingCount(this.jsonObject.getInt(JSONMapping.LISTING_COUNT));
        }

        //Listing Index...
        if (!this.jsonObject.isNull(JSONMapping.LISTING_INDEX)) {

            this.setListingIndex(this.jsonObject.getInt(JSONMapping.LISTING_INDEX));
        }

        //Listing Page...
        if (!this.jsonObject.isNull(JSONMapping.LISTING_PAGE)) {

            this.setListingPage(this.jsonObject.getInt(JSONMapping.LISTING_PAGE));
        }
    }

    /**
     * Gets the listing of {@code T} objects.
     *
     * @return List of {@code T} type objects.
     */
    public List<T> getListing() {
        return this.listing;
    }

    /**
     * Sets the listing of {@code T} objects.
     *
     * @param listingParam List of {@code T} type objects.
     */
    public void setListing(List<T> listingParam) {
        this.listing = listingParam;
    }

    /**
     * Gets the listing count.
     *
     * @return Listing count.
     */
    public Integer getListingCount() {
        return this.listingCount;
    }

    /**
     * Sets the listing count.
     *
     * @param listingCountParam Listing count.
     */
    public void setListingCount(Integer listingCountParam) {
        this.listingCount = listingCountParam;
    }

    /**
     * Gets the listing index.
     *
     * @return Listing index.
     */
    public Integer getListingIndex() {
        return this.listingIndex;
    }

    /**
     * Sets the listing index.
     *
     * @param listingIndexParam Listing index.
     */
    public void setListingIndex(Integer listingIndexParam) {
        this.listingIndex = listingIndexParam;
    }

    /**
     * Gets the listing page.
     *
     * @return Listing page.
     */
    public Integer getListingPage() {
        return listingPage;
    }

    /**
     * Sets the listing page.
     *
     * @param listingPageParam Listing page.
     */
    public void setListingPage(Integer listingPageParam) {
        this.listingPage = listingPageParam;
    }

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

        JSONObject returnVal = super.toJsonObject();

        //Listing...
        int listingCountFromListing = 0;
        if(this.getListing() != null && !this.getListing().isEmpty())
        {
            JSONArray jsonArray = new JSONArray();
            listingCountFromListing = this.getListing().size();
            for(T toAdd :this.getListing())
            {
                jsonArray.put(toAdd.toJsonObject());
            }

            returnVal.put(JSONMapping.LISTING, jsonArray);
        }

        //Listing count...
        if(this.getListingCount() == null)
        {
            returnVal.put(JSONMapping.LISTING_COUNT, new Integer(listingCountFromListing));
        }
        else
        {
            returnVal.put(JSONMapping.LISTING_COUNT, this.getListingCount());
        }

        //Listing index...
        if(this.getListingIndex() != null)
        {
            returnVal.put(JSONMapping.LISTING_INDEX, this.getListingIndex());
        }

        //Listing page...
        if(this.getListingIndex() != null)
        {
            returnVal.put(JSONMapping.LISTING_PAGE, this.getListingPage());
        }

        return returnVal;
    }

    /**
     * Provides the sub-class with a {@code JSONObject}, and
     * expects a {@code Java} object in return.
     *
     * @param jsonObjectParam The JSON object to convert to {@code T}.
     * @return T from constructor.
     *
     * @see JSONObject
     */
    @XmlTransient
    public abstract T getObjectFromJSONObject(JSONObject jsonObjectParam);

    /**
     * Returns whether the listing is empty.
     *
     * @return {@code true} if the listing is {@code null} or empty.
     */
    @XmlTransient
    public boolean isListingEmpty()
    {
        return (this.listing == null || this.listing.isEmpty());
    }
}


/*
 * 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.ws.auth;

import java.util.Date;

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

/**
 * Status of a previously issued authentication token.
 *
 * @author jasonbruwer
 * @since v1.0
 *
 * @see ABaseFluidJSONObject
 * @see ServiceTicket
 * @see AppRequestToken
 */
public class TokenStatus extends ABaseFluidJSONObject {

    public static final long serialVersionUID = 1L;

    private Long expirationTime;
    private String tokenMessage;

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

    /**
     * Gets the Expiration time in millis.
     *
     * @return Expiration Time.
     */
    public Long getExpirationTime() {
        return this.expirationTime;
    }

    /**
     * Sets the Expiration time in millis.
     *
     * @param expirationTime The expiration time.
     */
    public void setExpirationTime(Long expirationTime) {
        this.expirationTime = expirationTime;
    }

    /**
     * Gets the token message.
     *
     * @return The token message.
     */
    public String getTokenMessage() {
        return this.tokenMessage;
    }

    /**
     * Sets the token message.
     *
     * @param tokenMessage The token message to set.
     */
    public void setTokenMessage(String tokenMessage) {
        this.tokenMessage = tokenMessage;
    }

    /**
     * Check the {@code getExpirationTime} to confirm whether
     * the ticket has expired.
     *
     * @return Whether the ticket has expired.
     */
    public boolean isExpired()
    {
        if(this.getExpirationTime() == null)
        {
            return true;
        }

        Date expirationTime = new Date(this.getExpirationTime());

        return (expirationTime.before(new Date()));
    }
}


/*
 * 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.user;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

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

import com.fluidbpm.program.api.vo.user.User;
import com.fluidbpm.program.api.vo.user.UserNotification;
import com.fluidbpm.program.api.vo.user.UserNotificationListing;
import com.fluidbpm.program.api.vo.ws.WS;
import com.fluidbpm.ws.client.FluidClientException;
import com.fluidbpm.ws.client.v1.ABaseClientWS;

/**
 * Java Web Service Client for User Notification related actions.
 *
 * @author jasonbruwer
 * @since v1.8 2018-04-05
 *
 * @see JSONObject
 * @see WS.Path.UserNotification
 * @see com.fluidbpm.program.api.vo.user.UserNotification
 */
public class UserNotificationClient extends ABaseClientWS {

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

		this.setServiceTicket(serviceTicketParam);
	}

	/**
	 * Creates a new {@code UserNotification} for a user.
	 * The user will be notified through the Fluid User Dashboard or 3rd Party application.
	 *
	 * @param userNotificationParam The {@code UserNotification} to create.
	 * @return The Created User Notification.
	 *
	 * @see com.fluidbpm.program.api.vo.user.User
	 * @see UserNotification
	 */
	public UserNotification createUserNotification(UserNotification userNotificationParam)
	{
		if(userNotificationParam != null && this.serviceTicket != null)
		{
			userNotificationParam.setServiceTicket(this.serviceTicket);
		}

		return new UserNotification(this.putJson(
				userNotificationParam,
				WS.Path.UserNotification.Version1.userNotificationCreate()));
	}

	/**
	 * Creates a new {@code UserNotification} for a user.
	 * The user will be notified through the Fluid User Dashboard or 3rd Party application.
	 *
	 * @param userNotificationParam The User Notification to update.
	 * @return The Updated UserNotification.
	 *
	 * @see com.fluidbpm.program.api.vo.user.User
	 * @see UserNotification
	 */
	public UserNotification updateUserNotification(
			UserNotification userNotificationParam)
	{
		if(userNotificationParam != null && this.serviceTicket != null)
		{
			userNotificationParam.setServiceTicket(this.serviceTicket);
		}

		return new UserNotification(this.postJson(
				userNotificationParam,
				WS.Path.UserNotification.Version1.userNotificationUpdate()));
	}

	/**
	 * Marks the {@code userNotificationParam} notification as read.
	 *
	 * Notification marked as read asynchronously.
	 *
	 * @param userNotificationParam The User Notification to mark as read.
	 *
	 * @return The Updated UserNotification.
	 *
	 * @see com.fluidbpm.program.api.vo.user.User
	 * @see UserNotification
	 */
	public UserNotification markUserNotificationAsRead(
			UserNotification userNotificationParam)
	{
		return this.markUserNotificationAsRead(userNotificationParam,
				true);
	}

	/**
	 * Marks the {@code userNotificationParam} notification as read.
	 *
	 * @param userNotificationParam The User Notification to mark as read.
	 * @param asyncParam Should the notification mark-as-read asynchronously.
	 *
	 * @return The Updated UserNotification.
	 *
	 * @see com.fluidbpm.program.api.vo.user.User
	 * @see UserNotification
	 */
	public UserNotification markUserNotificationAsRead(
			UserNotification userNotificationParam,
			boolean asyncParam)
	{
		if(userNotificationParam != null && this.serviceTicket != null)
		{
			userNotificationParam.setServiceTicket(this.serviceTicket);
		}

		return new UserNotification(this.postJson(
				userNotificationParam,
				WS.Path.UserNotification.Version1.userNotificationMarkAsRead(asyncParam)));
	}

	/**
	 * Deletes the {@code UserNotification} provided.
	 * Id must be set on the {@code UserNotification}.
	 *
	 * @param userNotificationToDeleteParam The User Notification to Delete.
	 * @return The deleted User Notification.
	 */
	public UserNotification deleteUserNotification(
			UserNotification userNotificationToDeleteParam)
	{
		if(userNotificationToDeleteParam != null && this.serviceTicket != null)
		{
			userNotificationToDeleteParam.setServiceTicket(this.serviceTicket);
		}

		return new UserNotification(this.postJson(userNotificationToDeleteParam,
				WS.Path.UserNotification.Version1.userNotificationDelete()));
	}

	/**
	 * Deletes the {@code UserNotification} provided.
	 * Id must be set on the {@code UserNotification}.
	 *
	 * @param userNotificationPkParam The Id of the User Notification to fetch.
	 * @return The User Notification by PK.
	 */
	public UserNotification getUserNotificationById(
			Long userNotificationPkParam)
	{
		UserNotification userNoti = new UserNotification();
		userNoti.setId(userNotificationPkParam);

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

		try {
			return new UserNotification(this.postJson(
					userNoti, WS.Path.UserNotification.Version1.getById()));
		}
		//Json format issues...
		catch (JSONException jsonExcept) {
			throw new FluidClientException(jsonExcept.getMessage(),
					FluidClientException.ErrorCode.JSON_PARSING);
		}
	}

	/**
	 * Retrieves all {@code READ} User Notification items for the logged in user.
	 *
	 * @param queryLimitParam The query limit.
	 * @param offsetParam The query offset.
	 *
	 * @return The User Notification items for the logged in {@code User}.
	 */
	public List<UserNotification> getAllReadByLoggedInUser(
			int queryLimitParam,
			int offsetParam
	) {
		User loggedInUser = new User();

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

		try {
			return new UserNotificationListing(this.postJson(
					loggedInUser,
					WS.Path.UserNotification.Version1.getAllReadByUser(
							queryLimitParam,
							offsetParam))).getListing();
		} catch (JSONException jsonExcept) {
			//rethrow as a Fluid Client exception.
			throw new FluidClientException(jsonExcept.getMessage(),
					FluidClientException.ErrorCode.JSON_PARSING);
		}
	}

	/**
	 * Retrieves all {@code UNREAD} User Notification items for the logged in user.
	 *
	 * @param queryLimitParam The query limit.
	 * @param offsetParam The query offset.
	 *
	 * @return The User Notification items for the logged in {@code User}.
	 */
	public List<UserNotification> getAllUnReadByLoggedInUser(
			int queryLimitParam,
			int offsetParam
	) {
		User loggedInUser = new User();

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

		try {
			return new UserNotificationListing(this.postJson(
					loggedInUser,
					WS.Path.UserNotification.Version1.getAllUnReadByUser(
							queryLimitParam, offsetParam
					))).getListing();
		} catch (JSONException jsonExcept) {
			//rethrow as a Fluid Client exception.
			throw new FluidClientException(jsonExcept.getMessage(),
					FluidClientException.ErrorCode.JSON_PARSING);
		}
	}

	/**
	 * Retrieves all User Notification items for user {@code userParam}
	 * between date {@code fromDateParam} and {@code toDateParam}.
	 *
	 * @param userParam The user to retrieve notifications for.
	 * @param fromDateParam The notification from date.
	 * @param toDateParam The notification to date.
	 *
	 * @return The User Notification items for the logged in {@code User}.
	 */
	public List<UserNotification> getAllByUserAndDateBetween(
			User userParam,
			Date fromDateParam,
			Date toDateParam
	) {
		return this.getAllByUserAndDateBetween(
				userParam,
				-1,
				-1,
				fromDateParam,
				toDateParam
		);
	}

	/**
	 * Retrieves all User Notification items for user {@code userParam}
	 * between date {@code fromDateParam} and {@code toDateParam}.
	 *
	 * @param userParam The user to retrieve notifications for.
	 * @param queryLimitParam The query limit.
	 * @param offsetParam The query offset.
	 * @param fromDateParam The notification from date.
	 * @param toDateParam The notification to date.
	 *
	 * @return The User Notification items for the logged in {@code User}.
	 */
	public List<UserNotification> getAllByUserAndDateBetween(
			User userParam,
			int queryLimitParam,
			int offsetParam,
			Date fromDateParam,
			Date toDateParam
	) {
		if(this.serviceTicket != null && userParam != null) {
			userParam.setServiceTicket(this.serviceTicket);
		}

		long fromDate =
				(fromDateParam == null) ?
						System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7)
						: fromDateParam.getTime();
		long toDate =
				(toDateParam == null) ?
						System.currentTimeMillis() : toDateParam.getTime();

		try {
			return new UserNotificationListing(this.postJson(
					userParam,
					WS.Path.UserNotification.Version1.getAllByUserAndDate(
							queryLimitParam,
							offsetParam,
							fromDate,
							toDate
					))).getListing();
		} catch (JSONException jsonExcept) {
			//rethrow as a Fluid Client exception.
			throw new FluidClientException(jsonExcept.getMessage(),
					FluidClientException.ErrorCode.JSON_PARSING);
		}
	}
}


/*
 * 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.user;

import java.security.*;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import com.fluidbpm.program.api.util.UtilGlobal;
import com.fluidbpm.ws.client.FluidClientException;

/**
 * Utility class for AES-256 cryptography.
 *
 * @author jasonbruwer
 * @since v1.0
 *
 * @see Mac
 * @see Cipher
 */
public class AES256Local {

    public static final int IV_SIZE_BYTES = 16;
    public static final int SEED_SIZE_BYTES = 32;

    private static final String HMAC_ALGO = "HmacSHA256";

    private static final String ALGO_CBC = "AES/CBC/PKCS5Padding";
    private static final String KEY_ALGO = "AES";

    private static SecureRandom secureRandom;

    /**
     * Generate a new initialization vector with a {@code seedParam} byte
     * count.
     *
     * @param seedParam Size of the random bytes.
     * @return The software randomly generated bytes.
     */
    public static byte[] generateRandom(int seedParam) {

        if(AES256Local.secureRandom == null)
        {
            AES256Local.secureRandom = new SecureRandom();
        }

        return new IvParameterSpec(AES256Local.secureRandom.generateSeed(seedParam)).getIV();
    }

    /**
     * Generates an HMAC from {@code encryptedDataParam}.
     *
     * @param hMacKeyParam The symmetric key to use.
     * @param encryptedDataParam The encrypted data.
     * @return The HMAC result.
     */
    public static byte[] hmacSha256(byte[] hMacKeyParam, byte[] encryptedDataParam) {
        try {
            // hmac
            Mac hmac = Mac.getInstance(HMAC_ALGO);
            hmac.init(new SecretKeySpec(hMacKeyParam, HMAC_ALGO));

            return hmac.doFinal(encryptedDataParam);
        }
        //Changed for Java 1.6 compatibility...
        catch (NoSuchAlgorithmException except) {
            throw new FluidClientException("Unable to create HMAC from key. " + except.getMessage(), except,
                    FluidClientException.ErrorCode.AES_256);
        }
        catch (InvalidKeyException except) {
            throw new FluidClientException("Unable to create HMAC from key. " + except.getMessage(), except,
                    FluidClientException.ErrorCode.AES_256);
        }
    }

    /**
     * Generate a derived HMAC from {@code encryptedDataParam} using
     * other params.
     *
     * @param encryptedDataParam The encrypted data to generate HMAC from.
     * @param passwordParam Password (clear text) used to derive the key from.
     * @param saltParam The password salt as passed by the Init request.
     * @param seedParam Seed to be poisoned
     * @return Derived HMAC.
     */
    public static byte[] generateLocalHMAC(
            byte[] encryptedDataParam, String passwordParam, String saltParam, byte[] seedParam){

        byte[] poisonedSeed = poisonBytes(seedParam);

        byte[] passwordSha256 = sha256(
                passwordParam.concat(saltParam).getBytes());

        //Add the seed to the password and SHA-256...
        byte[] derivedKey = sha256(UtilGlobal.addAll(passwordSha256, poisonedSeed));

        return hmacSha256(derivedKey, encryptedDataParam);
    }

    /**
     * Generate a derived HMAC from {@code encryptedDataParam} using {@code keyParam}.
     *
     * @param encryptedDataParam The encrypted data to generate HMAC from.
     * @param keyParam Key used to derive the key from.
     * @param seedParam Seed to be poisoned
     * @return The HMAC for {@code encryptedDataParam}.
     */
    public static byte[] generateLocalHMACForReqToken(
            byte[] encryptedDataParam, byte[] keyParam, byte[] seedParam){

        byte[] poisonedSeed = poisonBytes(seedParam);

        //Add the seed to the password and SHA-256...
        byte[] derivedKey = sha256(UtilGlobal.addAll(keyParam, poisonedSeed));

        return hmacSha256(derivedKey, encryptedDataParam);
    }

    /**
     * Creates a derived version of {@code bytesToPoisonParam}.
     *
     * @param bytesToPoisonParam The bytes to poison.
     * @return The poisoned bytes.
     */
    private static byte[] poisonBytes(byte[] bytesToPoisonParam) {
        if (bytesToPoisonParam == null) {
            return null;
        }

        byte[] returnVal = new byte[bytesToPoisonParam.length];

        for (int index = 0; index < bytesToPoisonParam.length; index++) {
            byte poisoned = (byte) (bytesToPoisonParam[index] ^ 222);

            returnVal[index] = poisoned;
        }

        return returnVal;
    }

    /**
     * Decrypts the encrypted data.
     *
     * @param encryptedDataParam The Base64 data to decrypt.
     * @param passwordParam Password in the clear.
     * @param saltParam The password salt as passed by the Init request.
     * @param ivParam IV value used during packet encryption
     * @param seedParam The random seed.
     * @return Decrypted bytes / Clear text.
     */
    public static byte[] decryptInitPacket(
            byte[] encryptedDataParam,
            String passwordParam,
            String saltParam,
            byte[] ivParam,
            byte[] seedParam){

        //Stored like this in the database, so we have to get the password as stored in the database so that the
        // SHa256 and SALT combination will be valid...
        byte[] passwordSha256 = sha256(passwordParam.concat(saltParam).getBytes());

        //Add the seed to the password and SHA-256...
        byte[] derivedKey = sha256(UtilGlobal.addAll(passwordSha256, seedParam));

        //Decrypt with the derived key.
        return decrypt(derivedKey, encryptedDataParam, ivParam);
    }

    /**
     * Decrypt {@code dataToDecryptParam} using the {@code keyParam} key.
     *
     * @param keyParam The symmetric key.
     * @param dataToDecryptParam The encrypted data to decrypt.
     * @param ivParam The random IV.
     * @return Clear text.
     */
    public static byte[] decrypt(byte[] keyParam, byte[] dataToDecryptParam, byte[] ivParam) {

        Key key = new SecretKeySpec(keyParam, KEY_ALGO);

        try {
            Cipher cipher = Cipher.getInstance(ALGO_CBC);
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivParam));

            return cipher.doFinal(dataToDecryptParam);
        }
        //Changed for Java 1.6 compatibility...
        catch (InvalidKeyException except) {

            throw new FluidClientException("Key: Unable to decrypt data. " +
                    except.getMessage(), except, FluidClientException.ErrorCode.AES_256);
        } catch (InvalidAlgorithmParameterException except) {

            throw new FluidClientException("Algo: Unable to decrypt data. " +
                    except.getMessage(), except, FluidClientException.ErrorCode.AES_256);
        } catch (IllegalBlockSizeException except) {

            throw new FluidClientException("Block: Unable to decrypt data. " +
                    except.getMessage(), except, FluidClientException.ErrorCode.AES_256);
        } catch (NoSuchPaddingException except) {

            throw new FluidClientException("NoPadding: Unable to decrypt data. " +
                    except.getMessage(), except, FluidClientException.ErrorCode.AES_256);
        } catch (NoSuchAlgorithmException except) {

            throw new FluidClientException("NoAlgo: Unable to decrypt data. " +
                    except.getMessage(), except, FluidClientException.ErrorCode.AES_256);
        } catch (BadPaddingException except) {

            throw new FluidClientException("BadPadding: Unable to decrypt data. " +
                    except.getMessage(), except, FluidClientException.ErrorCode.AES_256);
        }
    }

    /**
     * Encrypts the {@code dataToEncryptParam} data using key {@code keyParam}.
     *
     * @param keyParam Key to encrypt with.
     * @param dataToEncryptParam Data to encrypt.
     * @param ivParam - 16 bytes for AES-256.
     * @return Cipher text.
     */
    public static byte[] encrypt(byte[] keyParam, byte[] dataToEncryptParam, byte[] ivParam) {
        if (dataToEncryptParam == null) {
            throw new FluidClientException("No data to encrypt provided. ",
                    FluidClientException.ErrorCode.AES_256);
        }

        Key key = new SecretKeySpec(keyParam, KEY_ALGO);

        try {
            Cipher cipher = Cipher.getInstance(ALGO_CBC);

            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivParam));

            return cipher.doFinal(dataToEncryptParam);
        }
        //Changed for Java 1.6 compatibility...
        catch (InvalidKeyException except) {
            throw new FluidClientException("Key: Unable to encrypt data. " + except.getMessage(), except,
                    FluidClientException.ErrorCode.AES_256);
        } catch (InvalidAlgorithmParameterException except) {
            throw new FluidClientException("Algo: Unable to encrypt data. " + except.getMessage(), except,
                    FluidClientException.ErrorCode.AES_256);
        } catch (IllegalBlockSizeException except) {
            throw new FluidClientException("Block: Unable to encrypt data. " + except.getMessage(), except,
                    FluidClientException.ErrorCode.AES_256);
        } catch (NoSuchPaddingException except) {
            throw new FluidClientException("NoPadding: Unable to encrypt data. " + except.getMessage(), except,
                    FluidClientException.ErrorCode.AES_256);
        } catch (NoSuchAlgorithmException except) {
            throw new FluidClientException("NoAlgo: Unable to encrypt data. " + except.getMessage(), except,
                    FluidClientException.ErrorCode.AES_256);
        } catch (BadPaddingException except) {
            throw new FluidClientException("BadPadding: Unable to encrypt data. " + except.getMessage(), except,
                    FluidClientException.ErrorCode.AES_256);
        }
    }

    /**
     * Compute SHA256 digest from {@code dataParam}.
     *
     * @param dataParam The data to SHA-256.
     * @return SHA256 digest.
     */
    public static byte[] sha256(final byte[] dataParam) {
        if (dataParam == null || dataParam.length == 0) {
            return new byte[] {};
        }

        try {
            final MessageDigest digest = MessageDigest.getInstance("SHA-256");
            return digest.digest(dataParam);
        }
        //
        catch (final NoSuchAlgorithmException e) {
            throw new IllegalStateException(e);
        }
    }
}


/*
 * 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.websocket;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import javax.websocket.DeploymentException;

import com.fluidbpm.program.api.util.UtilGlobal;
import com.fluidbpm.program.api.vo.ABaseFluidJSONObject;
import com.fluidbpm.program.api.vo.ABaseFluidVO;
import com.fluidbpm.program.api.vo.ABaseListing;
import com.fluidbpm.ws.client.FluidClientException;
import com.fluidbpm.ws.client.v1.ABaseClientWS;

/**
 * Base class for all REST related calls.
 *
 * @author jasonbruwer
 * @since v1.1
 *
 * @see ABaseClientWS
 */
public abstract class ABaseClientWebSocket<RespHandler extends IMessageResponseHandler> extends ABaseClientWS {

	protected String webSocketEndpointUrl;

	private WebSocketClient webSocketClient;
	private long timeoutInMillis;
	private Map<String,RespHandler> messageHandler = new Hashtable<>();

	protected IMessageReceivedCallback messageReceivedCallback;
	protected boolean compressResponse;

	/**
	 * The constant variables used.
	 */
	public static class Constant
	{
		public static final String HTTP = "http";
		public static final String HTTPS = "https";

		public static final String WS = "ws";
		public static final String WSS = "wss";

		public static final String SCHEME_SEP = "://";
		public static final String COLON = ":";
	}

	/**
	 * Default constructor.
	 *
	 * @param endpointBaseUrlParam URL to base endpoint.
	 * @param messageReceivedCallbackParam Optional callback object (observer).
	 * @param timeoutInMillisParam The timeout for the Web Socket response in millis.
	 * @param postFixForUrlParam The URL Postfix.
	 * @param compressResponseParam Expect the response to be compressed.
	 */
	public ABaseClientWebSocket(
			String endpointBaseUrlParam,
			IMessageReceivedCallback messageReceivedCallbackParam,
			long timeoutInMillisParam,
			String postFixForUrlParam,
			boolean compressResponseParam) {

		this(endpointBaseUrlParam, messageReceivedCallbackParam, timeoutInMillisParam, postFixForUrlParam);
		this.compressResponse = compressResponseParam;
	}

	/**
	 * Default constructor.
	 *
	 * @param endpointBaseUrlParam URL to base endpoint.
	 * @param messageReceivedCallbackParam Optional callback object (observer).
	 * @param timeoutInMillisParam The timeout for the Web Socket response in millis.
	 * @param postFixForUrlParam The URL Postfix.
	 */
	public ABaseClientWebSocket(
			String endpointBaseUrlParam,
			IMessageReceivedCallback messageReceivedCallbackParam,
			long timeoutInMillisParam,
			String postFixForUrlParam) {
		super(endpointBaseUrlParam);

		this.timeoutInMillis = timeoutInMillisParam;
		this.messageReceivedCallback = messageReceivedCallbackParam;

		if(this.webSocketEndpointUrl == null && this.endpointUrl != null)
		{
			this.webSocketEndpointUrl =
					this.getWebSocketBaseURIFrom(this.endpointUrl);
		}

		//Confirm Web Socket Endpoint is set.
		if(this.webSocketEndpointUrl == null ||
				this.webSocketEndpointUrl.trim().isEmpty())
		{
			throw new FluidClientException(
					"Base Web Socket Endpoint URL not set.",
					FluidClientException.ErrorCode.ILLEGAL_STATE_ERROR);
		}

		//Issue #23... Don't do double //...
		String completeUrl = null;
		if(this.webSocketEndpointUrl.endsWith(UtilGlobal.FORWARD_SLASH) &&
				postFixForUrlParam.startsWith(UtilGlobal.FORWARD_SLASH))
		{
			completeUrl = (this.webSocketEndpointUrl + postFixForUrlParam.substring(1));
		}
		else
		{
			completeUrl = (this.webSocketEndpointUrl + postFixForUrlParam);
		}

		try {
			this.webSocketClient = new WebSocketClient(
					new URI(completeUrl), this.messageHandler);
		}
		//Deploy...
		catch (DeploymentException e) {

			throw new FluidClientException(
					"Unable to create Web Socket client (Deployment). URL ["+ completeUrl+"]: "
							+e.getMessage(),
					e, FluidClientException.ErrorCode.WEB_SOCKET_DEPLOY_ERROR);
		}
		//I/O...
		catch (IOException e) {

			throw new FluidClientException(
					"Unable to create Web Socket client (I/O). URL ["+ completeUrl+"]:"+e.getMessage(),
					e, FluidClientException.ErrorCode.WEB_SOCKET_IO_ERROR);
		}
		//URI Syntax...
		catch (URISyntaxException e) {

			throw new FluidClientException(
					"Unable to create Web Socket client (URI). URL ["+completeUrl+"]: "+e.getMessage(),
					e, FluidClientException.ErrorCode.WEB_SOCKET_URI_SYNTAX_ERROR);
		}
	}

	/**
	 * Send the {@code baseFluidJSONObjectParam} via Web Socket.
	 *
	 * @param baseFluidJSONObjectParam The JSONObject to send.
	 * @param requestIdParam The unique request id.
	 *
	 * @see org.json.JSONObject
	 */
	public void sendMessage(
			ABaseFluidJSONObject baseFluidJSONObjectParam,
			String requestIdParam)
	{
		if(baseFluidJSONObjectParam != null) {
			baseFluidJSONObjectParam.setServiceTicket(this.serviceTicket);

			//Add the echo to the listing if [GenericListMessageHandler].
			if(this.getHandler(requestIdParam) instanceof AGenericListMessageHandler) {
				AGenericListMessageHandler listHandler =
						(AGenericListMessageHandler)this.getHandler(requestIdParam);
				listHandler.addExpectedMessage(baseFluidJSONObjectParam.getEcho());
			}
		}

		this.webSocketClient.sendMessage(baseFluidJSONObjectParam);
	}

	/**
	 * If the HTTP Client is set, this will
	 * close and clean any connections that needs to be closed.
	 *
	 * @since v1.1
	 */
	@Override
	public void closeAndClean()
	{
		CloseConnectionRunnable closeConnectionRunnable =
				new CloseConnectionRunnable(this);

		Thread closeConnThread = new Thread(
				closeConnectionRunnable,"Close ABaseClientWebSocket Connection");
		closeConnThread.start();
	}

	/**
	 * Close the SQL and ElasticSearch Connection, but not in
	 * a separate {@code Thread}.
	 */
	protected void closeConnectionNonThreaded()
	{
		if(this.webSocketClient == null)
		{
			return;
		}

		this.webSocketClient.closeSession();

		super.closeConnectionNonThreaded();
	}

	/**
	 * Initiate a new request process.
	 *
	 * Synchronized.
	 *
	 * @return A randomly generated identifier for the request.
	 */
	public synchronized String initNewRequest(){

		String returnVal = UUID.randomUUID().toString();
		this.messageHandler.put(returnVal, this.getNewHandlerInstance());

		return returnVal;
	}

	/**
	 * Create a new instance of the handler class for {@code this} client.
	 *
	 * @return new instance of the handler for response messages.
	 */
	public abstract RespHandler getNewHandlerInstance();

	/**
	 * Returns the {@code ThreadLocal} instance of MessageHandler.
	 *
	 * @param requestUniqueIdParam The unique request id.
	 *
	 * @see ThreadLocal
	 *
	 * @return The message handler.
	 */
	protected RespHandler getHandler(String requestUniqueIdParam) {
		return this.messageHandler.get(requestUniqueIdParam);
	}

	/**
	 * Remove the handler with identifier {@code requestUniqueIdParam}.
	 * @param requestUniqueIdParam The unique request id.
	 */
	protected void removeHandler(String requestUniqueIdParam){
		this.messageHandler.remove(requestUniqueIdParam);
	}

	/**
	 * Retrieves the Web Service URL from {@code webServiceURLParam}.
	 *
	 * @param webServiceURLParam The Web Service URL to convert.
	 * @return The Web Socket URL version of {@code webServiceURLParam}.
	 */
	private String getWebSocketBaseURIFrom(String webServiceURLParam)
	{
		if(webServiceURLParam == null)
		{
			return null;
		}

		if(webServiceURLParam.trim().length() == 0)
		{
			return UtilGlobal.EMPTY;
		}

		URI uri = URI.create(webServiceURLParam);
		StringBuilder returnBuffer = new StringBuilder();

		String scheme = uri.getScheme();

		if(scheme == null)
		{
			throw new FluidClientException(
					"Unable to get scheme from '"+webServiceURLParam+"' URL.",
					FluidClientException.ErrorCode.ILLEGAL_STATE_ERROR);
		}

		scheme = scheme.trim().toLowerCase();

		//https://localhost:8443/fluid-ws/
		//Scheme...
		if(Constant.HTTP.equals(scheme)) {

			returnBuffer.append(Constant.WS);
		}
		else if(Constant.HTTPS.equals(scheme)) {

			returnBuffer.append(Constant.WSS);
		}
		else {
			returnBuffer.append(uri.getScheme());
		}

		// ://
		returnBuffer.append(Constant.SCHEME_SEP);
		returnBuffer.append(uri.getHost());

		// 80 / 443
		if(uri.getPort() > 0)
		{
			returnBuffer.append(Constant.COLON);
			returnBuffer.append(uri.getPort());
		}

		// /fluid-ws/
		returnBuffer.append(uri.getPath());

		return returnBuffer.toString();
	}

	/**
	 * The timeout in millis.
	 *
	 * @return Timeout in millis.
	 */
	protected long getTimeoutInMillis() {
		return this.timeoutInMillis;
	}

	/**
	 * Confirms whether the Web-Socket client connection session is open.
	 *
	 * @return Whether the connection is valid or not.
	 *          {@code true} if the connection is valid.
	 */
	@Override
	public boolean isConnectionValid() {
		if(this.webSocketClient == null) {
			return false;
		}

		return this.webSocketClient.isSessionOpen();
	}

	/**
	 * Return the current user session id.
	 *
	 * @return {@code Session ID} if session is open, otherwise {@code null}.
	 */
	public String getSessionId() {
		if(this.webSocketClient == null) {
			return null;
		}

		return this.webSocketClient.getSessionId();
	}

	/**
	 * Set the {@code echo} value if not set.
	 *
	 * @param baseToSetEchoOnIfNotSetParam The value object to set {@code echo} on.
	 */
	protected void setEchoIfNotSet(ABaseFluidVO baseToSetEchoOnIfNotSetParam){

	    if(baseToSetEchoOnIfNotSetParam == null) {
			throw new FluidClientException(
					"Cannot provide 'null' for value object / pojo.",
					FluidClientException.ErrorCode.ILLEGAL_STATE_ERROR);
		} else if(baseToSetEchoOnIfNotSetParam.getEcho() == null ||
				baseToSetEchoOnIfNotSetParam.getEcho().trim().isEmpty()) {
			baseToSetEchoOnIfNotSetParam.setEcho(UUID.randomUUID().toString());
		}
	}

	/**
	 * Generate a verbose Exception message.
	 *
	 * @param prefixParam The component prefix.
	 * @param uniqueReqIdParam The unique request reference.
	 * @param numberOfSentItemsParam The number of items already sent.
	 * @return Verbose error message.
	 */
	protected String getExceptionMessageVerbose(
			String prefixParam,
			String uniqueReqIdParam,
			int numberOfSentItemsParam
	) {
		StringBuilder formFieldsCombined = new StringBuilder();
		int returnValSize = -1;
		RespHandler respHandler = this.getHandler(uniqueReqIdParam);
		if(respHandler instanceof AGenericListMessageHandler) {
			List<? extends ABaseFluidJSONObject> returnValue =
					((AGenericListMessageHandler)respHandler).getReturnValue();
			if(returnValue != null) {
				returnValSize = returnValue.size();

				returnValue.forEach(listingItm -> {
					if(listingItm instanceof ABaseListing) {
						ABaseListing castedToListing = (ABaseListing)listingItm;

						if(castedToListing != null) {
							castedToListing.getListing().forEach(formItm -> {
								formFieldsCombined.append(formItm.toString());
							});
						}
					} else {
						formFieldsCombined.append(listingItm.toString());
					}
				});
			}
		}

		return (prefixParam + ": " +
				"Timeout while waiting for all return data. There were '"+
				returnValSize +"' items after a Timeout of "+(
				TimeUnit.MILLISECONDS.toSeconds(this.getTimeoutInMillis()))+" seconds on req-ref-nr '"+
				uniqueReqIdParam+"'. Expected a total of '" + numberOfSentItemsParam + "' forms. Returned-Data '"+
				formFieldsCombined.toString()+"'.");
	}

	/**
	 * Utility class to close the connection in a thread.
	 */
	private static class CloseConnectionRunnable implements Runnable{

		private ABaseClientWebSocket baseClientWebSocket;

		/**
		 * The resource to close.
		 *
		 * @param baseClientWebSocketParam Base WS utility to close.
		 */
		CloseConnectionRunnable(ABaseClientWebSocket baseClientWebSocketParam) {
			this.baseClientWebSocket = baseClientWebSocketParam;
		}

		/**
		 * Performs the threaded operation.
		 */
		@Override
		public void run() {

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

			this.baseClientWebSocket.closeConnectionNonThreaded();
		}
	}
}


/*
 * 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.user;

import org.json.JSONObject;

import com.fluidbpm.program.api.vo.user.User;
import com.fluidbpm.program.api.vo.ws.WS;
import com.fluidbpm.ws.client.v1.ABaseClientWS;

/**
 * Client to create a {@code admin} user if not yet created.
 *
 * @author jasonbruwer
 * @since v1.8
 *
 * @see JSONObject
 * @see com.fluidbpm.program.api.vo.user.User
 */
public class AdminUserCreateClient extends ABaseClientWS {

    /**
     * Constructor which sets the server URL.
     *
     * @param urlParam The server URL.
     */
    public AdminUserCreateClient(String urlParam) {
        super(urlParam);
    }

    /**
     * Create a new administrator user for Fluid.
     * This function only works if there are no pre-existing admin user.
     *
     * @param passwordParam The {@code admin} users password.
     * @return The newly created admin user.
     *
     * @see User
     */
    public User createAdminUser(String passwordParam) {

        User adminUserCreate = new User();
        adminUserCreate.setPasswordClear(passwordParam);

        return new User(this.putJson(
                adminUserCreate, WS.Path.User.Version1.userCreateAdmin()));
    }
}


/*
 * 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.user;

import java.util.Date;

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

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

/**
 * <p>
 *     A notification for a user.
 *     The user notifications are divided into categories.
 * </p>
 *
 * @author jasonbruwer on 2018-03-27
 * @since v1.8
 *
 * @see User
 */
public class UserNotification extends ABaseFluidJSONObject {

    public static final long serialVersionUID = 1L;

    private Date dateRead;
    private Date dateCreated;

    private String message;
    private String expiringLink;

    private User user;
    private int userNotificationType;

    /**
     * The type of notification a notification call fall under.
     */
    public static class UserNotificationType
    {
        public static final int GLOBAL = 1;
        public static final int EMAIL_PROCESSED = 2;
        public static final int COLLEAGUE_COLLABORATION = 3;
        public static final int VERSION_RELEASE = 4;
        public static final int SYSTEM_ADMINISTRATIVE_CHANGE = 5;
        public static final int CRITICAL = 6;
    }

    /**
     * The JSON mapping for the {@code RoleToJobView} object.
     */
    public static class JSONMapping
    {
        public static final String DATE_READ = "dateRead";
        public static final String DATE_CREATED = "dateCreated";
        public static final String MESSAGE = "message";
        public static final String EXPIRING_LINK = "expiringLink";
        public static final String USER = "user";
        public static final String USER_NOTIFICATION_TYPE = "userNotificationType";
    }

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

    /**
     * Sets the Id associated with a 'Role To Job View'.
     *
     * @param roleToJobViewIdParam RoleToJobView Id.
     */
    public UserNotification(Long roleToJobViewIdParam) {
        super();

        this.setId(roleToJobViewIdParam);
    }

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

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

        //User...
        if (!this.jsonObject.isNull(JSONMapping.USER)) {
            this.setUser(new User(this.jsonObject.getJSONObject(
                    JSONMapping.USER)));
        }

        //Date Created...
        this.setDateCreated(this.getDateFieldValueFromFieldWithName(
                JSONMapping.DATE_CREATED));

        //Date Read...
        this.setDateRead(this.getDateFieldValueFromFieldWithName(
                JSONMapping.DATE_READ));

        //Message...
        if (!this.jsonObject.isNull(JSONMapping.MESSAGE)) {
            this.setMessage(this.jsonObject.getString(JSONMapping.MESSAGE));
        }

        //Expiring Link...
        if (!this.jsonObject.isNull(JSONMapping.EXPIRING_LINK)) {
            this.setExpiringLink(this.jsonObject.getString(
                    JSONMapping.EXPIRING_LINK));
        }

        //User Notification Type...
        if (!this.jsonObject.isNull(JSONMapping.USER_NOTIFICATION_TYPE)) {
            this.setUserNotificationType(this.jsonObject.getInt(
                    JSONMapping.USER_NOTIFICATION_TYPE));
        }
    }

    /**
     * Gets The {@code Date} the User Notification was created.
     *
     * @return Date Created.
     */
    public Date getDateCreated() {
        return this.dateCreated;
    }

    /**
     * Sets The {@code Date} the User Notification was created.
     *
     * @param dateCreatedParam Date Created.
     */
    public void setDateCreated(Date dateCreatedParam) {
        this.dateCreated = dateCreatedParam;
    }

    /**
     * Gets The {@code Date} the User Notification was read by the user.
     *
     * @return Date Read.
     */
    public Date getDateRead() {
        return this.dateRead;
    }

    /**
     * Sets {@code Date} the User Notification was read by the user.
     *
     * @param dateReadParam Date Read.
     */
    public void setDateRead(Date dateReadParam) {
        this.dateRead = dateReadParam;
    }

    /**
     * Gets the notification text message.
     *
     * @return Message.
     */
    public String getMessage() {
        return this.message;
    }

    /**
     * Sets the notification text message.
     *
     * @param messageParam Message.
     */
    public void setMessage(String messageParam) {
        this.message = messageParam;
    }

    /**
     * Gets the notification expiring link (if applicable).
     *
     * @return Expiring Link.
     */
    public String getExpiringLink() {
        return this.expiringLink;
    }

    /**
     * Sets the notification expiring link (if applicable).
     *
     * @param expiringLinkParam Expiring Link.
     */
    public void setExpiringLink(String expiringLinkParam) {
        this.expiringLink = expiringLinkParam;
    }

    /**
     * The {@code User} the notification is for.
     *
     * @return The {@code User}
     *
     * @see User
     */
    public User getUser() {
        return this.user;
    }

    /**
     * The {@code User} the notification is for.
     *
     * @param userParam The {@code User}
     *
     * @see User
     */
    public void setUser(User userParam) {
        this.user = userParam;
    }

    /**
     * The notification type.
     *
     * @return The {@code User} notification type.
     *
     * @see UserNotificationType
     */
    public int getUserNotificationType() {
        return this.userNotificationType;
    }

    /**
     * The notification type.
     *
     * @param userNotificationTypeParam The notification type.
     *
     * @see UserNotificationType
     */
    public void setUserNotificationType(int userNotificationTypeParam) {
        this.userNotificationType = userNotificationTypeParam;
    }

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

        JSONObject returnVal = super.toJsonObject();

        //User...
        if(this.getUser() != null)
        {
            returnVal.put(JSONMapping.USER,
                    this.getUser().toJsonObject());
        }

        //Date Created...
        if(this.getDateCreated() != null)
        {
            returnVal.put(JSONMapping.DATE_CREATED,
                    this.getDateAsLongFromJson(this.getDateCreated()));
        }

        //Date Read...
        if(this.getDateRead() != null)
        {
            returnVal.put(JSONMapping.DATE_READ,
                    this.getDateAsLongFromJson(this.getDateRead()));
        }

        //Expiring Link...
        if(this.getExpiringLink() != null)
        {
            returnVal.put(JSONMapping.EXPIRING_LINK, this.getExpiringLink());
        }

        //Message...
        if(this.getMessage() != null)
        {
            returnVal.put(JSONMapping.MESSAGE, this.getMessage());
        }

        //User Notification Type...
        returnVal.put(JSONMapping.USER_NOTIFICATION_TYPE, this.getUserNotificationType());

        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.program.api.vo.form;

import java.util.Date;

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

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

/**
 * Represents a Collaboration of a Form between multiple users.
 *
 * @author jasonbruwer
 * @since v1.9
 *
 * @see Form
 * @see User
 */
public class Collaboration extends ABaseFluidJSONObject {

    public static final long serialVersionUID = 1L;

    private Form formContainer;
    private User fromUser;
    private User toUser;

    private Date dateSent;
    private Date dateRead;
    private String message;

    /**
     * The JSON mapping for the {@code Collaboration} object.
     */
    public static class JSONMapping {
        public static final String FORM_CONTAINER= "formContainer";
        public static final String FROM_USER = "fromUser";
        public static final String TO_USER = "toUser";
        public static final String DATE_SENT = "dateSent";
        public static final String DATE_READ = "dateRead";
        public static final String MESSAGE = "message";
    }

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

    /**
     * Sets the collaboration for a creation.
     *
     * @param formContainerParam The Form for the collaboration.
     * @param fromUserParam The from user.
     * @param toUserParam The to user.
     *
     * @see Form
     * @see User
     */
    public Collaboration(Form formContainerParam, User fromUserParam, User toUserParam) {
        this();
        this.setFormContainer(formContainerParam);
        this.setFromUser(fromUserParam);
        this.setToUser(toUserParam);
    }

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

        //Form Container...
        if (!this.jsonObject.isNull(JSONMapping.FORM_CONTAINER)) {
            this.setFormContainer(
                    new Form(this.jsonObject.getJSONObject(JSONMapping.FORM_CONTAINER)));
        }

        //From User...
        if (!this.jsonObject.isNull(JSONMapping.FROM_USER)) {

            JSONObject jsonObj = this.jsonObject.getJSONObject(
                    JSONMapping.FROM_USER);
            User fromUser = new User();

            //User Id
            if (!jsonObj.isNull(User.JSONMapping.Elastic.USER_ID)) {
                fromUser.setId(jsonObj.getLong(User.JSONMapping.Elastic.USER_ID));
            }
            //Id is set, make use of that instead...
            else if (!jsonObj.isNull(
                    ABaseFluidJSONObject.JSONMapping.ID)) {
                fromUser.setId(jsonObj.getLong(ABaseFluidJSONObject.JSONMapping.ID));
            }

            //Username
            if (!jsonObj.isNull(User.JSONMapping.USERNAME)) {
                fromUser.setUsername(jsonObj.getString(User.JSONMapping.USERNAME));
            }

            this.setFromUser(fromUser);
        }

        //To User...
        if (!this.jsonObject.isNull(JSONMapping.TO_USER)) {

            JSONObject jsonObj = this.jsonObject.getJSONObject(JSONMapping.TO_USER);
            User toUser = new User();

            //User Id
            if (!jsonObj.isNull(User.JSONMapping.Elastic.USER_ID)) {
                toUser.setId(jsonObj.getLong(User.JSONMapping.Elastic.USER_ID));
            }
            //Id is set, make use of that instead...
            else if (!jsonObj.isNull(
                    ABaseFluidJSONObject.JSONMapping.ID)) {
                toUser.setId(jsonObj.getLong(ABaseFluidJSONObject.JSONMapping.ID));
            }

            //Username
            if (!jsonObj.isNull(User.JSONMapping.USERNAME)) {
                toUser.setUsername(jsonObj.getString(User.JSONMapping.USERNAME));
            }

            this.setToUser(toUser);
        }

        //Date Read...
        this.setDateRead(this.getDateFieldValueFromFieldWithName(JSONMapping.DATE_READ));

        //Date Sent...
        this.setDateSent(this.getDateFieldValueFromFieldWithName(JSONMapping.DATE_SENT));

        //Message...
        if (!this.jsonObject.isNull(JSONMapping.MESSAGE)) {
            this.setMessage(this.jsonObject.getString(JSONMapping.MESSAGE));
        }
    }

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

        //Form Container...
        if(this.getFormContainer() != null) {
            returnVal.put(JSONMapping.FORM_CONTAINER,
                    this.getFormContainer().toJsonObject());
        }

        //Message...
        if(this.getMessage() != null) {
            returnVal.put(JSONMapping.MESSAGE, this.getMessage());
        }

        //Date Read...
        if(this.getDateRead() != null) {
            returnVal.put(JSONMapping.DATE_READ,
                    this.getDateAsLongFromJson(this.getDateRead()));
        }

        //Date Sent...
        if(this.getDateSent() != null) {
            returnVal.put(JSONMapping.DATE_SENT,
                    this.getDateAsLongFromJson(this.getDateSent()));
        }

        //From User...
        if(this.getFromUser() != null) {
            returnVal.put(
                    JSONMapping.FROM_USER,
                    this.getFromUser().toJsonObject());
        }

        //To User...
        if(this.getToUser() != null) {
            returnVal.put(
                    JSONMapping.TO_USER,
                    this.getToUser().toJsonObject());
        }

        return returnVal;
    }

    /**
     * Gets the electronic Form Container.
     *
     * @return The Form.
     *
     * @see Form
     */
    public Form getFormContainer() {
        return this.formContainer;
    }

    /**
     * Sets the electronic Form Container.
     *
     * @param formContainerParam The Form.
     *
     * @see Form
     */
    public void setFormContainer(Form formContainerParam) {
        this.formContainer = formContainerParam;
    }

    /**
     * Gets the {@code User} who shared the {@code Form}.
     *
     * @return The 'FROM' User.
     *
     * @see User
     */
    public User getFromUser() {
        return this.fromUser;
    }

    /**
     * Sets the {@code User} who shared the {@code Form}.
     *
     * @param fromUserParam The 'FROM' User.
     *
     * @see User
     */
    public void setFromUser(User fromUserParam) {
        this.fromUser = fromUserParam;
    }

    /**
     * Gets the {@code User} who the {@code Form} is shared with.
     *
     * @return The 'TO' User.
     *
     * @see User
     */
    public User getToUser() {
        return this.toUser;
    }

    /**
     * Sets the {@code User} who the {@code Form} is shared with.
     *
     * @param toUserParam The 'TO' User.
     *
     * @see User
     */
    public void setToUser(User toUserParam) {
        this.toUser = toUserParam;
    }

    /**
     * Gets The {@code Date} the Collaboration was sent.
     *
     * @return Date Sent.
     */
    public Date getDateSent() {
        return this.dateSent;
    }

    /**
     * Sets The {@code Date} the Collaboration was sent.
     *
     * @param dateSentParam Date Sent.
     */
    public void setDateSent(Date dateSentParam) {
        this.dateSent = dateSentParam;
    }

    /**
     * Gets The {@code Date} the Collaboration was read by the 'TO' {@code User}.
     *
     * @return Date Read.
     */
    public Date getDateRead() {
        return this.dateRead;
    }

    /**
     * Sets The {@code Date} the Collaboration was read by the 'TO' {@code User}.
     *
     * @param dateReadParam Date Read.
     */
    public void setDateRead(Date dateReadParam) {
        this.dateRead = dateReadParam;
    }

    /**
     * Gets the message intended for the 'TO' {@code User}.
     *
     * @return Message
     */
    public String getMessage() {
        return this.message;
    }

    /**
     * Sets the message intended for the 'TO' {@code User}.
     *
     * @param messageParam The message for the 'TO' {@code User}.
     */
    public void setMessage(String messageParam) {
        this.message = messageParam;
    }
}