org.mitre.mat.engineclient.MATCgiClient.java Source code

Java tutorial

Introduction

Here is the source code for org.mitre.mat.engineclient.MATCgiClient.java

Source

/*
 * Copyright (C) 2009 The MITRE Corporation. See the toplevel
 * file LICENSE for license terms.
 */
package org.mitre.mat.engineclient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.NullNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.JsonParseException;
import org.mitre.mat.core.*;

/**
 * The class which implements CGI interaction with the MAT engine.
 *
 * @author sam
 */
public class MATCgiClient implements MATEngineClientInterface {

    // Folders.
    public static final String CORE_FOLDER = "core";
    public static final String EXPORT_FOLDER = "export";
    // Not in the core yet, but I just hate having to specialize Java.
    public static final String RECONCILIATION_FOLDER = "reconciliation";

    // Segment status.
    public static final String NON_GOLD_SEGMENT_STATUS = "non-gold";
    public static final String HUMAN_GOLD_SEGMENT_STATUS = "human gold";
    public static final String RECONCILED_SEGMENT_STATUS = "reconciled";
    public static final String IGNORE_SEGMENT_STATUS = "ignore";
    public static final String IGNORE_DURING_RECONCILIATION_SEGMENT_STATUS = "ignore during reconciliation";

    // Special votes.
    public static final String IGNORE_VOTE = "ignore";
    public static final String BAD_BOUNDARIES_VOTE = "bad boundaries";

    // Phases. These aren't in the core yet either.
    public static final String CROSSVALIDATION_CHALLENGE_PHASE = "crossvalidation_challenge";
    public static final String HUMAN_VOTE_PHASE = "human_vote";
    public static final String HUMAN_DECISION_PHASE = "human_decision";

    private String url;

    public MATCgiClient(String urlHostAndPort) {
        url = urlHostAndPort + "/MAT/cgi/MATCGI.cgi";
    }

    public MATDocumentInterface doSteps(MATDocumentInterface doc, String task, String workflow, String steps,
            HashMap<String, String> data) throws MATEngineClientException {
        return doFileOperation(doc, task, workflow, "steps", "steps", steps, data);
    }

    public MATDocumentInterface doUndoThrough(MATDocumentInterface doc, String task, String workflow, String steps,
            HashMap<String, String> data) throws MATEngineClientException {
        return doFileOperation(doc, task, workflow, "undo_through", "undo_through", steps, data);
    }

    protected MATDocumentInterface doFileOperation(MATDocumentInterface doc, String task, String workflow,
            String op, String opKey, String opVal, HashMap<String, String> data) throws MATEngineClientException {

        ArrayList pArray = new ArrayList();

        MATJSONEncoding e = new MATJSONEncoding();
        pArray.add(new NameValuePair("task", task));
        pArray.add(new NameValuePair("workflow", workflow));
        pArray.add(new NameValuePair("operation", op));
        pArray.add(new NameValuePair(opKey, opVal));
        pArray.add(new NameValuePair("file_type", "mat-json"));
        pArray.add(new NameValuePair("input", e.toEncodedString(doc)));

        JsonNode jsonValues = this.postHTTP(pArray, data);

        // Here's the output format. It's a hash of three elements: error (None if
        // there's no error), errorStep (None if there's no error), and
        // a list of success hashes, which have a val and steps.
        // See MATCGI_tpl.py. An error always terminates the processing,
        // so on the client, you process the successes and then the error.
        // The steps should be in order of execution, and so should the
        // successes. It's not EXACTLY enforced.

        HashMap stepMap = new HashMap();

        if (!jsonValues.isObject()) {
            throw new MATEngineClientException("CGI response isn't Javascript object");
        }

        JsonNode errNode = jsonValues.path("error");
        if ((!errNode.isMissingNode()) && (!errNode.isNull())) {
            // It's an error.
            String stepName = jsonValues.path("errorStep").getTextValue();
            String stepMsg = errNode.getTextValue();
            throw new MATEngineClientException("Step " + stepName + " failed: " + stepMsg);
        }

        JsonNode successes = jsonValues.path("successes");
        if (successes.isMissingNode() || (!successes.isArray()) || (successes.size() == 0)) {
            // I doubt that this will ever happen.
            throw new MATEngineClientException("No error, but no successful document either");
        }

        // Get the last one.
        JsonNode success = successes.get(successes.size() - 1);
        JsonNode val = success.path("val");
        MATDocumentInterface newDoc;

        try {
            newDoc = doc.getClass().newInstance();
        } catch (InstantiationException ex) {
            Logger.getLogger(MATCgiClient.class.getName()).log(Level.SEVERE, null, ex);
            throw new MATEngineClientException("Couldn't create a response document");
        } catch (IllegalAccessException ex) {
            Logger.getLogger(MATCgiClient.class.getName()).log(Level.SEVERE, null, ex);
            throw new MATEngineClientException("Couldn't create a response document");
        }

        try {
            e.fromJsonNode(newDoc, val);
        } catch (MATDocumentException ex) {
            throw new MATEngineClientException("Error during JSON decode: " + ex.toString());
        } catch (AnnotationException ex) {
            throw new MATEngineClientException("Error during JSON decode: " + ex.toString());
        }
        return newDoc;
    }

