org.mskcc.cbio.portal.servlet.QueryBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.mskcc.cbio.portal.servlet.QueryBuilder.java

Source

/*
 * Copyright (c) 2015 Memorial Sloan-Kettering Cancer Center.
 *
 * This library 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. The software and documentation provided hereunder
 * is on an "as is" basis, and Memorial Sloan-Kettering Cancer Center has no
 * obligations to provide maintenance, support, updates, enhancements or
 * modifications. In no event shall Memorial Sloan-Kettering Cancer Center be
 * liable to any party for direct, indirect, special, incidental or
 * consequential damages, including lost profits, arising out of the use of this
 * software and its documentation, even if Memorial Sloan-Kettering Cancer
 * Center has been advised of the possibility of such damage.
 */

/*
 * This file is part of cBioPortal.
 *
 * cBioPortal is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License.
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.mskcc.cbio.portal.servlet;

import org.mskcc.cbio.portal.dao.*;
import org.mskcc.cbio.portal.util.*;
import org.mskcc.cbio.portal.model.*;
import org.mskcc.cbio.portal.web_api.*;
import org.mskcc.cbio.portal.util.AccessControl;

import org.apache.commons.lang.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.owasp.validator.html.PolicyException;

import org.springframework.security.core.userdetails.UserDetails;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.rmi.RemoteException;

/**
 * Central Servlet for building queries.
 */
public class QueryBuilder extends HttpServlet {
    public static final String CLIENT_TRANSPOSE_MATRIX = "transpose_matrix";
    public static final String CANCER_TYPES_INTERNAL = "cancer_types";
    public static final String PROFILE_LIST_INTERNAL = "profile_list";
    public static final String CASE_SETS_INTERNAL = "case_sets";
    public static final String CANCER_STUDY_ID = "cancer_study_id";
    public static final String PATIENT_CASE_SELECT = "patient_case_select";
    public static final String CANCER_STUDY_LIST = "cancer_study_list";
    public static final String HAS_SURVIVAL_DATA = "has_survival_data";
    public static final String GENETIC_PROFILE_IDS = "genetic_profile_ids";
    public static final String GENE_SET_CHOICE = "gene_set_choice";
    public static final String CASE_SET_ID = "case_set_id";
    public static final String CASE_IDS = "case_ids";
    public static final String CASE_IDS_KEY = "case_ids_key";
    public static final String SET_OF_CASE_IDS = "set_of_case_ids";
    public static final String CLINICAL_PARAM_SELECTION = "clinical_param_selection";
    public static final String GENE_LIST = "gene_list";
    public static final String ACTION_NAME = "Action";
    public static final String XDEBUG = "xdebug";
    public static final String ACTION_SUBMIT = "Submit";
    public static final String STEP1_ERROR_MSG = "step1_error_msg";
    public static final String STEP2_ERROR_MSG = "step2_error_msg";
    public static final String STEP3_ERROR_MSG = "step3_error_msg";
    public static final String STEP4_ERROR_MSG = "step4_error_msg";
    public static final String PROFILE_DATA_SUMMARY = "profile_data_summary";
    public static final String DOWNLOAD_LINKS = "download_links";
    public static final String OUTPUT = "output";
    public static final String HTML_TITLE = "html_title";
    public static final String TAB_INDEX = "tab_index";
    public static final String TAB_DOWNLOAD = "tab_download";
    public static final String TAB_VISUALIZE = "tab_visualize";
    public static final String USER_ERROR_MESSAGE = "user_error_message";
    public static final String ATTRIBUTE_URL_BEFORE_FORWARDING = "ATTRIBUTE_URL_BEFORE_FORWARDING";
    public static final String Z_SCORE_THRESHOLD = "Z_SCORE_THRESHOLD";
    public static final String RPPA_SCORE_THRESHOLD = "RPPA_SCORE_THRESHOLD";
    public static final String MRNA_PROFILES_SELECTED = "MRNA_PROFILES_SELECTED";
    public static final String COMPUTE_LOG_ODDS_RATIO = "COMPUTE_LOG_ODDS_RATIO";
    public static final int MUTATION_DETAIL_LIMIT = 100;
    public static final String MUTATION_DETAIL_LIMIT_REACHED = "MUTATION_DETAIL_LIMIT_REACHED";
    public static final String XDEBUG_OBJECT = "xdebug_object";
    public static final String ONCO_PRINT_HTML = "oncoprint_html";
    public static final String INDEX_PAGE = "index.do";
    public static final String DATA_PRIORITY = "data_priority";
    public static final String SELECTED_PATIENT_SAMPLE_ID_MAP = "selected_patient_sample_id_map";
    public static final String DB_VERSION = "db_version";
    public static final String DB_ERROR = "db_error";
    private static final String DB_CONNECT_ERROR = ("An error occurred while trying to connect to the database."
            + "  This could happen if the database does not contain any cancer studies.");

