/*
 * Licensed Materials - Property of IBM Corp.
 * IBM UrbanCode Deploy
 * IBM Urbancode Release
 * (c) Copyright IBM Corporation 2016. All Rights Reserved.
 *
 * U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
 * GSA ADP Schedule Contract with IBM Corp.
 */
package com.urbancode.air.plugin.appscan

import java.io.StringWriter
import java.io.File
import java.io.FileInputStream
import java.net.URL
import java.net.URLEncoder
import java.text.Format
import java.text.SimpleDateFormat
import java.util.Iterator
import java.util.concurrent.TimeoutException
import java.util.concurrent.TimeUnit

import javax.xml.XMLConstants
import javax.xml.namespace.NamespaceContext
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.Transformer
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import javax.xml.xpath.XPath
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory

import org.apache.commons.io.FileUtils
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpPost
import org.apache.http.client.methods.HttpUriRequest
import org.apache.http.entity.InputStreamEntity
import org.apache.http.entity.StringEntity
import org.apache.http.HttpEntity
import org.apache.http.HttpResponse
import org.apache.http.util.EntityUtils
import org.w3c.dom.Document

import com.urbancode.air.XTrustProvider
import com.urbancode.commons.httpcomponentsutil.HttpClientBuilder

public class AppScanRestHelper {

    def user
    def password
    def baseUrl
    def disableCerts
    def client
    def appAPI
    String sessionId
    XPath xpath

    // Retrieve Reports parameters
    def reportsFIID
    def filepath
    def applicationID

    // Run Scan parameters
    def rerun
    def scanFIID
    def timeout

    // Create Scan parameters
    def appID
    def folderID
    def scanDescription
    def scanName
    def templateName

    // Configure Job Options parameters
    def httpAuth
    def httpPassword
    def httpUser
    def recordedTraffic
    def setAuto
    def scanLimit
    def scanPassword
    def startUrl
    def scanUser

    // AppScan Enterprise Configuration
    /**
     * Persist cookies across requests
     */
    def cookieContainer

    public AppScanRestHelper(props) {
        this.appAPI = false
        this.appID = props['appID']
        this.applicationID = props['applicationID']
        this.baseUrl = props['baseUrl']
        this.disableCerts = props['disableCerts']
        this.filepath = props['filepath']
        this.folderID = props['folderID']
        this.httpAuth = props['httpAuth']
        this.httpUser = props['httpUser']
        this.httpPassword = props['httpPassword']
        this.scanLimit = props['scanLimit']
        this.password = props['password']
        this.recordedTraffic = props['recordedTraffic']
        this.reportsFIID = props['reportsFIID']
        if (props['rerun']) {
            this.rerun = (props['rerun'] as int)
        }
        this.scanDescription = props['scanDescription']
        this.scanFIID = props['scanFIID']
        this.scanName = props['scanName']
        this.scanPassword = props['scanPassword']
        this.scanUser = props['scanUser']
        this.setAuto = props['setAuto']
        this.startUrl = props['startUrl']
        this.templateName = props['templateName']
        if(props['timeout']) {
            this.timeout = (props['timeout'] as int) * 12
        }
        this.user = props['user']
        this.cookieContainer = ""
        if (!baseUrl.substring(baseUrl.length()-1).equals("/")) {
            baseUrl += "/"
        }
        if (baseUrl.substring(baseUrl.length()-6, baseUrl.length()-5).equals(":")) {
            appAPI = true
        }
        if ((filepath) && (!filepath.substring(filepath.length()-1).equals("/"))) {
            filepath += "/"
        }

        HttpClientBuilder builder = new HttpClientBuilder()

        if (disableCerts) {
            XTrustProvider.install()
            builder.setTrustAllCerts(true)
        }

        builder.setUsername(user)
        builder.setPassword(password)
        builder.setPreemptiveAuthentication(true)

        this.client = builder.buildClient()

        System.setProperty("https.protocols", "TLSv1")

        // Initialization of XPath utilities
        XPathFactory xfactory = XPathFactory.newInstance()
        xpath = xfactory.newXPath()
        xpath.setNamespaceContext(_nsContext)

        try {
            this.sessionId = authenticate()
            println "Logged in."
        } catch (IOException e) {
            println "Failed to authenticate."
            e.printStackTrace()
            System.exit(1)
        }
    }