    public ArrayList<String> openWorkspace(String workspaceDir, String workspaceKey, String user)
            throws MATEngineClientException {
        ArrayList<NameValuePair> pArray = new ArrayList<NameValuePair>();
        pArray.add(new NameValuePair("workspace_key", workspaceKey));
        pArray.add(new NameValuePair("workspace_dir", workspaceDir));
        pArray.add(new NameValuePair("user", user));
        pArray.add(new NameValuePair("operation", "open_workspace"));

        JsonNode jsonValues = this.postHTTP(pArray, null);

        this.checkReturnValueValidity(jsonValues);

        ArrayList<String> returnPair = new ArrayList<String>();
        JsonNode dir = jsonValues.path("workspace_dir");
        if (!dir.isMissingNode()) {
            returnPair.add(dir.getTextValue());
        } else {
            throw new MATEngineClientException(
                    "Incorrectly formatted response to open_workspace (no workspace dir)");
        }
        JsonNode t = jsonValues.path("task");
        if (!t.isMissingNode()) {
            returnPair.add(t.getTextValue());
        } else {
            throw new MATEngineClientException("Incorrectly formatted response to open_workspace (no task)");
        }
        return returnPair;
    }

    // For the moment, I'm not about to decode the whole accursed structure
    // I can't figure out how to use the recursive Jackson decoder, 
    // and there's just too much to do it by hand. So I'll just add
    // methods as needed.

    // Originally, this structure mirrored the structure of the tag table.
    // But it seems that it would be better for it to mirror the structure
    // of what I do in my UI, namely, the tagName should be the EFFECTIVE
    // tag name, and there should be a reference to the actual tag
    // name, rather than a pointer down to more ContentTagInfo structures.

    public class ContentTagInfo {

        private String tagName;
        private String effectiveTagName;
        // Display info
        private String backgroundColor;
        private String foregroundColor;
        // These are the attrs when you're in a subspecification.
        private HashMap<String, String> attrs;

        public ContentTagInfo(String tName, JsonNode n, String effectiveTName, String taskName)
                throws MATEngineClientException {
            this.tagName = tName;
            this.effectiveTagName = effectiveTName;
            JsonNode display = n.get("display");
            if (display != null) {
                this.decodeDisplay(display.getTextValue(), taskName);
            }
            JsonNode attrsNode = n.get("apairs");
            if ((attrsNode != null) && (attrsNode.isArray())) {
                for (int i = 0; i < attrsNode.size(); i++) {
                    if (i == 0) {
                        attrs = new HashMap<String, String>();
                    }
                    attrs.put(attrsNode.get(i).get(0).getTextValue(), attrsNode.get(i).get(1).getTextValue());
                }
            }
        }

