edu.dfci.cccb.mev.controllers.HeatmapController.java Source code

Java tutorial

Introduction

Here is the source code for edu.dfci.cccb.mev.controllers.HeatmapController.java

Source

/**
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package edu.dfci.cccb.mev.controllers;

import static java.lang.Integer.MAX_VALUE;
import static java.util.UUID.randomUUID;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.OK;
import static org.springframework.http.HttpStatus.UNSUPPORTED_MEDIA_TYPE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.HEAD;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;

import javax.servlet.http.HttpServletResponse;

import lombok.extern.log4j.Log4j;

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MultipartFile;

import ch.lambdaj.Lambda;

import edu.dfci.cccb.mev.domain.AnnotationNotFoundException;
import edu.dfci.cccb.mev.domain.AnnotationSearchTerm;
import edu.dfci.cccb.mev.domain.Heatmap;
import edu.dfci.cccb.mev.domain.Heatmap.ClusteringAlgorhythm;
import edu.dfci.cccb.mev.domain.Heatmap.LimmaOutput;
import edu.dfci.cccb.mev.domain.HeatmapNotFoundException;
import edu.dfci.cccb.mev.domain.Heatmaps;
import edu.dfci.cccb.mev.domain.InvalidDimensionException;
import edu.dfci.cccb.mev.domain.LimmaParameter;
import edu.dfci.cccb.mev.domain.LimmaResult;
import edu.dfci.cccb.mev.domain.MatrixAnnotation;
import edu.dfci.cccb.mev.domain.MatrixData;
import edu.dfci.cccb.mev.domain.MatrixSelection;
import edu.dfci.cccb.mev.domain.MatrixSummary;

/**
 * @author levk
 * 
 */
// TODO: This should go into a separate heatmap package
@Controller
@RequestMapping("/heatmap")
@Log4j
public class HeatmapController implements InitializingBean, Closeable {

    private @Autowired Heatmaps heatmaps;
    private @Autowired Heatmap.Builder heatmapBuilder;
    private Heatmaps global = new Heatmaps();

    // Summary

    @RequestMapping(value = "/{id}/summary")
    @ResponseBody
    public MatrixSummary summary(@PathVariable("id") String id) throws HeatmapNotFoundException {
        return get(id).getSummary();
    }

    // GET

    @RequestMapping(method = GET)
    @ResponseBody
    public Collection<String> get() {
        return new ArrayList<String>() {
            private static final long serialVersionUID = 1L;

            {
                addAll(heatmaps.list());
                addAll(global.list());
            }
        };
    }

    @RequestMapping(value = "/{id}/data/[{startRow:[0-9]+}:{endRow:[0-9]+},{startColumn:[0-9]+}:{endColumn:[0-9]+}]")
    @ResponseBody
    public MatrixData data(@PathVariable("id") String id, @PathVariable("startRow") int startRow,
            @PathVariable("endRow") int endRow, @PathVariable("startColumn") int startColumn,
            @PathVariable("endColumn") int endColumn) throws HeatmapNotFoundException {
        if (log.isDebugEnabled())
            log.debug("Serving request for data for heatmap " + id + " starting row " + startRow + " ending row "
                    + endRow + " startingColumn " + startColumn + " ending column " + endColumn);
        return get(id).getData(startRow, endRow, startColumn, endColumn);
    }

    @RequestMapping(value = "/{id}/data", method = GET)
    @ResponseBody
    @Deprecated
    // To be removed after 10/1/2013
    public MatrixData data(@PathVariable("id") String id,
            @RequestParam(value = "startRow", required = false) Integer startRow,
            @RequestParam(value = "endRow", required = false) Integer endRow,
            @RequestParam(value = "startColumn", required = false) Integer startColumn,
            @RequestParam(value = "endColumn", required = false) Integer endColumn)
            throws HeatmapNotFoundException {
        if (log.isDebugEnabled())
            log.debug("Serving request for data for heatmap " + id + " starting row " + startRow + " ending row "
                    + endRow + " startingColumn " + startColumn + " ending column " + endColumn);
        return (startRow == null || endRow == null || startColumn == null || endColumn == null)
                ? get(id).getData(0, MAX_VALUE, 0, MAX_VALUE)
                : get(id).getData(startRow, endRow, startColumn, endColumn);
    }

