You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by re...@apache.org on 2015/03/19 14:43:55 UTC

[27/35] incubator-taverna-common-activities git commit: package names changed to org.apache.taverna.*

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/RESTActivityHealthChecker.java
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/RESTActivityHealthChecker.java b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/RESTActivityHealthChecker.java
deleted file mode 100644
index cbca70a..0000000
--- a/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/RESTActivityHealthChecker.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package net.sf.taverna.t2.activities.rest;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.taverna.t2.visit.VisitReport;
-import net.sf.taverna.t2.visit.VisitReport.Status;
-import net.sf.taverna.t2.workflowmodel.health.HealthCheck;
-import net.sf.taverna.t2.workflowmodel.health.HealthChecker;
-
-/**
- * A {@link HealthChecker} for a {@link RESTActivity}.
- *
- * @author Sergejs Aleksejevs
- */
-public class RESTActivityHealthChecker implements HealthChecker<RESTActivity> {
-	@Override
-	public boolean canVisit(Object subject) {
-		return (subject instanceof RESTActivity);
-	}
-
-	@Override
-	public VisitReport visit(RESTActivity activity, List<Object> ancestors) {
-		// collection of validation reports that this health checker will create
-		List<VisitReport> reports = new ArrayList<VisitReport>();
-
-		RESTActivityConfigurationBean configBean = activity.getConfigurationBean();
-		if (configBean.isValid()) {
-			reports.add(new VisitReport(RESTActivityHealthCheck.getInstance(), activity,
-					"REST Activity is configured correctly",
-					RESTActivityHealthCheck.CORRECTLY_CONFIGURED, Status.OK));
-		} else {
-			reports.add(new VisitReport(RESTActivityHealthCheck.getInstance(), activity,
-					"REST Activity - bad configuration",
-					RESTActivityHealthCheck.GENERAL_CONFIG_PROBLEM, Status.SEVERE));
-		}
-
-		// (possibly other types of reports could be added later)
-
-		// collection all reports together
-		Status worstStatus = VisitReport.getWorstStatus(reports);
-		VisitReport report = new VisitReport(RESTActivityHealthCheck.getInstance(), activity,
-				"REST Activity Report", HealthCheck.NO_PROBLEM, worstStatus, reports);
-
-		return report;
-	}
-
-	/**
-	 * Health check for the REST activity only involves
-	 * verifying details in the configuration bean -
-	 * that is quick.
-	 */
-	@Override
-	public boolean isTimeConsuming() {
-		return false;
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/URISignatureHandler.java
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/URISignatureHandler.java b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/URISignatureHandler.java
deleted file mode 100644
index 6a669f6..0000000
--- a/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/URISignatureHandler.java
+++ /dev/null
@@ -1,412 +0,0 @@
-package net.sf.taverna.t2.activities.rest;
-
-import java.io.UnsupportedEncodingException;
-import java.text.Normalizer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.log4j.Logger;
-
-
-/**
- * This class deals with URI signatures - essentially, strings that represent
- * some resource's URI with zero or more placeholders. URI signatures are known
- * at workflow definition time and represent the pattern of the complete URIs
- * that will be used at workflow run time.
- * 
- * An example of the URI signature is:
- * http://sysmo-db.org/sops/{sop_id}/experimental_conditions
- * /{cond_id}?condition_unit={unit}
- * 
- * Placeholders "{sop_id}", "{cond_id}" and "{unit}" will be replaced by the
- * real values prior to using the URI for a real request.
- * 
- * This class is concerned with validation of URI signatures, extraction of
- * placeholders and substituting placeholders to generate complete URIs.
- * 
- * @author Sergejs Aleksejevs
- */
-public class URISignatureHandler {
-	
-	private static Logger logger = Logger.getLogger(URISignatureHandler.class);
-	
-
-	public static final char PLACEHOLDER_START_SYMBOL = '{';
-	public static final char PLACEHOLDER_END_SYMBOL = '}';
-
-	/**
-	 * Extracts placeholders of the given URI signature with their positions in
-	 * the signature in the order of their occurrence.
-	 * 
-	 * Extraction is done in a robust way with signature validity checks being
-	 * carried out simultaneously. This makes sure that even with no explicit
-	 * validation (see {@link URISignatureHandler#isValid(String)}) no
-	 * unexpected faults occur.
-	 * 
-	 * @param uriSignature
-	 *            The URI signature to process.
-	 * @return A map of placeholders as they are encountered (from start to end)
-	 *         in the URI signature and their start positions. Keys of the map
-	 *         are the "titles" of the placeholders without opening and closing
-	 *         placeholder symbols; values are the URI signature string indices,
-	 *         where the title of the corresponding placeholder starts in the
-	 *         string.
-	 */
-	public static LinkedHashMap<String, Integer> extractPlaceholdersWithPositions(
-			String uriSignature) {
-		// no signature - nothing to process
-		if (uriSignature == null || uriSignature.isEmpty())
-			throw new URISignatureParsingException(
-					"URI signature is null or empty - nothing to process.");
-
-		LinkedHashMap<String, Integer> foundPlaceholdersWithPositions = new LinkedHashMap<>();
-
-		int nestingLevel = 0;
-		int startSymbolIdx = -1;
-
-		// go through the signature character by character trying to extract
-		// placeholders
-		for (int i = 0; i < uriSignature.length(); i++) {
-			switch (uriSignature.charAt(i)) {
-			case PLACEHOLDER_START_SYMBOL:
-				if (++nestingLevel != 1)
-					throw new URISignatureParsingException(
-							"Malformed URL: at least two parameter placeholder opening\n" +
-									"symbols follow each other without being closed appropriately\n" +
-							"(possibly the signature contains nested placeholders).");
-				startSymbolIdx = i;
-				break;
-
-			case PLACEHOLDER_END_SYMBOL:
-				if (--nestingLevel < 0)
-					throw new URISignatureParsingException(
-							"Malformed URL: parameter placeholder closing symbol found before the opening one.");
-				if (nestingLevel == 0) {
-					// correctly opened and closed placeholder found; check if
-					// it is a "fresh" one
-					String placeholderCandidate = uriSignature.substring(
-							startSymbolIdx + 1, i);
-					if (!foundPlaceholdersWithPositions
-							.containsKey(placeholderCandidate)) {
-						foundPlaceholdersWithPositions.put(
-								placeholderCandidate, startSymbolIdx + 1);
-					} else {
-						throw new URISignatureParsingException(
-								"Malformed URL: duplicate parameter placeholder \""
-										+ placeholderCandidate + "\" found.");
-					}
-				}
-				break;
-
-			default:
-				continue;
-			}
-		}
-
-		// the final check - make sure that after traversing the string, we are
-		// not "inside" one of the placeholders (e.g. this could happen if a
-		// placeholder
-		// opening symbol was found, but the closing one never occurred after
-		// that)
-		if (nestingLevel > 0)
-			throw new URISignatureParsingException(
-					"Malformed URL: parameter placeholder opening symbol found,\n"
-							+ "but the closing one has not been encountered.");
-
-		return foundPlaceholdersWithPositions;
-	}
-
-	/**
-	 * Works identical to
-	 * {@link URISignatureHandler#extractPlaceholdersWithPositions(String)}
-	 * except for returning only the list of placeholder titles - without
-	 * positions.
-	 * 
-	 * @param uriSignature
-	 *            The URI signature to process.
-	 * @return List of the placeholder titles in the order of their occurrence
-	 *         in the provided URI signature.
-	 */
-	public static List<String> extractPlaceholders(String uriSignature) {
-		return new ArrayList<>(extractPlaceholdersWithPositions(uriSignature)
-				.keySet());
-	}
-
-	/**
-	 * This method performs explicit validation of the URI signature. If the
-	 * validation succeeds, the method terminates quietly; in case of any
-	 * identified problems a {@link URISignatureParsingException} is thrown.
-	 * 
-	 * @param uriSignature
-	 *            The URI signature to validate.
-	 * @throws URISignatureParsingException
-	 */
-	public static void validate(String uriSignature)
-			throws URISignatureParsingException {
-		// this method essentially needs to do exactly the same thing
-		// as the method to extract the placeholders with their corresponding
-		// positions; all necessary validation is already performed there -
-		// hence the trick is simply to call that method (discarding its
-		// output),
-		// while keeping track of any exceptions that may be generated by the
-		// called method;
-		//
-		// for this simply call the placeholder extraction method - any
-		// exceptions
-		// will be forwarded up the method call stack; in case of success, the
-		// method
-		// will terminate quietly
-		extractPlaceholdersWithPositions(uriSignature);
-	}
-	
-	 /**
-	  * Check if the URL string contains "unsafe" characters, i.e. characters 
-	  * that need URL-encoding.
-	  * From RFC 1738: "...Only alphanumerics [0-9a-zA-Z], the special 
-	  * characters "$-_.+!*'()," (not including the quotes) and reserved 
-	  * characters used for their reserved purposes may be 
-	  * used unencoded within a URL." 
-	  * Reserved characters are: ";/?:@&=" ..." and "%" used for escaping.
-	 */
-	public static void checkForUnsafeCharacters(String candidateURLSignature) throws URISignatureParsingException{
-		String allowedURLCharactersString = new String("abcdefghijklmnopqrstuvwxyz0123456789$-_.+!*'(),;/?:@&=%");
-		char[] allowedURLCharactersArray = allowedURLCharactersString.toCharArray();
-		List<Character> allowedURLCharactersList = new ArrayList<Character>();
-		    for (char value : allowedURLCharactersArray)
-		    	allowedURLCharactersList.add(new Character(value));
-
-		int index = 0;
-		String unsafeCharactersDetected = "";
-		while (index < candidateURLSignature.length()){
-			char character = candidateURLSignature.charAt(index);
-			if (character == '{') { // a start of a parameter
-				// This is a paramater name - ignore until we find the closing '}'
-				index++;
-				while(character != '}' && index < candidateURLSignature.length()){
-					character = candidateURLSignature.charAt(index);
-					index++;
-				}
-			} else if (!allowedURLCharactersList.contains(Character
-					.valueOf(Character.toLowerCase(character)))) {
-				// We found an unsafe character in the URL - add to the list of unsafe characters
-				unsafeCharactersDetected += "'" + character + "', ";
-				index++;
-			} else {
-				index++;
-			}
-		}
-		String message = "";
-		unsafeCharactersDetected = unsafeCharactersDetected.trim();
-		if (unsafeCharactersDetected.endsWith(",")){ // remove the last ","
-			unsafeCharactersDetected = unsafeCharactersDetected.substring(0, unsafeCharactersDetected.lastIndexOf(','));
-		}
-		if  (!unsafeCharactersDetected.equals("")){
-			message += "REST service's URL contains unsafe characters that need\nto be URL-encoded or the service will most probably fail:\n"+ unsafeCharactersDetected;	
-			throw new URISignatureParsingException(message);
-		}
-	}
-
-
-	/**
-	 * Tests whether the provided URI signature is valid or not.
-	 * 
-	 * @param uriSignature
-	 *            URI signature to check for validity.
-	 * @return <code>true</code> if the URI signature is valid;
-	 *         <code>false</code> otherwise.
-	 */
-	public static boolean isValid(String uriSignature) {
-		try {
-			// no exceptions are generated by validate(), the validation has
-			// succeeded
-			validate(uriSignature);
-			return (true);
-		} catch (URISignatureParsingException e) {
-			return false;
-		}
-	}
-
-	/**
-	 * Substitutes real values for all placeholders encountered in the URI
-	 * signature and produces a complete URI that can be used directly.
-	 * 
-	 * @param uriSignature
-	 *            The URI signature to use as a basis.
-	 * @param parameters
-	 *            Map of {name,value} pairs for all placeholders in the
-	 *            signature. These values will be used to replace the
-	 *            placeholders in the signature.
-	 * @param escapeParameters
-	 *            Whether to URL-escape paramaters before placing them in the
-	 *            final URL.
-	 * @return A complete URI with all placeholders replaced by the provided
-	 *         values.
-	 * @throws URISignatureParsingException
-	 *             Thrown if there is a problem with the provided URI signature
-	 *             (e.g. null, empty, ill-formed, etc).
-	 * @throws URIGenerationFromSignatureException
-	 *             Thrown if there is a problem with the provided parameter map
-	 *             (e.g. null, empty, not containing enough values for some of
-	 *             the placeholders found in <code>uriSignature</code>.
-	 */
-	public static String generateCompleteURI(String uriSignature,
-			Map<String, String> specifiedParameters, boolean escapeParameters)
-			throws URISignatureParsingException,
-			URIGenerationFromSignatureException {
-		StringBuilder completeURI = new StringBuilder(uriSignature);
-
-		// no need to make any checks on the uriSignature - it is
-		// already handled by extractPlaceholdersWithPositions() --
-		// if something goes wrong a runtime exception will be thrown
-		// during placeholder extraction
-		LinkedHashMap<String, Integer> placeholdersWithPositions = extractPlaceholdersWithPositions(uriSignature);
-
-		// check that the URI signature contains some placeholders
-		if (placeholdersWithPositions.keySet().size() > 0) {
-			Map<String, String> parameters;
-			// some work will actually have to be done to replace placeholders
-			// with real values;
-			// check that the parameter map contains some values
-			if (specifiedParameters == null || specifiedParameters.isEmpty()) {
-				parameters = Collections.emptyMap();
-			} else {
-				parameters = specifiedParameters;
-			}
-
-			// the 'placeholders' linked list is guaranteed to be in the order
-			// of occurrence of placeholders in the URI signature;
-			// this will allow to traverse the URI signature and replace the
-			// placeholders in the reverse order --
-			// this way it is possible to use the indices of placeholders that
-			// were already found during their extraction to
-			// improve performance
-			LinkedList<String> placeholders = new LinkedList<String>(
-					placeholdersWithPositions.keySet());
-			Collections.reverse(placeholders);
-			Iterator<String> placeholdersIterator = placeholders.iterator();
-
-			while (placeholdersIterator.hasNext()) {
-				String placeholder = placeholdersIterator.next();
-				int placeholderStartPos = placeholdersWithPositions
-						.get(placeholder) - 1;
-				int placeholderEndPos = placeholderStartPos
-						+ placeholder.length() + 2;
-				if (parameters.containsKey(placeholder)) {
-					if (escapeParameters) {
-						completeURI.replace(placeholderStartPos,
-								placeholderEndPos, urlEncodeQuery(parameters
-										.get(placeholder)));
-					} else {
-						completeURI.replace(placeholderStartPos,
-								placeholderEndPos, parameters.get(placeholder));
-					}
-				} else {
-					int qnPos = completeURI.lastIndexOf("?", placeholderStartPos);
- 					int ampPos = completeURI.lastIndexOf("&", placeholderStartPos);
- 					int slashPos = completeURI.lastIndexOf("/", placeholderStartPos);
- 					int startParamPos = Math.max(qnPos, ampPos);
-					if (startParamPos > -1 && startParamPos > slashPos) {
- 						// We found an optional parameter, so delete all of it
- 						if (qnPos > ampPos) {
- 							// It might be the first or only parameter so delete carefully
- 							if (placeholderEndPos >= (completeURI.length() - 1)) {
- 								// No parameters
- 								completeURI.replace(startParamPos, placeholderEndPos, "");
- 							} else {
- 								// Remove the & from the following parameter, not the ? that starts
- 								completeURI.replace(startParamPos + 1, placeholderEndPos + 1, "");
- 							}
-						} else {
- 							// Just delete the optional parameter in total
-							completeURI.replace(startParamPos, placeholderEndPos, "");
- 						}
- 					} else {
- 						throw new URIGenerationFromSignatureException(
- 								"Parameter map does not contain a key/value for \""
- 										+ placeholder + "\" mandatory placeholder");
- 					}
-				}
-			}
-		}
-		/*
-		 * else { NO PLACEHOLDERS, SO NOTHING TO REPLACE WITH REAL VALUES - JUST
-		 * RETURN THE ORIGINAL 'uriSignature' }
-		 */
-
-		return (completeURI.toString());
-	}
-
-	/**
-	 * Exceptions of this type may be thrown when errors occur during URI
-	 * signature parsing - these will often indicate the reason for failure
-	 * (e.g. missing URI signature, nested placeholders, ill-formed signature,
-	 * etc).
-	 * 
-	 * @author Sergejs Aleksejevs
-	 */
-	@SuppressWarnings("serial")
-	public static class URISignatureParsingException extends
-			IllegalArgumentException {
-		public URISignatureParsingException() {
-		}
-
-		public URISignatureParsingException(String message) {
-			super(message);
-		}
-	}
-
-	/**
-	 * Exceptions of this type may be thrown during generation of a complete URI
-	 * from the provided signature and parameter hash. These may occur because
-	 * of wrong parameters, etc.
-	 * 
-	 * @author Sergejs Aleksejevs
-	 */
-	@SuppressWarnings("serial")
-	public static class URIGenerationFromSignatureException extends
-			RuntimeException {
-		public URIGenerationFromSignatureException() {
-		}
-
-		public URIGenerationFromSignatureException(String message) {
-			super(message);
-		}
-	}
-
-	/**
-	 * Prepares the string to serve as a part of url query to the server.
-	 * 
-	 * @param query
-	 *            The string that needs URL encoding.
-	 * @return URL encoded string that can be inserted into the request URL.
-	 */
-	public static String urlEncodeQuery(String query) {
-		String ns = Normalizer.normalize(query, Normalizer.Form.NFC);
-		byte[] bb = null;
-		try {
-			bb = ns.getBytes("UTF-8");
-		} catch (UnsupportedEncodingException e) {
-			logger.error(e);
-			return query;
-		}
-		
-		StringBuffer sb = new StringBuffer();
-		
-		for (int i = 0; i < bb.length; i++) {
-		    int b = bb[i] & 0xff;
-		    if (!Character.isLetterOrDigit(b) || (b >= 0x80)) {
-		    	sb.append("%");
-		    	sb.append(Integer.toHexString(b).toUpperCase());
-			} else {
-		    	sb.append((char)b);
-		    }
-		}
-		return sb.toString();
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/HTTPRequest.java
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/HTTPRequest.java b/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/HTTPRequest.java
new file mode 100644
index 0000000..6e78e89
--- /dev/null
+++ b/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/HTTPRequest.java
@@ -0,0 +1,104 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package org.apache.taverna.activities.rest;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.taverna.activities.rest.RESTActivity.HTTP_METHOD;
+import net.sf.taverna.t2.workflowmodel.processor.config.ConfigurationBean;
+import net.sf.taverna.t2.workflowmodel.processor.config.ConfigurationProperty;
+
+/**
+ * HTTP Request configuration bean.
+ *
+ * @author David Withers
+ */
+@ConfigurationBean(uri = RESTActivity.URI + "#Request")
+public class HTTPRequest {
+
+	private HTTP_METHOD method;
+
+	private String absoluteURITemplate;
+
+	private List<HTTPRequestHeader> headers = new ArrayList<HTTPRequestHeader>();
+
+	public HTTP_METHOD getMethod() {
+		return method;
+	}
+
+	@ConfigurationProperty(name = "mthd", label = "HTTP Method", uri="http://www.w3.org/2011/http#mthd")
+	public void setMethod(URI method) {
+		setMethod(HTTP_METHOD.valueOf(method.getFragment()));
+	}
+
+	public void setMethod(HTTP_METHOD method) {
+		this.method = method;
+	}
+
+	public String getAbsoluteURITemplate() {
+		return absoluteURITemplate;
+	}
+
+	@ConfigurationProperty(name = "absoluteURITemplate", label = "URL Template")
+	public void setAbsoluteURITemplate(String absoluteURITemplate) {
+		this.absoluteURITemplate = absoluteURITemplate;
+	}
+
+	public List<HTTPRequestHeader> getHeaders() {
+		return headers;
+	}
+
+	@ConfigurationProperty(name = "headers", label = "HTTP Request Headers", uri="http://www.w3.org/2011/http#headers")
+	public void setHeaders(List<HTTPRequestHeader> headers) {
+		this.headers = headers;
+	}
+
+	public HTTPRequestHeader getHeader(String name) {
+		for (HTTPRequestHeader httpRequestHeader : headers) {
+			if (httpRequestHeader.getFieldName().equals(name)) {
+				return httpRequestHeader;
+			}
+		}
+		return null;
+	}
+
+	public void setHeader(String name, String value) {
+		HTTPRequestHeader httpRequestHeader = getHeader(name);
+		if (httpRequestHeader == null) {
+			httpRequestHeader = new HTTPRequestHeader();
+			httpRequestHeader.setFieldName(name);
+			headers.add(httpRequestHeader);
+		}
+		httpRequestHeader.setFieldValue(value);
+	}
+
+	public void setHeader(String name, boolean use100Continue) {
+		HTTPRequestHeader httpRequestHeader = getHeader(name);
+		if (httpRequestHeader == null) {
+			httpRequestHeader = new HTTPRequestHeader();
+			httpRequestHeader.setFieldName(name);
+			headers.add(httpRequestHeader);
+		}
+		httpRequestHeader.setUse100Continue(use100Continue);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/HTTPRequestHandler.java
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/HTTPRequestHandler.java b/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/HTTPRequestHandler.java
new file mode 100644
index 0000000..8c1a2bb
--- /dev/null
+++ b/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/HTTPRequestHandler.java
@@ -0,0 +1,605 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package org.apache.taverna.activities.rest;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.ProxySelector;
+import java.net.URL;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.SSLContext;
+
+import org.apache.taverna.activities.rest.RESTActivity.DATA_FORMAT;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
+import org.apache.http.impl.conn.SingleClientConnManager;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.ExecutionContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.log4j.Logger;
+
+/**
+ * This class deals with the actual remote REST service invocation. The main
+ * four HTTP methods (GET | POST | PUT | DELETE) are supported. <br/>
+ * <br/>
+ *
+ * Configuration for request execution is obtained from the related REST
+ * activity - encapsulated in a configuration bean.
+ *
+ * @author Sergejs Aleksejevs
+ * @author Alex Nenadic
+ */
+public class HTTPRequestHandler {
+	private static final int HTTPS_DEFAULT_PORT = 443;
+	private static final String CONTENT_TYPE_HEADER_NAME = "Content-Type";
+	private static final String ACCEPT_HEADER_NAME = "Accept";
+	private static Logger logger = Logger.getLogger(HTTPRequestHandler.class);
+
+	public static String PROXY_HOST = "http.proxyHost";
+	public static String PROXY_PORT = "http.proxyPort";
+	public static String PROXY_USERNAME = "http.proxyUser";
+	public static String PROXY_PASSWORD = "http.proxyPassword";
+
+	/**
+	 * This method is the entry point to the invocation of a remote REST
+	 * service. It accepts a number of parameters from the related REST activity
+	 * and uses those to assemble, execute and fetch results of a relevant HTTP
+	 * request.
+	 *
+	 * @param requestURL
+	 *            The URL for the request to be made. This cannot be taken from
+	 *            the <code>configBean</code>, because this should be the
+	 *            complete URL which may be directly used to make the request (
+	 *            <code>configBean</code> would only contain the URL signature
+	 *            associated with the REST activity).
+	 * @param configBean
+	 *            Configuration of the associated REST activity is passed to
+	 *            this class as a configuration bean. Settings such as HTTP
+	 *            method, MIME types for "Content-Type" and "Accept" headers,
+	 *            etc are taken from the bean.
+	 * @param inputMessageBody
+	 *            Body of the message to be sent to the server - only needed for
+	 *            POST and PUT requests; for GET and DELETE it will be
+	 *            discarded.
+	 * @return
+	 */
+	@SuppressWarnings("deprecation")
+	public static HTTPRequestResponse initiateHTTPRequest(String requestURL,
+			RESTActivityConfigurationBean configBean, Object inputMessageBody,
+			Map<String, String> urlParameters, CredentialsProvider credentialsProvider) {
+		ClientConnectionManager connectionManager = null;
+		if (requestURL.toLowerCase().startsWith("https")) {
+			// Register a protocol scheme for https that uses Taverna's
+			// SSLSocketFactory
+			try {
+				URL url = new URL(requestURL); // the URL object which will
+				// parse the port out for us
+				int port = url.getPort();
+				if (port == -1) // no port was defined in the URL
+					port = HTTPS_DEFAULT_PORT; // default HTTPS port
+				Scheme https = new Scheme("https", new org.apache.http.conn.ssl.SSLSocketFactory(
+						SSLContext.getDefault()), port);
+				SchemeRegistry schemeRegistry = new SchemeRegistry();
+				schemeRegistry.register(https);
+				connectionManager = new SingleClientConnManager(null,
+						schemeRegistry);
+			} catch (MalformedURLException ex) {
+				logger.error("Failed to extract port from the REST service URL: the URL "
+						+ requestURL + " is malformed.", ex);
+				// This will cause the REST activity to fail but this method
+				// seems not to throw an exception so we'll just log the error
+				// and let it go through
+			} catch (NoSuchAlgorithmException ex2) {
+				// This will cause the REST activity to fail but this method
+				// seems not to throw an exception so we'll just log the error
+				// and let it go through
+				logger.error(
+						"Failed to create SSLContext for invoking the REST service over https.",
+						ex2);
+			}
+		}
+
+		switch (configBean.getHttpMethod()) {
+		case GET:
+			return doGET(connectionManager, requestURL, configBean, urlParameters, credentialsProvider);
+		case POST:
+			return doPOST(connectionManager, requestURL, configBean, inputMessageBody, urlParameters, credentialsProvider);
+		case PUT:
+			return doPUT(connectionManager, requestURL, configBean, inputMessageBody, urlParameters, credentialsProvider);
+		case DELETE:
+			return doDELETE(connectionManager, requestURL, configBean, urlParameters, credentialsProvider);
+		default:
+			return new HTTPRequestResponse(new Exception("Error: something went wrong; "
+					+ "no failure has occurred, but but unexpected HTTP method (\""
+					+ configBean.getHttpMethod() + "\") encountered."));
+		}
+	}
+
+	private static HTTPRequestResponse doGET(ClientConnectionManager connectionManager,
+			String requestURL, RESTActivityConfigurationBean configBean,
+			Map<String, String> urlParameters, CredentialsProvider credentialsProvider) {
+		HttpGet httpGet = new HttpGet(requestURL);
+		return performHTTPRequest(connectionManager, httpGet, configBean, urlParameters, credentialsProvider);
+	}
+
+	private static HTTPRequestResponse doPOST(ClientConnectionManager connectionManager,
+			String requestURL, RESTActivityConfigurationBean configBean, Object inputMessageBody,
+			Map<String, String> urlParameters, CredentialsProvider credentialsProvider) {
+		HttpPost httpPost = new HttpPost(requestURL);
+
+		// TODO - decide whether this is needed for PUT requests, too (or just
+		// here, for POST)
+		// check whether to send the HTTP Expect header or not
+		if (!configBean.getSendHTTPExpectRequestHeader())
+			httpPost.getParams().setBooleanParameter("http.protocol.expect-continue", false);
+
+		// If the user wants to set MIME type for the 'Content-Type' header
+		if (!configBean.getContentTypeForUpdates().isEmpty())
+			httpPost.setHeader(CONTENT_TYPE_HEADER_NAME, configBean.getContentTypeForUpdates());
+		try {
+			HttpEntity entity = null;
+			if (inputMessageBody == null) {
+				entity = new StringEntity("");
+			} else if (configBean.getOutgoingDataFormat() == DATA_FORMAT.String) {
+				entity = new StringEntity((String) inputMessageBody);
+			} else {
+				entity = new ByteArrayEntity((byte[]) inputMessageBody);
+			}
+			httpPost.setEntity(entity);
+		} catch (UnsupportedEncodingException e) {
+			return (new HTTPRequestResponse(new Exception("Error occurred while trying to "
+					+ "attach a message body to the POST request. See attached cause of this "
+					+ "exception for details.")));
+		}
+		return performHTTPRequest(connectionManager, httpPost, configBean, urlParameters, credentialsProvider);
+	}
+
+	private static HTTPRequestResponse doPUT(ClientConnectionManager connectionManager,
+			String requestURL, RESTActivityConfigurationBean configBean, Object inputMessageBody,
+			Map<String, String> urlParameters, CredentialsProvider credentialsProvider) {
+		HttpPut httpPut = new HttpPut(requestURL);
+		if (!configBean.getContentTypeForUpdates().isEmpty())
+			httpPut.setHeader(CONTENT_TYPE_HEADER_NAME, configBean.getContentTypeForUpdates());
+		try {
+			HttpEntity entity = null;
+			if (inputMessageBody == null) {
+				entity = new StringEntity("");
+			} else if (configBean.getOutgoingDataFormat() == DATA_FORMAT.String) {
+				entity = new StringEntity((String) inputMessageBody);
+			} else {
+				entity = new ByteArrayEntity((byte[]) inputMessageBody);
+			}
+			httpPut.setEntity(entity);
+		} catch (UnsupportedEncodingException e) {
+			return new HTTPRequestResponse(new Exception("Error occurred while trying to "
+					+ "attach a message body to the PUT request. See attached cause of this "
+					+ "exception for details."));
+		}
+		return performHTTPRequest(connectionManager, httpPut, configBean, urlParameters, credentialsProvider);
+	}
+
+	private static HTTPRequestResponse doDELETE(ClientConnectionManager connectionManager,
+			String requestURL, RESTActivityConfigurationBean configBean,
+			Map<String, String> urlParameters, CredentialsProvider credentialsProvider) {
+		HttpDelete httpDelete = new HttpDelete(requestURL);
+		return performHTTPRequest(connectionManager, httpDelete, configBean, urlParameters, credentialsProvider);
+	}
+
+	/**
+	 * TODO - REDIRECTION output:: if there was no redirection, should just show
+	 * the actual initial URL?
+	 *
+	 * @param httpRequest
+	 * @param acceptHeaderValue
+	 */
+	private static HTTPRequestResponse performHTTPRequest(
+			ClientConnectionManager connectionManager, HttpRequestBase httpRequest,
+			RESTActivityConfigurationBean configBean,
+			Map<String, String> urlParameters, CredentialsProvider credentialsProvider) {
+		// headers are set identically for all HTTP methods, therefore can do
+		// centrally - here
+
+		// If the user wants to set MIME type for the 'Accepts' header
+		String acceptsHeaderValue = configBean.getAcceptsHeaderValue();
+		if ((acceptsHeaderValue != null) && !acceptsHeaderValue.isEmpty()) {
+			httpRequest.setHeader(ACCEPT_HEADER_NAME,
+					URISignatureHandler.generateCompleteURI(acceptsHeaderValue, urlParameters, configBean.getEscapeParameters()));
+		}
+
+		// See if user wanted to set any other HTTP headers
+		ArrayList<ArrayList<String>> otherHTTPHeaders = configBean.getOtherHTTPHeaders();
+		if (!otherHTTPHeaders.isEmpty())
+			for (ArrayList<String> httpHeaderNameValuePair : otherHTTPHeaders)
+				if (httpHeaderNameValuePair.get(0) != null
+						&& !httpHeaderNameValuePair.get(0).isEmpty()) {
+					String headerParameterizedValue = httpHeaderNameValuePair.get(1);
+					String headerValue = URISignatureHandler.generateCompleteURI(headerParameterizedValue, urlParameters, configBean.getEscapeParameters());
+					httpRequest.setHeader(httpHeaderNameValuePair.get(0), headerValue);
+				}
+
+		try {
+			HTTPRequestResponse requestResponse = new HTTPRequestResponse();
+			DefaultHttpClient httpClient = new DefaultHttpClient(connectionManager, null);
+			((DefaultHttpClient) httpClient).setCredentialsProvider(credentialsProvider);
+			HttpContext localContext = new BasicHttpContext();
+
+			// Set the proxy settings, if any
+			if (System.getProperty(PROXY_HOST) != null
+					&& !System.getProperty(PROXY_HOST).isEmpty()) {
+				// Instruct HttpClient to use the standard
+				// JRE proxy selector to obtain proxy information
+				ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(httpClient
+						.getConnectionManager().getSchemeRegistry(), ProxySelector.getDefault());
+				httpClient.setRoutePlanner(routePlanner);
+				// Do we need to authenticate the user to the proxy?
+				if (System.getProperty(PROXY_USERNAME) != null
+						&& !System.getProperty(PROXY_USERNAME).isEmpty())
+					// Add the proxy username and password to the list of
+					// credentials
+					httpClient.getCredentialsProvider().setCredentials(
+							new AuthScope(System.getProperty(PROXY_HOST), Integer.parseInt(System
+									.getProperty(PROXY_PORT))),
+							new UsernamePasswordCredentials(System.getProperty(PROXY_USERNAME),
+									System.getProperty(PROXY_PASSWORD)));
+			}
+
+			// execute the request
+			HttpResponse response = httpClient.execute(httpRequest, localContext);
+
+			// record response code
+			requestResponse.setStatusCode(response.getStatusLine().getStatusCode());
+			requestResponse.setReasonPhrase(response.getStatusLine().getReasonPhrase());
+
+			// record header values for Content-Type of the response
+			requestResponse.setResponseContentTypes(response.getHeaders(CONTENT_TYPE_HEADER_NAME));
+
+			// track where did the final redirect go to (if there was any)
+			HttpHost targetHost = (HttpHost) localContext
+					.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
+			HttpUriRequest targetRequest = (HttpUriRequest) localContext
+					.getAttribute(ExecutionContext.HTTP_REQUEST);
+			requestResponse.setRedirectionURL("" + targetHost + targetRequest.getURI());
+			requestResponse.setRedirectionHTTPMethod(targetRequest.getMethod());
+			requestResponse.setHeaders(response.getAllHeaders());
+
+			/* read and store response body
+			 (check there is some content - negative length of content means
+			 unknown length;
+			 zero definitely means no content...)*/
+			// TODO - make sure that this test is sufficient to determine if
+			// there is no response entity
+			if (response.getEntity() != null && response.getEntity().getContentLength() != 0)
+				requestResponse.setResponseBody(readResponseBody(response.getEntity()));
+
+			// release resources (e.g. connection pool, etc)
+			httpClient.getConnectionManager().shutdown();
+			return requestResponse;
+		} catch (Exception ex) {
+			return new HTTPRequestResponse(ex);
+		}
+	}
+
+	/**
+	 * Dispatcher method that decides on the method of reading the server
+	 * response data - either as a string or as binary data.
+	 *
+	 * @param entity
+	 * @return
+	 * @throws IOException
+	 */
+	private static Object readResponseBody(HttpEntity entity) throws IOException {
+		if (entity == null)
+			return null;
+
+		/*
+		 * test whether the data is binary or textual - for binary data will
+		 * read just as it is, for textual data will attempt to perform charset
+		 * conversion from the original one into UTF-8
+		 */
+
+		if (entity.getContentType() == null)
+			// HTTP message contains a body but content type is null??? - we
+			// have seen services like this
+			return readFromInputStreamAsBinary(entity.getContent());
+
+		String contentType = entity.getContentType().getValue().toLowerCase();
+		if (contentType.startsWith("text") || contentType.contains("charset="))
+			// read as text
+			return readResponseBodyAsString(entity);
+		// read as binary - enough to pass the input stream, not the
+		// whole entity
+		return readFromInputStreamAsBinary(entity.getContent());
+	}
+
+	/**
+	 * Worker method that extracts the content of the received HTTP message as a
+	 * string. It also makes use of the charset that is specified in the
+	 * Content-Type header of the received data to read it appropriately.
+	 *
+	 * @param entity
+	 * @return
+	 * @throws IOException
+	 */
+	private static String readResponseBodyAsString(HttpEntity entity) throws IOException {
+		/*
+		 * From RFC2616 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+		 * Content-Type = "Content-Type" ":" media-type, where media-type = type
+		 * "/" subtype *( ";" parameter ) can have 0 or more parameters such as
+		 * "charset", etc. Linear white space (LWS) MUST NOT be used between the
+		 * type and subtype, nor between an attribute and its value. e.g.
+		 * Content-Type: text/html; charset=ISO-8859-4
+		 */
+
+		// get charset name
+		String charset = null;
+		String contentType = entity.getContentType().getValue().toLowerCase();
+
+		String[] contentTypeParts = contentType.split(";");
+		for (String contentTypePart : contentTypeParts) {
+			contentTypePart = contentTypePart.trim();
+			if (contentTypePart.startsWith("charset="))
+				charset = contentTypePart.substring("charset=".length());
+		}
+
+		// read the data line by line
+		StringBuilder responseBodyString = new StringBuilder();
+		try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+				entity.getContent(), charset != null ? charset : "UTF-8"))) {
+
+			String str;
+			while ((str = reader.readLine()) != null)
+				responseBodyString.append(str + "\n");
+
+			return responseBodyString.toString();
+		}
+	}
+
+	/**
+	 * Worker method that extracts the content of the input stream as binary
+	 * data.
+	 *
+	 * @param inputStream
+	 * @return
+	 * @throws IOException
+	 */
+	public static byte[] readFromInputStreamAsBinary(InputStream inputStream) throws IOException {
+		// use BufferedInputStream for better performance
+		try (BufferedInputStream in = new BufferedInputStream(inputStream)) {
+			// this list is to hold all fetched data
+			List<byte[]> data = new ArrayList<byte[]>();
+
+			// set up buffers for reading the data
+			int bufLength = 100 * 1024; // 100K
+			byte[] buf = new byte[bufLength];
+			byte[] currentPortionOfData = null;
+			int currentlyReadByteCount = 0;
+
+			// read the data portion by portion into a list
+			while ((currentlyReadByteCount = in.read(buf, 0, bufLength)) != -1) {
+				currentPortionOfData = new byte[currentlyReadByteCount];
+				System.arraycopy(buf, 0, currentPortionOfData, 0, currentlyReadByteCount);
+				data.add(currentPortionOfData);
+			}
+
+			// now check how much data was read and return that as a single byte
+			// array
+			if (data.size() == 1)
+				// just a single block of data - return it as it is
+				return data.get(0);
+
+			// there is more than one block of data -- calculate total
+			// length of data
+			bufLength = 0;
+			for (byte[] portionOfData : data)
+				bufLength += portionOfData.length;
+
+			// allocate a single large byte array that could contain all
+			// data
+			buf = new byte[bufLength];
+
+			// fill this byte array with data from all fragments
+			int lastFilledPositionInOutputArray = 0;
+			for (byte[] portionOfData : data) {
+				System.arraycopy(portionOfData, 0, buf,
+						lastFilledPositionInOutputArray, portionOfData.length);
+				lastFilledPositionInOutputArray += portionOfData.length;
+			}
+
+			return buf;
+		}
+	}
+
+	/**
+	 * All fields have public accessor, but private mutators. This is because it
+	 * should only be allowed to modify the HTTPRequestResponse partially inside
+	 * the HTTPRequestHandler class only. For users of this class it will behave
+	 * as immutable.
+	 *
+	 * @author Sergejs Aleksejevs
+	 */
+	public static class HTTPRequestResponse {
+		private int statusCode;
+		private String reasonPhrase;
+		private String redirectionURL;
+		private String redirectionHTTPMethod;
+		private Header[] responseContentTypes;
+		private Object responseBody;
+
+		private Exception exception;
+		private Header[] allHeaders;
+
+		/**
+		 * Private default constructor - will only be accessible from
+		 * HTTPRequestHandler. Values for the entity will then be set using the
+		 * private mutator methods.
+		 */
+		private HTTPRequestResponse() {
+			/*
+			 * do nothing here - values will need to be manually set later by
+			 * using private mutator methods
+			 */
+		}
+		
+		public void setHeaders(Header[] allHeaders) {
+			this.allHeaders = allHeaders;
+		}
+	
+	    public Header[] getHeaders() {
+	    	return allHeaders;
+	    }
+	
+	    public List<String> getHeadersAsStrings() {
+	    	List<String> headerStrings = new ArrayList<String>();
+	    	for (Header h : getHeaders()) {
+	    		headerStrings.add(h.toString());
+	    	}
+	    	return headerStrings;
+	    }
+
+		/**
+		 * Standard public constructor for a regular case, where all values are
+		 * known and the request has succeeded.
+		 *
+		 * @param statusCode
+		 * @param reasonPhrase
+		 * @param redirection
+		 * @param responseContentTypes
+		 * @param responseBody
+		 */
+		public HTTPRequestResponse(int statusCode, String reasonPhrase, String redirectionURL,
+				String redirectionHTTPMethod, Header[] responseContentTypes, String responseBody) {
+			this.statusCode = statusCode;
+			this.reasonPhrase = reasonPhrase;
+			this.redirectionURL = redirectionURL;
+			this.redirectionHTTPMethod = redirectionHTTPMethod;
+			this.responseContentTypes = responseContentTypes;
+			this.responseBody = responseBody;
+		}
+
+		/**
+		 * Standard public constructor for an error case, where an error has
+		 * occurred and request couldn't be executed because of an internal
+		 * exception (rather than an error received from the remote server).
+		 *
+		 * @param exception
+		 */
+		public HTTPRequestResponse(Exception exception) {
+			this.exception = exception;
+		}
+
+		private void setStatusCode(int statusCode) {
+			this.statusCode = statusCode;
+		}
+
+		public int getStatusCode() {
+			return statusCode;
+		}
+
+		public String getReasonPhrase() {
+			return reasonPhrase;
+		}
+
+		private void setReasonPhrase(String reasonPhrase) {
+			this.reasonPhrase = reasonPhrase;
+		}
+
+		public String getRedirectionURL() {
+			return redirectionURL;
+		}
+
+		private void setRedirectionURL(String redirectionURL) {
+			this.redirectionURL = redirectionURL;
+		}
+
+		public String getRedirectionHTTPMethod() {
+			return redirectionHTTPMethod;
+		}
+
+		private void setRedirectionHTTPMethod(String redirectionHTTPMethod) {
+			this.redirectionHTTPMethod = redirectionHTTPMethod;
+		}
+
+		public Header[] getResponseContentTypes() {
+			return responseContentTypes;
+		}
+
+		private void setResponseContentTypes(Header[] responseContentTypes) {
+			this.responseContentTypes = responseContentTypes;
+		}
+
+		public Object getResponseBody() {
+			return responseBody;
+		}
+
+		private void setResponseBody(Object outputBody) {
+			this.responseBody = outputBody;
+		}
+
+		/**
+		 * @return <code>true</code> if an exception has occurred while the HTTP
+		 *         request was executed. (E.g. this doesn't indicate a server
+		 *         error - just that the request couldn't be successfully
+		 *         executed. It could have been a network timeout, etc).
+		 */
+		public boolean hasException() {
+			return (this.exception != null);
+		}
+
+		public Exception getException() {
+			return exception;
+		}
+
+		/**
+		 * @return <code>true</code> if HTTP code of server response is either
+		 *         4xx or 5xx.
+		 */
+		public boolean hasServerError() {
+			return (statusCode >= 400 && statusCode < 600);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/HTTPRequestHeader.java
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/HTTPRequestHeader.java b/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/HTTPRequestHeader.java
new file mode 100644
index 0000000..abaff68
--- /dev/null
+++ b/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/HTTPRequestHeader.java
@@ -0,0 +1,65 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package org.apache.taverna.activities.rest;
+
+import net.sf.taverna.t2.workflowmodel.processor.config.ConfigurationBean;
+import net.sf.taverna.t2.workflowmodel.processor.config.ConfigurationProperty;
+
+/**
+ * HTTP Request Header configuration bean
+ *
+ * @author David Withers
+ */
+@ConfigurationBean(uri = "http://www.w3.org/2011/http#RequestHeader")
+public class HTTPRequestHeader {
+
+	private String fieldName, fieldValue;
+
+	private boolean use100Continue;
+
+	public String getFieldName() {
+		return fieldName;
+	}
+
+	@ConfigurationProperty(name = "fieldName", label = "HTTP Header Name")
+	public void setFieldName(String fieldName) {
+		this.fieldName = fieldName;
+	}
+
+	public String getFieldValue() {
+		return fieldValue;
+	}
+
+	@ConfigurationProperty(name = "fieldValue", label = "HTTP Header Value")
+	public void setFieldValue(String fieldValue) {
+		this.fieldValue = fieldValue;
+	}
+
+	public boolean isUse100Continue() {
+		return use100Continue;
+	}
+
+	@ConfigurationProperty(name = "use100Continue", label = "Use 100 Continue", required = false, uri = RESTActivity.URI
+			+ "#use100Continue")
+	public void setUse100Continue(boolean use100Continue) {
+		this.use100Continue = use100Continue;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/RESTActivity.java
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/RESTActivity.java b/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/RESTActivity.java
new file mode 100644
index 0000000..87b29be
--- /dev/null
+++ b/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/RESTActivity.java
@@ -0,0 +1,364 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package org.apache.taverna.activities.rest;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.taverna.activities.rest.HTTPRequestHandler.HTTPRequestResponse;
+import org.apache.taverna.activities.rest.URISignatureHandler.URISignatureParsingException;
+import net.sf.taverna.t2.invocation.InvocationContext;
+import net.sf.taverna.t2.reference.ErrorDocument;
+import net.sf.taverna.t2.reference.ReferenceService;
+import net.sf.taverna.t2.reference.T2Reference;
+import net.sf.taverna.t2.workflowmodel.processor.activity.AbstractAsynchronousActivity;
+import net.sf.taverna.t2.workflowmodel.processor.activity.ActivityConfigurationException;
+import net.sf.taverna.t2.workflowmodel.processor.activity.AsynchronousActivityCallback;
+
+import org.apache.http.client.CredentialsProvider;
+import org.apache.log4j.Logger;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Generic REST activity that is capable to perform all four HTTP methods.
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class RESTActivity extends AbstractAsynchronousActivity<JsonNode> {
+
+	public static final String URI = "http://ns.taverna.org.uk/2010/activity/rest";
+
+	private static Logger logger = Logger.getLogger(RESTActivity.class);
+
+	// This generic activity can deal with any of the four HTTP methods
+	public static enum HTTP_METHOD {
+		GET, POST, PUT, DELETE
+	};
+
+	// Default choice of data format (especially, for outgoing data)
+	public static enum DATA_FORMAT {
+		String(String.class), Binary(byte[].class);
+
+		private final Class<?> dataFormat;
+
+		DATA_FORMAT(Class<?> dataFormat) {
+			this.dataFormat = dataFormat;
+		}
+
+		public Class<?> getDataFormat() {
+			return this.dataFormat;
+		}
+	};
+
+	// These ports are default ones; additional ports will be dynamically
+	// generated from the
+	// URI signature used to configure the activity
+	public static final String IN_BODY = "inputBody";
+	public static final String OUT_RESPONSE_BODY = "responseBody";
+	public static final String OUT_RESPONSE_HEADERS = "responseHeaders";
+	public static final String OUT_STATUS = "status";
+	public static final String OUT_REDIRECTION = "redirection";
+	public static final String OUT_COMPLETE_URL = "actualURL";
+
+	// Configuration bean for this activity - essentially defines a particular
+	// instance
+	// of the activity through the values of its parameters
+	private RESTActivityConfigurationBean configBean;
+	private JsonNode json;
+
+	private CredentialsProvider credentialsProvider;
+
+	public RESTActivity(CredentialsProvider credentialsProvider) {
+		this.credentialsProvider = credentialsProvider;
+	}
+
+	@Override
+	public JsonNode getConfiguration() {
+		return json;
+	}
+
+	public RESTActivityConfigurationBean getConfigurationBean() {
+		return configBean;
+	}
+
+	@Override
+	public void configure(JsonNode json) throws ActivityConfigurationException {
+		this.json = json;
+		configBean = new RESTActivityConfigurationBean(json);
+		// Check configBean is valid - mainly check the URI signature for being
+		// well-formed and
+		// other details being present and valid;
+		//
+		// NB! The URI signature will still be valid if there are no
+		// placeholders at all - in this
+		// case for GET and DELETE methods no input ports will be generated and
+		// a single input
+		// port for input message body will be created for POST / PUT methods.
+		if (!configBean.isValid()) {
+			throw new ActivityConfigurationException(
+					"Bad data in the REST activity configuration bean - "
+							+ "possible causes are: missing or ill-formed URI signature, missing or invalid MIME types for the "
+							+ "specified HTTP headers ('Accept' | 'Content-Type'). This should not have happened, as validation "
+							+ "on the UI had to be performed prior to accepting this configuration.");
+		}
+
+		// (Re)create input/output ports depending on configuration
+		configurePorts();
+	}
+
+	protected void configurePorts() {
+		// all input ports are dynamic and depend on the configuration
+		// of the particular instance of the REST activity
+
+		// now process the URL signature - extract all placeholders and create
+		// an input data type for each
+		Map<String, Class<?>> activityInputs = new HashMap<>();
+		List<String> placeholders = URISignatureHandler.extractPlaceholders(configBean
+				.getUrlSignature());
+		String acceptsHeaderValue = configBean.getAcceptsHeaderValue();
+		if (acceptsHeaderValue != null && !acceptsHeaderValue.isEmpty())
+			try {
+				List<String> acceptsPlaceHolders = URISignatureHandler
+						.extractPlaceholders(acceptsHeaderValue);
+				acceptsPlaceHolders.removeAll(placeholders);
+				placeholders.addAll(acceptsPlaceHolders);
+			} catch (URISignatureParsingException e) {
+				logger.error(e);
+			}
+		for (ArrayList<String> httpHeaderNameValuePair : configBean.getOtherHTTPHeaders())
+			try {
+				List<String> headerPlaceHolders = URISignatureHandler
+						.extractPlaceholders(httpHeaderNameValuePair.get(1));
+				headerPlaceHolders.removeAll(placeholders);
+				placeholders.addAll(headerPlaceHolders);
+			} catch (URISignatureParsingException e) {
+				logger.error(e);
+			}
+		for (String placeholder : placeholders)
+			// these inputs will have a dynamic name each;
+			// the data type is string as they are the values to be
+			// substituted into the URL signature at the execution time
+			activityInputs.put(placeholder, String.class);
+
+		// all inputs have now been configured - store the resulting set-up in
+		// the config bean;
+		// this configuration will be reused during the execution of activity,
+		// so that existing
+		// set-up could simply be referred to, rather than "re-calculated"
+		configBean.setActivityInputs(activityInputs);
+
+		// ---- CREATE OUTPUTS ----
+		// all outputs are of depth 0 - i.e. just a single value on each;
+
+		// output ports for Response Body and Status are static - they don't
+		// depend on the configuration of the activity;
+		addOutput(OUT_RESPONSE_BODY, 0);
+		addOutput(OUT_STATUS, 0);
+		if (configBean.getShowActualUrlPort())
+			addOutput(OUT_COMPLETE_URL, 0);
+		if (configBean.getShowResponseHeadersPort())
+			addOutput(OUT_RESPONSE_HEADERS, 1);
+
+		// Redirection port may be hidden/shown
+		if (configBean.getShowRedirectionOutputPort())
+			addOutput(OUT_REDIRECTION, 0);
+	}
+
+	/**
+	 * Uses HTTP method value of the config bean of the current instance of
+	 * RESTActivity.
+	 *
+	 * @see RESTActivity#hasMessageBodyInputPort(HTTP_METHOD)
+	 */
+	public boolean hasMessageBodyInputPort() {
+		return hasMessageBodyInputPort(configBean.getHttpMethod());
+	}
+
+	/**
+	 * Return value of this method has a number of implications - various input
+	 * ports and configuration options for this activity are applied based on
+	 * the selected HTTP method.
+	 *
+	 * @param httpMethod
+	 *            HTTP method to make the decision for.
+	 * @return True if this instance of the REST activity uses HTTP POST / PUT
+	 *         methods; false otherwise.
+	 */
+	public static boolean hasMessageBodyInputPort(HTTP_METHOD httpMethod) {
+		return httpMethod == HTTP_METHOD.POST || httpMethod == HTTP_METHOD.PUT;
+	}
+
+	/**
+	 * This method executes pre-configured instance of REST activity. It
+	 * resolves inputs of the activity and registers its outputs; the real
+	 * invocation of the HTTP request is performed by
+	 * {@link HTTPRequestHandler#initiateHTTPRequest(String, RESTActivityConfigurationBean, String)}
+	 * .
+	 */
+	@Override
+	public void executeAsynch(final Map<String, T2Reference> inputs,
+			final AsynchronousActivityCallback callback) {
+		// Don't execute service directly now, request to be run asynchronously
+		callback.requestRun(new Runnable() {
+			private Logger logger = Logger.getLogger(RESTActivity.class);
+
+			@Override
+			public void run() {
+
+				InvocationContext context = callback.getContext();
+				ReferenceService referenceService = context.getReferenceService();
+
+				// ---- RESOLVE INPUTS ----
+
+				// RE-ASSEMBLE REQUEST URL FROM SIGNATURE AND PARAMETERS
+				// (just use the configuration that was determined in
+				// configurePorts() - all ports in this set are required)
+				Map<String, String> urlParameters = new HashMap<>();
+				try {
+					for (String inputName : configBean.getActivityInputs().keySet())
+						urlParameters.put(inputName, (String) referenceService.renderIdentifier(
+								inputs.get(inputName), configBean.getActivityInputs()
+										.get(inputName), context));
+				} catch (Exception e) {
+					// problem occurred while resolving the inputs
+					callback.fail("REST activity was unable to resolve all necessary inputs"
+							+ "that contain values for populating the URI signature placeholders "
+							+ "with values.", e);
+
+					// make sure we don't call callback.receiveResult later
+					return;
+				}
+				String completeURL = URISignatureHandler.generateCompleteURI(
+						configBean.getUrlSignature(), urlParameters,
+						configBean.getEscapeParameters());
+
+				// OBTAIN THE INPUT BODY IF NECESSARY
+				// ("IN_BODY" is treated as *optional* for now)
+				Object inputMessageBody = null;
+				if (hasMessageBodyInputPort() && inputs.containsKey(IN_BODY)) {
+					inputMessageBody = referenceService.renderIdentifier(inputs.get(IN_BODY),
+							configBean.getOutgoingDataFormat().getDataFormat(), context);
+				}
+
+				// ---- DO THE ACTUAL SERVICE INVOCATION ----
+				HTTPRequestResponse requestResponse = HTTPRequestHandler.initiateHTTPRequest(
+						completeURL, configBean, inputMessageBody, urlParameters,
+						credentialsProvider);
+
+				// test if an internal failure has occurred
+				if (requestResponse.hasException()) {
+					callback.fail(
+							"Internal error has occurred while trying to execute the REST activity",
+							requestResponse.getException());
+
+					// make sure we don't call callback.receiveResult later
+					return;
+				}
+
+				// ---- REGISTER OUTPUTS ----
+				Map<String, T2Reference> outputs = new HashMap<String, T2Reference>();
+
+				T2Reference responseBodyRef = null;
+				if (requestResponse.hasServerError()) {
+					// test if a server error has occurred -- if so, return
+					// output as an error document
+
+					// Check if error returned is a string - sometimes services return byte[]
+					ErrorDocument errorDocument = null;
+					if (requestResponse.getResponseBody() == null) {
+						// No response body - register empty string
+						errorDocument = referenceService.getErrorDocumentService().registerError(
+								"", 0, context);
+					} else {
+						if (requestResponse.getResponseBody() instanceof String) {
+							errorDocument = referenceService.getErrorDocumentService()
+									.registerError((String) requestResponse.getResponseBody(), 0,
+											context);
+						} else if (requestResponse.getResponseBody() instanceof byte[]) {
+							// Do the only thing we can - try to convert to
+							// UTF-8 encoded string
+							// and hope we'll get back something intelligible
+							String str = null;
+							try {
+								str = new String((byte[]) requestResponse.getResponseBody(),
+										"UTF-8");
+							} catch (UnsupportedEncodingException e) {
+								logger.error(
+										"Failed to reconstruct the response body byte[]"
+										+ " into string using UTF-8 encoding",
+										e);
+								// try with no encoding, probably will get garbage
+								str = new String((byte[]) requestResponse.getResponseBody());
+							}
+							errorDocument = referenceService.getErrorDocumentService()
+									.registerError(str, 0, context);
+						} else {
+							// Do what we can - call toString() method and hope
+							// for the best
+							errorDocument = referenceService.getErrorDocumentService()
+									.registerError(requestResponse.getResponseBody().toString(), 0,
+											context);
+						}
+					}
+					responseBodyRef = referenceService.register(errorDocument, 0, true, context);
+				} else if (requestResponse.getResponseBody() != null) {
+					// some response data is available
+					responseBodyRef = referenceService.register(requestResponse.getResponseBody(),
+							0, true, context);
+				} else {
+					// no data was received in response to the request - must
+					// have been just a response header...
+					responseBodyRef = referenceService.register("", 0, true, context);
+				}
+				outputs.put(OUT_RESPONSE_BODY, responseBodyRef);
+
+				T2Reference statusRef = referenceService.register(requestResponse.getStatusCode(),
+						0, true, context);
+				outputs.put(OUT_STATUS, statusRef);
+
+				if (configBean.getShowActualUrlPort()) {
+					T2Reference completeURLRef = referenceService.register(completeURL, 0, true,
+							context);
+					outputs.put(OUT_COMPLETE_URL, completeURLRef);
+				}
+				if (configBean.getShowResponseHeadersPort())
+					outputs.put(OUT_RESPONSE_HEADERS, referenceService.register(
+							requestResponse.getHeadersAsStrings(), 1, true, context));
+
+				// only put an output to the Redirection port if the processor
+				// is configured to display that port
+				if (configBean.getShowRedirectionOutputPort()) {
+					T2Reference redirectionRef = referenceService.register(
+							requestResponse.getRedirectionURL(), 0, true, context);
+					outputs.put(OUT_REDIRECTION, redirectionRef);
+				}
+
+				// return map of output data, with empty index array as this is
+				// the only and final result (this index parameter is used if
+				// pipelining output)
+				callback.receiveResult(outputs, new int[0]);
+			}
+		});
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/RESTActivityConfigurationBean.java
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/RESTActivityConfigurationBean.java b/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/RESTActivityConfigurationBean.java
new file mode 100644
index 0000000..f52128b
--- /dev/null
+++ b/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/RESTActivityConfigurationBean.java
@@ -0,0 +1,325 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package org.apache.taverna.activities.rest;
+
+import static org.apache.taverna.activities.rest.RESTActivity.hasMessageBodyInputPort;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import org.apache.taverna.activities.rest.RESTActivity.DATA_FORMAT;
+import org.apache.taverna.activities.rest.RESTActivity.HTTP_METHOD;
+import net.sf.taverna.t2.workflowmodel.processor.config.ConfigurationBean;
+import net.sf.taverna.t2.workflowmodel.processor.config.ConfigurationProperty;
+
+/**
+ * Beans of this class store configuration information for REST activities.
+ * Configuration is comprised of the HTTP method to use, URL signature, and MIME
+ * types for Accept and Content-Type HTTP request headers. Additional value is
+ * used to record the format of outgoing data - binary or string. <br/>
+ * <br/>
+ * Also, derived attribute "activityInputs" is generated by identifying all
+ * "input ports" in the provided URL signature. <br/>
+ * <br/>
+ * Complete request URL (obtained by substituting values into the placeholders
+ * of the URL signature) is not stored, as it represents an instantiation of the
+ * activity invocation. The same applies for the input message body sent along
+ * with POST / PUT requests.
+ * 
+ * @author Sergejs Aleksejevs
+ */
+@SuppressWarnings("serial")
+@ConfigurationBean(uri = RESTActivity.URI + "#Config")
+public class RESTActivityConfigurationBean implements Serializable {
+	private static final List<String> knownHeaders = Arrays.asList("Accept",
+			"Content-Type", "Expect");
+	private RESTActivity.DATA_FORMAT outgoingDataFormat;
+
+	private boolean showRedirectionOutputPort;
+	private boolean showActualUrlPort;
+	private boolean showResponseHeadersPort;
+
+	// whether to perform URL escaping of passed parameters, true by default
+	private boolean escapeParameters = true;
+
+	// only need to store the configuration of inputs, as all of them are
+	// dynamic;
+	// only inputs that constitute components of URL signature are to be stored
+	// in this map all outputs are currently fixed, so no need to keep
+	// configuration of those
+	private Map<String, Class<?>> activityInputs;
+
+	private HTTPRequest request;
+
+	/**
+	 * @return An instance of the {@link RESTActivityConfigurationBean}
+	 *         pre-configured with default settings for all parameters.
+	 */
+	public static RESTActivityConfigurationBean getDefaultInstance() {
+		// TODO - set sensible default values here
+		RESTActivityConfigurationBean defaultBean = new RESTActivityConfigurationBean();
+		defaultBean.setRequest(new HTTPRequest());
+		defaultBean.setHttpMethod(RESTActivity.HTTP_METHOD.GET);
+		defaultBean.setAcceptsHeaderValue("application/xml");
+		defaultBean.setContentTypeForUpdates("application/xml");
+		defaultBean.setUrlSignature("http://www.uniprot.org/uniprot/{id}.xml");
+		defaultBean.setOutgoingDataFormat(RESTActivity.DATA_FORMAT.String);
+		// not ticked by default to allow to post to Twitter
+		defaultBean.setSendHTTPExpectRequestHeader(false);
+		// not showing the Redirection output port by default to make processor
+		// look simpler
+		defaultBean.setShowRedirectionOutputPort(false);
+		defaultBean.setShowActualUrlPort(false);
+		defaultBean.setShowResponseHeadersPort(false);
+		defaultBean.setEscapeParameters(true);
+		defaultBean.setOtherHTTPHeaders(new ArrayList<ArrayList<String>>());
+		return (defaultBean);
+	}
+
+	public RESTActivityConfigurationBean() {
+
+	}
+
+	public RESTActivityConfigurationBean(JsonNode json) {
+		JsonNode requestNode = json.get("request");
+		HTTPRequest request = new HTTPRequest();
+		if (requestNode.has("httpMethod")) {
+			request.setMethod(HTTP_METHOD.valueOf(requestNode.get("httpMethod")
+					.textValue()));
+		} else {
+			request.setMethod(HTTP_METHOD.GET);
+		}
+		request.setAbsoluteURITemplate(requestNode.get("absoluteURITemplate")
+				.textValue());
+		setRequest(request);
+		setAcceptsHeaderValue("application/xml");
+		setContentTypeForUpdates("application/xml");
+		setSendHTTPExpectRequestHeader(false);
+		if (requestNode.has("headers")) {
+			for (JsonNode headerNode : requestNode.get("headers")) {
+				String headerName = headerNode.get("header").textValue();
+				String headerValue = headerNode.get("value").textValue();
+				if ("Expect".equals(headerName)) {
+					request.setHeader(headerName, true);
+				} else {
+					request.setHeader(headerName, headerValue);
+				}
+			}
+		}
+		if (json.has("outgoingDataFormat")) {
+			setOutgoingDataFormat(DATA_FORMAT.valueOf(json.get(
+					"outgoingDataFormat").textValue()));
+		} else {
+			setOutgoingDataFormat(DATA_FORMAT.String);
+		}
+		if (json.has("showRedirectionOutputPort")) {
+			setShowRedirectionOutputPort(json.get("showRedirectionOutputPort")
+					.booleanValue());
+		} else {
+			setShowRedirectionOutputPort(false);
+		}
+		if (json.has("showActualURLPort")) {
+			setShowActualUrlPort(json.get("showActualURLPort").booleanValue());
+		} else {
+			setShowActualUrlPort(false);
+		}
+		if (json.has("showResponseHeadersPort")) {
+			setShowResponseHeadersPort(json.get("showResponseHeadersPort")
+					.booleanValue());
+		} else {
+			setShowResponseHeadersPort(false);
+		}
+		if (json.has("escapeParameters")) {
+			setEscapeParameters(json.get("escapeParameters").booleanValue());
+		} else {
+			setEscapeParameters(true);
+		}
+	}
+
+	/**
+	 * Tests validity of the configuration held in this bean. <br/>
+	 * <br/>
+	 * Performed tests are as follows: <br/>
+	 * * <code>httpMethod</code> is known to be valid - it's an enum; <br/>
+	 * * <code>urlSignature</code> - uses
+	 * {@link URISignatureHandler#isValid(String)} to test validity; <br/>
+	 * * <code>acceptsHeaderValue</code> and <code>contentTypeForUpdates</code>
+	 * must not be empty. <br/>
+	 * <br/>
+	 * <code>contentTypeForUpdates</code> is only checked if the
+	 * <code>httpMethod</code> is such that it is meant to use the Content-Type
+	 * header (that is POST / PUT only).
+	 * 
+	 * @return <code>true</code> if the configuration in the bean is valid;
+	 *         <code>false</code> otherwise.
+	 */
+	public boolean isValid() {
+		if (getUrlSignature() == null
+				|| !URISignatureHandler.isValid(getUrlSignature()))
+			return false;
+		return (hasMessageBodyInputPort(getHttpMethod())
+				&& getContentTypeForUpdates() != null
+				&& getContentTypeForUpdates().length() > 0 && outgoingDataFormat != null)
+				|| !hasMessageBodyInputPort(getHttpMethod());
+	}
+
+	public void setHttpMethod(RESTActivity.HTTP_METHOD httpMethod) {
+		request.setMethod(httpMethod);
+	}
+
+	public RESTActivity.HTTP_METHOD getHttpMethod() {
+		return request.getMethod();
+	}
+
+	public String getUrlSignature() {
+		return request.getAbsoluteURITemplate();
+	}
+
+	public void setUrlSignature(String urlSignature) {
+		request.setAbsoluteURITemplate(urlSignature);
+	}
+
+	public String getAcceptsHeaderValue() {
+		HTTPRequestHeader header = request.getHeader("Accept");
+		return header == null ? null : header.getFieldValue();
+	}
+
+	public void setAcceptsHeaderValue(String acceptsHeaderValue) {
+		request.setHeader("Accept", acceptsHeaderValue);
+	}
+
+	public String getContentTypeForUpdates() {
+		HTTPRequestHeader header = request.getHeader("Content-Type");
+		return header == null ? null : header.getFieldValue();
+	}
+
+	public void setContentTypeForUpdates(String contentTypeForUpdates) {
+		request.setHeader("Content-Type", contentTypeForUpdates);
+	}
+
+	public void setActivityInputs(Map<String, Class<?>> activityInputs) {
+		this.activityInputs = activityInputs;
+	}
+
+	public Map<String, Class<?>> getActivityInputs() {
+		return activityInputs;
+	}
+
+	public RESTActivity.DATA_FORMAT getOutgoingDataFormat() {
+		return outgoingDataFormat;
+	}
+
+	@ConfigurationProperty(name = "outgoingDataFormat", label = "Send data as", required = false)
+	public void setOutgoingDataFormat(
+			RESTActivity.DATA_FORMAT outgoingDataFormat) {
+		this.outgoingDataFormat = outgoingDataFormat;
+	}
+
+	public boolean getSendHTTPExpectRequestHeader() {
+		HTTPRequestHeader header = request.getHeader("Expect");
+		return header == null ? false : header.isUse100Continue();
+	}
+
+	public void setSendHTTPExpectRequestHeader(
+			boolean sendHTTPExpectRequestHeader) {
+		request.setHeader("Expect", sendHTTPExpectRequestHeader);
+	}
+
+	public boolean getShowRedirectionOutputPort() {
+		return showRedirectionOutputPort;
+	}
+
+	@ConfigurationProperty(name = "showRedirectionOutputPort", label = "Show 'Redirection' output port", required = false)
+	public void setShowRedirectionOutputPort(boolean showRedirectionOutputPort) {
+		this.showRedirectionOutputPort = showRedirectionOutputPort;
+	}
+
+	@ConfigurationProperty(name = "escapeParameters", label = "Escape URL parameter values", required = false)
+	public void setEscapeParameters(boolean escapeParameters) {
+		this.escapeParameters = Boolean.valueOf(escapeParameters);
+	}
+
+	public boolean getEscapeParameters() {
+		return escapeParameters;
+	}
+
+	public void setOtherHTTPHeaders(
+			ArrayList<ArrayList<String>> otherHTTPHeaders) {
+		for (ArrayList<String> otherHTTPHeader : otherHTTPHeaders)
+			request.setHeader(otherHTTPHeader.get(0), otherHTTPHeader.get(1));
+	}
+
+	public ArrayList<ArrayList<String>> getOtherHTTPHeaders() {
+		ArrayList<ArrayList<String>> otherHTTPHeaders = new ArrayList<>();
+		List<HTTPRequestHeader> headers = request.getHeaders();
+		for (HTTPRequestHeader header : headers)
+			if (!knownHeaders.contains(header.getFieldName())) {
+				ArrayList<String> nameValuePair = new ArrayList<>();
+				nameValuePair.add(header.getFieldName());
+				nameValuePair.add(header.getFieldValue());
+				otherHTTPHeaders.add(nameValuePair);
+			}
+		return otherHTTPHeaders;
+	}
+
+	/**
+	 * @return the showActualUrlPort
+	 */
+	public boolean getShowActualUrlPort() {
+		return showActualUrlPort;
+	}
+
+	/**
+	 * @param showActualUrlPort
+	 *            the showActualUrlPort to set
+	 */
+	public void setShowActualUrlPort(boolean showActualUrlPort) {
+		this.showActualUrlPort = showActualUrlPort;
+	}
+
+	/**
+	 * @return the showResponseHeadersPort
+	 */
+	public boolean getShowResponseHeadersPort() {
+		return showResponseHeadersPort;
+	}
+
+	/**
+	 * @param showResponseHeadersPort
+	 *            the showResponseHeadersPort to set
+	 */
+	public void setShowResponseHeadersPort(boolean showResponseHeadersPort) {
+		this.showResponseHeadersPort = showResponseHeadersPort;
+	}
+
+	public HTTPRequest getRequest() {
+		return request;
+	}
+
+	@ConfigurationProperty(name = "request", label = "HTTP Request")
+	public void setRequest(HTTPRequest request) {
+		this.request = request;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/433612be/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/RESTActivityCredentialsProvider.java
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/RESTActivityCredentialsProvider.java b/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/RESTActivityCredentialsProvider.java
new file mode 100644
index 0000000..1e019af
--- /dev/null
+++ b/taverna-rest-activity/src/main/java/org/apache/taverna/activities/rest/RESTActivityCredentialsProvider.java
@@ -0,0 +1,197 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package org.apache.taverna.activities.rest;
+
+import java.net.URI;
+import java.net.URLEncoder;
+import java.security.Principal;
+
+import javax.management.remote.JMXPrincipal;
+
+import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
+import net.sf.taverna.t2.security.credentialmanager.UsernamePassword;
+
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+//import org.apache.http.client.CredentialsProvider;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.log4j.Logger;
+
+/**
+ * This CredentialsProvider acts as a mediator between the Apache HttpClient and
+ * Taverna's CredentialManager that stores all user's credentials.
+ *
+ * The only role of it is to retrieve stored details from CredentialManager when
+ * they are required for HTTP authentication.
+ *
+ * @author Sergejs Aleksejevs
+ * @author Alex Nenadic
+ */
+public class RESTActivityCredentialsProvider extends BasicCredentialsProvider {
+	private static Logger logger = Logger.getLogger(RESTActivityCredentialsProvider.class);
+	
+	private static final int DEFAULT_HTTP_PORT = 80;
+	private static final int DEFAULT_HTTPS_PORT = 443;
+	
+	private static final String HTTP_PROTOCOL = "http";
+	private static final String HTTPS_PROTOCOL = "https";
+	
+	private CredentialManager credentialManager;
+	
+	public RESTActivityCredentialsProvider(CredentialManager credentialManager) {
+		this.credentialManager = credentialManager;
+	}
+	
+	@Override
+	public Credentials getCredentials(AuthScope authscope) {
+		logger.info("Looking for credentials for: Host - " + authscope.getHost() + ";" + "Port - "
+				+ authscope.getPort() + ";" + "Realm - " + authscope.getRealm() + ";"
+				+ "Authentication scheme - " + authscope.getScheme());
+		
+		// Ask the superclass first
+		Credentials creds = super.getCredentials(authscope);
+		if (creds != null) {
+			/*
+			 * We have used setCredentials() on this class (for proxy host,
+			 * port, username,password) just before we invoked the http request,
+			 * which will then pick the proxy credentials up from here.
+			 */
+			return creds;
+		}
+		
+		// Otherwise, ask Credential Manager if is can provide the credential
+		String AUTHENTICATION_REQUEST_MSG = "This REST service requires authentication in "
+				+ authscope.getRealm();
+
+		try {
+			UsernamePassword credentials = null;
+
+			/*
+			 * if port is 80 - use HTTP, don't append port if port is 443 - use
+			 * HTTPS, don't append port any other port - append port + do 2
+			 * tests:
+			 * 
+			 * --- test HTTPS first has...()
+			 * --- if not there, do get...() for HTTP (which will save the thing)
+			 *
+			 * (save both these entries for HTTP + HTTPS if not there)
+			 */
+
+			// build the service URI back to front
+			StringBuilder serviceURI = new StringBuilder();
+			serviceURI.insert(0, "/#" + URLEncoder.encode(authscope.getRealm(), "UTF-16"));
+			if (authscope.getPort() != DEFAULT_HTTP_PORT
+					&& authscope.getPort() != DEFAULT_HTTPS_PORT) {
+				// non-default port - add port name to the URI
+				serviceURI.insert(0, ":" + authscope.getPort());
+			}
+			serviceURI.insert(0, authscope.getHost());
+			serviceURI.insert(0, "://");
+
+			// now the URI is complete, apart from the protocol name
+			if (authscope.getPort() == DEFAULT_HTTP_PORT
+					|| authscope.getPort() == DEFAULT_HTTPS_PORT) {
+				// definitely HTTP or HTTPS
+				serviceURI.insert(0, (authscope.getPort() == DEFAULT_HTTP_PORT ? HTTP_PROTOCOL
+						: HTTPS_PROTOCOL));
+
+				// request credentials from CrendentialManager
+				credentials = credentialManager.getUsernameAndPasswordForService(
+						URI.create(serviceURI.toString()), true, AUTHENTICATION_REQUEST_MSG);
+			} else {
+				/*
+				 * non-default port - will need to try both HTTP and HTTPS; just
+				 * check (no pop-up will be shown) if credentials are there -
+				 * one protocol that matched will be used; if
+				 */
+				if (credentialManager.hasUsernamePasswordForService(URI.create(HTTPS_PROTOCOL
+						+ serviceURI.toString()))) {
+					credentials = credentialManager.getUsernameAndPasswordForService(
+							URI.create(HTTPS_PROTOCOL + serviceURI.toString()), true,
+							AUTHENTICATION_REQUEST_MSG);
+				} else if (credentialManager.hasUsernamePasswordForService(URI.create(HTTP_PROTOCOL
+						+ serviceURI.toString()))) {
+					credentials = credentialManager.getUsernameAndPasswordForService(
+							URI.create(HTTP_PROTOCOL + serviceURI.toString()), true,
+							AUTHENTICATION_REQUEST_MSG);
+				} else {
+					/*
+					 * Neither of the two options succeeded, request details with a
+					 * popup for HTTP...
+					 */
+					credentials = credentialManager.getUsernameAndPasswordForService(
+							URI.create(HTTP_PROTOCOL + serviceURI.toString()), true,
+							AUTHENTICATION_REQUEST_MSG);
+
+					/*
+					 * ...then save a second entry with HTTPS protocol (if the
+					 * user has chosen to save the credentials)
+					 */
+					if (credentials != null && credentials.isShouldSave()) {
+						credentialManager.addUsernameAndPasswordForService(credentials,
+								URI.create(HTTPS_PROTOCOL + serviceURI.toString()));
+					}
+				}
+			}
+
+			if (credentials != null) {
+				logger.info("Credentials obtained successfully");
+				return new RESTActivityCredentials(credentials.getUsername(),
+						credentials.getPasswordAsString());
+			}
+		} catch (Exception e) {
+			logger.error(
+					"Unexpected error while trying to obtain user's credential from CredentialManager",
+					e);
+		}
+
+		// error or nothing was found
+		logger.info("Credentials not found - the user must have refused to enter them.");
+		return null;
+	}
+
+	/**
+	 * This class encapsulates user's credentials that this CredentialsProvider
+	 * can pass to Apache HttpClient.
+	 *
+	 * @author Sergejs Aleksejevs
+	 */
+	public class RESTActivityCredentials implements Credentials {
+		// this seems to be the simplest existing standard implementation of
+		// Principal interface
+		private final JMXPrincipal user;
+		private final String password;
+
+		public RESTActivityCredentials(String username, String password) {
+			this.user = new JMXPrincipal(username);
+			this.password = password;
+		}
+
+		@Override
+		public String getPassword() {
+			return password;
+		}
+
+		@Override
+		public Principal getUserPrincipal() {
+			return user;
+		}
+	}
+}