Java tutorial
/** * 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(); } }