    private static Log LOG = LogFactory.getLog(QueryBuilder.class);

    public static final String CANCER_TYPES_MAP = "cancer_types_map";

    private ServletXssUtil servletXssUtil;

    // class which process access control to cancer studies
    private AccessControl accessControl;

    /**
     * Initializes the servlet.
     *
     * @throws ServletException Serlvet Init Error.
     */
    public void init() throws ServletException {
        super.init();
        try {
            servletXssUtil = ServletXssUtil.getInstance();
            accessControl = SpringUtil.getAccessControl();
        } catch (PolicyException e) {
            throw new ServletException(e);
        }
    }

    /**
     * Handles HTTP GET Request.
     *
     * @param httpServletRequest  Http Servlet Request Object.
     * @param httpServletResponse Http Servlet Response Object.
     * @throws ServletException Servlet Error.
     * @throws IOException      IO Error.
     */
    protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws ServletException, IOException {
        doPost(httpServletRequest, httpServletResponse);
    }

    /**
     * Handles HTTP POST Request.
     *
     * @param httpServletRequest  Http Servlet Request Object.
     * @param httpServletResponse Http Servlet Response Object.
     * @throws ServletException Servlet Error.
     * @throws IOException      IO Error.
     */
    protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws ServletException, IOException {

        XDebug xdebug = new XDebug(httpServletRequest);
        xdebug.startTimer();

        xdebug.logMsg(this, "Attempting to initiate new user query.");

        if (httpServletRequest.getRequestURL() != null) {
            httpServletRequest.setAttribute(ATTRIBUTE_URL_BEFORE_FORWARDING,
                    httpServletRequest.getRequestURL().toString());
        }

        //  Get User Selected Action
        String action = httpServletRequest.getParameter(ACTION_NAME);

        String patientCaseSelect = httpServletRequest.getParameter(PATIENT_CASE_SELECT);

        //  Get User Selected Cancer Type
        String cancerTypeId = httpServletRequest.getParameter(CANCER_STUDY_ID);

        //  Get User Selected Genetic Profiles
        HashSet<String> geneticProfileIdSet = getGeneticProfileIds(httpServletRequest, xdebug);

        //  Get User Defined Gene List
        String geneList = httpServletRequest.getParameter(GENE_LIST);
        if (httpServletRequest instanceof XssRequestWrapper) {
            geneList = ((XssRequestWrapper) httpServletRequest).getRawParameter(GENE_LIST);
        }
        geneList = servletXssUtil.getCleanInput(geneList);
        httpServletRequest.setAttribute(GENE_LIST, geneList);

        //  Get all Cancer Types
        try {
            List<CancerStudy> cancerStudyList = accessControl.getCancerStudies();

            if (cancerTypeId == null) {
                cancerTypeId = cancerStudyList.get(0).getCancerStudyStableId();
            }

            httpServletRequest.setAttribute(CANCER_STUDY_ID, cancerTypeId);
            httpServletRequest.setAttribute(CANCER_TYPES_INTERNAL, cancerStudyList);

            //  Get Genetic Profiles for Selected Cancer Type
            ArrayList<GeneticProfile> profileList = GetGeneticProfiles.getGeneticProfiles(cancerTypeId);
            httpServletRequest.setAttribute(PROFILE_LIST_INTERNAL, profileList);

            //  Get Patient Sets for Selected Cancer Type
            xdebug.logMsg(this, "Using Cancer Study ID:  " + cancerTypeId);
            ArrayList<SampleList> sampleSets = GetSampleLists.getSampleLists(cancerTypeId);
            xdebug.logMsg(this, "Total Number of Patient Sets:  " + sampleSets.size());
            SampleList sampleSet = new SampleList();
            sampleSet.setName("User-defined Patient List");
            sampleSet.setDescription("User defined patient list.");
            sampleSet.setStableId("-1");
            sampleSets.add(sampleSet);
            httpServletRequest.setAttribute(CASE_SETS_INTERNAL, sampleSets);

            //  Get User Selected Patient Set
            String sampleSetId = httpServletRequest.getParameter(CASE_SET_ID);
            if (sampleSetId != null) {
                httpServletRequest.setAttribute(CASE_SET_ID, sampleSetId);
            } else {
                if (sampleSets.size() > 0) {
                    SampleList zeroSet = sampleSets.get(0);
                    httpServletRequest.setAttribute(CASE_SET_ID, zeroSet.getStableId());
                }
            }
            String sampleIds = httpServletRequest.getParameter(CASE_IDS);
            // TODO allowing only new line and tab chars, getRawParameter may be vulnerable here...
            if (sampleIds != null) {
                sampleIds = sampleIds.replaceAll("\\\\n", "\n").replaceAll("\\\\t", "\t");
            }

            httpServletRequest.setAttribute(XDEBUG_OBJECT, xdebug);

            String dbPortalVersion = GlobalProperties.getDbVersion();
            String dbVersion = DaoInfo.getVersion();
            LOG.info("version - " + dbPortalVersion);
            LOG.info("version - " + dbVersion);
            if (!dbPortalVersion.equals(dbVersion)) {
                String extraMessage = "";
                //extra message for the cases where property is missing (will happen often in transition period to this new versioning model):
                if (dbPortalVersion.equals("0"))
                    extraMessage = "The db.version property also not found in your portal.properties file. This new property needs to be added by the administrator.";
                httpServletRequest.setAttribute(DB_ERROR, "Current DB Version: " + dbVersion + "<br/>"
                        + "DB version expected by Portal: " + dbPortalVersion + "<br/>" + extraMessage);
            }

            boolean errorsExist = validateForm(action, profileList, geneticProfileIdSet, sampleSetId, sampleIds,
                    httpServletRequest);
            if (action != null && action.equals(ACTION_SUBMIT) && (!errorsExist)) {

                processData(cancerTypeId, geneList, geneticProfileIdSet, profileList, sampleSetId, sampleIds,
                        sampleSets, patientCaseSelect, getServletContext(), httpServletRequest, httpServletResponse,
                        xdebug);
            } else {
                if (errorsExist) {
                    httpServletRequest.setAttribute(QueryBuilder.USER_ERROR_MESSAGE,
                            "Please fix the errors below.");
                }
                RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsp/index.jsp");
                dispatcher.forward(httpServletRequest, httpServletResponse);
            }
        } catch (RemoteException e) {
            xdebug.logMsg(this, "Got Remote Exception:  " + e.getMessage());
            forwardToErrorPage(httpServletRequest, httpServletResponse, DB_CONNECT_ERROR, xdebug);
        } catch (DaoException e) {
            xdebug.logMsg(this, "Got Database Exception:  " + e.getMessage());
            forwardToErrorPage(httpServletRequest, httpServletResponse, DB_CONNECT_ERROR, xdebug);
        } catch (ProtocolException e) {
            xdebug.logMsg(this, "Got Protocol Exception:  " + e.getMessage());
            forwardToErrorPage(httpServletRequest, httpServletResponse, DB_CONNECT_ERROR, xdebug);
        }
    }

