com.microfocus.application.automation.tools.octane.configuration.JobConfigurationProxy.java Source code

Java tutorial

Introduction

Here is the source code for com.microfocus.application.automation.tools.octane.configuration.JobConfigurationProxy.java

Source

/*
 *
 *  Certain versions of software and/or documents (Material?) accessible here may contain branding from
 *  Hewlett-Packard Company (now HP Inc.) and Hewlett Packard Enterprise Company.  As of September 1, 2017,
 *  the Material is now offered by Micro Focus, a separately owned and operated company.  Any reference to the HP
 *  and Hewlett Packard Enterprise/HPE marks is historical in nature, and the HP and Hewlett Packard Enterprise/HPE
 *  marks are the property of their respective owners.
 * __________________________________________________________________
 * MIT License
 *
 *  Copyright 2012-2018 Micro Focus or one of its affiliates.
 *
 * The only warranties for products and services of Micro Focus and its affiliates
 * and licensors (Micro Focus?) are set forth in the express warranty statements
 * accompanying such products and services. Nothing herein should be construed as
 * constituting an additional warranty. Micro Focus shall not be liable for technical
 * or editorial errors or omissions contained herein.
 * The information contained herein is subject to change without notice.
 * ___________________________________________________________________
 *
 */

package com.microfocus.application.automation.tools.octane.configuration;

import com.hp.octane.integrations.OctaneClient;
import com.hp.octane.integrations.OctaneSDK;
import com.hp.octane.integrations.dto.DTOFactory;
import com.hp.octane.integrations.dto.entities.Entity;
import com.hp.octane.integrations.dto.entities.EntityConstants;
import com.hp.octane.integrations.dto.entities.ResponseEntityList;
import com.hp.octane.integrations.dto.general.CIServerInfo;
import com.hp.octane.integrations.dto.general.ListItem;
import com.hp.octane.integrations.dto.general.Taxonomy;
import com.hp.octane.integrations.dto.parameters.CIParameter;
import com.hp.octane.integrations.dto.pipelines.PipelineContext;
import com.hp.octane.integrations.dto.pipelines.PipelineContextList;
import com.hp.octane.integrations.dto.pipelines.PipelineNode;
import com.hp.octane.integrations.services.entities.EntitiesService;
import com.microfocus.application.automation.tools.model.OctaneServerSettingsModel;
import com.microfocus.application.automation.tools.octane.CIJenkinsServicesImpl;
import com.microfocus.application.automation.tools.octane.Messages;
import com.microfocus.application.automation.tools.octane.model.ModelFactory;
import com.microfocus.application.automation.tools.octane.model.processors.parameters.ParameterProcessors;
import com.microfocus.application.automation.tools.octane.model.processors.projects.JobProcessorFactory;
import hudson.model.Job;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.kohsuke.stapler.bind.JavaScriptMethod;

import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * This class is a proxy between JS UI code and server-side job configuration.
 */
public class JobConfigurationProxy {
    private final static Logger logger = LogManager.getLogger(JobConfigurationProxy.class);
    private static final DTOFactory dtoFactory = DTOFactory.getInstance();

    final private Job job;

    private static final String NOT_SPECIFIED = "-- Not specified --";

    JobConfigurationProxy(Job job) {
        this.job = job;
    }

    @JavaScriptMethod
    public JSONObject createPipelineOnServer(JSONObject pipelineObject) {
        JSONObject result = new JSONObject();

        PipelineNode pipelineNode = ModelFactory.createStructureItem(job);
        String instanceId = pipelineObject.getString("instanceId");
        CIServerInfo ciServerInfo = CIJenkinsServicesImpl.getJenkinsServerInfo();
        ciServerInfo.setInstanceId(instanceId);
        OctaneClient octaneClient = OctaneSDK.getClientByInstanceId(instanceId);
        try {

            PipelineContext pipelineContext = dtoFactory.newDTO(PipelineContext.class)
                    .setContextName(pipelineObject.getString("name"))
                    .setWorkspace(pipelineObject.getLong("workspaceId"))
                    .setReleaseId(pipelineObject.getLong("releaseId")).setStructure(pipelineNode)
                    .setServer(ciServerInfo);
            PipelineContext createdPipelineContext = octaneClient.getPipelineContextService()
                    .createPipeline(octaneClient.getInstanceId(), pipelineNode.getJobCiId(), pipelineContext);

            //WORKAROUND BEGIN
            //getting workspaceName - because the workspaceName is not returned from configuration API
            List<Entity> workspaces = getWorkspacesById(octaneClient,
                    Collections.singletonList(pipelineContext.getWorkspaceId()));
            if (workspaces.size() != 1) {
                throw new ClientException("WorkspaceName could not be retrieved for workspaceId: "
                        + pipelineContext.getWorkspaceId());
            }
            //WORKAROUND END

            JSONObject pipelineJSON = fromPipeline(createdPipelineContext, workspaces.get(0));
            enrichPipelineInstanceId(pipelineJSON, instanceId);

            //WORKAROUND BEGIN
            //all metadata have to be loaded in separate REST calls for this pipeline: releaseName, taxonomyNames and listFieldNames are not returned from configuration API
            enrichPipelineInternal(pipelineJSON, octaneClient);
            //WORKAROUND END
            result.put("pipeline", pipelineJSON);

            JSONArray fieldsMetadata = convertToJsonMetadata(
                    getPipelineListNodeFieldsMetadata(octaneClient, createdPipelineContext.getWorkspaceId()));
            result.put("fieldsMetadata", fieldsMetadata);

        } catch (ClientException e) {
            logger.warn("Failed to create pipeline", e);
            return error(e.getMessage(), e.getLink());
        } catch (Exception e) {
            logger.warn("Failed to create pipeline", e);
            return error(e.getMessage());
        }
        return result;
    }