    @RequestMapping(value = "/{id}/annotation/{dimension}", method = GET)
    @ResponseBody
    public Collection<String> annotationTypes(@PathVariable("id") String id,
            @PathVariable("dimension") String dimension)
            throws HeatmapNotFoundException, InvalidDimensionException {
        if (isRow(dimension))
            return get(id).getRowAnnotationTypes();
        else if (isColumn(dimension))
            return get(id).getColumnAnnotationTypes();
        else
            throw new InvalidDimensionException(dimension);
    }

    @RequestMapping(value = "/{id}/annotation/{dimension}/{startIndex}-{endIndex}/{type}", method = GET)
    @ResponseBody
    public List<MatrixAnnotation<?>> annotation(@PathVariable("id") String id,
            @PathVariable("dimension") String dimension, @PathVariable("startIndex") int startIndex,
            @PathVariable("endIndex") int endIndex, @PathVariable("type") String type)
            throws HeatmapNotFoundException, InvalidDimensionException, AnnotationNotFoundException {
        if (isRow(dimension))
            return get(id).getRowAnnotation(startIndex, endIndex, type);
        else if (isColumn(dimension))
            return get(id).getColumnAnnotation(startIndex, endIndex, type);
        else
            throw new InvalidDimensionException(dimension);
    }

    @RequestMapping(value = "/{id}/annotation/{dimension}/{index}", method = GET)
    @ResponseBody
    public List<MatrixAnnotation<?>> annotation(@PathVariable("id") String id,
            @PathVariable("dimension") String dimension, @PathVariable("index") int index)
            throws HeatmapNotFoundException, InvalidDimensionException, AnnotationNotFoundException {
        if (isRow(dimension))
            return get(id).getRowAnnotation(index);
        else if (isColumn(dimension))
            return get(id).getColumnAnnotation(index);
        else
            throw new InvalidDimensionException(dimension);
    }

    @RequestMapping(value = "/{id}/selection/{dimension}", method = GET)
    @ResponseBody
    public Collection<String> selectionIds(@PathVariable("id") String id,
            @PathVariable("dimension") String dimension)
            throws HeatmapNotFoundException, InvalidDimensionException {
        if (isRow(dimension))
            return get(id).getRowSelectionIds();
        else if (isColumn(dimension))
            return get(id).getColumnSelectionIds();
        else
            throw new InvalidDimensionException(dimension);
    }

    @RequestMapping(value = "/{hm-id}/selection/{dimension}/{s-id}", method = GET)
    @ResponseBody
    public MatrixSelection selection(@PathVariable("hm-id") String heatmapId,
            @PathVariable("dimension") String dimension, @PathVariable("s-id") String selectionId,
            @RequestParam("start") int start, @RequestParam("end") int end)
            throws HeatmapNotFoundException, InvalidDimensionException {
        if (isRow(dimension))
            return get(heatmapId).getRowSelection(selectionId, start, end);
        else if (isColumn(dimension))
            return get(heatmapId).getColumnSelection(selectionId, start, end);
        else
            throw new InvalidDimensionException(dimension);
    }

    @RequestMapping(value = "/{id}/analysis/EuclidianClustering/{dimension}", method = GET)
    @ResponseBody
    // TODO: do a proper exception instead of IOException
    public Object cluster(@PathVariable("id") String id, @PathVariable("dimension") String dimension)
            throws HeatmapNotFoundException, InvalidDimensionException, IOException {
        Heatmap current = get(id);
        Heatmap clustered = null;
        if (isRow(dimension))
            clustered = current.clusterRows(ClusteringAlgorhythm.EUCLEDIAN);
        else if (isColumn(dimension))
            clustered = current.clusterColumns(ClusteringAlgorhythm.EUCLEDIAN);
        else
            throw new InvalidDimensionException(dimension);
        if (clustered != current) {
            String newId = id + "-" + dimension + "-clustered";
            heatmaps.put(newId, clustered);
            return newId;
        }
        if (isRow(dimension))
            return clustered.getRowClusters();
        else
            return clustered.getColumnClusters();
    }