    public String[] retrieveReportInfo() {
        try {
            String[] outProps = getReportInfo()
            println "Retrieved Reports successfully."
            return outProps
        } catch (Exception e) {
            println "Failed report retrieval."
            e.printStackTrace()
            System.exit(1)
        }
    }

    public String[] retrievePDFReportInfo() {
        try {
            getPDFReportInfo()
            println "Retrieved PDF Report successfully."
        } catch (Exception e) {
            println "Failed report retrieval."
            e.printStackTrace()
            System.exit(1)
        }
    }

    public void runScan() {
        def runNumber = 1
        def retry = true
        if (rerun) {
            runNumber += rerun
        }

        while (retry) {
            try {
                runScanByFIID()
                println "Scan and Report Pack finished successfully."
                retry = false
            } catch (Exception e) {
                println "Failed scan."
                e.printStackTrace()
                stopScanByFIID()
                if (runNumber > 1) {
                    println "Rerunning scan."
                    runNumber -= 1
                }
                else {
                    System.exit(1)
                }
            }
        }

    }

    public void listTemplates() {
        Document doc = sendGetRequest(baseUrl + "ase/services/templates")
        printOuterXml(doc)
    }

    public String[] createScan() {
        try {
            String[] outProps = createScanByTemplateID()
            println "Scan created successfully."
            return outProps
        } catch (Exception e) {
            println "Failed scan creation."
            e.printStackTrace()
            System.exit(1)
        }
    }

    public void configJobOptions() {
        try {
            configureJobOptions()
            println "Job options configured successfully."
        } catch (Exception e) {
            println "Failed configuration."
            e.printStackTrace()
            System.exit(1)
        }
    }

    private String[] createScanByTemplateID() {
        String createURL = baseUrl + "ase/services/"

        // Get templateFIID
        print("Retrieving the identifier of the " + templateName + " template...");
        Document doc = sendGetRequest(baseUrl + "ase/services/templates")
        String templateId = (String) xpath.evaluate("//content-scan-job[name='" + templateName + "']/id/text()", doc, XPathConstants.STRING);
        println "Template ID: ${templateId}"


        if (folderID) {
            createURL += "folders/${folderID}/folderitems?templateid=${templateId}"
        }
        else {
            createURL += "folderitems?templateid=${templateId}"
        }
        if (appID) {
            createURL += "&appid=${appID}"
        }

        println "Creating ${scanName} based on the ${templateName} template."
        def postData = "name=" + URLEncoder.encode(scanName, "UTF-8") + "&description=" + URLEncoder.encode(scanDescription, "UTF-8")
        doc = sendPostRequest(createURL, postData)
        println "Scan information:"

        String createdScanFIID = (String) xpath.evaluate("//folder-items/content-scan-job/id/text()", doc, XPathConstants.STRING)
        String createdReportsFIID = (String) xpath.evaluate("//folder-items/report-pack/id/text()", doc, XPathConstants.STRING)
        String createdReportsURL = (String) xpath.evaluate("//folder-items/report-pack/reports/@href", doc, XPathConstants.STRING)

        println "scanFIID: ${createdScanFIID}"
        println "reportsFIID: $createdReportsFIID"
        println "reportsURL: ${createdReportsURL}"

        String[] outProps = [createdScanFIID, createdReportsFIID, createdReportsURL]
        return outProps

    }