        private void checkColor(String color, String taskName) throws MATEngineClientException {
            // So the color possibilities in CSS are:
            // a name
            // # + 3 hex chars
            // # + 6 hex chars
            // rgb(...)
            // and in css 3, rgba(, hsl(, hsla(.
            // I'm going to ignore everything but the names and # + 6 hex.
            // CSS seems to support color hexes without a leading #,
            // and I can't do anything about that.
            // Actually, because we're interpreting this, I think
            // we're going to insist that the colors have pound-sign hashes.
            // Otherwise, the client can't tell the difference between color names
            // and color elements. So if you're using a Java client, you can't
            // use color names at all.
            boolean fail = false;
            if (color.charAt(0) != '#') {
                fail = true;
            } else if (color.length() != 7) {
                fail = true;
            }
            if (fail) {
                if (this.effectiveTagName != null) {
                    throw new MATEngineClientException(
                            "color specification '" + color + "' obtained from the server for effective tag '"
                                    + this.effectiveTagName + "' in task '" + taskName + "' is not a # + 6 hex");
                } else {
                    throw new MATEngineClientException(
                            "color specification '" + color + "' obtained from the server for tag '" + this.tagName
                                    + "' in task '" + taskName + "' is not a # + 6 hex");
                }
            }
        }

        private void decodeDisplay(String cssValue, String taskName) throws MATEngineClientException {
            // First, break on ;, then strip, then break on :.
            String[] components = cssValue.split(";");
            for (int i = 0; i < components.length; i++) {
                String component = components[i].trim();
                if (component.length() > 0) {
                    String[] lr = component.split(":", 2);
                    String left = lr[0].trim();
                    if (left.equals("background-color")) {
                        this.backgroundColor = lr[1].trim();
                        this.checkColor(this.backgroundColor, taskName);
                    } else if (left.equals("color")) {
                        this.foregroundColor = lr[1].trim();
                        this.checkColor(this.foregroundColor, taskName);
                    }
                }

            }

        }

        public String getTagName() {
            return this.tagName;
        }

        public String getEffectiveTagName() {
            return this.effectiveTagName;
        }

        public String getBackgroundColor() {
            return this.backgroundColor;
        }

        public String getForegroundColor() {
            return this.foregroundColor;
        }

        public HashMap<String, String> getAttrs() {
            return this.attrs;
        }
    }

    public WorkspaceFileResult listWorkspaceFolder(String workspaceDir, String workspaceKey, String folder)
            throws MATEngineClientException {
        ArrayList<String> args = new ArrayList<String>(Arrays.asList(folder));
        return this.doToplevelWorkspaceOperation(workspaceDir, workspaceKey, "list", args);
    }

    public WorkspaceFileResult openWorkspaceFile(String workspaceDir, String workspaceKey, String folder,
            String basename) throws MATEngineClientException {
        return this.openWorkspaceFile(workspaceDir, workspaceKey, folder, basename, null);
    }

    public WorkspaceFileResult openWorkspaceFile(String workspaceDir, String workspaceKey, String folder,
            String basename, HashMap<String, String> data) throws MATEngineClientException {
        // Can't use the toplevel openWorkspaceFile because it doesn't
        // do the result properly.
        ArrayList<String> opArgs = new ArrayList<String>();
        opArgs.add(folder);
        opArgs.add(basename);
        return this.doToplevelWorkspaceOperation(workspaceDir, workspaceKey, "open_file", opArgs, data);
    }

    protected void checkReturnValueValidity(JsonNode jsonValues) throws MATEngineClientException {
        if (!jsonValues.isObject()) {
            throw new MATEngineClientException("CGI response isn't Javascript object");
        }
        JsonNode successNode = jsonValues.path("success");
        if (!successNode.isBoolean()) {
            // It's an error.
            throw new MATEngineClientException("Incorrectly formatted response to workspace operation");
        }
        if (!successNode.getBooleanValue()) {
            // Find the error.
            String errString = jsonValues.path("error").getTextValue();
            throw new MATEngineClientException("Workspace operation error: " + errString);
        }
    }

    // The file result should contain affected folders,
    // target folder, and the document. In this case, you
    // have no control over what kind of document you get;
    // it's a MATDocument.
    public class WorkspaceFileResult {

        private MATDocument doc = null;
        private ArrayList<String> affectedFolders = null;
        private String targetFolder = null;
        private String basename = null;
        private ArrayList<String> files = null;

        // A lot of these variables will never be seen
        // in MAT 2.0 initial, but it's such a pain to do
        // these overrides in Java, I'm just putting them all in.

