edu.stanford.epad.plugins.qifpwrapper.QIFPHandler.java Source code

Java tutorial

Introduction

Here is the source code for edu.stanford.epad.plugins.qifpwrapper.QIFPHandler.java

Source

/*******************************************************************************
 * Copyright (c) 2015 The Board of Trustees of the Leland Stanford Junior University
 * BY CLICKING ON "ACCEPT," DOWNLOADING, OR OTHERWISE USING EPAD, YOU AGREE TO THE FOLLOWING TERMS AND CONDITIONS:
 * STANFORD ACADEMIC SOFTWARE SOURCE CODE LICENSE FOR
 * "ePAD Annotation Platform for Radiology Images"
 *
 * This Agreement covers contributions to and downloads from the ePAD project ("ePAD") maintained by The Board of Trustees 
 * of the Leland Stanford Junior University ("Stanford"). 
 *
 * *   Part A applies to downloads of ePAD source code and/or data from ePAD. 
 *
 * *   Part B applies to contributions of software and/or data to ePAD (including making revisions of or additions to code 
 * and/or data already in ePAD), which may include source or object code. 
 *
 * Your download, copying, modifying, displaying, distributing or use of any ePAD software and/or data from ePAD 
 * (collectively, the "Software") is subject to Part A. Your contribution of software and/or data to ePAD (including any 
 * that occurred prior to the first publication of this Agreement) is a "Contribution" subject to Part B. Both Parts A and 
 * B shall be governed by and construed in accordance with the laws of the State of California without regard to principles 
 * of conflicts of law. Any legal action involving this Agreement or the Research Program will be adjudicated in the State 
 * of California. This Agreement shall supersede and replace any license terms that you may have agreed to previously with 
 * respect to ePAD.
 *
 * PART A. DOWNLOADING AGREEMENT - LICENSE FROM STANFORD WITH RIGHT TO SUBLICENSE ("SOFTWARE LICENSE").
 * 1. As used in this Software License, "you" means the individual downloading and/or using, reproducing, modifying, 
 * displaying and/or distributing Software and the institution or entity which employs or is otherwise affiliated with you. 
 * Stanford  hereby grants you, with right to sublicense, with respect to Stanford's rights in the Software, a 
 * royalty-free, non-exclusive license to use, reproduce, make derivative works of, display and distribute the Software, 
 * provided that: (a) you adhere to all of the terms and conditions of this Software License; (b) in connection with any 
 * copy, distribution of, or sublicense of all or any portion of the Software, the terms and conditions in this Software 
 * License shall appear in and shall apply to such copy and such sublicense, including without limitation all source and 
 * executable forms and on any user documentation, prefaced with the following words: "All or portions of this licensed 
 * product  have been obtained under license from The Board of Trustees of the Leland Stanford Junior University. and are 
 * subject to the following terms and conditions" AND any user interface to the Software or the "About" information display 
 * in the Software will display the following: "Powered by ePAD http://epad.stanford.edu;" (c) you preserve and maintain 
 * all applicable attributions, copyright notices and licenses included in or applicable to the Software; (d) modified 
 * versions of the Software must be clearly identified and marked as such, and must not be misrepresented as being the 
 * original Software; and (e) you consider making, but are under no obligation to make, the source code of any of your 
 * modifications to the Software freely available to others on an open source basis.
 *
 * 2. The license granted in this Software License includes without limitation the right to (i) incorporate the Software 
 * into your proprietary programs (subject to any restrictions applicable to such programs), (ii) add your own copyright 
 * statement to your modifications of the Software, and (iii) provide additional or different license terms and conditions 
 * in your sublicenses of modifications of the Software; provided that in each case your use, reproduction or distribution 
 * of such modifications otherwise complies with the conditions stated in this Software License.
 * 3. This Software License does not grant any rights with respect to third party software, except those rights that 
 * Stanford has been authorized by a third party to grant to you, and accordingly you are solely responsible for (i) 
 * obtaining any permissions from third parties that you need to use, reproduce, make derivative works of, display and 
 * distribute the Software, and (ii) informing your sublicensees, including without limitation your end-users, of their 
 * obligations to secure any such required permissions.
 * 4. You agree that you will use the Software in compliance with all applicable laws, policies and regulations including, 
 * but not limited to, those applicable to Personal Health Information ("PHI") and subject to the Institutional Review 
 * Board requirements of the your institution, if applicable. Licensee acknowledges and agrees that the Software is not 
 * FDA-approved, is intended only for research, and may not be used for clinical treatment purposes. Any commercialization 
 * of the Software is at the sole risk of you and the party or parties engaged in such commercialization. You further agree 
 * to use, reproduce, make derivative works of, display and distribute the Software in compliance with all applicable 
 * governmental laws, regulations and orders, including without limitation those relating to export and import control.
 * 5. You or your institution, as applicable, will indemnify, hold harmless, and defend Stanford against any third party 
 * claim of any kind made against Stanford arising out of or related to the exercise of any rights granted under this 
 * Agreement, the provision of Software, or the breach of this Agreement. Stanford provides the Software AS IS and WITH ALL 
 * FAULTS.  Stanford makes no representations and extends no warranties of any kind, either express or implied.  Among 
 * other things, Stanford disclaims any express or implied warranty in the Software:
 * (a)  of merchantability, of fitness for a particular purpose,
 * (b)  of non-infringement or 
 * (c)  arising out of any course of dealing.
 *
 * Title and copyright to the Program and any associated documentation shall at all times remain with Stanford, and 
 * Licensee agrees to preserve same. Stanford reserves the right to license the Program at any time for a fee.
 * 6. None of the names, logos or trademarks of Stanford or any of Stanford's affiliates or any of the Contributors, or any 
 * funding agency, may be used to endorse or promote products produced in whole or in part by operation of the Software or 
 * derived from or based on the Software without specific prior written permission from the applicable party.
 * 7. Any use, reproduction or distribution of the Software which is not in accordance with this Software License shall 
 * automatically revoke all rights granted to you under this Software License and render Paragraphs 1 and 2 of this 
 * Software License null and void.
 * 8. This Software License does not grant any rights in or to any intellectual property owned by Stanford or any 
 * Contributor except those rights expressly granted hereunder.
 *
 * PART B. CONTRIBUTION AGREEMENT - LICENSE TO STANFORD WITH RIGHT TO SUBLICENSE ("CONTRIBUTION AGREEMENT").
 * 1. As used in this Contribution Agreement, "you" means an individual providing a Contribution to ePAD and the 
 * institution or entity which employs or is otherwise affiliated with you.
 * 2. This Contribution Agreement applies to all Contributions made to ePAD at any time. By making a Contribution you 
 * represent that: (i) you are legally authorized and entitled by ownership or license to make such Contribution and to 
 * grant all licenses granted in this Contribution Agreement with respect to such Contribution; (ii) if your Contribution 
 * includes any patient data, all such data is de-identified in accordance with U.S. confidentiality and security laws and 
 * requirements, including but not limited to the Health Insurance Portability and Accountability Act (HIPAA) and its 
 * regulations, and your disclosure of such data for the purposes contemplated by this Agreement is properly authorized and 
 * in compliance with all applicable laws and regulations; and (iii) you have preserved in the Contribution all applicable 
 * attributions, copyright notices and licenses for any third party software or data included in the Contribution.
 * 3. Except for the licenses you grant in this Agreement, you reserve all right, title and interest in your Contribution.
 * 4. You hereby grant to Stanford, with the right to sublicense, a perpetual, worldwide, non-exclusive, no charge, 
 * royalty-free, irrevocable license to use, reproduce, make derivative works of, display and distribute the Contribution. 
 * If your Contribution is protected by patent, you hereby grant to Stanford, with the right to sublicense, a perpetual, 
 * worldwide, non-exclusive, no-charge, royalty-free, irrevocable license under your interest in patent rights embodied in 
 * the Contribution, to make, have made, use, sell and otherwise transfer your Contribution, alone or in combination with 
 * ePAD or otherwise.
 * 5. You acknowledge and agree that Stanford ham may incorporate your Contribution into ePAD and may make your 
 * Contribution as incorporated available to members of the public on an open source basis under terms substantially in 
 * accordance with the Software License set forth in Part A of this Agreement. You further acknowledge and agree that 
 * Stanford shall have no liability arising in connection with claims resulting from your breach of any of the terms of 
 * this Agreement.
 * 6. YOU WARRANT THAT TO THE BEST OF YOUR KNOWLEDGE YOUR CONTRIBUTION DOES NOT CONTAIN ANY CODE OBTAINED BY YOU UNDER AN 
 * OPEN SOURCE LICENSE THAT REQUIRES OR PRESCRIBES DISTRBUTION OF DERIVATIVE WORKS UNDER SUCH OPEN SOURCE LICENSE. (By way 
 * of non-limiting example, you will not contribute any code obtained by you under the GNU General Public License or other 
 * so-called "reciprocal" license.)
 *******************************************************************************/