    @JavaScriptMethod
    public JSONObject updatePipelineOnSever(JSONObject pipelineObject) {
        JSONObject result = new JSONObject();

        String instanceId = pipelineObject.getString("instanceId");
        OctaneClient octaneClient = OctaneSDK.getClientByInstanceId(instanceId);

        try {
            long pipelineId = pipelineObject.getLong("id");

            //build taxonomies
            LinkedList<Taxonomy> taxonomies = new LinkedList<>();
            JSONArray taxonomyTags = pipelineObject.getJSONArray("taxonomyTags");
            for (JSONObject jsonObject : toCollection(taxonomyTags)) {
                taxonomies.add(dtoFactory.newDTO(Taxonomy.class).setId(jsonObject.optLong("tagId"))
                        .setName(jsonObject.getString("tagName"))
                        .setParent(dtoFactory.newDTO(Taxonomy.class).setId(jsonObject.optLong("tagTypeId"))
                                .setName(jsonObject.getString("tagTypeName"))));
            }

            //build list fields
            Map<String, List<ListItem>> fields = new HashMap<>();
            JSONArray fieldTags = pipelineObject.getJSONArray("fieldTags");
            for (JSONObject jsonObject : toCollection(fieldTags)) {
                List<ListItem> assignedValues = new LinkedList<>();
                for (JSONObject value : toCollection(jsonObject.getJSONArray("values"))) {
                    String id;
                    if (value.containsKey("id")) {
                        id = value.getString("id");
                    } else {
                        id = null;
                    }
                    assignedValues
                            .add(dtoFactory.newDTO(ListItem.class).setId(id).setName(value.getString("name")));
                }
                fields.put(jsonObject.getString("name"), assignedValues);
            }

            final String jobCiId = JobProcessorFactory.getFlowProcessor(job).getTranslateJobName();

            PipelineContext pipelineContext = dtoFactory.newDTO(PipelineContext.class)
                    .setContextEntityId(pipelineId).setContextName(pipelineObject.getString("name"))
                    .setWorkspace(pipelineObject.getLong("workspaceId"))
                    .setReleaseId(pipelineObject.getLong("releaseId"))
                    .setIgnoreTests(pipelineObject.getBoolean("ignoreTests")).setTaxonomies(taxonomies)
                    .setListFields(fields);

            PipelineContext pipeline = octaneClient.getPipelineContextService()
                    .updatePipeline(octaneClient.getInstanceId(), jobCiId, pipelineContext);

            //WORKAROUND BEGIN
            //getting workspaceName - because the workspaceName is not returned from configuration API
            List<Entity> workspaces = getWorkspacesById(octaneClient,
                    Collections.singletonList(pipeline.getWorkspaceId()));
            if (workspaces.size() != 1) {
                throw new ClientException(
                        "WorkspaceName could not be retrieved for workspaceId: " + pipeline.getWorkspaceId());
            }
            //WORKAROUND END

            //TODO uncomment after getting pipelineContext
            JSONObject pipelineJSON = fromPipeline(pipeline, workspaces.get(0));
            enrichPipelineInstanceId(pipelineJSON, instanceId);

            //WORKAROUND BEGIN
            //all metadata have to be loaded in separate REST calls for this pipeline: releaseName, taxonomyNames and listFieldNames are not returned from configuration API
            enrichPipelineInternal(pipelineJSON, octaneClient);
            //WORKAROUND END
            result.put("pipeline", pipelineJSON);

            //Server might do partial sucess
            //So need to validate each item if it succedded or not
            //For now we add handling of duplicate pipeline name
            String originalName = pipelineObject.get("name").toString();
            String updatedName = pipelineJSON.get("name").toString();
            if (!originalName.equalsIgnoreCase(updatedName)) {
                JSONObject errorObj = new JSONObject();
                errorObj.put("message",
                        "Failed to update pipeline name. Make sure not to enter the name of an existing pipeline.");
                result.put("error", errorObj);
            }
        } catch (ClientException e) {
            logger.warn("Failed to update pipeline", e);
            return error(e.getMessage(), e.getLink());
        } catch (Exception e) {
            logger.warn("Failed to update pipeline", e);
            return error("Unable to update pipeline");
        }

        return result;
    }

    @JavaScriptMethod
    public JSONObject deleteTests(JSONObject pipelineObject) {
        JSONObject result = new JSONObject();
        String instanceId = pipelineObject.getString("instanceId");
        OctaneClient octaneClient = OctaneSDK.getClientByInstanceId(instanceId);
        try {
            long pipelineId = pipelineObject.getLong("id");
            long workspaceId = pipelineObject.getLong("workspaceId");
            octaneClient.getPipelineContextService().deleteTestsFromPipelineNodes(job.getName(), pipelineId,
                    workspaceId);
            result.put("Test deletion was succeful", "");
        } catch (Exception e) {
            logger.warn("Failed to delete tests", e);
            return error("Unable to delete tests");
        }

        return result;
    }