        // I'm not going to use this QUITE yet.
        private boolean workspaceIsOpen = false;
        private String lockId = null;
        private boolean readOnly = false;
        private String status = null;
        private String prioritizationClass = null;
        private String prioritizationMode = null;
        private String reconciliationPhase = null;
        private ArrayList<String> userNames = null;
        private ArrayList<String> segmentIDs = null;
        private ArrayList<String> availablePhases = null;
        private ArrayList<HashMap<String, String>> basenameInfo = null;

        public WorkspaceFileResult(JsonNode jsonNode) throws MATEngineClientException {
            JsonNode docNode = jsonNode.path("doc");
            if (!docNode.isMissingNode()) {
                MATJSONEncoding e = new MATJSONEncoding();
                this.doc = new MATDocument();
                try {
                    e.fromJsonNode(this.doc, docNode);
                } catch (MATDocumentException ex) {
                    throw new MATEngineClientException("Error during JSON decode: " + ex.toString());
                } catch (AnnotationException ex) {
                    throw new MATEngineClientException("Error during JSON decode: " + ex.toString());
                }
            }
            JsonNode targetNode = jsonNode.path("target");
            if (!targetNode.isMissingNode()) {
                this.targetFolder = targetNode.getTextValue();
            }
            JsonNode basenameNode = jsonNode.path("basename");
            if (!basenameNode.isMissingNode()) {
                this.basename = basenameNode.getTextValue();
            }
            JsonNode folders = jsonNode.path("affected_folders");
            if (!folders.isMissingNode()) {
                this.affectedFolders = new ArrayList<String>();
                Iterator<JsonNode> nodeIterator = folders.getElements();
                while (nodeIterator.hasNext()) {
                    this.affectedFolders.add(nodeIterator.next().getTextValue());
                }
            }
            // Gonna be using this object for double duty -
            // it will digest the  output of doToplevelWorkspaceOperation
            // as well as the file level.
            JsonNode fileArray = jsonNode.path("files");
            if (!fileArray.isMissingNode()) {
                this.files = new ArrayList<String>();
                Iterator<JsonNode> nodeIterator = fileArray.getElements();
                while (nodeIterator.hasNext()) {
                    this.files.add(nodeIterator.next().getTextValue());
                }
            }

            // If we've gotten this far, this is always true.
            this.workspaceIsOpen = true;

            JsonNode node = jsonNode;

            JsonNode readOnlyNode = node.path("read_only");
            if (!readOnlyNode.isMissingNode()) {
                this.readOnly = readOnlyNode.getBooleanValue();
            }
            JsonNode statusNode = node.path("status");
            if (!statusNode.isMissingNode()) {
                this.status = statusNode.getTextValue();
            }
            JsonNode prioritizationNode = node.path("prioritization_class");
            if (!prioritizationNode.isMissingNode()) {
                this.prioritizationClass = prioritizationNode.getTextValue();
            }
            JsonNode prioritizationModeNode = node.path("prioritization_mode");
            if (!prioritizationModeNode.isMissingNode()) {
                this.prioritizationMode = prioritizationModeNode.getTextValue();
            }
            JsonNode lockIdNode = node.path("lock_id");
            if (!lockIdNode.isMissingNode()) {
                this.lockId = lockIdNode.getTextValue();
            }
            JsonNode recNode = node.path("reconciliation_phase");
            if (!recNode.isMissingNode()) {
                this.reconciliationPhase = recNode.getTextValue();
            }
            JsonNode userArray = node.path("users");
            if (!userArray.isMissingNode()) {
                this.userNames = new ArrayList<String>();
                Iterator<JsonNode> nodeIterator = userArray.getElements();
                while (nodeIterator.hasNext()) {
                    this.userNames.add(nodeIterator.next().getTextValue());
                }
            }
            JsonNode phaseArray = node.path("available_phases");
            if (!phaseArray.isMissingNode()) {
                this.availablePhases = new ArrayList<String>();
                Iterator<JsonNode> nodeIterator = phaseArray.getElements();
                while (nodeIterator.hasNext()) {
                    this.availablePhases.add(nodeIterator.next().getTextValue());
                }
            }
            JsonNode idArray = node.path("segment_ids");
            if ((!idArray.isMissingNode()) && (!idArray.isNull())) {
                this.segmentIDs = new ArrayList<String>();
                Iterator<JsonNode> nodeIterator = idArray.getElements();
                while (nodeIterator.hasNext()) {
                    this.segmentIDs.add(nodeIterator.next().getTextValue());
                }
            }
            // This is now completely different. The result is no longer
            // an array of 2-element arrays of strings, but rather
            // an array of dictionaries. The dictionary is guaranteed to
            // contain a "basename" entry.
            JsonNode basenameInfoArray = node.path("basename_info");
            if (!basenameInfoArray.isMissingNode()) {
                this.basenameInfo = new ArrayList<HashMap<String, String>>();
                Iterator<JsonNode> nodeIterator = basenameInfoArray.getElements();
                while (nodeIterator.hasNext()) {
                    JsonNode val = nodeIterator.next();
                    HashMap<String, String> v = new HashMap<String, String>();
                    this.basenameInfo.add(v);
                    if (val.isObject()) {
                        // Iterate through the keys, get the string values,
                        // set them.
                        Iterator<String> fields = val.getFieldNames();
                        while (fields.hasNext()) {
                            String field = fields.next();
                            v.put(field, val.get(field).getTextValue());
                        }
                    }
                }
            }

        }