    @RequestMapping(value = "/{id}/analysis/limma({dimension},{experiment},{control})/{output}", method = { GET,
            HEAD })
    @ResponseStatus(OK)
    public void limma(@PathVariable("id") String id, @PathVariable("experiment") String experiment,
            @PathVariable("control") String control, @PathVariable("output") String output,
            @PathVariable("dimension") String dimension, HttpServletResponse response)
            throws HeatmapNotFoundException, IOException, InvalidDimensionException {
        response.setContentType("text/plain");
        response.setHeader("Content-Disposition", "attachment;filename=" + output + ".txt");
        Heatmap heatmap = get(id);
        File limma;
        if (isRow(dimension)) {
            limma = heatmap.limmaRows(experiment, control, LimmaOutput.valueOf(output.toUpperCase()));
        } else if (isColumn(dimension)) {
            limma = heatmap.limmaColumns(experiment, control, LimmaOutput.valueOf(output.toUpperCase()));
        } else
            throw new InvalidDimensionException(dimension);
        IOUtils.copy(new FileInputStream(limma), response.getOutputStream());
        response.flushBuffer();
    }

    @RequestMapping(value = "/{id}/analysis/limmaView({dimension},{experiment},{control})/{output}", method = { GET,
            HEAD })
    @ResponseBody
    public List<LimmaResult> limmaView(@PathVariable("id") String id, @PathVariable("experiment") String experiment,
            @PathVariable("control") String control, @PathVariable("output") String output,
            @PathVariable("dimension") String dimension)
            throws HeatmapNotFoundException, IOException, InvalidDimensionException {
        if (isRow(dimension))
            return get(id).limmaRowsData(experiment, control, LimmaOutput.valueOf(output.toUpperCase()));
        else if (isColumn(dimension))
            return get(id).limmaColumnsData(experiment, control, LimmaOutput.valueOf(output.toUpperCase()));
        else
            throw new InvalidDimensionException(dimension);
    }

    @RequestMapping(value = "/{id}/analysis/limma({dimension},{experiment},{control})", method = HEAD)
    @ResponseStatus(OK)
    public void launchLimma(@PathVariable("id") String id, @PathVariable("dimension") String dimension,
            @PathVariable("experiment") String experiment, @PathVariable("control") String control)
            throws HeatmapNotFoundException, IOException, InvalidDimensionException {
        if (isRow(dimension))
            get(id).limmaRows(experiment, control, LimmaOutput.values()[0]);
        else if (isColumn(dimension))
            get(id).limmaColumns(experiment, control, LimmaOutput.values()[0]);
        else
            throw new InvalidDimensionException(dimension);
    }

    @RequestMapping(value = "/{id}/analysis/limma/{dimension}", method = GET)
    @ResponseBody
    public Collection<LimmaParameter> limmaList(@PathVariable("id") String id,
            @PathVariable("dimension") String dimension)
            throws HeatmapNotFoundException, InvalidDimensionException {
        if (isRow(dimension))
            return get(id).limmaCalculatedRows();
        else if (isColumn(dimension))
            return get(id).limmaCalculatedColumns();
        else
            throw new InvalidDimensionException(dimension);
    }

    @RequestMapping(value = "/{id}/download", method = GET)
    @ResponseStatus(OK)
    public void download(@PathVariable("id") String id, HttpServletResponse response)
            throws HeatmapNotFoundException, IOException, AnnotationNotFoundException {
        response.setContentType("text/plain");
        response.setHeader("Content-Disposition", "attachment;filename=" + id + ".txt");
        get(id).toStream(response.getOutputStream());
        response.flushBuffer();
    }

    // POST

    @RequestMapping(value = "/{id}/export/{dimension}", method = POST)
    @ResponseBody
    public String export(@PathVariable("id") String id, @PathVariable("dimension") String dimension,
            @RequestParam("selection") String[] selections)
            throws HeatmapNotFoundException, IOException, InvalidDimensionException {
        Heatmap source = get(id), result;
        if (isRow(dimension))
            result = source.exportRowSelections(selections);
        else if (isColumn(dimension))
            result = source.exportColumnSelections(selections);
        else
            throw new InvalidDimensionException(dimension);
        String name;
        heatmaps.put(name = id + "-" + Lambda.join(selections, "-"), result);
        return name;
    }