    @JavaScriptMethod
    public JSONObject loadJobConfigurationFromServer(String instanceId) {
        OctaneClient octaneClient = OctaneSDK.getClientByInstanceId(instanceId);

        JSONObject ret = new JSONObject();
        JSONObject workspaces = new JSONObject();
        JSONArray fieldsMetadata = new JSONArray();
        try {
            boolean isUftJob = false;
            List<CIParameter> parameters = ParameterProcessors.getConfigs(job);
            if (parameters != null) {
                for (CIParameter parameter : parameters) {
                    if (parameter != null && parameter.getName() != null && parameter.getName().equals("suiteId")) {
                        isUftJob = true;
                        break;
                    }
                }
            }
            ret.put("isUftJob", isUftJob);

            final String jobCiId = JobProcessorFactory.getFlowProcessor(job).getTranslateJobName();
            PipelineContextList pipelineContextList = octaneClient.getPipelineContextService()
                    .getJobConfiguration(octaneClient.getInstanceId(), jobCiId);

            if (!pipelineContextList.getData().isEmpty()) {
                Map<Long, List<PipelineContext>> workspacesMap = pipelineContextList.buildWorkspace2PipelinesMap();
                //WORKAROUND BEGIN
                //getting workspaceName - because the workspaceName is not returned from configuration API
                Map<Long, String> relatedWorkspaces = new HashMap<>();
                List<Entity> workspaceList = getWorkspacesById(octaneClient, workspacesMap.keySet());
                for (Entity workspace : workspaceList) {
                    relatedWorkspaces.put(Long.parseLong(workspace.getId()), workspace.getName());
                }
                //WORKAROUND END

                Map<Entity, List<PipelineContext>> sortedWorkspacesMap = new TreeMap<>(
                        Comparator.comparing(Entity::getName));
                Comparator<PipelineContext> pipelineComparator = Comparator
                        .comparing(PipelineContext::getContextEntityName);

                //create workspaces JSON Object
                for (Entry<Long, List<PipelineContext>> workspacePipelines : workspacesMap.entrySet()) {
                    Entity relatedWorkspace = dtoFactory.newDTO(Entity.class);

                    relatedWorkspace.setId(workspacePipelines.getKey().toString());
                    relatedWorkspace.setName(relatedWorkspaces.get(workspacePipelines.getKey()));

                    JSONObject relatedPipelinesJSON = new JSONObject();

                    for (PipelineContext relatedPipeline : workspacePipelines.getValue()) {
                        JSONObject pipelineJSON = fromPipeline(relatedPipeline, relatedWorkspace);
                        enrichPipelineInstanceId(pipelineJSON, instanceId);
                        relatedPipelinesJSON.put(String.valueOf(relatedPipeline.getContextEntityId()),
                                pipelineJSON);
                    }
                    JSONObject workspaceJSON = new JSONObject();
                    workspaceJSON.put("id", relatedWorkspace.getId());
                    workspaceJSON.put("name", relatedWorkspace.getName());
                    workspaceJSON.put("pipelines", relatedPipelinesJSON);
                    workspaces.put(String.valueOf(relatedWorkspace.getId()), workspaceJSON);

                    //inserting this workspace into sortedMap (sorted by workspaceName and by pipelineName, so that we can pick first workspace and its first pipeline as preselected values
                    LinkedList<PipelineContext> workspacePipelinesList = new LinkedList<>(
                            workspacePipelines.getValue());
                    workspacePipelinesList.sort(pipelineComparator);
                    sortedWorkspacesMap.put(relatedWorkspace, workspacePipelinesList);
                }

                //create currentPipeline JSON Object
                //currently the first pipeline in the first workspace is picked
                Entity preSelectedWorkspace = sortedWorkspacesMap.keySet().iterator().next();
                PipelineContext preSelectedPipeline = sortedWorkspacesMap.get(preSelectedWorkspace).get(0);
                JSONObject preSelectedPipelineJSON = fromPipeline(preSelectedPipeline, preSelectedWorkspace);
                enrichPipelineInstanceId(preSelectedPipelineJSON, instanceId);
                //WORKAROUND BEGIN
                //all metadata have to be loaded in separate REST calls for this pipeline: releaseName, taxonomyNames and listFieldNames are not returned from configuration API
                enrichPipelineInternal(preSelectedPipelineJSON, octaneClient);
                //WORKAROUND END
                ret.put("currentPipeline", preSelectedPipelineJSON);

                //retrieving metadata fields for preselected workspace
                fieldsMetadata = convertToJsonMetadata(getPipelineListNodeFieldsMetadata(octaneClient,
                        Long.parseLong(preSelectedWorkspace.getId())));
            }

            ret.put("workspaces", workspaces);
            ret.put("fieldsMetadata", fieldsMetadata);

        } catch (Exception e) {
            logger.warn("Failed to retrieve job configuration", e);
            return error("Unable to retrieve job configuration");
        }

        return ret;
    }

    @JavaScriptMethod
    public JSONObject loadWorkspaceConfiguration(JSONObject pipelineJSON) {
        String instanceId = pipelineJSON.getString("instanceId");
        OctaneClient octaneClient = OctaneSDK.getClientByInstanceId(instanceId);
        JSONObject ret = new JSONObject();

        try {
            JSONArray fieldsMetadata = convertToJsonMetadata(
                    getPipelineListNodeFieldsMetadata(octaneClient, pipelineJSON.getLong("workspaceId")));
            ret.put("fieldsMetadata", fieldsMetadata);
            enrichPipelineInternal(pipelineJSON, octaneClient);
            ret.put("pipeline", pipelineJSON);
        } catch (Exception e) {
            logger.warn("Failed to retrieve metadata for workspace", e);
            return error("Unable to retrieve metadata for workspace");
        }

        return ret;
    }