        public MATDocument getDocument() {
            return this.doc;
        }

        // I need this because I'm reusing this slot later,
        // and I'm not adding it from the constructor.
        public void setDocument(MATDocument doc) {
            this.doc = doc;
        }

        public ArrayList<String> getAffectedFolders() {
            return this.affectedFolders;
        }

        public String getTargetFolder() {
            return this.targetFolder;
        }

        public String getBasename() {
            return this.basename;
        }

        public ArrayList<String> getFiles() {
            return this.files;
        }

        public String getLockId() {
            return lockId;
        }

        public boolean getReadOnly() {
            return readOnly;
        }

        public String getStatus() {
            return status;
        }

        public String getPrioritizationClass() {
            return prioritizationClass;
        }

        public String getPrioritizationMode() {
            return prioritizationMode;
        }

        public String getReconciliationPhase() {
            return reconciliationPhase;
        }

        public ArrayList<String> getUserNames() {
            return userNames;
        }

        public ArrayList<String> getSegmentIDs() {
            return segmentIDs;
        }

        public ArrayList<String> getAvailablePhases() {
            return availablePhases;
        }

        public ArrayList<HashMap<String, String>> getBasenameInfo() {
            return basenameInfo;
        }

        public void describe() {
            if (this.doc != null) {
                MATJSONEncoding e = new MATJSONEncoding();
                System.out.println("Doc:");
                System.out.println(e.toEncodedString(this.doc));
            }
            if (this.basename != null) {
                System.out.println("Basename: " + this.basename);
            }
            if (this.status != null) {
                System.out.println("Status: " + this.status);
            }
            if (this.targetFolder != null) {
                System.out.println("Target folder: " + this.targetFolder);
            }
            if (this.affectedFolders != null) {
                System.out.println("Affected folders:");
                for (int i = 0; i < this.affectedFolders.size(); i++) {
                    System.out.println(this.affectedFolders.get(i));
                }
            }
            if (this.files != null) {
                System.out.println("Files:");
                for (int i = 0; i < this.files.size(); i++) {
                    System.out.println(this.files.get(i));
                }
            }

            if (this.readOnly) {
                System.out.println("Read-only: true");
            }
            if (this.reconciliationPhase != null) {
                System.out.println("Reconciliation phase: " + this.reconciliationPhase);
            }
            if (this.lockId != null) {
                System.out.println("Lock ID: " + this.lockId);
            }
            if (this.prioritizationClass != null) {
                System.out.println("Prioritization class: " + this.prioritizationClass);
            }
            if (this.prioritizationMode != null) {
                System.out.println("Prioritization mode:" + this.prioritizationMode);
            }

            if (this.userNames != null) {
                System.out.println("Users:");
                for (int i = 0; i < this.userNames.size(); i++) {
                    System.out.println(this.userNames.get(i));
                }
            }
            if (this.segmentIDs != null) {
                System.out.println("Segment IDs:");
                for (int i = 0; i < this.segmentIDs.size(); i++) {
                    System.out.println(this.segmentIDs.get(i));
                }
            }
            if (this.availablePhases != null) {
                System.out.println("Available phases:");
                for (int i = 0; i < this.availablePhases.size(); i++) {
                    System.out.println(this.availablePhases.get(i));
                }
            }
            if (this.basenameInfo != null) {
                System.out.println("Basename info:");
                Iterator it = this.basenameInfo.iterator();

                while (it.hasNext()) {
                    HashMap<String, String> v = (HashMap<String, String>) it.next();
                    boolean first = true;
                    String s = "";
                    Iterator it2 = v.entrySet().iterator();
                    String k = null;
                    while (it2.hasNext()) {
                        Map.Entry subE = (Map.Entry) it2.next();
                        String subK = (String) subE.getKey();
                        String subV = (String) subE.getValue();
                        if (subV != null) {
                            if (subK.equals("basename")) {
                                k = subV;
                                continue;
                            } else {
                                if (!first) {
                                    s = s.concat(", ");
                                } else {
                                    first = false;
                                }
                                s = s.concat(subK + " " + subV);
                            }
                        }
                    }
                    System.out.println(k + " (" + s + ")");
                }
            }
        }
    }