package edu.stanford.epad.plugins.qifpwrapper;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.io.IOUtils;

import org.json.JSONObject;

import edu.stanford.epad.common.dicom.DCM4CHEEUtil;
import edu.stanford.epad.common.plugins.AbstractPluginServletHandler;
import edu.stanford.epad.common.plugins.PluginAIMUtil;
import edu.stanford.epad.common.plugins.PluginDicomUtil;
import edu.stanford.epad.common.plugins.PluginEventPoster;
import edu.stanford.epad.common.plugins.PluginHandler;
import edu.stanford.epad.common.plugins.PluginXNATUtil;
import edu.stanford.epad.common.plugins.impl.PluginAIMOptions;
import edu.stanford.epad.common.util.EPADConfig;
import edu.stanford.epad.common.util.EPADFileUtils;
import edu.stanford.epad.common.util.EPADLogger;
import edu.stanford.epad.common.util.Lexicon;
import edu.stanford.hakan.aim4api.base.AimException;
import edu.stanford.hakan.aim4api.base.CD;
import edu.stanford.hakan.aim4api.base.DicomImageReferenceEntity;
import edu.stanford.hakan.aim4api.base.DicomSegmentationEntity;
import edu.stanford.hakan.aim4api.base.Image;
import edu.stanford.hakan.aim4api.base.ImageAnnotationCollection;
import edu.stanford.hakan.aim4api.base.ImageCollection;
import edu.stanford.hakan.aim4api.base.ImageSeries;
import edu.stanford.hakan.aim4api.base.ImageStudy;