    private void configureJobOptions() {
        String scanURL = "ase/services/folderitems/" + scanFIID
        Document doc = sendGetRequest(baseUrl + scanURL)

        String optionsURL = (String) xpath.evaluate("//content-scan-job/options/@href", doc, XPathConstants.STRING)

        def postData
        // Set the starting URL
        if(startUrl) {
            println "Updating the starting URL."
            postData = "value=" + URLEncoder.encode(startUrl, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.STARTING_URLS, postData)
        }
        // Set automatic login
        if(setAuto) {
            println "Updating the scan to auto form fill."
            postData = "value=3"
            doc = sendPostRequest(optionsURL + "/" + OptionType.AUTOMATIC_LOGIN, postData)
        }
        // Set the autoform username
        if(scanUser) {
            println "Updating the auto form fill username."
            postData = "value=" + URLEncoder.encode(scanUser, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.AUTOMATIC_LOGIN_USERNAME, postData)
        }
        // Set the autoform password
        if(scanPassword) {
            println "Updating the auto form fill password."
            postData = "value=" + URLEncoder.encode(scanPassword, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.AUTOMATIC_LOGIN_PASSWORD, postData)
        }
        // Set the http authentication
        if(httpAuth) {
            println "Updating the HTTP authentication."
            postData = "value=" + URLEncoder.encode(httpAuth, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.HTTP_AUTHENTICATION, postData)
        }
        // Set the http username
        if(httpUser) {
            println "Updating the HTTP username."
            postData = "value=" + URLEncoder.encode(httpUser, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.HTTP_USERNAME, postData)
        }
        // Set the http password
        if(httpPassword) {
            println "Updating the HTTP username."
            postData = "value=" + URLEncoder.encode(httpPassword, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.HTTP_PASSWORD, postData)
        }

        //Set recorded traffic
        if (recordedTraffic) {
            println "Updating the recorded traffic."
            File file = new File(recordedTraffic)

            HttpPost postRequest = new HttpPost(scanURL + "/httptrafficdata")

            FileInputStream fileInputStream = new FileInputStream(file)
            InputStreamEntity reqEntity = new InputStreamEntity(fileInputStream, file.length())
            postRequest.setEntity(reqEntity);
            reqEntity.setContentType("binary/octet-stream");

            postRequest.addHeader("Content-Type", "application/octet-stream")
            postRequest.addHeader("Accept", "application/json,application/xml")

            HttpResponse response = client.execute(request)
            doc = convertToDOM(response)
            checkForError(doc)
        }

        // Set the scan limit
        if(scanLimit) {
            println "Updating the scan limit."
            postData = "value=" + URLEncoder.encode(scanLimit, "UTF-8")
            doc = sendPostRequest(optionsURL + "/" + OptionType.SCAN_LIMIT, postData)
        }
    }

    private void runScanByFIID() {
        // Set scan URL for later use and pull out some information about the scan
        String reportPackURL = ""
        def reportPackLastRunDateTime = ""
        String scanURL = "ase/services/folderitems/" + scanFIID
        Document doc = sendGetRequest(baseUrl + scanURL)

        String scanLastRunDateTime = (String) xpath.evaluate("//content-scan-job/last-run/text()", doc, XPathConstants.STRING)
        println "Scan last run: " + scanLastRunDateTime

        // Set report URL for later use and pull out some information about the report pack
        if(reportsFIID) {
            reportPackURL = "ase/services/folderitems/" + reportsFIID
            doc = sendGetRequest(baseUrl + reportPackURL)

            String reportsURL = (String) xpath.evaluate("//report-pack/reports/@href", doc, XPathConstants.STRING)
            println "Reports URL: " + reportsURL
            reportPackLastRunDateTime = xpath.evaluate("//report-pack/last-run/text()", doc, XPathConstants.STRING)
            println "Reports last run: " + reportPackLastRunDateTime
        }

        def postData

        // Run the scan by setting the action
        println "Running the scan."
        postData = "action=" + ScanAction.RUN
        doc = sendPostRequest(baseUrl + scanURL, postData)

        // Wait for scan completion
        waitForCompletion(baseUrl + scanURL, scanLastRunDateTime)
        println "Scan completed successfully."
        // Wait for report pack completion
        if(reportsFIID) {
            println "Waiting for report pack completion."
            waitForCompletion(baseUrl + reportPackURL, reportPackLastRunDateTime)
            println "Reports finished successfully."
            getReportInfo()
        }
    }