    protected WorkspaceFileResult digestResult(JsonNode jsonValues) throws MATEngineClientException {
        return new WorkspaceFileResult(jsonValues);
    }

    public WorkspaceFileResult doWorkspaceOperation(String workspaceDir, String workspaceKey, String folder,
            String operation, String basename) throws MATEngineClientException {
        return this.doWorkspaceOperation(workspaceDir, workspaceKey, folder, operation, basename, null);
    }

    public WorkspaceFileResult doWorkspaceOperation(String workspaceDir, String workspaceKey, String folder,
            String operation, String basename, HashMap<String, String> data) throws MATEngineClientException {
        ArrayList<NameValuePair> pArray = new ArrayList<NameValuePair>();
        pArray.add(new NameValuePair("workspace_key", workspaceKey));
        pArray.add(new NameValuePair("workspace_dir", workspaceDir));
        pArray.add(new NameValuePair("folder", folder));
        pArray.add(new NameValuePair("ws_operation", operation));
        pArray.add(new NameValuePair("operation", "do_workspace_operation"));
        pArray.add(new NameValuePair("file", basename));

        JsonNode jsonValues = this.postHTTP(pArray, data);

        // The response has a success field. If success is true,
        // there will be some other data about folders that have been
        // affected, etc. I'm not quite interested in that at the moment,
        // since that information is used to update displays, and right
        // now I'm not focusing on that. We can update that later.

        this.checkReturnValueValidity(jsonValues);

        return this.digestResult(jsonValues);
    }

    public WorkspaceFileResult doToplevelWorkspaceOperation(String workspaceDir, String workspaceKey,
            String operation, ArrayList<String> args) throws MATEngineClientException {
        return this.doToplevelWorkspaceOperation(workspaceDir, workspaceKey, operation, args, null);
    }

    public WorkspaceFileResult doToplevelWorkspaceOperation(String workspaceDir, String workspaceKey,
            String operation, ArrayList<String> args, HashMap<String, String> data)
            throws MATEngineClientException {
        ArrayList<NameValuePair> pArray = new ArrayList<NameValuePair>();
        pArray.add(new NameValuePair("workspace_key", workspaceKey));
        pArray.add(new NameValuePair("workspace_dir", workspaceDir));
        pArray.add(new NameValuePair("ws_operation", operation));
        pArray.add(new NameValuePair("operation", "do_toplevel_workspace_operation"));
        if (args != null) {
            for (int i = 0; i < args.size(); i++) {
                pArray.add(new NameValuePair("arg" + (i + 1), args.get(i)));
            }
        }

        JsonNode jsonValues = this.postHTTP(pArray, data);

        // The response has a success field. If success is true,
        // there will be some other data about folders that have been
        // affected, etc. I'm not quite interested in that at the moment,
        // since that information is used to update displays, and right
        // now I'm not focusing on that. We can update that later.

        this.checkReturnValueValidity(jsonValues);

        return this.digestResult(jsonValues);
    }