    /**
     * Gets all Genetic Profile IDs.
     *
     * These values are passed with parameter names like this:
     *
     * genetic_profile_ids
     * genetic_profile_ids_MUTATION
     * genetic_profile_ids_MUTATION_EXTENDED
     * genetic_profile_ids_COPY_NUMBER_ALTERATION
     * genetic_profile_ids_MRNA_EXPRESSION
     *
     *
     * @param httpServletRequest HTTPServlet Request.
     * @return HashSet of GeneticProfileIDs.
     */
    private HashSet<String> getGeneticProfileIds(HttpServletRequest httpServletRequest, XDebug xdebug) {
        HashSet<String> geneticProfileIdSet = new HashSet<String>();
        Enumeration nameEnumeration = httpServletRequest.getParameterNames();
        while (nameEnumeration.hasMoreElements()) {
            String currentName = (String) nameEnumeration.nextElement();
            if (currentName.startsWith(GENETIC_PROFILE_IDS)) {
                String geneticProfileIds[] = httpServletRequest.getParameterValues(currentName);
                if (geneticProfileIds != null && geneticProfileIds.length > 0) {
                    for (String geneticProfileId : geneticProfileIds) {
                        xdebug.logMsg(this,
                                "Received Genetic Profile ID:  " + currentName + ":  " + geneticProfileId);
                        geneticProfileIdSet.add(geneticProfileId);
                    }
                }
            }
        }
        httpServletRequest.setAttribute(GENETIC_PROFILE_IDS, geneticProfileIdSet);
        return geneticProfileIdSet;
    }