    private static JSONObject fromPipeline(final PipelineContext pipeline, Entity relatedWorkspace) {
        JSONObject pipelineJSON = new JSONObject();
        pipelineJSON.put("id", pipeline.getContextEntityId());
        pipelineJSON.put("name", pipeline.getContextEntityName());
        pipelineJSON.put("releaseId", pipeline.getReleaseId() != null ? pipeline.getReleaseId() : -1);
        pipelineJSON.put("isRoot", pipeline.isPipelineRoot());
        pipelineJSON.put("workspaceId", relatedWorkspace.getId());
        pipelineJSON.put("workspaceName", relatedWorkspace.getName());
        pipelineJSON.put("ignoreTests", pipeline.getIgnoreTests());
        addTaxonomyTags(pipelineJSON, pipeline);
        addFields(pipelineJSON, pipeline);

        return pipelineJSON;
    }

    @JavaScriptMethod
    public JSONObject enrichPipeline(JSONObject pipelineJSON) {
        String instanceId = pipelineJSON.getString("instanceId");
        OctaneClient octaneClient = OctaneSDK.getClientByInstanceId(instanceId);
        JSONObject ret = new JSONObject();
        try {
            enrichPipelineInternal(pipelineJSON, octaneClient);
            ret.put("pipeline", pipelineJSON);
        } catch (Exception e) {
            logger.warn("Failed to retrieve metadata for pipeline", e);
            return error("Unable to retrieve metadata for pipeline");
        }

        return ret;
    }

    private static void enrichPipelineInternal(JSONObject pipelineJSON, OctaneClient octaneClient) {
        enrichRelease(pipelineJSON, octaneClient);
        enrichTaxonomies(pipelineJSON, octaneClient);
        enrichFields(pipelineJSON, octaneClient);
    }

    private static void enrichPipelineInstanceId(JSONObject pipelineJSON, String instanceId) {
        pipelineJSON.put("instanceId", instanceId);
        pipelineJSON.put("instanceCaption", ConfigurationService.getSettings(instanceId).getCaption());
    }

    private static void enrichRelease(JSONObject pipeline, OctaneClient octaneClient) {
        long workspaceId = pipeline.getLong("workspaceId");
        if (pipeline.containsKey("releaseId") && pipeline.getLong("releaseId") != -1) {
            long releaseId = pipeline.getLong("releaseId");
            String releaseName = getReleasesById(octaneClient, Arrays.asList(releaseId), workspaceId).get(0)
                    .getName();
            pipeline.put("releaseName", releaseName);
        }
    }

    private static void enrichTaxonomies(JSONObject pipeline, OctaneClient octaneClient) {
        JSONArray ret = new JSONArray();
        if (pipeline.has("taxonomyTags")) {

            JSONArray taxonomyTags = pipeline.getJSONArray("taxonomyTags");
            List<Long> taxonomyIdsList = new LinkedList<>();
            for (int i = 0; i < taxonomyTags.size(); i++) {
                JSONObject taxonomy = taxonomyTags.getJSONObject(i);
                if (taxonomy.has("tagId")) {
                    taxonomyIdsList.add(taxonomy.getLong("tagId"));
                }
            }
            List<Taxonomy> taxonomies = convertTaxonomies(
                    getTaxonomiesById(octaneClient, taxonomyIdsList, pipeline.getLong("workspaceId")));
            for (Taxonomy tax : taxonomies) {
                ret.add(tag(tax));
            }
        }
        pipeline.put("taxonomyTags", ret);
    }

    private static void enrichFields(JSONObject pipeline, OctaneClient client) {
        JSONObject ret = new JSONObject();

        if (pipeline.has("fields")) {
            long workspaceId = pipeline.getLong("workspaceId");
            JSONObject pipelineFields = pipeline.getJSONObject("fields");
            Iterator<?> keys = pipelineFields.keys();
            //iteration over listFields
            while (keys.hasNext()) {
                String key = (String) keys.next();
                if (pipelineFields.get(key) instanceof JSONArray) {
                    List<String> fieldTagsIdsList = new LinkedList<>();
                    //getting all ids assigned to listField
                    for (JSONObject singleField : toCollection(pipelineFields.getJSONArray(key))) {
                        fieldTagsIdsList.add(singleField.getString("id"));
                    }
                    //retrieving names of assigned items
                    if (fieldTagsIdsList.size() > 0) {
                        List<Entity> enrichedFields = getListItemsById(client, fieldTagsIdsList, workspaceId);
                        JSONArray values = new JSONArray();
                        for (Entity item : enrichedFields) {
                            JSONObject value = new JSONObject();
                            value.put("id", item.getId());
                            value.put("name", item.getName());
                            values.add(value);
                        }
                        ret.put(key, values);
                    }
                }
            }
        }
        pipeline.put("fields", ret);
    }