@PluginHandler
public class QIFPHandler extends AbstractPluginServletHandler {
    public static class EPADSessionResponse {
        public final int statusCode;
        public final String response;
        public final String message;

        public EPADSessionResponse(int responseCode, String response, String message) {
            this.statusCode = responseCode;
            this.response = response;
            this.message = message;
        }
    }

    private static final EPADLogger log = EPADLogger.getInstance();
    private static final String LOGIN_EXCEPTION_MESSAGE = "Internal login error";
    private static final String EPAD_UNAUTHORIZED_MESSAGE = "Unauthorized/login failed";
    private static final String EPAD_LOGIN_ERROR_MESSAGE = "Unexpected EPAD login response";
    private static HashMap<String, String[]> runningPlugins = null;
    //<instanceid,{aimID,projectID,workflowID}
    //runningPlugins.put(instanceId, new String[] {imageAnnotationCollection.getUniqueIdentifier().getRoot(), projectID, workflowID});

    @Override
    public void init() {
        log.info("qifp wrapper: init");
        runningPlugins = new HashMap<>();

    }

    @Override
    public void destroy() {
        log.info("qifp wrapper: destroy");
        runningPlugins = null;
    }

    @Override
    public int doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        //specificly for calling the plugin for recording results by StatusListenerHandler
        if (httpServletResponse == null) {
            String status = httpServletRequest.getParameter("status");
            String workFlowInstanceID = httpServletRequest.getParameter("workFlowInstanceID");
            String workFlowID = httpServletRequest.getParameter("workFlowID");
            log.info("plugin calling back Status:" + status + " WorkFlowID:" + workFlowID + " WorkFlowInstanceID:"
                    + workFlowInstanceID);
            if (status.equalsIgnoreCase("Completed"))
                saveTheFeatures(workFlowID, workFlowInstanceID);
            return HttpServletResponse.SC_OK;

        }
        //otherwise
        return doGet(httpServletRequest, httpServletResponse);
    }

    @Override
    public String getVersion() {
        return "1.0 - 2016-08-4";
    }

    @Override
    public String getName() {
        return "qifp";
    }

    @Override
    public String getAuthorsContactInfo() {
        return "Emel Alkim";
    }

    @Override
    public String getDescription() {
        return "QIFP Wrapper for triggering plugins in QIFP";
    }

    static int fileNum = 1;

    //copied from download util
    public static File generateZipFile(List<File> files, String dirPath) {

        File dir_file = new File(dirPath);
        File zip_file = new File(dirPath + "/temp_" + (fileNum++) + ".zip");
        int dir_l = dir_file.getAbsolutePath().length();

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(zip_file);
        } catch (FileNotFoundException e1) {
            log.warning("File not found", e1);
            return null;
        }

        ZipOutputStream zipout = new ZipOutputStream(fos);
        zipout.setLevel(1);
        for (int i = 0; i < files.size(); i++) {
            File f = (File) files.get(i);
            if (f.canRead()) {
                log.info("Adding file: " + f.getAbsolutePath());
                try {
                    zipout.putNextEntry(new ZipEntry(f.getAbsolutePath().substring(dir_l + 1)));
                } catch (Exception e) {
                    log.warning("Error adding to zip file", e);
                    return null;
                }
                BufferedInputStream fr;
                try {
                    fr = new BufferedInputStream(new FileInputStream(f));

                    byte buffer[] = new byte[0xffff];
                    int b;
                    while ((b = fr.read(buffer)) != -1)
                        zipout.write(buffer, 0, b);

                    fr.close();
                    zipout.closeEntry();

                } catch (Exception e) {
                    log.warning("Error closing zip file", e);
                    return null;
                }
            }
        }

        try {
            zipout.finish();
            fos.flush();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        log.info("file zipped and returning as " + zip_file.getAbsolutePath());
        return zip_file;

    }

    @Override
    public int doGet(HttpServletRequest request, HttpServletResponse response) {
        int statusCode;

        String aimID = request.getParameter("aimFile");
        String projectID = request.getParameter("projectID");
        if (aimID == null) {
            log.warning("qifp wrapper: warning - missing aimFile parameter");
            statusCode = HttpServletResponse.SC_BAD_REQUEST;
        } else if (projectID == null) {
            log.warning("qifp wrapper: warning - missing projectID parameter");
            statusCode = HttpServletResponse.SC_BAD_REQUEST;
        } else {
            PrintWriter responseStream = null;
            log.info("qifp wrapper: AIM ID " + aimID);
            log.info("qifp wrapper: Project ID " + projectID);

            try {
                ImageAnnotationCollection templateImageAnnotationCollection = PluginAIMUtil
                        .getImageAnnotationCollectionFromServer(aimID, projectID);
                responseStream = response.getWriter();

                if (templateImageAnnotationCollection != null) {
                    // String username = imageAnnotation.getListUser().get(0).getLoginName();
                    String event_status = "Started";
                    String pluginName = getName();
                    String sessionID = PluginXNATUtil.getJSessionIDFromRequest(request);
                    PluginAIMOptions pluginAIMOptions = new PluginAIMOptions(pluginName, sessionID,
                            templateImageAnnotationCollection);

                    // Notification for the GUI that the plugin has started
                    PluginEventPoster.postPluginEvent(event_status, pluginAIMOptions);

                    PluginAIMUtil.saveAnnotationToAnnotationsDirectory(templateImageAnnotationCollection);

                    /* Prepare inputs to invoke the plugin */

                    // Get DICOM path               
                    DicomImageReferenceEntity dicomImageReferenceEntity = (DicomImageReferenceEntity) templateImageAnnotationCollection
                            .getImageAnnotations().get(0).getImageReferenceEntityCollection().get(0);
                    ImageStudy imageStudy = dicomImageReferenceEntity.getImageStudy();
                    String studyUID = imageStudy.getInstanceUid().getRoot();
                    String seriesUID = imageStudy.getImageSeries().getInstanceUid().getRoot();
                    //               String imageUID = imageStudy.getImageSeries().getImageCollection().get(0).getSopInstanceUid().getRoot();

                    String username = templateImageAnnotationCollection.getUser().getLoginName().getValue();
                    if (username.equalsIgnoreCase("shared"))
                        username = null;
                    List<File> dicomFiles = new ArrayList<>();
                    List<String> imageUIDs = PluginDicomUtil.getDicomImageUIDsInSeries(studyUID, seriesUID,
                            sessionID, username);
                    for (String imageUID : imageUIDs) {
                        File dicomFile = File.createTempFile(imageUID, ".dcm");
                        DCM4CHEEUtil.downloadDICOMFileFromWADO(studyUID, seriesUID, imageUID, dicomFile);
                        log.info("qifp wrapper: adding image " + imageUID);
                        dicomFiles.add(dicomFile);
                    }

                    log.info("qifp wrapper: zipping dicom files");

                    File dicomZip = generateZipFile(dicomFiles, dicomFiles.get(0).getParent());

                    File dsoFile = null;
                    File dsoZip = null;
                    boolean result = false;
                    // Get DSO if any               
                    if (templateImageAnnotationCollection.getImageAnnotations().get(0)
                            .getSegmentationEntityCollection() != null
                            && templateImageAnnotationCollection.getImageAnnotations().get(0)
                                    .getSegmentationEntityCollection().size() != 0) {
                        DicomSegmentationEntity dicomSegmentationEntity = (DicomSegmentationEntity) templateImageAnnotationCollection
                                .getImageAnnotations().get(0).getSegmentationEntityCollection().get(0);
                        String dsoImageUID = dicomSegmentationEntity.getSopInstanceUid().getRoot();
                        log.info("qifp wrapper: dsoimageuid " + dsoImageUID);
                        dsoFile = File.createTempFile(dsoImageUID, ".dcm");
                        DCM4CHEEUtil.downloadDICOMFileFromWADO(studyUID, "*", dsoImageUID, dsoFile);

                        log.info("qifp wrapper: zipping dso files");
                        List<File> dsoFiles = new ArrayList<>();
                        dsoFiles.add(dsoFile);

                        dsoZip = generateZipFile(dsoFiles, dsoFile.getParent());
                        result = invokePlugin(templateImageAnnotationCollection, dicomZip, dsoZip, projectID);
                    } else {
                        log.warning("qifp wrapper: qifp requires dso");
                    }

                    if (result) {
                        event_status = "complete";
                        log.info("qifp wrapper: plugin execution complete");
                    } else {
                        event_status = "failed";
                        log.warning("qifp wrapper: plugin execution failed");
                    }

                    PluginEventPoster.postPluginEvent(event_status, pluginAIMOptions);
                } else {
                    responseStream.println("AIM file not found");
                    log.warning("qifp wrapper: Could not find AIM file");
                    statusCode = HttpServletResponse.SC_BAD_REQUEST;
                }
                statusCode = HttpServletResponse.SC_OK;
            } catch (IOException ioe) {
                printErrorPage(ioe, responseStream);
                log.warning("qifp wrapper: IO error invoking plugin", ioe);
                statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
            } catch (Exception e) {
                printErrorPage(e, responseStream);
                log.warning("qifp wrapper: Error invoking plugin", e);
                statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
            }
        }
        return statusCode;
    }

    private static String buildAuthorizationString(String username, String password) {
        String authString = username + ":" + password;
        byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
        String authStringEnc = new String(authEncBytes);

        return authStringEnc;
    }

    private static String buildEPADSessionURL(String epadHost, int port) {
        return buildEPADBaseURL(epadHost, port, "/qifp/session/");
    }

    private static String buildEPADBaseURL(String host, int port, String base) {
        return buildEPADBaseURL(host, port, base, "");
    }

    private static String buildEPADBaseURL(String host, int port, String base, String ext) {
        StringBuilder sb = new StringBuilder();
        if (!host.startsWith("http"))
            sb.append("http://");
        sb.append(host);
        if (port != -1)
            sb.append(":").append(port);
        sb.append(base);
        sb.append(ext);

        return sb.toString();
    }

    private static EPADSessionResponse getEPADSessionID(String username, String password, String epadHost,
            int port) {
        String epadSessionURL = buildEPADSessionURL(epadHost, port);
        HttpClient client = new HttpClient();
        PostMethod method = new PostMethod(epadSessionURL);
        String authString = buildAuthorizationString(username, password);
        EPADSessionResponse epadSessionResponse;
        int epadStatusCode;

        try {
            log.info("Invoking EPAD session service for user " + username + " at " + epadSessionURL);
            method.setRequestHeader("Authorization", "Basic " + authString);
            epadStatusCode = client.executeMethod(method);
            log.info("Successfully invoked EPAD session service for user " + username + "; status code = "
                    + epadStatusCode);
        } catch (IOException e) {
            log.warning("Error calling EPAD session service for user " + username, e);
            epadStatusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
        }

        try {
            if (epadStatusCode == HttpServletResponse.SC_OK || epadStatusCode == HttpServletResponse.SC_FOUND) {
                try {
                    StringBuilder sb = new StringBuilder();
                    InputStreamReader isr = null;
                    try {
                        isr = new InputStreamReader(method.getResponseBodyAsStream());
                        int read = 0;
                        char[] chars = new char[128];
                        while ((read = isr.read(chars)) > 0) {
                            sb.append(chars, 0, read);
                        }
                    } finally {
                        IOUtils.closeQuietly(isr);
                    }
                    String jsessionID = sb.toString();
                    epadSessionResponse = new EPADSessionResponse(HttpServletResponse.SC_OK, jsessionID, "");
                    log.debug("Session ID " + jsessionID + " generated for user " + username); // TODO temp
                } catch (IOException e) {
                    log.warning(LOGIN_EXCEPTION_MESSAGE, e);
                    epadSessionResponse = new EPADSessionResponse(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                            null, LOGIN_EXCEPTION_MESSAGE + ": " + e.getMessage());
                }
            } else if (epadStatusCode == HttpServletResponse.SC_UNAUTHORIZED) {
                log.warning(EPAD_UNAUTHORIZED_MESSAGE);
                epadSessionResponse = new EPADSessionResponse(epadStatusCode, null, EPAD_UNAUTHORIZED_MESSAGE);
            } else {
                log.warning(EPAD_LOGIN_ERROR_MESSAGE + "; EPAD status code = " + epadStatusCode);
                epadSessionResponse = new EPADSessionResponse(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null,
                        EPAD_LOGIN_ERROR_MESSAGE + "; EPAD status code = " + epadStatusCode);
            }
        } finally {
            method.releaseConnection();
        }
        return epadSessionResponse;
    }

    public static int invalidateRemoteEPADSessionID(String epadSessionID, String epadHost, int port) {
        String epadSessionURL = buildEPADSessionURL(epadHost, port);
        HttpClient client = new HttpClient();
        DeleteMethod method = new DeleteMethod(epadSessionURL);
        int epadStatusCode;

        method.setRequestHeader("Cookie", "JSESSIONID=" + epadSessionID);

        try {
            epadStatusCode = client.executeMethod(method);
        } catch (IOException e) {
            log.warning("Error calling EPAD session service to invalidate session ID", e);
            epadStatusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
        } finally {
            method.releaseConnection();
        }

        if (epadStatusCode != HttpServletResponse.SC_OK)
            log.warning("EPAD delete session call returned status code " + epadStatusCode);

        return epadStatusCode;
    }

    public static String sendRequest(String url, String epadSessionID, File dicomsZip, File dsosZip,
            String statusURL) throws Exception {
        HttpClient client = new HttpClient();
        PostMethod postMethod = new PostMethod(url);
        if (epadSessionID != null)
            postMethod.setRequestHeader("Cookie", "JSESSIONID=" + epadSessionID);
        try {
            Part[] parts = { new FilePart("dicoms", dicomsZip), new FilePart("dsos", dsosZip),
                    new StringPart("statusUrl", statusURL) };

            postMethod.setRequestEntity(new MultipartRequestEntity(parts, postMethod.getParams()));

            int response = client.executeMethod(postMethod);
            String responseStr = postMethod.getResponseBodyAsString();
            JSONObject responseJson = new JSONObject(responseStr);

            String instanceId = (String) responseJson.get("workflowInstanceID");

            if (response == HttpServletResponse.SC_OK)
                ;
            return instanceId;

        } catch (Exception e) {
            log.warning("Exception calling ePAD", e);
            return null;
        } finally {
            postMethod.releaseConnection();
        }

    }

    private String shortenHost(String host) {
        return host.split("\\.")[0];//needs to excape character as it uses regex
    }

    private boolean invokePlugin(ImageAnnotationCollection imageAnnotationCollection, File dicomZip, File dsoZip,
            String projectID) {

        try {

            String workflowID = "3DFeatureExtractionOld";
            //         String host="epad-prod8.stanford.edu";
            //         String url="http://"+host+":8090/qifp/v2/workflows/"+ workflowID +"/run/?projectID="+"epad-dev4";
            String statusUrl = buildEPADBaseURL(EPADConfig.xnatServer, EPADConfig.epadPort,
                    "/epad/statuslistener/");

            String runBase = "/qifp/v2/workflows/" + workflowID + "/run/?projectID="
                    + shortenHost(EPADConfig.xnatServer);
            String url = buildEPADBaseURL(EPADConfig.qifpServer, EPADConfig.qifpPort, runBase);

            log.info("qifp wrapper: connecting qifp");
            EPADSessionResponse resp = getEPADSessionID(EPADConfig.qifpUserName, EPADConfig.qifpUserPass,
                    EPADConfig.qifpServer, EPADConfig.qifpPort);
            String sessionID = resp.response;
            log.info("qifp wrapper: login response " + resp.response + " url " + url);

            //         log.info("qifp wrapper: check if files exist");

            log.info("qifp wrapper: send request");
            String instanceId = sendRequest(url, sessionID, dicomZip, dsoZip, statusUrl);
            if (instanceId != null) {//plugin request sent successfully
                runningPlugins.put(instanceId, new String[] {
                        imageAnnotationCollection.getUniqueIdentifier().getRoot(), projectID, workflowID });
            }
            log.info("qifp wrapper: disconnect");

            invalidateRemoteEPADSessionID(sessionID, EPADConfig.qifpServer, EPADConfig.qifpPort);

        } catch (Throwable t) {
            log.warning("qifp wrapper: Failed invoking plugin", t);
            return false;
        }
        return true;
    }

    private void printErrorPage(Throwable throwable, PrintWriter out) {
        if (out != null) {
            out.println("ERROR");
            outputException(throwable, out);
            out.flush();
        }
    }

    private String buildFileUrl(String workflowId, String instanceID) {
        String fileBase = "/qifp/v2/workflows/" + workflowId + "/instances/" + instanceID
                + "/files/?onlyFeatureResult=true&format=stream";
        return buildEPADBaseURL(EPADConfig.qifpServer, EPADConfig.qifpPort, fileBase);

    }

    private String[] getInfoOfInstance(String workflowId, String instanceID) {

        String[] info = runningPlugins.get(instanceID);
        if (info == null) {
            log.warning("Plugin " + instanceID + " not saved as running, has no information about it!");
            //for testing
            info = new String[] { "69.831.5531915320.26123.862964462.28970358.604159.9166759.8.5141", "dsotest",
                    "3DFeatureExtraction" };
            return info;
            //         return null;
        }
        log.info("the plugin info:instanceID:" + instanceID + " aimID:" + info[0] + " projectID:" + info[1]);
        return info;

    }

    public String updateName(String name) {
        Integer number = 1;
        Integer index = name.lastIndexOf("-");
        try {

            number = Integer.parseInt(name.substring(index + 1).trim());
            number++;
        } catch (Exception e) {
        }
        if (number != 1) {
            name = name.substring(0, index) + "-" + number;
        } else
            name = name + "-" + number;
        return name;
    }

    public void saveTheFeatures(String workflowId, String instanceID) {
        String[] info = getInfoOfInstance(workflowId, instanceID);
        Lexicon lex = Lexicon.getInstance();
        double featureVersion = 1.0;
        try {
            ImageAnnotationCollection imageAnnotationCollection = PluginAIMUtil
                    .getImageAnnotationCollectionFromServer(info[0], info[1]);
            EPADSessionResponse session = getEPADSessionID(EPADConfig.qifpUserName, EPADConfig.qifpUserPass,
                    EPADConfig.qifpServer, EPADConfig.qifpPort);
            try {
                //get features
                ArrayList<String[]> features = downloadFeaturesFromRemote(EPADConfig.qifpUserName,
                        buildFileUrl(workflowId, instanceID), session.response);
                log.info("Number of features:" + features.size());
                //create a new unique id for the annotation
                //temporary. Do not create a new aim
                //            log.info("new aimID:" + imageAnnotationCollection.refreshUniqueIdentifier());
                CD pluginLex = lex.get("99EPADP2");
                //set the template
                //the codevalue and codemeaning of template 
                CD typeCode = new CD("qifp", "epad-plugin", pluginLex.getCodeSystemName(),
                        pluginLex.getCodeSystemVersion());

                imageAnnotationCollection.getImageAnnotation().getListTypeCode().clear();
                imageAnnotationCollection.getImageAnnotation().addTypeCode(typeCode);
                //add the new features
                imageAnnotationCollection = PluginAIMUtil.addFeatures(imageAnnotationCollection, features,
                        featureVersion, pluginLex);
                log.info("constructed aim:" + imageAnnotationCollection.toStringXML());
                //do not change the name
                //            if (imageAnnotationCollection.getImageAnnotation().getName().getValue().contains(getName())){
                //               String newName=updateName(imageAnnotationCollection.getImageAnnotation().getName().getValue());
                //               imageAnnotationCollection.getImageAnnotation().getName().setValue(newName);
                //            } 
                //            else 
                //               imageAnnotationCollection.getImageAnnotation().getName().setValue(imageAnnotationCollection.getImageAnnotation().getName().getValue()+" by "+ getName());
                //save the annotation
                //temporary update the old one don't create new
                //            PluginAIMUtil.sendImageAnnotationToServer(imageAnnotationCollection, info[1], info[0]);
                PluginAIMUtil.sendImageAnnotationToServer(imageAnnotationCollection, info[1]);
                PluginAIMUtil.saveAnnotationToAnnotationsDirectory(imageAnnotationCollection);
                log.info("done with plugin with instance id:" + instanceID);
                runningPlugins.remove(instanceID);
            } catch (Exception e) {
                log.warning("Download file failed ", e);
            }

        } catch (AimException e) {
            log.warning("Aim api throw an exception:", e);
        }

    }

    public static ArrayList<String[]> downloadFeaturesFromRemote(String username, String epadURL,
            String epadSessionID) throws Exception {
        ArrayList<String[]> list = null;
        HttpClient client = new HttpClient();
        GetMethod method = new GetMethod(epadURL);
        method.setRequestHeader("Cookie", "JSESSIONID=" + epadSessionID);
        int statusCode = 0;
        try {
            log.info("Calling remote epad " + epadURL);
            statusCode = client.executeMethod(method);
        } catch (Exception e) {
            log.warning("Exception calling ePAD", e);
            statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
        }
        File uploadStoreDir = new File(
                EPADConfig.getEPADWebServerUploadDir() + "temp" + System.currentTimeMillis());
        uploadStoreDir.mkdirs();
        File zipfile = new File(uploadStoreDir, "epad.zip");
        long total = 0;
        if (statusCode == HttpServletResponse.SC_OK) {
            OutputStream outputStream = null;
            try {
                outputStream = new FileOutputStream(zipfile);
                InputStream inputStream = method.getResponseBodyAsStream();
                int read = 0;
                byte[] bytes = new byte[4096];
                while ((read = inputStream.read(bytes)) != -1) {
                    outputStream.write(bytes, 0, read);
                    total = total + read;
                }
            } finally {
                IOUtils.closeQuietly(outputStream);
                method.releaseConnection();
            }
            log.info("zip file path:" + zipfile.getAbsolutePath());
            EPADFileUtils.extractFolder(zipfile.getAbsolutePath());
            Collection<File> files = EPADFileUtils.getAllFilesWithExtension(uploadStoreDir, "csv", true);
            for (File file : files) //should be just the result features file
            {
                log.info("file:" + file.getName());
                list = addFeaturesToList(file);
            }
            log.debug("Remote EPAD download:" + total + " bytes");
        } else {
            log.warning("Remote EPAD URL:" + epadURL + " Status:" + statusCode);
        }
        //remove file so epad doesn't try to get it
        uploadStoreDir.delete();
        return list;
    }

    private static ArrayList<String[]> addFeaturesToList(File file) {
        ArrayList<String[]> list = new ArrayList<>();
        //read the lines and add to the list
        BufferedReader br = null;
        String line = "";
        String cvsSplitBy = ",";

        try {
            int i = 0;
            br = new BufferedReader(new FileReader(file));
            while ((line = br.readLine()) != null) {
                if (++i > 6) { //the first six lines are just patient and series information, skip them
                    // use comma as separator
                    String[] feature = line.split(cvsSplitBy);

                    list.add(feature);
                }

            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return list;
    }

    /**
     * Get the concurrent path if possible, otherwise get the absolute path.
     * 
     * @param file File
     * @return String path of file. Concurrent path if possible.
     */
    private static String getRealPath(File file) {
        try {
            return file.getCanonicalPath();
        } catch (IOException ioe) {
            return file.getAbsolutePath();
        }
    }

}