You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/02/23 11:17:03 UTC

[22/28] incubator-taverna-common-activities git commit: Revert "temporarily empty repository"

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/390c286b/taverna-interaction-activity/src/main/resources/pmrpc.js
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/resources/pmrpc.js b/taverna-interaction-activity/src/main/resources/pmrpc.js
new file mode 100644
index 0000000..0edc8cf
--- /dev/null
+++ b/taverna-interaction-activity/src/main/resources/pmrpc.js
@@ -0,0 +1,686 @@
+/*
+ * pmrpc 0.6 - Inter-widget remote procedure call library based on HTML5 
+ *             postMessage API and JSON-RPC. https://github.com/izuzak/pmrpc
+ *
+ * Copyright 2011 Ivan Zuzak, Marko Ivankovic
+ *
+ * Licensed 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.
+ */
+
+pmrpc = self.pmrpc =  function() {
+  // check if JSON library is available
+  if (typeof JSON === "undefined" || typeof JSON.stringify === "undefined" || 
+      typeof JSON.parse === "undefined") {
+    throw "pmrpc requires the JSON library";
+  }
+  
+  // TODO: make "contextType" private variable
+  // check if postMessage APIs are available
+  if (typeof this.postMessage === "undefined" &&  // window or worker
+        typeof this.onconnect === "undefined") {  // shared worker
+      throw "pmrpc requires the HTML5 cross-document messaging and worker APIs";
+  }
+    
+  // Generates a version 4 UUID
+  function generateUUID() {
+    var uuid = [], nineteen = "89AB", hex = "0123456789ABCDEF";
+    for (var i=0; i<36; i++) {
+      uuid[i] = hex[Math.floor(Math.random() * 16)];
+    }
+    uuid[14] = '4';
+    uuid[19] = nineteen[Math.floor(Math.random() * 4)];
+    uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
+    return uuid.join('');
+  }
+  
+  // TODO: remove this - make everything a regex?
+  // Converts a wildcard expression into a regular expression
+  function convertWildcardToRegex(wildcardExpression) {
+    var regex = wildcardExpression.replace(
+                  /([\^\$\.\+\?\=\!\:\|\\\/\(\)\[\]\{\}])/g, "\\$1");
+    regex = "^" + regex.replace(/\*/g, ".*") + "$";
+    return regex;
+  }
+  
+  // Checks whether a domain satisfies the access control list. The access 
+  // control list has a whitelist and a blacklist. In order to satisfy the acl, 
+  // the domain must be on the whitelist, and must not be on the blacklist.
+  function checkACL(accessControlList, origin) {
+    var aclWhitelist = accessControlList.whitelist;
+    var aclBlacklist = accessControlList.blacklist;
+      
+    var isWhitelisted = false;
+    var isBlacklisted = false;
+    
+    for (var i=0; i<aclWhitelist.length; ++i) {
+      var aclRegex = convertWildcardToRegex(aclWhitelist[i]);
+      if(origin.match(aclRegex)) {
+        isWhitelisted = true;
+        break;
+      }
+    }
+     
+    for (var j=0; i<aclBlacklist.length; ++j) {
+      var aclRegex = convertWildcardToRegex(aclBlacklist[j]);
+      if(origin.match(aclRegex)) {
+        isBlacklisted = true;
+        break;
+      }
+    }
+    
+    return isWhitelisted && !isBlacklisted;
+  }
+  
+  // Calls a function with either positional or named parameters
+  // In either case, additionalParams will be appended to the end
+  function invokeProcedure(fn, self, params, additionalParams) {
+    if (!(params instanceof Array)) {
+      // get string representation of function
+      var fnDef = fn.toString();
+      
+      // parse the string representation and retrieve order of parameters
+      var argNames = fnDef.substring(fnDef.indexOf("(")+1, fnDef.indexOf(")"));
+      argNames = (argNames === "") ? [] : argNames.split(", ");
+      
+      var argIndexes = {};
+      for (var i=0; i<argNames.length; i++) {
+        argIndexes[argNames[i]] = i;
+      }
+      
+      // construct an array of arguments from a dictionary
+      var callParameters = [];
+      for (var paramName in params) {
+        if (typeof argIndexes[paramName] !== "undefined") {
+          callParameters[argIndexes[paramName]] = params[paramName];
+        } else {
+          throw "No such param!";
+        }
+      }
+      
+      params = callParameters;
+    }
+    
+    // append additional parameters
+    if (typeof additionalParams !== "undefined") {
+      params = params.concat(additionalParams);
+    }
+    
+    // invoke function with specified context and arguments array
+    return fn.apply(self, params);
+  }
+  
+  // JSON encode an object into pmrpc message
+  function encode(obj) {
+    return "pmrpc." + JSON.stringify(obj);
+  }
+  
+  // JSON decode a pmrpc message
+  function decode(str) {
+    return JSON.parse(str.substring("pmrpc.".length));
+  }
+
+  // Creates a base JSON-RPC object, usable for both request and response.
+  // As of JSON-RPC 2.0 it only contains one field "jsonrpc" with value "2.0"
+  function createJSONRpcBaseObject() {
+    var call = {};
+    call.jsonrpc = "2.0";
+    return call;
+  }
+
+  // Creates a JSON-RPC request object for the given method and parameters
+  function createJSONRpcRequestObject(procedureName, parameters, id) {
+    var call = createJSONRpcBaseObject();
+    call.method = procedureName;
+    call.params = parameters;
+    if (typeof id !== "undefined") {
+      call.id = id;
+    }
+    return call;
+  }
+
+  // Creates a JSON-RPC error object complete with message and error code
+  function createJSONRpcErrorObject(errorcode, message, data) {
+    var error = {};
+    error.code = errorcode;
+    error.message = message;
+    error.data = data;
+    return error;
+  }
+
+  // Creates a JSON-RPC response object.
+  function createJSONRpcResponseObject(error, result, id) {
+    var response = createJSONRpcBaseObject();
+    response.id = id;
+    
+    if (typeof error === "undefined" || error === null) {
+      response.result = (result === "undefined") ? null : result;
+    } else {
+      response.error = error;
+    }
+    
+    return response;
+  }
+
+  // dictionary of services registered for remote calls
+  var registeredServices = {};
+  // dictionary of requests being processed on the client side
+  var callQueue = {};
+  
+  var reservedProcedureNames = {};
+  // register a service available for remote calls
+  // if no acl is given, assume that it is available to everyone
+  function register(config) {
+    if (config.publicProcedureName in reservedProcedureNames) {
+      return false;
+    } else {
+      registeredServices[config.publicProcedureName] = {
+        "publicProcedureName" : config.publicProcedureName,
+        "procedure" : config.procedure,
+        "context" : config.procedure.context,
+        "isAsync" : typeof config.isAsynchronous !== "undefined" ?
+                      config.isAsynchronous : false,
+        "acl" : typeof config.acl !== "undefined" ? 
+                  config.acl : {whitelist: ["*"], blacklist: []}};
+      return true;
+    }
+  }
+
+  // unregister a previously registered procedure
+  function unregister(publicProcedureName) {
+    if (publicProcedureName in reservedProcedureNames) {
+      return false;
+    } else {
+      delete registeredServices[publicProcedureName];
+      return true;
+    }
+  }
+
+  // retreive service for a specific procedure name
+  function fetchRegisteredService(publicProcedureName){
+    return registeredServices[publicProcedureName];
+  }
+  
+  // receive and execute a pmrpc call which may be a request or a response
+  function processPmrpcMessage(eventParams) {
+    var serviceCallEvent = eventParams.event;
+    var eventSource = eventParams.source;
+    var isWorkerComm = typeof eventSource !== "undefined" && eventSource !== null;
+    
+    // if the message is not for pmrpc, ignore it.
+    if (serviceCallEvent.data.indexOf("pmrpc.") !== 0) {
+      return;
+    } else {
+      var message = decode(serviceCallEvent.data);
+      //if (typeof console !== "undefined" && console.log !== "undefined" && (typeof this.frames !== "undefined")) { console.log("Received:" + encode(message)); }
+      if (typeof message.method !== "undefined") {
+        // this is a request
+        
+        // ako je 
+        var newServiceCallEvent = {
+          data : serviceCallEvent.data,
+          source : isWorkerComm ? eventSource : serviceCallEvent.source,
+          origin : isWorkerComm ? "*" : serviceCallEvent.origin,
+          shouldCheckACL : !isWorkerComm
+        };
+        
+        response = processJSONRpcRequest(message, newServiceCallEvent);
+        
+        // return the response
+        if (response !== null) {
+          sendPmrpcMessage(
+            newServiceCallEvent.source, response, newServiceCallEvent.origin);
+        }
+      } else {
+        // this is a response
+        processJSONRpcResponse(message);
+      }
+    }
+  }
+  
+  // Process a single JSON-RPC Request
+  function processJSONRpcRequest(request, serviceCallEvent, shouldCheckACL) {
+    if (request.jsonrpc !== "2.0") {
+      // Invalid JSON-RPC request    
+      return createJSONRpcResponseObject(
+        createJSONRpcErrorObject(-32600, "Invalid request.", 
+          "The recived JSON is not a valid JSON-RPC 2.0 request."),
+        null,
+        null);
+    }
+    
+    var id = request.id;
+    var service = fetchRegisteredService(request.method);
+    
+    if (typeof service !== "undefined") {
+      // check the acl rights
+      if (!serviceCallEvent.shouldCheckACL || 
+            checkACL(service.acl, serviceCallEvent.origin)) {
+        try {
+          if (service.isAsync) {
+            // if the service is async, create a callback which the service
+            // must call in order to send a response back
+            var cb = function (returnValue) {
+                       sendPmrpcMessage(
+                         serviceCallEvent.source,
+                         createJSONRpcResponseObject(null, returnValue, id),
+                         serviceCallEvent.origin);
+                     };
+            invokeProcedure(
+              service.procedure, service.context, request.params, [cb, serviceCallEvent]);
+            return null;
+          } else {
+            // if the service is not async, just call it and return the value
+            var returnValue = invokeProcedure(
+                                service.procedure,
+                                service.context, 
+                                request.params, [serviceCallEvent]);
+            return (typeof id === "undefined") ? null : 
+              createJSONRpcResponseObject(null, returnValue, id);
+          }
+        } catch (error) {
+          if (typeof id === "undefined") {
+            // it was a notification nobody cares if it fails
+            return null;
+          }
+          
+          if (error === "No such param!") {
+            return createJSONRpcResponseObject(
+              createJSONRpcErrorObject(
+                -32602, "Invalid params.", error.description),
+              null,
+              id);            
+          }            
+          
+          // the -1 value is "application defined"
+          return createJSONRpcResponseObject(
+            createJSONRpcErrorObject(
+              -1, "Application error.", error.description),
+            null,
+            id);
+        }
+      } else {
+        // access denied
+        return (typeof id === "undefined") ? null : createJSONRpcResponseObject(
+          createJSONRpcErrorObject(
+            -2, "Application error.", "Access denied on server."),
+          null,
+          id);
+      }
+    } else {
+      // No such method
+      return (typeof id === "undefined") ? null : createJSONRpcResponseObject(
+        createJSONRpcErrorObject(
+          -32601,
+          "Method not found.", 
+          "The requestd remote procedure does not exist or is not available."),
+        null,
+        id);
+    }
+  }
+  
+  // internal rpc service that receives responses for rpc calls 
+  function processJSONRpcResponse(response) {
+    var id = response.id;
+    var callObj = callQueue[id];
+    if (typeof callObj === "undefined" || callObj === null) {
+      return;
+    } else {
+      delete callQueue[id];
+    }
+    
+    // check if the call was sucessful or not
+    if (typeof response.error === "undefined") {
+      callObj.onSuccess( { 
+        "destination" : callObj.destination,
+        "publicProcedureName" : callObj.publicProcedureName,
+        "params" : callObj.params,
+        "status" : "success",
+        "returnValue" : response.result} );
+    } else {
+      callObj.onError( { 
+        "destination" : callObj.destination,
+        "publicProcedureName" : callObj.publicProcedureName,
+        "params" : callObj.params,
+        "status" : "error",
+        "description" : response.error.message + " " + response.error.data} );
+    }
+  }
+  
+  // call remote procedure
+  function call(config) {
+    // check that number of retries is not -1, that is a special internal value
+    if (config.retries && config.retries < 0) {
+      throw new Exception("number of retries must be 0 or higher");
+    }
+    
+    var destContexts = [];
+    
+    if (typeof config.destination === "undefined" || config.destination === null || config.destination === "workerParent") {
+      destContexts = [{context : null, type : "workerParent"}];
+    } else if (config.destination === "publish") {
+      destContexts = findAllReachableContexts();
+    } else if (config.destination instanceof Array) {
+      for (var i=0; i<config.destination.length; i++) {
+        if (config.destination[i] === "workerParent") {
+          destContexts.push({context : null, type : "workerParent"});
+        } else if (typeof config.destination[i].frames !== "undefined") {
+          destContexts.push({context : config.destination[i], type : "window"});
+        } else {
+          destContexts.push({context : config.destination[i], type : "worker"});
+        }
+      }
+    } else {
+      if (typeof config.destination.frames !== "undefined") {
+        destContexts.push({context : config.destination, type : "window"});
+      } else {
+        destContexts.push({context : config.destination, type : "worker"});
+      }
+    }
+        
+    for (var i=0; i<destContexts.length; i++) {
+      var callObj = {
+        destination : destContexts[i].context,
+        destinationDomain : typeof config.destinationDomain === "undefined" ? ["*"] : (typeof config.destinationDomain === "string" ? [config.destinationDomain] : config.destinationDomain),
+        publicProcedureName : config.publicProcedureName,
+        onSuccess : typeof config.onSuccess !== "undefined" ? 
+                      config.onSuccess : function (){},
+        onError : typeof config.onError !== "undefined" ? 
+                      config.onError : function (){},
+        retries : typeof config.retries !== "undefined" ? config.retries : 5,
+        timeout : typeof config.timeout !== "undefined" ? config.timeout : 500,
+        status : "requestNotSent"
+      };
+      
+      isNotification = typeof config.onError === "undefined" && typeof config.onSuccess === "undefined";
+      params = (typeof config.params !== "undefined") ? config.params : [];
+      callId = generateUUID();
+      callQueue[callId] = callObj; 
+      
+      if (isNotification) {
+        callObj.message = createJSONRpcRequestObject(
+                    config.publicProcedureName, params);
+      } else {
+        callObj.message = createJSONRpcRequestObject(
+                            config.publicProcedureName, params, callId);
+      }
+      
+      waitAndSendRequest(callId);
+    }
+  }
+  
+  // Use the postMessage API to send a pmrpc message to a destination
+  function sendPmrpcMessage(destination, message, acl) {
+    //if (typeof console !== "undefined" && console.log !== "undefined" && (typeof this.frames !== "undefined")) { console.log("Sending:" + encode(message)); }
+    if (typeof destination === "undefined" || destination === null) {
+      self.postMessage(encode(message));
+    } else if (typeof destination.frames !== "undefined") {
+      return destination.postMessage(encode(message), acl);
+    } else {
+      destination.postMessage(encode(message));
+    }
+  }
+    
+  // Execute a remote call by first pinging the destination and afterwards
+  // sending the request
+  function waitAndSendRequest(callId) {
+    var callObj = callQueue[callId];
+    if (typeof callObj === "undefined") {
+      return;
+    } else if (callObj.retries <= -1) {      
+      processJSONRpcResponse(
+        createJSONRpcResponseObject(
+          createJSONRpcErrorObject(
+          -4, "Application error.", "Destination unavailable."),
+          null,
+          callId));
+    } else if (callObj.status === "requestSent") {
+      return;
+    } else if (callObj.retries === 0 || callObj.status === "available") {
+      callObj.status = "requestSent";
+      callObj.retries = -1;
+      callQueue[callId] = callObj;
+      for (var i=0; i<callObj.destinationDomain.length; i++) {
+        sendPmrpcMessage(
+          callObj.destination, callObj.message, callObj.destinationDomain[i], callObj);
+        self.setTimeout(function() { waitAndSendRequest(callId); }, callObj.timeout);
+      }
+    } else {
+      // if we can ping some more - send a new ping request
+      callObj.status = "pinging";
+      callObj.retries = callObj.retries - 1;
+      
+      call({
+        "destination" : callObj.destination,
+        "publicProcedureName" : "receivePingRequest",
+        "onSuccess" : function (callResult) {
+                        if (callResult.returnValue === true &&
+                            typeof callQueue[callId] !== 'undefined') {
+                          callQueue[callId].status = "available";
+                          waitAndSendRequest(callId);
+                        }
+                      },
+        "params" : [callObj.publicProcedureName], 
+        "retries" : 0,
+        "destinationDomain" : callObj.destinationDomain});
+      callQueue[callId] = callObj;
+      self.setTimeout(function() { waitAndSendRequest(callId); }, callObj.timeout / callObj.retries);
+    }
+  }
+  
+  // attach the pmrpc event listener 
+  function addCrossBrowserEventListerner(obj, eventName, handler, bubble) {
+    if ("addEventListener" in obj) {
+      // FF
+      obj.addEventListener(eventName, handler, bubble);
+    } else {
+      // IE
+      obj.attachEvent("on" + eventName, handler);
+    }
+  }
+  
+  function createHandler(method, source, destinationType) {
+    return function(event) {
+      var params = {event : event, source : source, destinationType : destinationType};
+      method(params);
+    };
+  }
+  
+  if ('window' in this) {
+    // window object - window-to-window comm
+    var handler = createHandler(processPmrpcMessage, null, "window");
+    addCrossBrowserEventListerner(this, "message", handler, false);
+  } else if ('onmessage' in this) {
+    // dedicated worker - parent X to worker comm
+    var handler = createHandler(processPmrpcMessage, this, "worker");
+    addCrossBrowserEventListerner(this, "message", handler, false);
+  } else if ('onconnect' in this) {
+    // shared worker - parent X to shared-worker comm
+    var connectHandler = function(e) {
+      //this.sendPort = e.ports[0];
+      var handler = createHandler(processPmrpcMessage, e.ports[0], "sharedWorker");      
+      addCrossBrowserEventListerner(e.ports[0], "message", handler, false);
+      e.ports[0].start();
+    };
+    addCrossBrowserEventListerner(this, "connect", connectHandler, false);
+  } else {
+    throw "Pmrpc must be loaded within a browser window or web worker.";
+  }
+  
+  // Override Worker and SharedWorker constructors so that pmrpc may relay
+  // messages. For each message received from the worker, call pmrpc processing
+  // method. This is child worker to parent communication.
+    
+  var createDedicatedWorker = this.Worker;
+  this.nonPmrpcWorker = createDedicatedWorker;
+  var createSharedWorker = this.SharedWorker;
+  this.nonPmrpcSharedWorker = createSharedWorker;
+  
+  var allWorkers = [];
+  
+  this.Worker = function(scriptUri) {
+    var newWorker = new createDedicatedWorker(scriptUri);
+    allWorkers.push({context : newWorker, type : 'worker'});   
+    var handler = createHandler(processPmrpcMessage, newWorker, "worker");
+    addCrossBrowserEventListerner(newWorker, "message", handler, false);
+    return newWorker;
+  };
+  
+  this.SharedWorker = function(scriptUri, workerName) {
+    var newWorker = new createSharedWorker(scriptUri, workerName);
+    allWorkers.push({context : newWorker, type : 'sharedWorker'});    
+    var handler = createHandler(processPmrpcMessage, newWorker.port, "sharedWorker");
+    addCrossBrowserEventListerner(newWorker.port, "message", handler, false);
+    newWorker.postMessage = function (msg, portArray) {
+      return newWorker.port.postMessage(msg, portArray);
+    };
+    newWorker.port.start();
+    return newWorker;
+  };
+  
+  // function that receives pings for methods and returns responses 
+  function receivePingRequest(publicProcedureName) {
+    return typeof fetchRegisteredService(publicProcedureName) !== "undefined";
+  }
+  
+  function subscribe(params) {
+    return register(params);
+  }
+  
+  function unsubscribe(params) {
+    return unregister(params);
+  }
+  
+  function findAllWindows() {
+    var allWindowContexts = [];
+    
+    if (typeof window !== 'undefined') {
+      allWindowContexts.push( { context : window.top, type : 'window' } );
+      
+      // walk through all iframes, starting with window.top
+      for (var i=0; typeof allWindowContexts[i] !== 'undefined'; i++) {
+        var currentWindow = allWindowContexts[i];
+        for (var j=0; j<currentWindow.context.frames.length; j++) {
+          allWindowContexts.push({ 
+            context : currentWindow.context.frames[j],
+            type : 'window'
+          });
+        }
+      }
+    } else {
+      allWindowContexts.push( {context : this, type : 'workerParent'} );
+    }
+    
+    return allWindowContexts;
+  }
+  
+  function findAllWorkers() {
+    return allWorkers;
+  }
+  
+  function findAllReachableContexts() {
+    var allWindows = findAllWindows();
+    var allWorkers = findAllWorkers();
+    var allContexts = allWindows.concat(allWorkers);
+    
+    return allContexts;
+  }
+  
+  // register method for receiving and returning pings
+  register({
+    "publicProcedureName" : "receivePingRequest",
+    "procedure" : receivePingRequest});
+  
+  function getRegisteredProcedures() {
+    var regSvcs = [];
+    var origin = typeof this.frames !== "undefined" ? (window.location.protocol + "//" + window.location.host + (window.location.port !== "" ? ":" + window.location.port : "")) : "";
+    for (publicProcedureName in registeredServices) {
+      if (publicProcedureName in reservedProcedureNames) {
+        continue;
+      } else {
+        regSvcs.push( { 
+          "publicProcedureName" : registeredServices[publicProcedureName].publicProcedureName,
+          "acl" : registeredServices[publicProcedureName].acl,
+          "origin" : origin
+        } );
+      }
+    }
+    return regSvcs;
+  }
+  
+  // register method for returning registered procedures
+  register({
+    "publicProcedureName" : "getRegisteredProcedures",
+    "procedure" : getRegisteredProcedures});
+    
+  function discover(params) {
+    var windowsForDiscovery = null;
+    
+    if (typeof params.destination === "undefined") {
+      windowsForDiscovery = findAllReachableContexts();
+      for (var i=0; i<windowsForDiscovery.length; i++) {
+        windowsForDiscovery[i] = windowsForDiscovery[i].context;
+      }
+    } else {
+      windowsForDiscovery = params.destination;
+    }
+    var originRegex = typeof params.origin === "undefined" ?
+      ".*" : params.origin; 
+    var nameRegex = typeof params.publicProcedureName === "undefined" ?
+      ".*" : params.publicProcedureName;
+    
+    var counter = windowsForDiscovery.length;
+    
+    var discoveredMethods = [];
+    function addToDiscoveredMethods(methods, destination) {
+      for (var i=0; i<methods.length; i++) {
+        if (methods[i].origin.match(originRegex) && methods[i].publicProcedureName.match(nameRegex)) {
+          discoveredMethods.push({
+            publicProcedureName : methods[i].publicProcedureName,
+            destination : destination,
+            procedureACL : methods[i].acl,
+            destinationOrigin : methods[i].origin
+          });
+        }
+      }
+    }
+    
+    pmrpc.call({
+      destination : windowsForDiscovery,
+      destinationDomain : "*",
+      publicProcedureName : "getRegisteredProcedures",
+      onSuccess : function (callResult) {
+                    counter--;
+                    addToDiscoveredMethods(callResult.returnValue, callResult.destination);
+                    if (counter === 0) {
+                      params.callback(discoveredMethods);
+                    }
+                  },
+      onError : function (callResult) {
+                  counter--;
+                  if (counter === 0) {
+                    params.callback(discoveredMethods);
+                  }
+                }
+    });
+  }
+  
+  reservedProcedureNames = {"getRegisteredProcedures" : null, "receivePingRequest" : null};
+  
+  // return public methods
+  return {
+    register : register,
+    unregister : unregister,
+    call : call,
+    discover : discover
+  };
+}();

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/390c286b/taverna-interaction-activity/src/main/resources/schema.json
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/resources/schema.json b/taverna-interaction-activity/src/main/resources/schema.json
new file mode 100644
index 0000000..dcde96e
--- /dev/null
+++ b/taverna-interaction-activity/src/main/resources/schema.json
@@ -0,0 +1,31 @@
+{
+	"$schema": "http://json-schema.org/draft-03/schema#",
+    "id": "http://ns.taverna.org.uk/2010/activity/interaction.schema.json",
+    "title": Interaction activity configuration",
+    "type": "object",
+    "properties": {
+        "@context": {
+            "description": "JSON-LD context for interpreting the configuration as RDF",
+            "required": true,
+            "enum": ["http://ns.taverna.org.uk/2010/activity/interaction.context.json"]
+        },
+        "presentationOrigin": {
+        	"type": "string",
+        	"required": true,
+        	"minLength": 1,
+        	"description": "The URL of the presentation page, or the identifier of the standard template"
+        },
+        "interactionActivityType": {
+        	"type": "string",
+        	"required": true,
+        	"minLength": 1,
+		"enum" : [ "VelocityTemplate", "LocallyPresentedHtml"],
+        	"description": "Indication of the type of the definition for the interaction"
+        },
+        "progressNotification": {
+        	"type": "boolean",
+        	"required": true,
+        	"description": "True if the interaction should not block the workflow run"
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/390c286b/taverna-interaction-activity/src/main/resources/select.vm
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/resources/select.vm b/taverna-interaction-activity/src/main/resources/select.vm
new file mode 100644
index 0000000..6fe5764
--- /dev/null
+++ b/taverna-interaction-activity/src/main/resources/select.vm
@@ -0,0 +1,61 @@
+#require("valueList",1)
+#require("message")
+#require("title")
+#produce("answer")
+<!doctype html>
+<html>
+  <head>
+      <meta charset="utf-8" />
+      <title></title>
+      <style>
+      </style>
+  </head>
+  <body>
+
+       <script type="text/javascript" src="$pmrpcUrl"></script>
+
+       <script type="text/javascript">
+
+         function reply() {
+           pmrpc.call({
+             destination : "publish",
+             publicProcedureName : "reply",
+             params : ["OK", {"answer" : document.myform.mySelect.options[document.myform.mySelect.selectedIndex].value}],
+             onSuccess : function() {document.getElementsByTagName('body')[0].innerHTML='<h1>Data submitted</h1>';},
+             onFailure: function() {document.getElementsByTagName('body')[0].innerHTML='<h1>Data submission failed</h1>';}
+           });
+	       return true;
+         }
+        
+         function cancel() {
+           pmrpc.call({
+             destination : "publish",
+             publicProcedureName : "reply",
+             params : ["Cancelled", {}],
+             onSuccess : function() {document.getElementsByTagName('body')[0].innerHTML='<h1>Cancelled</h1>';},
+             onFailure: function() {document.getElementsByTagName('body')[0].innerHTML='<h1>Cancellation failed</h1>';}
+           });
+	       return true;
+         }
+         
+         pmrpc.call({
+           destination : "publish",
+           publicProcedureName : "setTitle",
+           params : ["$!title"]});
+
+       </script>
+  
+  <h2>$!message</h2>
+    <form name="myform" onSubmit="reply(); return false;">
+      <select name="mySelect">
+#foreach( $value in $valueList )
+      <option value="$value">$value</option>
+#end
+      </select>
+      <br/>
+      <input type="button" value="Cancel" onClick = "cancel()"/>
+      <input type="button" value="Submit" onClick = "reply()"/>
+    </form> 
+  </body>
+</html>
+

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/390c286b/taverna-interaction-activity/src/main/resources/tell.vm
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/resources/tell.vm b/taverna-interaction-activity/src/main/resources/tell.vm
new file mode 100644
index 0000000..948c023
--- /dev/null
+++ b/taverna-interaction-activity/src/main/resources/tell.vm
@@ -0,0 +1,54 @@
+#require("message")
+#require("title")
+#produce("answer")
+<!doctype html>
+<html>
+  <head>
+      <meta charset="utf-8" />
+      <title></title>
+      <style>
+      </style>
+  </head>
+  <body>
+
+       <script type="text/javascript" src="$pmrpcUrl"></script>
+
+       <script type="text/javascript">
+
+         function reply() {
+           pmrpc.call({
+             destination : "publish",
+             publicProcedureName : "reply",
+             params : ["OK", {"answer" : "answer"}],
+             onSuccess : function() {document.getElementsByTagName('body')[0].innerHTML='<h1>Data submitted</h1>';},
+             onFailure: function() {document.getElementsByTagName('body')[0].innerHTML='<h1>Data submission failed</h1>';}
+           });
+	       return true;
+         }
+         
+         function cancel() {
+           pmrpc.call({
+             destination : "publish",
+             publicProcedureName : "reply",
+             params : ["Cancelled", {}],
+             onSuccess : function() {document.getElementsByTagName('body')[0].innerHTML='<h1>Cancelled</h1>';},
+             onFailure: function() {document.getElementsByTagName('body')[0].innerHTML='<h1>Cancellation failed</h1>';}
+           });
+	       return true;
+         }
+         
+         pmrpc.call({
+           destination : "publish",
+           publicProcedureName : "setTitle",
+           params : ["$!title"]});
+
+       </script>
+  
+  <h2>Message: $!message</h2>
+    <form name="myform" onSubmit="reply(); return false;">
+      <input type="button" value="Cancel" onClick = "cancel()"/>
+      <input type="button" value="OK" onClick = "reply()"/>
+    </form> 
+  </body>
+</html>
+

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/390c286b/taverna-interaction-activity/src/main/resources/warn.vm
----------------------------------------------------------------------
diff --git a/taverna-interaction-activity/src/main/resources/warn.vm b/taverna-interaction-activity/src/main/resources/warn.vm
new file mode 100644
index 0000000..ae9f573
--- /dev/null
+++ b/taverna-interaction-activity/src/main/resources/warn.vm
@@ -0,0 +1,54 @@
+#require("message")
+#require("title")
+#produce("answer")
+<!doctype html>
+<html>
+  <head>
+      <meta charset="UTF-8" />
+      <title></title>
+      <style>
+      </style>
+  </head>
+  <body>
+
+       <script type="text/javascript" src="$pmrpcUrl"></script>
+
+       <script type="text/javascript">
+
+         function reply() {
+           pmrpc.call({
+             destination : "publish",
+             publicProcedureName : "reply",
+             params : ["OK", {"answer" : "answer"}],
+             onSuccess : function() {document.getElementsByTagName('body')[0].innerHTML='<h1>Data submitted</h1>';},
+             onFailure: function() {document.getElementsByTagName('body')[0].innerHTML='<h1>Data submission failed</h1>';}
+           });
+	       return true;
+         }
+         
+         function cancel() {
+           pmrpc.call({
+             destination : "publish",
+             publicProcedureName : "reply",
+             params : ["Cancelled", {}],
+             onSuccess : function() {document.getElementsByTagName('body')[0].innerHTML='<h1>Cancelled</h1>';},
+             onFailure: function() {document.getElementsByTagName('body')[0].innerHTML='<h1>Cancellation failed</h1>';}
+           });
+	       return true;
+         }
+         
+         pmrpc.call({
+           destination : "publish",
+           publicProcedureName : "setTitle",
+           params : ["$!title"]});
+
+       </script>
+  
+  <h2>Warning: $!message</h2>
+    <form name="myform" onSubmit="reply(); return false;">
+      <input type="button" value="Cancel" onClick = "cancel()"/>
+      <input type="button" value="OK" onClick = "reply()"/>
+    </form> 
+  </body>
+</html>
+

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/390c286b/taverna-rest-activity/pom.xml
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/pom.xml b/taverna-rest-activity/pom.xml
new file mode 100644
index 0000000..aa0cba3
--- /dev/null
+++ b/taverna-rest-activity/pom.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.taverna.commonactivities</groupId>
+		<artifactId>taverna-common-activities</artifactId>
+		<version>2.1.0-incubating-SNAPSHOT</version>
+	</parent>
+	<artifactId>taverna-rest-activity</artifactId>
+	<name>Apache Taverna REST Activity</name>
+	<packaging>bundle</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.taverna.engine</groupId>
+			<artifactId>taverna-reference-api</artifactId>
+			<version>${taverna.engine.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.taverna.engine</groupId>
+			<artifactId>taverna-workflowmodel-api</artifactId>
+			<version>${taverna.engine.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.taverna.engine</groupId>
+			<artifactId>taverna-credential-manager</artifactId>
+			<version>${taverna.engine.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>commons-codec</groupId>
+			<artifactId>commons-codec</artifactId>
+			<version>${commons.codec.version}</version>
+		</dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient-osgi</artifactId>
+            <version>${apache.httpclient.version}</version>
+            <exclusions>
+              <!-- These are all embedded within httpclient-osgi -->
+              <exclusion>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpclient</artifactId>
+              </exclusion>
+              <exclusion>
+                <groupId>commons-codec</groupId>
+                <artifactId>commons-codec</artifactId>
+              </exclusion>
+              <exclusion>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpmime</artifactId>
+              </exclusion>
+              <exclusion>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpclient-cache</artifactId>
+              </exclusion>
+              <exclusion>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>fluent-hc</artifactId>
+              </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore-osgi</artifactId>
+            <version>${apache.httpcore.version}</version>
+            <exclusions>
+              <exclusion>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpcore</artifactId>
+              </exclusion>
+              <exclusion>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpcore-nio</artifactId>
+              </exclusion>
+            </exclusions>
+        </dependency>
+
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>${junit.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.taverna.engine</groupId>
+			<artifactId>taverna-activity-test-utils</artifactId>
+			<version>${taverna.engine.version}</version>
+			<scope>test</scope>
+		</dependency>
+
+	</dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-common-activities/blob/390c286b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/HTTPRequest.java
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/HTTPRequest.java b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/HTTPRequest.java
new file mode 100644
index 0000000..9ff240b
--- /dev/null
+++ b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/HTTPRequest.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (C) 2011 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.activities.rest;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.taverna.t2.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/390c286b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/HTTPRequestHandler.java
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/HTTPRequestHandler.java b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/HTTPRequestHandler.java
new file mode 100644
index 0000000..6393550
--- /dev/null
+++ b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/HTTPRequestHandler.java
@@ -0,0 +1,586 @@
+package net.sf.taverna.t2.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 net.sf.taverna.t2.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/390c286b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/HTTPRequestHeader.java
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/HTTPRequestHeader.java b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/HTTPRequestHeader.java
new file mode 100644
index 0000000..5885870
--- /dev/null
+++ b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/HTTPRequestHeader.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (C) 2011 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package net.sf.taverna.t2.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/390c286b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/RESTActivity.java
----------------------------------------------------------------------
diff --git a/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/RESTActivity.java b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/RESTActivity.java
new file mode 100644
index 0000000..76625f8
--- /dev/null
+++ b/taverna-rest-activity/src/main/java/net/sf/taverna/t2/activities/rest/RESTActivity.java
@@ -0,0 +1,345 @@
+package net.sf.taverna.t2.activities.rest;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.sf.taverna.t2.activities.rest.HTTPRequestHandler.HTTPRequestResponse;
+import net.sf.taverna.t2.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]);
+			}
+		});
+	}
+}