    @JavaScriptMethod
    public JSONObject searchListItems(String logicalListName, String term, String instanceId, long workspaceId,
            boolean multiValue, boolean extensible) {
        int defaultSize = 10;
        JSONObject ret = new JSONObject();
        OctaneClient octaneClient = OctaneSDK.getClientByInstanceId(instanceId);
        try {
            ResponseEntityList listItemPagedList = queryListItems(octaneClient, logicalListName, term, workspaceId,
                    defaultSize);
            List<Entity> listItems = listItemPagedList.getData();
            boolean moreResults = listItemPagedList.getTotalCount() > listItems.size();

            JSONArray retArray = new JSONArray();
            if (moreResults) {
                retArray.add(createMoreResultsJson());
            }

            if (!multiValue) {
                String quotedTerm = Pattern.quote(term.toLowerCase());
                if (Pattern.matches(".*" + quotedTerm + ".*", NOT_SPECIFIED.toLowerCase())) {
                    JSONObject notSpecifiedItemJson = new JSONObject();
                    notSpecifiedItemJson.put("id", -1);
                    notSpecifiedItemJson.put("text", NOT_SPECIFIED);
                    retArray.add(notSpecifiedItemJson);
                }
            }

            for (Entity item : listItems) {
                if (!toBeFiltered(item)) {
                    JSONObject itemJson = new JSONObject();
                    itemJson.put("id", item.getId());
                    itemJson.put("text", item.getName());
                    retArray.add(itemJson);
                }

            }
            // we shall use "if (extensible){}" on following line, but we do not have UI ready for the case: multiValue = true & extensible = true
            if (extensible && !multiValue) {
                //if exactly one item matches, we do not want to bother user with "new value" item
                if ((listItems.size() != 1)
                        || (!listItems.get(0).getName().toLowerCase().equals(term.toLowerCase()))) {
                    retArray.add(createNewValueJson("0"));
                }
            }

            ret.put("results", retArray);
        } catch (Exception e) {
            logger.warn("Failed to retrieve list items", e);
            return error("Unable to retrieve job configuration");
        }

        return ret;
    }

    private boolean toBeFiltered(Entity item) {
        return (item.getStringValue(EntityConstants.Base.LOGICAL_NAME_FIELD)
                .equalsIgnoreCase("list_node.testing_tool_type.manual"));
    }

    @JavaScriptMethod
    public JSONObject searchReleases(String term, String instanceId, long workspaceId) {
        int defaultSize = 5;
        JSONObject ret = new JSONObject();
        OctaneClient octaneClient = OctaneSDK.getClientByInstanceId(instanceId);

        try {

            ResponseEntityList releasePagedList = queryReleasesByName(octaneClient, term, workspaceId, defaultSize);
            List<Entity> releases = releasePagedList.getData();
            boolean moreResults = releasePagedList.getTotalCount() > releases.size();

            JSONArray retArray = new JSONArray();
            if (moreResults) {
                retArray.add(createMoreResultsJson());
            }

            String quotedTerm = Pattern.quote(term.toLowerCase());
            if (Pattern.matches(".*" + quotedTerm + ".*", NOT_SPECIFIED.toLowerCase())) {
                JSONObject notSpecifiedItemJson = new JSONObject();
                notSpecifiedItemJson.put("id", -1);
                notSpecifiedItemJson.put("text", NOT_SPECIFIED);
                retArray.add(notSpecifiedItemJson);
            }

            for (Entity release : releases) {
                JSONObject relJson = new JSONObject();
                relJson.put("id", release.getId());
                relJson.put("text", release.getName());
                retArray.add(relJson);
            }
            ret.put("results", retArray);

        } catch (Exception e) {
            logger.warn("Failed to retrieve releases", e);
            return error("Unable to retrieve releases");
        }

        return ret;
    }

    @JavaScriptMethod
    public JSONObject searchWorkspaces(String term, String instanceId) {
        int defaultSize = 5;
        JSONObject ret = new JSONObject();
        OctaneClient octaneClient = OctaneSDK.getClientByInstanceId(instanceId);

        try {
            ResponseEntityList workspacePagedList = queryWorkspacesByName(octaneClient, term, defaultSize);
            List<Entity> workspaces = workspacePagedList.getData();
            boolean moreResults = workspacePagedList.getTotalCount() > workspaces.size();

            JSONArray retArray = new JSONArray();
            if (moreResults) {
                retArray.add(createMoreResultsJson());
            }

            for (Entity workspace : workspaces) {
                JSONObject relJson = new JSONObject();
                relJson.put("id", workspace.getId());
                relJson.put("text", workspace.getName());
                retArray.add(relJson);
            }
            ret.put("results", retArray);

        } catch (Exception e) {
            logger.warn("Failed to retrieve workspaces", e);
            return error("Unable to retrieve workspaces");
        }

        return ret;
    }

    @JavaScriptMethod
    public JSONObject searchSharedSpaces(String term) {
        JSONObject ret = new JSONObject();
        JSONArray retArray = new JSONArray();

        for (OctaneServerSettingsModel model : ConfigurationService.getAllSettings()) {
            if (StringUtils.isNotEmpty(term) && !model.getCaption().toLowerCase().contains(term.toLowerCase())) {
                continue;
            }
            JSONObject relJson = new JSONObject();
            relJson.put("id", model.getIdentity());
            relJson.put("text", model.getCaption());
            retArray.add(relJson);
        }
        ret.put("results", retArray);

        return ret;
    }