    @RequestMapping(params = "format=tsv", method = POST)
    @ResponseBody
    public String add(@RequestParam("filedata") MultipartFile data) throws InvalidHeatmapFormatException {
        String name = data.getOriginalFilename();
        String id = name;
        if (heatmaps.contains(id))
            for (int count = 1; heatmaps.contains(id = name + "-" + count); count++)
                ;
        put(id, data);
        return id;
    }

    @RequestMapping(value = "/{id}/annotation/{dimension}", method = { POST, PUT })
    @ResponseStatus(OK)
    public void annotate(@PathVariable("id") String id, @PathVariable("dimension") String dimension,
            @RequestParam("filedata") MultipartFile data)
            throws HeatmapNotFoundException, InvalidDimensionException, IOException {
        if (isRow(dimension))
            get(id).setRowAnnotations(data.getInputStream());
        else if (isColumn(dimension))
            get(id).setColumnAnnotations(data.getInputStream());
        else
            throw new InvalidDimensionException(dimension);
    }

    @RequestMapping(value = "/{id}/annotation/{dimension}/search", method = { POST, PUT })
    @ResponseBody
    public List<Integer> search(@PathVariable("id") String id, @PathVariable("dimension") String dimension,
            @RequestBody AnnotationSearchTerm[] search) throws HeatmapNotFoundException, InvalidDimensionException {
        if (isRow(dimension))
            return get(id).findByRow(search);
        else if (isColumn(dimension))
            return get(id).findByColumn(search);
        else
            throw new InvalidDimensionException(dimension);
    }

    @RequestMapping(value = "/{hm-id}/selection/{dimension}", method = POST)
    @ResponseBody
    public String select(@PathVariable("hm-id") String heatmapId, @PathVariable("dimension") String dimension,
            @RequestBody MatrixSelection selection)
            throws HeatmapNotFoundException, InvalidDimensionException, IndexOutOfBoundsException {
        String result = randomUUID().toString();
        select(heatmapId, dimension, result, selection);
        return result;
    }

    // PUT

    @RequestMapping(value = "/{id}", params = "format=tsv", method = PUT, produces = APPLICATION_JSON_VALUE)
    @ResponseStatus(OK)
    public void put(@PathVariable("id") String id, @RequestParam("filedata") MultipartFile data)
            throws InvalidHeatmapFormatException {
        try {
            Heatmap heatmap = heatmapBuilder.build(data);
            heatmaps.put(id, heatmap);
            if (log.isDebugEnabled())
                log.debug("Put heatmap " + heatmap + " keyed " + id);
        } catch (IOException | RuntimeException e) {
            throw new InvalidHeatmapFormatException(e);
        }
    }

    @RequestMapping(value = "/{hm-id}/selection/{dimension}/{s-id}", method = PUT)
    @ResponseStatus(OK)
    public void select(@PathVariable("hm-id") String heatmapId, @PathVariable("dimension") String dimension,
            @PathVariable("s-id") String selectionId, @RequestBody MatrixSelection selection)
            throws HeatmapNotFoundException, InvalidDimensionException, IndexOutOfBoundsException {
        if (isRow(dimension))
            get(heatmapId).setRowSelection(selectionId, selection);
        else if (isColumn(dimension))
            get(heatmapId).setColumnSelection(selectionId, selection);
        else
            throw new InvalidDimensionException(dimension);
    }

    // DELETE

    @RequestMapping(value = "/{id}", method = DELETE)
    @ResponseStatus(OK)
    public void delete(@PathVariable("id") String id) throws HeatmapNotFoundException {
        heatmaps.delete(id);
    }

    @RequestMapping(value = "/{hm-id}/selection/{dimension}/{s-id}", method = POST)
    @ResponseStatus(OK)
    public void delete(@PathVariable("hm-id") String heatmapId, @PathVariable("dimension") String dimension,
            @RequestParam("s-id") String selectionId)
            throws HeatmapNotFoundException, InvalidDimensionException, IndexOutOfBoundsException {
        if (isRow(dimension))
            get(heatmapId).deleteRowSelection(selectionId);
        else if (isColumn(dimension))
            get(heatmapId).deleteColumnSelections(selectionId);
        else
            throw new InvalidDimensionException(dimension);
    }