    private void stopScanByFIID() {
        // Set scan URL for later use
        String scanURL = "ase/services/folderitems/" + scanFIID
        Document doc = sendGetRequest(baseUrl + scanURL)

        String scanLastRunDateTime = (String) xpath.evaluate("//content-scan-job/last-run/text()", doc, XPathConstants.STRING)

        // Run the scan by setting the action
        println "Stopping the scan."
        String postData = "action=" + ScanAction.END

        // Wait for scan to stop
        waitForStop(baseUrl + scanURL, scanLastRunDateTime)
    }

    private String[] getReportInfo() {
        // Set report URL for later use and pull out some information about the report pack
        String reportPackURL = "ase/services/folderitems/" + reportsFIID
        Document doc = sendGetRequest(baseUrl + reportPackURL)

        String reportsURL = (String) xpath.evaluate("//report-pack/reports/@href", doc, XPathConstants.STRING)
        println reportsURL
        def reportPackLastRunDateTime = xpath.evaluate("//report-pack/last-run/text()", doc, XPathConstants.STRING)
        println "Report Pack Last Run: ${reportPackLastRunDateTime}"

        // Get report
        println "Retrieving report summary information..."
        doc = sendGetRequest(reportsURL)

        String secIssuesCount = (String) xpath.evaluate("//report[name='Security Issues']/summary/sentence/text()", doc, XPathConstants.STRING)
        println "Security Issues:" + secIssuesCount

        String secIssuesDataURL = (String) xpath.evaluate("//report[name='Security Issues']/data/@href", doc, XPathConstants.STRING)
        println secIssuesDataURL

        String highSevIssuesCount = (String) xpath.evaluate("//report[name='Security Issues']/issue-counts-severity/issue-count[severity/name='High']/count/text()", doc, XPathConstants.STRING)
        println "High Severity Issues: ${highSevIssuesCount}"
        String mediumSevIssuesCount = (String) xpath.evaluate("//report[name='Security Issues']/issue-counts-severity/issue-count[severity/name='Medium']/count/text()", doc, XPathConstants.STRING)
        println "Medium Severity Issues: ${mediumSevIssuesCount}"


        Format formatter = new SimpleDateFormat("ddMMMyyyy-HHmmss");
        String date = formatter.format(new Date());

        def filename = reportsFIID + "-" + date + ".txt"
        File file = new File(filepath + filename)
        def output = new BufferedWriter(new FileWriter(file))
        // Retrieve all data
        String secIssuesAllDataURL = secIssuesDataURL + "?mode=all"
        println "Retrieving report detail information..."
        doc = sendGetRequest(secIssuesAllDataURL)

        String text
        if (filepath) {
            text = getXmlString(doc)
            output.write(text)
        }
        else {
            printOuterXml(doc)
        }

        // Get the schema for this report
        String secIssuesSchemaURL = secIssuesDataURL + "?metadata=schema"
        println "Retrieving report schema information..."
        doc = sendGetRequest(secIssuesSchemaURL)

        if (filepath) {
            text = getXmlString(doc)
            output.write(text)
            output.close()
            println "Saved report detail and schema information to ${filepath}"
        }
        else {
            printOuterXml(doc)
        }

        def isReadyForDeployment = ((highSevIssuesCount.toInteger()+mediumSevIssuesCount.toInteger())>0) ? false : true
        String[] outProps = [highSevIssuesCount, mediumSevIssuesCount, isReadyForDeployment]
        return outProps
    }