    @JavaScriptMethod
    public JSONObject searchTaxonomies(String term, String instanceId, long workspaceId,
            JSONArray pipelineTaxonomies) {
        int defaultSize = 20;
        JSONObject ret = new JSONObject();
        OctaneClient octaneClient = OctaneSDK.getClientByInstanceId(instanceId);
        try {

            //currently existing taxonomies on pipeline -> we need to show these options as disabled
            List<Long> pipelineTaxonomiesList = new LinkedList<>();
            for (int i = 0; i < pipelineTaxonomies.size(); i++) {
                JSONObject pipelineTaxonomy = pipelineTaxonomies.getJSONObject(i);
                if (pipelineTaxonomy.containsKey("tagId") && pipelineTaxonomy.containsKey("tagTypeId")) { // we need to compare only taxonomies which already exist on server
                    pipelineTaxonomiesList.add(pipelineTaxonomy.getLong("tagId"));
                }
            }
            //retrieving taxonomies from server
            ResponseEntityList foundTaxonomies = queryTaxonomiesByName(octaneClient, term, workspaceId,
                    defaultSize);
            final List<Entity> foundTaxonomiesList = foundTaxonomies.getData();
            boolean moreResults = foundTaxonomies.getTotalCount() > foundTaxonomiesList.size();

            //creating map <TaxonomyCategoryID : Set<Taxonomy>>
            // for easier creating result JSON
            Map<String, Set<Entity>> taxonomyMap = new HashMap<>();
            Map<String, String> taxonomyCategories = new HashMap<>();
            for (Entity taxonomy : foundTaxonomiesList) {
                Entity root = (Entity) taxonomy.getField(EntityConstants.Taxonomy.CATEGORY_NAME);
                if (root != null) {
                    if (taxonomyMap.containsKey(root.getId())) {
                        taxonomyMap.get(root.getId()).add(taxonomy);
                    } else {
                        taxonomyMap.put(root.getId(), new LinkedHashSet<>(Collections.singletonList(taxonomy)));
                        taxonomyCategories.put(root.getId(), root.getName());
                    }
                }
            }

            //writing result json
            JSONArray select2InputArray = new JSONArray();
            JSONObject allTags = new JSONObject();
            JSONObject tagTypesByName = new JSONObject();
            JSONObject tagTypes = new JSONObject();

            //show warning, that there are more results and user should filter more specific
            if (moreResults) {
                select2InputArray.add(createMoreResultsJson());
            }

            for (Entry<String, Set<Entity>> taxonomyType : taxonomyMap.entrySet()) {
                String tagTypeId = taxonomyType.getKey();
                String tagTypeName = taxonomyCategories.get(tagTypeId);
                JSONArray childrenArray = new JSONArray();

                JSONObject optgroup = new JSONObject();
                optgroup.put("text", tagTypeName);

                //for tagTypesByName
                JSONObject tagTypeJson = new JSONObject();
                tagTypeJson.put("tagTypeId", tagTypeId);
                tagTypeJson.put("tagTypeName", tagTypeName);
                JSONArray tagTypeByNameValues = new JSONArray();

                for (Entity tax : taxonomyType.getValue()) {
                    //creating input format for select2, so that this structure does not have to be refactored in javascript
                    JSONObject taxonomyJson = new JSONObject();
                    taxonomyJson.put("id", tax.getId());
                    taxonomyJson.put("text", tax.getName());
                    taxonomyJson.put("value", tax.getId());
                    if (pipelineTaxonomiesList.contains(tax.getId())) {
                        taxonomyJson.put("disabled", "disabled");
                    }
                    childrenArray.add(taxonomyJson);

                    //for allTags - adding tag into table of selected ones
                    Entity root = (Entity) tax.getField(EntityConstants.Taxonomy.CATEGORY_NAME);
                    JSONObject tagObject = new JSONObject();
                    tagObject.put("tagId", tax.getId());
                    tagObject.put("tagName", tax.getName());
                    tagObject.put("tagTypeId", root.getId());
                    tagObject.put("tagTypeName", root.getName());
                    allTags.put(String.valueOf(tax.getId()), tagObject);

                    //for tagTypesByName
                    JSONObject tagTypeByNameValue = new JSONObject();
                    tagTypeByNameValue.put("tagId", tax.getId());
                    tagTypeByNameValue.put("tagName", tax.getName());
                    tagTypeByNameValues.add(tagTypeByNameValue);
                }
                //New value.. for current type
                JSONObject newValueJson = createNewValueJson(
                        Long.toString(tagTypeValue(Long.parseLong(tagTypeId))));
                childrenArray.add(newValueJson);

                optgroup.put("children", childrenArray);
                select2InputArray.add(optgroup);
                tagTypeJson.put("values", tagTypeByNameValues);
                tagTypesByName.put(tagTypeName, tagTypeJson);
                tagTypes.put(tagTypeId, tagTypeJson);
            }

            // New type... New value...
            JSONObject optgroup = new JSONObject();
            optgroup.put("text", "New type...");
            JSONObject newValueJson = createNewValueJson("newTagType");
            JSONArray childrenArray = new JSONArray();
            childrenArray.add(newValueJson);
            optgroup.put("children", childrenArray);
            select2InputArray.add(optgroup);

            ret.put("select2Input", select2InputArray);
            ret.put("allTags", allTags);
            ret.put("tagTypesByName", tagTypesByName);
            ret.put("tagTypes", tagTypes);
            ret.put("more", moreResults);

        } catch (Exception e) {
            logger.warn("Failed to retrieve environments", e);
            return error("Unable to retrieve environments");
        }

        return ret;
    }

    private static JSONObject createMoreResultsJson() {
        JSONObject moreResultsJson = new JSONObject();
        moreResultsJson.put("id", "moreResultsFound");
        moreResultsJson.put("text", Messages.TooManyResults());
        moreResultsJson.put("warning", "true");
        moreResultsJson.put("disabled", "disabled");
        return moreResultsJson;
    }

    private static JSONObject createNewValueJson(String id) {
        JSONObject newValueJson = new JSONObject();
        newValueJson.put("id", id);
        newValueJson.put("text", "New value...");
        newValueJson.put("newValue", "true");
        return newValueJson;
    }