    // Exceptions

    @ExceptionHandler({ HeatmapNotFoundException.class, AnnotationNotFoundException.class,
            IndexOutOfBoundsException.class })
    @ResponseStatus(NOT_FOUND)
    @ResponseBody
    public String handleNotFoundException(Exception e) {
        log.warn("Unbound resource", e);
        return e.getLocalizedMessage();
    }

    @ExceptionHandler(InvalidDimensionException.class)
    @ResponseStatus(BAD_REQUEST)
    @ResponseBody
    public String handleBadRequestException(InvalidDimensionException e) {
        log.warn("Bad REST call", e);
        return e.getLocalizedMessage();
    }

    @ExceptionHandler(InvalidHeatmapFormatException.class)
    @ResponseStatus(UNSUPPORTED_MEDIA_TYPE)
    @ResponseBody
    public String handleBadDataException(InvalidHeatmapFormatException e) {
        log.warn("Bad upload data", e);
        return e.getLocalizedMessage();
    }

    // Helpers

    private boolean isRow(String dimension) {
        return "row".equals(dimension);
    }

    private boolean isColumn(String dimension) {
        return "column".equals(dimension);
    }

    private Heatmap get(String id) throws HeatmapNotFoundException {
        try {
            return heatmaps.get(id);
        } catch (HeatmapNotFoundException e) {
            try {
                return global.get(id);
            } catch (HeatmapNotFoundException e2) {
                throw e;
            }
        }
    }

    /* (non-Javadoc)
     * @see
     * org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */
    @Override
    public void afterPropertiesSet() throws Exception {
        new Thread() {
            public void run() {

                Properties definitions = new Properties() {
                    private static final long serialVersionUID = 1L;

                    {
                        try {
                            load(Heatmaps.class.getResourceAsStream("/configuration/heatmap.globals.properties"));
                        } catch (IOException e) {
                            log.warn("Unable to load global heatmaps", e);
                        }
                    }
                };

                Properties annotations = new Properties() {
                    private static final long serialVersionUID = 1L;

                    {
                        try {
                            load(Heatmaps.class
                                    .getResourceAsStream("/configuration/heatmap.globals.annotation.properties"));
                        } catch (NullPointerException | IOException e) {
                            log.warn("Unable to load global heatmap annotations");
                        }
                    }
                };

                for (String key : definitions.stringPropertyNames()) {
                    File data = new File(definitions.get(key).toString());
                    log.debug("Loading " + data + " as " + key);
                    if (!data.exists()) {
                        log.debug("File " + data + " not found");
                        continue;
                    } else
                        try {
                            log.debug(data + " found");
                            Heatmap heatmap;
                            global.put(key,
                                    heatmap = heatmapBuilder.build(new FileInputStream(data), data.length(), key));
                            log.debug("Loaded " + data);
                            String location;
                            try {
                                if ((location = annotations.getProperty(key + ".column")) != null
                                        && new File(location).exists())
                                    heatmap.setColumnAnnotations(new FileInputStream(location));
                                log.debug("Loaded column annotations for " + key + " from " + location);
                            } catch (IOException e) {
                                log.warn("Unable to load column annotations for " + key, e);
                            }
                            try {
                                if ((location = annotations.getProperty(key + ".row")) != null
                                        && new File(location).exists())
                                    heatmap.setRowAnnotations(new FileInputStream(location));
                                log.debug("Loaded row annotations for " + key + " from " + location);
                            } catch (IOException e) {
                                log.warn("Unable to load row annotations for " + key, e);
                            }
                        } catch (IOException e) {
                            log.warn("Unable to load global heatmap " + key + " data at " + data, e);
                        }
                }

                log.info("Finished loading global heatmaps");
            }
        }.start();
    }

    /* (non-Javadoc)
     * @see java.io.Closeable#close() */
    @Override
    public void close() throws IOException {
        global.close();
    }
}