    private String[] getPDFReportInfo() {
        String body = "{\"config\": {\"executiveSummaryIncluded\":false,\"advisoriesIncluded\":false,\"pdfPageBreakOnIssue\":false,\"sortByURL\":false,\"issueConfig\":{\"includeAdditionalInfo\":false,\"reportOptionVariantConfig\":{\"variantLimit\": 0,\"requestResponseIncluded\":false,\"differencesIncluded\": false}}},\"layout\": {\"reportOptionLayoutCoverPage\": {\"companyLogo\": \"\",\"additionalLogo\":\"\",\"includeDate\":false,\"includeReportType\":false,\"reportTitle\":\"\",\"description\":\"\"},\"reportOptionLayoutBody\":{\"header\":\"\",\"footer\":\"\"},\"includeTableOfContents\":false},\"reportFileType\":\"PDF\",\"issueIdsAndQueries\":[\"${scanName} (${scanFIID})\"]}"

        StringEntity params = new StringEntity(body)

        def securityDetailURL = "ase/api/issues/reports/securitydetails?appId=" + applicationID

        HttpPost securityDetailRequest = new HttpPost(baseUrl + securityDetailURL)
        HttpResponse response = doRequest(securityDetailRequest, params)
        def statusLine = response.getStatusLine()
        def statusCode = statusLine.getStatusCode()

        if (statusCode != 202) {
            println("[Error] Generation failed with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
            throw new RuntimeException(response.entity?.content?.text)
        }
        else {
            println("[OK] Report generation was successful.")
        }
        def locations = response.getHeaders("Location")
        String location = locations[0]
        location = location.substring(10)
        println ("report status location: " + location)

        HttpGet statusRequest = new HttpGet(location)
        // Get report status
        while (statusCode != 201) {

            response = doRequest(statusRequest)
            statusLine = response.getStatusLine()
            statusCode = statusLine.getStatusCode()

            if (statusCode == 201) {
                println("[OK] Report ready.")
            }
            else if (statusCode != 200) {
                println("[Error] Report failure with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
                throw new RuntimeException(response.entity?.content?.text)
            }
            else {
                println "In progress"
            }
            TimeUnit.SECONDS.sleep(5)
        }
        locations = response.getHeaders("Location")
        location = locations[0]
        location = location.substring(10)
        println ("report location: " + location)

        // Get report
        HttpGet reportRequest = new HttpGet(location)
        response = doPDFRequest(reportRequest)
        statusLine = response.getStatusLine()
        statusCode = statusLine.getStatusCode()

        if (statusCode != 200) {
            println("[Error] Download failed with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
            throw new RuntimeException(response.entity?.content?.text)
        }
        else {
            println("")
        }

        // Create file
        Format formatter = new SimpleDateFormat("ddMMMyyyy-HHmmss");
        String date = formatter.format(new Date());
        def filename = "AppScanReportOutput-" + date + ".zip"
        File file = new File(filepath + filename)

        outputZipFile(response, file)
    }

    private void outputZipFile(def response, File file) {
        // Download file in a stream
        InputStream input = null
        FileOutputStream output = null
        byte[] buffer = new byte[1024]
        try {
            input = response.getEntity().getContent()
            output = new FileOutputStream(file)
            for (int length; (length = input.read(buffer)) > 0;) {
                output.write(buffer, 0, length)
            }
        } finally {
            if (output != null) try { output.close(); } catch (IOException logOrIgnore) {}
            if (input != null) try { input.close(); } catch (IOException logOrIgnore) {}
        }
    }

    private static String getXmlString(Document response) throws Exception {
        Transformer transformer = TransformerFactory.newInstance().newTransformer()
        StringWriter writer = new StringWriter()
        transformer.transform(new DOMSource(response.getDocumentElement()), new StreamResult(writer))
        return writer.toString()
    }

    // creates a new LW-SSO token
    private String authenticate() {
        HttpPost loginRequest
        HttpResponse response
        def statusLine
        def statusCode
        String sessionId = null
        // Authentication headers tacked on by HttpClientBuilder
        if (appAPI) {
            String auth = "{\"userId\":\"${user.replace("\\","\\\\")}\",\"password\":\"${password}\",\"featureKey\":\"AppScanEnterpriseUser\"}"
            StringEntity params = new StringEntity(auth)

            loginRequest = new HttpPost(baseUrl + "ase/api/login")
            response = doRequest(loginRequest, params)
            statusLine = response.getStatusLine()
            statusCode = statusLine.getStatusCode()

            if (statusCode != 200) {
                println("[Error] Authentication failed with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
                throw new RuntimeException(response.entity?.content?.text)
            }
            else {
                println("[OK] Authentication was successful.")
            }

            HttpEntity r_entity = response.getEntity()
            def ent = EntityUtils.toString(r_entity)
            String [] resp = ent.split("\"")
            sessionId = resp[5]
            println "session id: " + sessionId

        }
        else {
            def loginPostData = "userid=" + user + "&password=" + password
            loginRequest = new HttpPost(baseUrl + "ase/services/login")
            response = doRequest(loginRequest, loginPostData)
            statusLine = response.getStatusLine()
            statusCode = statusLine.getStatusCode()

            if (statusCode != 200) {
                println("[Error] Authentication failed with an Http response code of ${statusCode}: ${statusLine.getReasonPhrase()}")
                throw new RuntimeException(response.entity?.content?.text)
            }
            else {
                println("[OK] Authentication was successful.")
            }
        }
        return sessionId
    }

    // Execute a general http request
    private HttpResponse doRequest(HttpUriRequest request) {
        return doRequest(request, null)
    }

    // Execute a general http request
    private HttpResponse doPDFRequest(HttpUriRequest request) {
        request.addHeader("Content-Type", "application/octet-stream")
        request.addHeader("Accept", "application/json,application/xml,application/octet-stream")

        if (sessionId) {
            request.addHeader("asc_xsrf_token", sessionId)
        }
        HttpResponse response = client.execute(request)

        return response
    }

    // Specify a json string to provide a string entity to the http request
    private HttpResponse doRequest(HttpUriRequest request, String contentString) {
        request.addHeader("Content-Type", "application/x-www-form-urlencoded")
        request.addHeader("Accept", "application/json,application/xml")

        if (sessionId) {
            request.addHeader("asc_xsrf_token", sessionId)
        }

        if (contentString) {
            StringEntity input

            try {
                input = new StringEntity(contentString)
            }
            catch(UnsupportedEncodingException ex) {
                println("Unsupported characters in http request content: ${contentString}")
                System.exit(1)
            }

            request.setEntity(input)
        }

        HttpResponse response = client.execute(request)

        return response
    }

    // Specify a json string to provide a string entity to the http request
    private HttpResponse doRequest(HttpUriRequest request, StringEntity contentEntity) {
        request.addHeader("Content-Type", "application/json")
        request.addHeader("Accept", "application/json,application/xml")

        if (sessionId) {
            request.addHeader("asc_xsrf_token", sessionId)
        }

        request.setEntity(contentEntity);

        HttpResponse response = client.execute(request)

        return response
    }

    private Document sendGetRequest(String url) {
        HttpGet getRequest = new HttpGet(url)
        def response = doRequest(getRequest)
        Document doc = convertToDOM(response)
        checkForError(doc)
        return doc
    }

    private Document sendPostRequest(String url, def postData) {
        HttpPost postRequest = new HttpPost(url)
        def response = doRequest(postRequest, postData)
        Document doc = convertToDOM(response)
        checkForError(doc)
        return doc
    }

    private void waitForCompletion(String url, String lastRunDate) throws Exception {
        int retries = 0
        String prevStatus = ""
        while (retries < timeout) {
            Document doc = sendGetRequest(url)

            // Get the status and print it
            String status = (String) xpath.evaluate("//state/name/text()", doc, XPathConstants.STRING)
            if (!status.equals(prevStatus)) {
                println "Status: " + status
            }

            Double scanState = (Double) xpath.evaluate("//state/id/text()", doc, XPathConstants.NUMBER)
            if (scanState.intValue() == ScanState.READY) {
                // If scan marked ready, check last run time to verify completion
                String lastRunDateTime = (String) xpath.evaluate("//last-run/text()", doc, XPathConstants.STRING)

                if (lastRunDateTime.compareTo(lastRunDate) != 0) {
                    println "Completed successfully."
                    return
                }

            } else if (scanState.intValue() == ScanState.SUSPENDED) {
                // An error has occurred
                println "Object has suspended. Check AppScan Enterprise Console for more information on the scan: "
                if(baseUrl.endsWith('ase/')) {
                    print baseUrl + "Jobs/JobStatistics.aspx?fiid=" + scanFIID
                }
                throw new Exception("Scan Suspended")
            } else if (scanState.intValue() == ScanState.CANCELING) {
                // The scan was cancelled
                println "Object is canceling."
                throw new Exception("Scan Cancelled")
            }

            if (!status.equals(prevStatus)) {
                println "Continue to wait for completion..."
                prevStatus = status
            }
            retries++
            Thread.sleep(5000)
        }

        throw new TimeoutException("Wait threshold exceeded.")
    }

    private void waitForStop(String url, String lastRunDate) throws Exception {
        int retries = 0
        String prevStatus = ""
        while (retries < 7) {
            Document doc = sendGetRequest(url)

            // Get the status and print it
            String status = (String) xpath.evaluate("//state/name/text()", doc, XPathConstants.STRING)
            if (!status.equals(prevStatus)) {
                println status
            }

            Double scanState = (Double) xpath.evaluate("//state/id/text()", doc, XPathConstants.NUMBER)
            if (scanState.intValue() == ScanState.READY) {
                // If scan marked ready, check last run time to verify completion
                String lastRunDateTime = (String) xpath.evaluate("//last-run/text()", doc, XPathConstants.STRING)

                if (lastRunDateTime.compareTo(lastRunDate) != 0) {
                    println "Scan stopped."
                    return
                }

            }
            retries++
            Thread.sleep(5000)
        }

        throw new Exception("[Error] Wait threshold exceeded.")
    }

    // Checks response for errors
    private void checkForError(Document doc) throws Exception {
        if((String) xpath.evaluate("//error/text()", doc, XPathConstants.STRING)) {
            println "*** Error Occurred ***"

            String code = (String) xpath.evaluate("//code/text()", doc, XPathConstants.STRING)
            if(code) {
                println code
            }
            String message = (String) xpath.evaluate("//message/text()", doc, XPathConstants.STRING)
            if(message) {
                println message
            }
            String help = (String) xpath.evaluate("//error/help/@weblink", doc, XPathConstants.STRING)
            if(help) {
                println help
            }
            throw new Exception("Unexpected error.")
        }
    }

    private static void printOuterXml(Document response) throws Exception {
        Transformer transformer = TransformerFactory.newInstance().newTransformer()
        transformer.setOutputProperty("omit-xml-declaration", "yes")

        StringWriter writer = new StringWriter()
        transformer.transform(new DOMSource(response.getDocumentElement()), new StreamResult(writer))
        println writer.toString()
    }

    private static Document convertToDOM(HttpResponse response) {
        HttpEntity r_entity = response.getEntity()
        def ent = EntityUtils.toString(r_entity)
        Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new StringBufferInputStream(ent)) //here
        return doc
    }

    private static NamespaceContext _nsContext
    static {
        _nsContext = new NamespaceContext() {
            public String getNamespaceURI(String prefix) {
                if (prefix.equalsIgnoreCase("ase"))
                    return "http://www.ibm.com/Rational/AppScanEnterprise"
                return XMLConstants.NULL_NS_URI
            }

            public String getPrefix(String arg0) {
                return null
            }

            public Iterator<?> getPrefixes(String arg0) {
                return null
            }
        }
    }

    private class ScanAction {
        public static int RUN = 2
        public static int SUSPEND = 3
        public static int CANCEL = 4
        public static int END = 5
    }
    private class OptionType {
        public static String STARTING_URLS = "epcsCOTListOfStartingUrls"
        public static String AUTOMATIC_LOGIN = "elCOTLoginSequenceType"
        public static String AUTOMATIC_LOGIN_USERNAME = "esCOTAutoFormFillUserNameValue"
        public static String AUTOMATIC_LOGIN_PASSWORD = "esCOTAutoFormFillPasswordValue"
        public static String HTTP_AUTHENTICATION = "ebCOTHttpAuthentication"
        public static String HTTP_USERNAME = "esCOTHttpUser"
        public static String HTTP_PASSWORD = "esCOTHttpPassword"
        public static String SCAN_LIMIT = "elCOTScanLimit"
    }

    private class ScanState {
        public static int READY = 1
        public static int STARTING = 2
        public static int RUNNING = 3
        public static int RESUMING = 6
        public static int CANCELING = 7
        public static int SUSPENDING = 8
        public static int SUSPENDED = 9
        public static int POSTPROCESSING = 10
        public static int ENDING = 12
    }
}