    private static long tagTypeValue(long n) {
        // mapping to ensure negative value (solve the "0" tag type ID)
        return -(n + 1);
    }

    private static void addTaxonomyTags(JSONObject result, PipelineContext pipeline) {
        JSONArray pipelineTaxonomies = new JSONArray();
        for (Taxonomy taxonomy : pipeline.getTaxonomies()) {
            pipelineTaxonomies.add(tag(taxonomy));
        }
        result.put("taxonomyTags", pipelineTaxonomies);
    }

    private static void addFields(JSONObject result, PipelineContext pipeline) {
        JSONObject listFields = new JSONObject();

        for (Map.Entry<String, List<ListItem>> fieldEntry : pipeline.getListFields().entrySet()) {
            JSONArray assignedValuesArray = listFieldValues(fieldEntry.getValue());
            listFields.put(fieldEntry.getKey(), assignedValuesArray);
        }
        result.put("fields", listFields);
    }

    private static JSONArray listFieldValues(List<ListItem> listItems) {
        JSONArray ret = new JSONArray();

        for (ListItem item : listItems) {
            JSONObject value = new JSONObject();
            value.put("id", item.getId());
            if (item.getName() != null) {
                value.put("name", item.getName());
            }
            ret.add(value);
        }
        return ret;
    }

    private static Collection<JSONObject> toCollection(JSONArray array) {
        return (Collection<JSONObject>) array.subList(0, array.size());
    }

    private static JSONObject tag(Long tagId, String value) {
        JSONObject tag = new JSONObject();
        tag.put("tagId", String.valueOf(tagId));
        tag.put("tagName", value);
        return tag;
    }

    private static JSONObject tag(Taxonomy taxonomy) {
        JSONObject tag = tag(taxonomy.getId(), taxonomy.getName());
        if (taxonomy.getParent() != null) {
            tag.put("tagTypeId", String.valueOf(taxonomy.getParent().getId()));
            tag.put("tagTypeName", taxonomy.getParent().getName());
        }
        return tag;
    }

    private static JSONObject error(String message) {
        return error(message, null);
    }

    private static JSONObject error(String message, ExceptionLink exceptionLink) {
        JSONObject result = new JSONObject();
        JSONArray errors = new JSONArray();
        JSONObject error = new JSONObject();
        error.put("message", message);
        if (exceptionLink != null) {
            error.put("url", exceptionLink.getUrl());
            error.put("label", exceptionLink.getLabel());
        }
        errors.add(error);
        result.put("errors", errors);
        return result;
    }

    private static class ClientException extends Exception {

        private ExceptionLink link;

        ClientException(String message) {
            this(message, null);
        }

        ClientException(String message, ExceptionLink link) {
            super(message);
            this.link = link;
        }

        public ExceptionLink getLink() {
            return link;
        }
    }

    private static class ExceptionLink {

        private String url;
        private String label;

        ExceptionLink(String url, String label) {
            this.url = url;
            this.label = label;
        }

        public String getUrl() {
            return url;
        }

        public String getLabel() {
            return label;
        }
    }

    private static ResponseEntityList queryWorkspacesByName(OctaneClient octaneClient, String name, int limit) {
        return queryEntitiesByName(octaneClient, name, null, "workspaces", limit);
    }

    private static ResponseEntityList queryReleasesByName(OctaneClient octaneClient, String name, long workspaceId,
            int limit) {
        return queryEntitiesByName(octaneClient, name, workspaceId, "releases", limit);
    }

    private static ResponseEntityList queryTaxonomiesByName(OctaneClient octaneClient, String name,
            long workspaceId, int limit) {

        EntitiesService entityService = octaneClient.getEntitiesService();

        List<String> conditions = new LinkedList();
        conditions.add("!category={null}");
        if (!StringUtils.isEmpty(name)) {
            conditions.add(com.hp.octane.integrations.services.entities.QueryHelper
                    .condition(EntityConstants.Base.NAME_FIELD, "*" + name + "*"));
            conditions.add("("
                    + com.hp.octane.integrations.services.entities.QueryHelper.condition("name", "*" + name + "*")
                    + "||" + com.hp.octane.integrations.services.entities.QueryHelper.conditionRef("category",
                            "name", "*" + name + "*")
                    + ")");
        }

        String url = entityService.buildEntityUrl(workspaceId, "taxonomy_nodes", conditions,
                Arrays.asList(EntityConstants.Base.NAME_FIELD, EntityConstants.Taxonomy.CATEGORY_NAME), 0, limit,
                EntityConstants.Base.NAME_FIELD);
        ResponseEntityList result = entityService.getPagedEntities(url);
        return result;
    }

    private static ResponseEntityList queryEntitiesByName(OctaneClient octaneClient, String name, Long workspaceId,
            String collectionName, int limit) {
        EntitiesService entityService = octaneClient.getEntitiesService();

        List<String> conditions = new LinkedList();
        if (!StringUtils.isEmpty(name)) {
            conditions.add(com.hp.octane.integrations.services.entities.QueryHelper
                    .condition(EntityConstants.Base.NAME_FIELD, "*" + name + "*"));
        }
        String url = entityService.buildEntityUrl(workspaceId, collectionName, conditions,
                Arrays.asList(EntityConstants.Base.NAME_FIELD), 0, limit, EntityConstants.Base.NAME_FIELD);
        ResponseEntityList result = entityService.getPagedEntities(url);
        return result;
    }

