/*
* Copyright 2004-2014 SmartBear Software
*
* Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the Licence for the specific language governing permissions and limitations
* under the Licence.
*/
package com.eviware.soapui.testondemand;
import com.eviware.soapui.SoapUI;
import com.eviware.soapui.SoapUISystemProperties;
import com.eviware.soapui.config.*;
import com.eviware.soapui.config.TestOnDemandLocationsRequestDocumentConfig.TestOnDemandLocationsRequest;
import com.eviware.soapui.config.TestOnDemandLocationsRequestDocumentConfig.TestOnDemandLocationsRequest.Request;
import com.eviware.soapui.config.TestOnDemandUploadRequestDocumentConfig.TestOnDemandUploadRequest;
import com.eviware.soapui.impl.wsdl.WsdlProject;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedPostMethod;
import com.eviware.soapui.impl.wsdl.support.CompressionSupport;
import com.eviware.soapui.impl.wsdl.support.http.HttpClientSupport;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.settings.SSLSettings;
import com.eviware.soapui.support.xml.XmlUtils;
import com.google.common.base.Strings;
import com.google.common.io.ByteStreams;
import javax.annotation.Nonnull;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlOptions;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* @author Erik R. Yverling
* <p/>
* Calls the AlertSite API for running Test On Demand.
*/
public class TestOnDemandCaller {
// FIXME Should these be in a configuration file instead?
private final static String DEFAULT_PROTOCOL = "https";
private final static String PROTOCOL_DELIMITER = "://";
private final static String PROD_HOST = "www.alertsite.com";
private static final String LOCATIONS_PATH = "/restapi/v2/devices/list/locations";
private static final String UPLOAD_PATH = "/restapi/v2/devices/upload/testondemand";
private final static String TEST_ON_DEMAND_DOMAIN = getDomain();
private static final String LOCATIONS_URI = getProtocol() + PROTOCOL_DELIMITER + TEST_ON_DEMAND_DOMAIN
+ LOCATIONS_PATH;
private static final String UPLOAD_URI = getProtocol() + PROTOCOL_DELIMITER + TEST_ON_DEMAND_DOMAIN + UPLOAD_PATH;
private static final String REDIRECT_URL_XPATH_EXPRESSION = "//RedirectURL";
private static final String LOCATION_XPATH_EXPRESSION = "//Location";
private static final String LOCATION_CODE_XPATH_EXPRESSION = "LocCode";
private static final String LOCATION_NAME_XPATH_EXPRESSION = "LocName";
private static final String LOCATION_SERVER_IP_ADDRESSES_XPATH_EXPRESSION = "LocIPs";
private static final String API_VERSION = "2";
private static final String APPLICATION_ZIP = "application/zip";
private static final String BASE64 = "base64";
private static final String USER_AGENT = "soapUI-" + SoapUI.SOAPUI_VERSION;
private static final String LOCATIONS_NAME = "ListLocations";
private static final String LOCATIONS_PARAMETER = "server_attrib=ITEST";
private static final String UPLOAD_NAME = "TestOnDemand";
private static final String UPLOAD_PARAMETER_LOCATION_PREFIX = "test_location=";
private static final String SERVER_IP_ADDRESSES_DELIMETER = ",";
protected static final String COULD_NOT_SAVE_TEMPORARY_PROJECT_MESSAGE = "Could not save temporary project file before sending TestCase";
private final XPath xpath = XPathFactory.newInstance().newXPath();
private static final Logger log = Logger.getLogger(TestOnDemandCaller.class);
@Nonnull
public List<Location> getLocations() throws Exception {
Document responseDocument = makeCall(LOCATIONS_URI, generateLocationsRequestXML());
NodeList locationNodes = (NodeList) xpath.evaluate(LOCATION_XPATH_EXPRESSION, responseDocument,
XPathConstants.NODESET);
List<Location> locations = new ArrayList<Location>();
for (int i = 0; i < locationNodes.getLength(); i++) {
Node locationNode = locationNodes.item(i);
String name = (String) xpath.evaluate(LOCATION_NAME_XPATH_EXPRESSION, locationNode, XPathConstants.STRING);
String code = (String) xpath.evaluate(LOCATION_CODE_XPATH_EXPRESSION, locationNode, XPathConstants.STRING);
String unformattedServerIPAddresses = (String) xpath.evaluate(LOCATION_SERVER_IP_ADDRESSES_XPATH_EXPRESSION,
locationNode, XPathConstants.STRING);
String[] serverIPAddresses = new String[0];
if (!unformattedServerIPAddresses.isEmpty()) {
serverIPAddresses = unformattedServerIPAddresses.split(SERVER_IP_ADDRESSES_DELIMETER);
}
locations.add(new Location(code, name, serverIPAddresses));
}
return locations;
}
// FIXME We should do some performance testing of large soapUI project files.
@Nonnull
public String sendTestCase(@Nonnull WsdlTestCase testCase, @Nonnull Location location) throws Exception {
final ExtendedPostMethod post = new ExtendedPostMethod();
post.setURI(new URI(UPLOAD_URI));
String locationCode = location.getCode();
String encodedTestSuiteName = getBase64EncodedString(testCase.getTestSuite().getName().getBytes());
String encodedTestCaseName = getBase64EncodedString(testCase.getName().getBytes());
File tempProjectFile = saveTemporaryProject(testCase.getTestSuite().getProject());
byte[] projectFileData = getBytes(tempProjectFile.getAbsolutePath());
byte[] zipedProjectFileData = zipBytes(testCase.getTestSuite().getProject().getName(), projectFileData);
String encodedZipedProjectFile = getBase64EncodedString(zipedProjectFileData);
String projectPassword = testCase.getTestSuite().getProject().getShadowPassword();
String encodedProjectPassword = getBase64EncodedString(Strings.nullToEmpty(projectPassword).getBytes());
String keystoreFilePath = SoapUI.getSettings().getString(SSLSettings.KEYSTORE, "");
byte[] keystoreFileData = getBytes(keystoreFilePath);
String encodedKeystoreFile = getBase64EncodedString(keystoreFileData);
String encodedKeystorePassword = getBase64EncodedString(SoapUI.getSettings()
.getString(SSLSettings.KEYSTORE_PASSWORD, "").getBytes());
String requestContent = generateUploadRequestXML(locationCode, encodedTestSuiteName, encodedTestCaseName,
encodedZipedProjectFile, encodedProjectPassword, encodedKeystoreFile, encodedKeystorePassword);
byte[] compressedRequestContent = CompressionSupport.compress(CompressionSupport.ALG_GZIP,
requestContent.getBytes());
post.setEntity(new ByteArrayEntity(compressedRequestContent));
Document responseDocument = makeCall(UPLOAD_URI, requestContent);
String redirectURL = (String) xpath.evaluate(REDIRECT_URL_XPATH_EXPRESSION, responseDocument,
XPathConstants.STRING);
if (Strings.isNullOrEmpty(redirectURL)) {
throw new RuntimeException("The RedirectURL element is missing in the response message");
}
return redirectURL;
}
// FIXME Add to utility class and make DependencyValidator.saveProject() use this
protected File saveTemporaryProject(WsdlProject project) {
File tempFile = null;
try {
tempFile = File.createTempFile("project-temp-", ".xml", null);
project.saveIn(tempFile);
} catch (IOException e) {
SoapUI.logError(e, COULD_NOT_SAVE_TEMPORARY_PROJECT_MESSAGE);
}
return tempFile;
}
private Document makeCall(String uri, String requestContent) throws Exception {
final ExtendedPostMethod post = new ExtendedPostMethod();
post.setURI(new URI(uri));
post.setEntity(new StringEntity(requestContent));
// FIXME Should we remove the logging printouts before release? The upload request maybe would be to large?
log.debug("Sending request to " + uri);
log.debug(requestContent);
HttpClientSupport.execute(post);
byte[] responseBody = post.getResponseBody();
log.debug("Got response from " + uri);
log.debug(new String(responseBody));
String reponseBodyAsString = new String(responseBody);
return XmlUtils.parseXml(reponseBodyAsString);
}
private String generateLocationsRequestXML() {
TestOnDemandLocationsRequest locationsRequest = TestOnDemandLocationsRequestDocumentConfig.Factory.newInstance()
.addNewTestOnDemandLocationsRequest();
Request request = locationsRequest.addNewRequest();
request.setApiVersion(API_VERSION);
TestOnDemandHeaderConfig header = request.addNewHeader();
header.setUserAgent(USER_AGENT);
TestOnDemandCommandConfig command = request.addNewBody().addNewCommand();
command.setName(LOCATIONS_NAME);
command.setParameters(LOCATIONS_PARAMETER);
return locationsRequest.xmlText(getXmlOptionsWithoutNamespaces());
}
private String generateUploadRequestXML(String locationCode, String encodedTestSuiteName,
String encodedTestCaseName, String encodedZipedProjectFile, String encodedProjectPassword,
String encodedKeystoreFile, String encodedKeystorePassword) {
TestOnDemandUploadRequest uploadRequestConfig = TestOnDemandUploadRequestDocumentConfig.Factory.newInstance()
.addNewTestOnDemandUploadRequest();
com.eviware.soapui.config.TestOnDemandUploadRequestDocumentConfig.TestOnDemandUploadRequest.Request requestConfig = uploadRequestConfig
.addNewRequest();
requestConfig.setApiVersion(API_VERSION);
TestOnDemandHeaderConfig headerConfig = requestConfig.addNewHeader();
headerConfig.setUserAgent(USER_AGENT);
TestOnDemandUploadBodyConfig bodyConfig = requestConfig.addNewBody();
TestOnDemandCommandConfig commandConfig = bodyConfig.addNewCommand();
commandConfig.setName(UPLOAD_NAME);
commandConfig.setParameters(UPLOAD_PARAMETER_LOCATION_PREFIX + locationCode);
TestOnDemandTxnConfig txnConfig = bodyConfig.addNewTxn();
TestOnDemandTestSuiteConfig testSuiteConfig = txnConfig.addNewTestSuite();
testSuiteConfig.setEnctype(BASE64);
testSuiteConfig.setStringValue(encodedTestSuiteName);
TestOnDemandTestCaseConfig testCaseConfig = txnConfig.addNewTestCase();
testCaseConfig.setEnctype(BASE64);
testCaseConfig.setStringValue(encodedTestCaseName);
TestOnDemandContentConfig contentConfig = txnConfig.addNewContent();
contentConfig.setEnctype(BASE64);
contentConfig.setType(APPLICATION_ZIP);
contentConfig.setStringValue(encodedZipedProjectFile);
TestOnDemandProjectPasswordConfig projectPasswordConfig = txnConfig.addNewPassword();
projectPasswordConfig.setEnctype(BASE64);
projectPasswordConfig.setStringValue(encodedProjectPassword);
TestOnDemandKeystoreConfig keystoreConfig = bodyConfig.addNewKeystore();
TestOnDemandFileConfig fileConfig = keystoreConfig.addNewFile();
fileConfig.setEnctype(BASE64);
fileConfig.setStringValue(encodedKeystoreFile);
TestOnDemandKeystorePasswordConfig keystorePasswordConfig = keystoreConfig.addNewPassword();
keystorePasswordConfig.setEnctype(BASE64);
keystorePasswordConfig.setStringValue(encodedKeystorePassword);
return uploadRequestConfig.xmlText(getXmlOptionsWithoutNamespaces());
}
private XmlOptions getXmlOptionsWithoutNamespaces() {
XmlOptions options = new XmlOptions();
options.setUseDefaultNamespace();
Map<String, String> namespaces = new HashMap<String, String>();
namespaces.put("", "http://eviware.com/soapui/config");
options.setSaveImplicitNamespaces(namespaces);
return options;
}
private static byte[] getBytes(String filePath) throws IOException {
byte[] byteArray = new byte[0];
if (!Strings.isNullOrEmpty(filePath)) {
File file = new File(filePath);
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
byteArray = ByteStreams.toByteArray(inputStream);
} finally {
IOUtils.closeQuietly(inputStream);
}
}
return byteArray;
}
private static byte[] zipBytes(String filename, byte[] dataToBeZiped) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zipedOutputStream = new ZipOutputStream(outputStream);
ZipEntry entry = new ZipEntry(filename);
entry.setSize(dataToBeZiped.length);
try {
zipedOutputStream.putNextEntry(entry);
zipedOutputStream.write(dataToBeZiped);
} finally {
zipedOutputStream.closeEntry();
zipedOutputStream.close();
}
return outputStream.toByteArray();
}
private static String getDomain() {
String customEndpoint = System.getProperty(SoapUISystemProperties.TEST_ON_DEMAND_HOST);
if (customEndpoint == null) {
return PROD_HOST;
} else {
return customEndpoint;
}
}
private static String getProtocol() {
return System.getProperty(SoapUISystemProperties.TEST_ON_DEMAND_PROTOCOL, DEFAULT_PROTOCOL);
}
private static String getBase64EncodedString(byte[] bytesToEncode) {
return new String(Base64.encodeBase64(bytesToEncode));
}
}