    /**
     * process a good request
     * 
    */
    private void processData(String cancerStudyStableId, String geneList, HashSet<String> geneticProfileIdSet,
            ArrayList<GeneticProfile> profileList, String sampleSetId, String sampleIds,
            ArrayList<SampleList> sampleSetList, String patientCaseSelect, ServletContext servletContext,
            HttpServletRequest request, HttpServletResponse response, XDebug xdebug)
            throws IOException, ServletException, DaoException {

        request.setAttribute(PATIENT_CASE_SELECT, patientCaseSelect);

        HashSet<String> setOfSampleIds = null;

        String sampleIdsKey = null;

        // user-specified patients, but patient_ids parameter is missing,
        // so try to retrieve sample_ids by using sample_ids_key parameter.
        // this is required for survival plot requests  
        if (sampleSetId.equals("-1") && sampleIds == null) {
            sampleIdsKey = request.getParameter(CASE_IDS_KEY);

            if (sampleIdsKey != null) {
                sampleIds = SampleSetUtil.getSampleIds(sampleIdsKey);
            }
        }

        if (!sampleSetId.equals("-1")) {
            for (SampleList sampleSet : sampleSetList) {
                if (sampleSet.getStableId().equals(sampleSetId)) {
                    sampleIds = sampleSet.getSampleListAsString();
                    setOfSampleIds = new HashSet<String>(sampleSet.getSampleList());
                    break;
                }
            }
        }
        //if user specifies patients, add these to hashset, and send to GetMutationData
        else if (sampleIds != null) {
            String[] sampleIdSplit = sampleIds.split("\\s+");
            setOfSampleIds = new HashSet<String>();

            for (String sampleID : sampleIdSplit) {
                if (null != sampleID) {
                    setOfSampleIds.add(sampleID);
                }
            }

            sampleIds = sampleIds.replaceAll("\\s+", " ");
        }

        if (setOfSampleIds == null || setOfSampleIds.isEmpty()) {
            redirectStudyUnavailable(request, response);
        }

        request.setAttribute(SET_OF_CASE_IDS, sampleIds);

        // Map user selected samples Ids to patient Ids
        HashMap<String, String> patientSampleIdMap = new HashMap<String, String>();
        CancerStudy selectedCancerStudy = DaoCancerStudy.getCancerStudyByStableId(cancerStudyStableId);
        int cancerStudyInternalId = selectedCancerStudy.getInternalId();
        Iterator<String> itr = setOfSampleIds.iterator();
        while (itr.hasNext()) {
            String sampleId = itr.next();
            ArrayList<String> sampleIdList = new ArrayList<String>();
            sampleIdList.add(sampleId);

            Sample sample = DaoSample.getSampleByCancerStudyAndSampleId(cancerStudyInternalId, sampleId);
            Patient patient = DaoPatient.getPatientById(sample.getInternalPatientId());
            patientSampleIdMap.put(sampleId, patient.getStableId());
        }
        request.setAttribute(SELECTED_PATIENT_SAMPLE_ID_MAP, patientSampleIdMap);

        if (sampleIdsKey == null) {
            sampleIdsKey = SampleSetUtil.shortenSampleIds(sampleIds);
        }

        // retrieve information about the cancer types
        Map<String, List<String>> cancerTypeInfo = DaoClinicalData.getCancerTypeInfo(cancerStudyInternalId);
        request.setAttribute(CANCER_TYPES_MAP, cancerTypeInfo);

        // this will create a key even if the patient set is a predefined set,
        // because it is required to build a patient id string in any case
        request.setAttribute(CASE_IDS_KEY, sampleIdsKey);

        Iterator<String> profileIterator = geneticProfileIdSet.iterator();
        ArrayList<DownloadLink> downloadLinkSet = new ArrayList<>();
        while (profileIterator.hasNext()) {
            String profileId = profileIterator.next();
            GeneticProfile profile = GeneticProfileUtil.getProfile(profileId, profileList);
            if (null == profile) {
                continue;
            }
            GetProfileData remoteCall = new GetProfileData(profile,
                    new ArrayList<>(Arrays.asList(geneList.split(" "))), StringUtils.join(setOfSampleIds, " "));
            DownloadLink downloadLink = new DownloadLink(profile,
                    new ArrayList<>(Arrays.asList(geneList.split(" "))), sampleIds, remoteCall.getRawContent());
            downloadLinkSet.add(downloadLink);
        }

        request.getSession().setAttribute(DOWNLOAD_LINKS, downloadLinkSet);
        String tabIndex = request.getParameter(QueryBuilder.TAB_INDEX);
        if (tabIndex != null && tabIndex.equals(QueryBuilder.TAB_VISUALIZE)) {
            double zScoreThreshold = ZScoreUtil.getZScore(geneticProfileIdSet, profileList, request);
            double rppaScoreThreshold = ZScoreUtil.getRPPAScore(request);
            request.setAttribute(Z_SCORE_THRESHOLD, zScoreThreshold);
            request.setAttribute(RPPA_SCORE_THRESHOLD, rppaScoreThreshold);

            // Store download links in session (for possible future retrieval).
            RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsp/visualize.jsp");
            dispatcher.forward(request, response);
        } else if (tabIndex != null && tabIndex.equals(QueryBuilder.TAB_DOWNLOAD)) {
            ShowData.showDataAtSpecifiedIndex(servletContext, request, response, 0, xdebug);
        }
    }