    private static ResponseEntityList queryListItems(OctaneClient octaneClient, String logicalListName, String name,
            long workspaceId, int limit) {
        int myLimit = limit;
        EntitiesService entityService = octaneClient.getEntitiesService();
        List<String> conditions = new LinkedList();
        if (!StringUtils.isEmpty(name)) {
            //list node are not filterable by name
            //conditions.add(com.hp.octane.integrations.services.entities.QueryHelper.condition(EntityConstants.Base.NAME_FIELD, "*" + name + "*"));
            myLimit = 100;
        }
        if (!StringUtils.isEmpty(logicalListName)) {
            conditions.add(com.hp.octane.integrations.services.entities.QueryHelper.conditionRef("list_root",
                    EntityConstants.Base.LOGICAL_NAME_FIELD, logicalListName));
        }

        String url = entityService.buildEntityUrl(workspaceId, "list_nodes", conditions, null, 0, myLimit, null);
        ResponseEntityList result = entityService.getPagedEntities(url);
        ResponseEntityList myResult = result;
        if (!StringUtils.isEmpty(name)) {
            List<Entity> data = result.getData().stream()
                    .filter(l -> l.getName().toLowerCase().contains(name.toLowerCase())).limit(limit)
                    .collect(Collectors.toList());
            myResult = (ResponseEntityList) dtoFactory.newDTO(ResponseEntityList.class).setData(data);
        }
        return myResult;
    }

    private static List<Entity> getWorkspacesById(OctaneClient client, Collection<?> itemIds) {
        return getEntitiesById(client, null, "workspaces", itemIds);
    }

    private static List<Entity> getListItemsById(OctaneClient client, Collection<?> itemIds, long workspaceId) {
        return getEntitiesById(client, workspaceId, EntityConstants.Lists.COLLECTION_NAME, itemIds);
    }

    private static List<Entity> getReleasesById(OctaneClient client, Collection<?> itemIds, long workspaceId) {
        return getEntitiesById(client, workspaceId, EntityConstants.Release.COLLECTION_NAME, itemIds);
    }

    private static List<Entity> getTaxonomiesById(OctaneClient client, Collection<?> itemIds, long workspaceId) {
        return getEntitiesById(client, workspaceId, EntityConstants.Taxonomy.COLLECTION_NAME, itemIds);
    }

    private static List<Taxonomy> convertTaxonomies(List<Entity> entities) {
        List<Taxonomy> taxonomies = new ArrayList<>();
        for (Entity entity : entities) {
            Taxonomy taxonomy = dtoFactory.newDTO(Taxonomy.class);
            taxonomy.setId(Long.parseLong(entity.getId())).setName(entity.getName());
            if (entity.containsField(EntityConstants.Taxonomy.CATEGORY_NAME)) {
                Taxonomy parent = dtoFactory.newDTO(Taxonomy.class);
                parent.setId(Long.parseLong(entity.getId())).setName(entity.getName());
                taxonomy.setParent(parent);
            }
            taxonomies.add(taxonomy);
        }
        return taxonomies;
    }

    private static List<Entity> getEntitiesById(OctaneClient octaneClient, Long workspaceId, String collectionName,
            Collection<?> itemIds) {
        return octaneClient.getEntitiesService().getEntitiesByIds(workspaceId, collectionName, itemIds);
    }

    private static List<Entity> getPipelineListNodeFieldsMetadata(OctaneClient octaneClient, long workspaceId) {
        List<String> conditions = new LinkedList<>();
        conditions.add(
                com.hp.octane.integrations.services.entities.QueryHelper.condition("entity_name", "pipeline_node"));
        conditions.add(com.hp.octane.integrations.services.entities.QueryHelper.conditionIn("name",
                Arrays.asList("test_tool_type", "test_level", "test_type", "test_framework"), false));

        EntitiesService entityService = octaneClient.getEntitiesService();
        String url = entityService.buildEntityUrl(workspaceId, "metadata/fields", conditions, null, 0, null, null);
        ResponseEntityList list = entityService.getPagedEntities(url);
        return list.getData();
    }

    private static JSONArray convertToJsonMetadata(List<Entity> listNodeFieldMetadataList) {
        JSONArray fieldMetadataArray = new JSONArray();
        for (Entity entity : listNodeFieldMetadataList) {
            Map<String, Object> fieldTypeData = (Map<String, Object>) entity.getField("field_type_data");
            List targets = (List) fieldTypeData.get("targets");
            Map<String, String> listNodeTarget = (Map<String, String>) targets.get(0);

            //find pipeline_tagging feature
            List<Map<String, Object>> fieldFeatures = (List<Map<String, Object>>) entity.getField("field_features");
            Map<String, Object> pipelineTaggingFeature = null;
            for (Map<String, Object> feature : fieldFeatures) {
                if (feature.get("name").equals("pipeline_tagging")) {
                    pipelineTaggingFeature = feature;
                }
            }

            JSONObject fieldMetadataJSON = new JSONObject();
            fieldMetadataJSON.put("name", entity.getName());
            fieldMetadataJSON.put("listName", entity.getStringValue("label"));
            fieldMetadataJSON.put("logicalListName", listNodeTarget.get("logical_name"));
            fieldMetadataJSON.put("extensible", pipelineTaggingFeature.get("extensibility"));
            fieldMetadataJSON.put("multiValue", fieldTypeData.get("multiple"));
            fieldMetadataJSON.put("order", pipelineTaggingFeature.get("order"));
            fieldMetadataArray.add(fieldMetadataJSON);
        }
        return fieldMetadataArray;
    }
}