    // This is a rare
    public WorkspaceFileResult importFileIntoWorkspace(String workspaceDir, String workspaceKey, String folder,
            MATDocumentInterface doc, String basename, HashMap<String, String> data)
            throws MATEngineClientException {
        ArrayList<NameValuePair> pArray = new ArrayList<NameValuePair>();
        MATJSONEncoding e = new MATJSONEncoding();
        pArray.add(new NameValuePair("workspace_key", workspaceKey));
        pArray.add(new NameValuePair("workspace_dir", workspaceDir));
        pArray.add(new NameValuePair("operation", "import_into_workspace"));
        pArray.add(new NameValuePair("folder", folder));
        pArray.add(new NameValuePair("file", basename));
        pArray.add(new NameValuePair("doc", e.toEncodedString(doc)));

        JsonNode jsonValues = this.postHTTP(pArray, data);

        // The response has a success field. If success is true,
        // there will be some other data about folders that have been
        // affected, etc. I'm not quite interested in that at the moment,
        // since that information is used to update displays, and right
        // now I'm not focusing on that. We can update that later.

        this.checkReturnValueValidity(jsonValues);

        return this.digestResult(jsonValues);
    }

    protected JsonNode postHTTP(ArrayList<NameValuePair> pArrayList, HashMap<String, String> data)
            throws MATEngineClientException {

        // Serialize the document, send it to HTTP, deserialize.
        if (data != null) {
            int mapsize = data.size();

            Iterator keyValuePairs1 = data.entrySet().iterator();
            for (int i = 0; i < mapsize; i++) {
                Map.Entry entry = (Map.Entry) keyValuePairs1.next();
                Object key = entry.getKey();
                Object value = entry.getValue();
                pArrayList.add(new NameValuePair((String) key, (String) value));
            }
        }

        NameValuePair[] pArray = (NameValuePair[]) pArrayList.toArray(new NameValuePair[1]);
        HttpClientParams params = new HttpClientParams();
        params.setContentCharset("utf-8");
        HttpClient client = new HttpClient(params);

        PostMethod method = new PostMethod(url);

        method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                new DefaultHttpMethodRetryHandler(0, false));

        method.setRequestBody(pArray);

        String resString = null;

        try {
            // Execute the method.
            int statusCode = client.executeMethod(method);

            if (statusCode != HttpStatus.SC_OK) {
                throw new MATEngineClientException("HTTP method failed: " + method.getStatusLine());
            }
            BufferedReader b = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream(), "utf-8"));
            StringBuffer buf = new StringBuffer();
            int READ_LEN = 2048;
            char[] cbuf = new char[READ_LEN];

            while (true) {
                int chars = b.read(cbuf, 0, READ_LEN);
                if (chars < 0) {
                    break;
                }
                // You may not read READ_LEN chars, but
                // that doesn't mean you're done.
                buf.append(cbuf, 0, chars);
            }
            resString = new String(buf);
        } catch (HttpException e) {
            throw new MATEngineClientException("Fatal protocol violation: " + e.getMessage());
        } catch (IOException e) {
            throw new MATEngineClientException("Fatal transport error: " + e.getMessage());
        } finally {
            // Release the connection.
            method.releaseConnection();
        }

        JsonNode responseObj = null;
        JsonFactory jsonFact = new JsonFactory();
        JsonNode jsonValues;

        JsonParser parser;
        try {
            parser = jsonFact.createJsonParser(new StringReader(resString));
            return new ObjectMapper().readTree(parser);
        } catch (org.codehaus.jackson.JsonParseException ex) {
            Logger.getLogger(MATCgiClient.class.getName()).log(Level.SEVERE, null, ex);
            throw new MATEngineClientException("Couldn't digest the following string as JSON: " + resString);
        } catch (IOException ex) {
            Logger.getLogger(MATCgiClient.class.getName()).log(Level.SEVERE, null, ex);
            throw new MATEngineClientException("Couldn't interpret response document: " + ex.getMessage());
        }
    }
}