    private void redirectStudyUnavailable(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setAttribute(QueryBuilder.USER_ERROR_MESSAGE,
                "The selected cancer study is currently being updated, please try back later.");
        RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsp/index.jsp");
        dispatcher.forward(request, response);
    }

    /**
     * validate the portal web input form.
     */
    private boolean validateForm(String action, ArrayList<GeneticProfile> profileList,
            HashSet<String> geneticProfileIdSet, String sampleSetId, String sampleIds,
            HttpServletRequest httpServletRequest) throws DaoException {
        boolean errorsExist = false;
        String tabIndex = httpServletRequest.getParameter(QueryBuilder.TAB_INDEX);
        if (action != null) {
            if (action.equals(ACTION_SUBMIT)) {
                // is user authorized for the study
                String cancerStudyIdentifier = (String) httpServletRequest.getAttribute(CANCER_STUDY_ID);
                cancerStudyIdentifier = StringEscapeUtils.escapeJavaScript(cancerStudyIdentifier);

                if (accessControl.isAccessibleCancerStudy(cancerStudyIdentifier).size() != 1) {
                    httpServletRequest.setAttribute(STEP1_ERROR_MSG,
                            "You are not authorized to view the cancer study with id: '" + cancerStudyIdentifier
                                    + "'. ");
                    errorsExist = true;
                } else {
                    UserDetails ud = accessControl.getUserDetails();
                    if (ud != null) {
                        LOG.info("QueryBuilder.validateForm: Query initiated by user: " + ud.getUsername());
                    }
                }

                if (geneticProfileIdSet.size() == 0) {
                    if (tabIndex == null || tabIndex.equals(QueryBuilder.TAB_DOWNLOAD)) {
                        httpServletRequest.setAttribute(STEP2_ERROR_MSG, "Please select a genetic profile below. ");
                    } else {
                        httpServletRequest.setAttribute(STEP2_ERROR_MSG,
                                "Please select one or more genetic profiles below. ");
                    }
                    errorsExist = true;
                }

                // user-defined patient set
                if (sampleIds != null && sampleSetId != null && sampleSetId.equals("-1")) {
                    // empty patient list
                    if (sampleIds.trim().length() == 0) {
                        httpServletRequest.setAttribute(STEP3_ERROR_MSG, "Please enter at least one ID below. ");

                        errorsExist = true;
                    } else {
                        List<String> invalidSamples = SampleSetUtil.validateSampleSet(cancerStudyIdentifier,
                                sampleIds);

                        String sampleSetErrMsg = "Invalid samples(s) for the selected cancer study:";

                        // non-empty list, but contains invalid patient IDs
                        if (invalidSamples.size() > 0) {
                            // append patient ids to the message
                            for (String sampleId : invalidSamples) {
                                sampleSetErrMsg += " " + sampleId;
                            }

                            httpServletRequest.setAttribute(STEP3_ERROR_MSG, sampleSetErrMsg);

                            errorsExist = true;
                        }
                    }
                }

                //  Additional validation rules
                //  If we have selected mRNA Expression Data Check Box, but failed to
                //  select an mRNA profile, this is an error.
                String mRNAProfileSelected = httpServletRequest.getParameter(QueryBuilder.MRNA_PROFILES_SELECTED);
                if (mRNAProfileSelected != null && mRNAProfileSelected.equalsIgnoreCase("on")) {

                    //  Make sure that at least one of the mRNA profiles is selected
                    boolean mRNAProfileRadioSelected = false;
                    for (int i = 0; i < profileList.size(); i++) {
                        GeneticProfile geneticProfile = profileList.get(i);
                        if (geneticProfile.getGeneticAlterationType() == GeneticAlterationType.MRNA_EXPRESSION
                                && geneticProfileIdSet.contains(geneticProfile.getStableId())) {
                            mRNAProfileRadioSelected = true;
                        }
                    }
                    if (mRNAProfileRadioSelected == false) {
                        httpServletRequest.setAttribute(STEP2_ERROR_MSG, "Please select an mRNA profile.");
                        errorsExist = true;
                    }
                }
            }
        }

        return errorsExist;
    }

    private void forwardToErrorPage(HttpServletRequest request, HttpServletResponse response, String userMessage,
            XDebug xdebug) throws ServletException, IOException {
        request.setAttribute("xdebug_object", xdebug);
        request.setAttribute(USER_ERROR_MESSAGE, userMessage);
        RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/WEB-INF/jsp/error.jsp");
        dispatcher.forward(request, response);
    }
}