/* * Copyright 2015-2016 OpenCB * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.opencb.opencga.core.common.networks; import org.apache.commons.codec.binary.Base64; import org.opencb.opencga.core.common.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.*; import java.util.regex.Pattern; public class Layout { protected Logger logger = LoggerFactory.getLogger(this.getClass()); protected Properties properties; protected String homePath; static HashSet<String> graphvizLayoutAlgorithms; static HashMap<String, String> graphvizOutputFormats; static { graphvizLayoutAlgorithms = new HashSet<String>(); graphvizLayoutAlgorithms.add("circo"); graphvizLayoutAlgorithms.add("dot"); graphvizLayoutAlgorithms.add("fdp"); graphvizLayoutAlgorithms.add("neato"); graphvizLayoutAlgorithms.add("osage"); graphvizLayoutAlgorithms.add("sfdp"); graphvizLayoutAlgorithms.add("twopi"); } static { graphvizOutputFormats = new HashMap<String, String>(); graphvizOutputFormats.put("dot", "text"); graphvizOutputFormats.put("jpg", "jpeg"); graphvizOutputFormats.put("jpeg", "jpeg"); graphvizOutputFormats.put("jpe", "jpeg"); graphvizOutputFormats.put("svg", "svg"); graphvizOutputFormats.put("svgz", "zip"); graphvizOutputFormats.put("png", "png"); graphvizOutputFormats.put("plain", "plain"); } public Layout() throws IOException { homePath = System.getenv("OPENCGA_HOME"); String utilsPath = homePath + "/conf/utils.properties"; properties = new Properties(); properties.load(Files.newInputStream(Paths.get(utilsPath))); } public LayoutResp layout(String layoutAlgorithm, String outputFormat, String dotData, String filename, String base64, String jsonpCallback) { LayoutResp resp = new LayoutResp(); logger.debug("LayoutWSServer: layout() method"); if (graphvizLayoutAlgorithms.contains(layoutAlgorithm)) { if (graphvizOutputFormats.containsKey(outputFormat)) { if (dotData != null && !dotData.equals("")) { logger.debug("Algorithm layout: " + layoutAlgorithm + ", output format: " + outputFormat + ", dot: " + dotData); try { // logger.info("defaultConfig:" + properties.toString()); Path randomFolder = Paths.get(properties.getProperty("TMP.FOLDER") + "/" + StringUtils.randomString(20) + "_layout"); logger.debug("Creating output folder: " + randomFolder); Files.createDirectory(randomFolder); String inputFile = randomFolder + "/input.dot"; String outputFile = randomFolder + "/" + filename + "." + outputFormat; Path inputPath = Paths.get(inputFile); Path outputPath = Paths.get(outputFile); logger.debug("Writting dot data file: " + inputFile); // Files.write(completedFilePath, Files.readAllBytes(partPath), StandardOpenOption.APPEND); Files.write(inputPath, dotData.getBytes(), StandardOpenOption.CREATE_NEW); // IOUtils.write(inputFile, dotData); int exitValue = executeGraphviz(new File(inputFile), layoutAlgorithm, outputFormat, new File(outputFile)); if (exitValue == 0 && Files.exists(outputPath)) { // FileUtils.checkFile(outputFile); if (base64 != null && base64.trim().equalsIgnoreCase("true")) { logger.debug("Encoding in Base64 the dot output file..."); byte[] binaryBytes = toByteArray(new FileInputStream(outputFile)); byte[] base64Bytes = Base64.encodeBase64(binaryBytes); String encodedString = new String(base64Bytes); if (jsonpCallback != null && !jsonpCallback.equals("")) { // return Response.ok("var " + jsonpCallback + " = (" + encodedString + ")", MediaType.APPLICATION_JSON_TYPE).build(); resp.setData("var " + jsonpCallback + " = (" + encodedString + ")"); resp.setType("json"); return resp; } else { // return Response.ok(encodedString, MediaType.TEXT_PLAIN).header("content-disposition","attachment; filename = "+filename+"."+outputFormat).build(); // return Response.ok(encodedString, MediaType.TEXT_PLAIN).build(); resp.setData(encodedString); resp.setType("text"); return resp; } } else { // returning the Graphviz output file byte[] bytes = toByteArray(new FileInputStream(new File(outputFile))); // return Response.ok(bytes, MediaType.APPLICATION_OCTET_STREAM).header("content-disposition","attachment; filename = "+filename+"."+outputFormat).build(); // return createOkResponse(bytes, MediaType.APPLICATION_OCTET_STREAM_TYPE, filename+"."+outputFormat); resp.setData(bytes); resp.setType("bytes"); resp.setFileName(filename + "." + outputFormat); return resp; } } else { // return Response.ok("Graphviz exit status not 0: '"+exitValue+"'", MediaType.TEXT_PLAIN).header("content-disposition","attachment; filename = "+filename+".err.log").build(); // return createOkResponse("Graphviz exit status not 0: '"+exitValue+"'", MediaType.TEXT_PLAIN_TYPE, filename+".err.log"); resp.setData("Graphviz exit status not 0: '" + exitValue + "'"); resp.setType("text"); resp.setFileName(filename + ".err.log"); return resp; } } catch (Exception e) { logger.error("Error in LayoutWSServer, layout() method: " + e); if (base64 != null && base64.trim().equalsIgnoreCase("true")) { // return Response.ok("Error in LayoutWSServer, layout() method:\n"+StringUtils.getStackTrace(e), MediaType.TEXT_PLAIN).build(); // return createOkResponse("Error in LayoutWSServer, layout() method:\n"+StringUtils.getStackTrace(e), MediaType.TEXT_PLAIN_TYPE); resp.setData("Error in LayoutWSServer, layout() method:\n" + e); resp.setType("text"); return resp; } else { // return Response.ok("Error in LayoutWSServer, layout() method:\n"+StringUtils.getStackTrace(e), MediaType.TEXT_PLAIN).header("content-disposition","attachment; filename = "+filename+".err.log").build(); // return createOkResponse("Error in LayoutWSServer, layout() method:\n"+StringUtils.getStackTrace(e), MediaType.TEXT_PLAIN_TYPE, filename+".err.log"); resp.setData("Error in LayoutWSServer, layout() method:\n" + e); resp.setType("text"); resp.setFileName(filename + ".err.log"); return resp; } } } else { if (base64 != null && base64.trim().equalsIgnoreCase("true")) { // return Response.ok("dot data '"+dotData+"' is not valid", MediaType.TEXT_PLAIN).build(); // return createOkResponse("dot data '"+dotData+"' is not valid", MediaType.TEXT_PLAIN_TYPE); resp.setData("dot data '" + dotData + "' is not valid"); resp.setType("text"); return resp; } else { // return Response.ok("dot data '"+dotData+"' is not valid", MediaType.TEXT_PLAIN).header("content-disposition","attachment; filename = "+filename+".err.log").build(); // return createOkResponse("dot data '"+dotData+"' is not valid", MediaType.TEXT_PLAIN_TYPE, filename+".err.log"); resp.setData("dot data '" + dotData + "' is not valid"); resp.setType("text"); resp.setFileName(filename + ".err.log"); return resp; } } } else { if (base64 != null && base64.trim().equalsIgnoreCase("true")) { // return Response.ok("Format '"+outputFormat+"' is not valid", MediaType.TEXT_PLAIN).build(); // return createOkResponse("Format '"+outputFormat+"' is not valid", MediaType.TEXT_PLAIN_TYPE); resp.setData("Format '" + outputFormat + "' is not valid"); resp.setType("text"); return resp; } else { // return Response.ok("Format '"+outputFormat+"' is not valid", MediaType.TEXT_PLAIN).header("content-disposition","attachment; filename = "+filename+".err.log").build(); // return createOkResponse("Format '"+outputFormat+"' is not valid", MediaType.TEXT_PLAIN_TYPE, filename+".err.log"); resp.setData("Format '" + outputFormat + "' is not valid"); resp.setType("text"); resp.setFileName(filename + ".err.log"); return resp; } } } else { if (base64 != null && base64.trim().equalsIgnoreCase("true")) { // return Response.ok("Algorithm '"+layoutAlgorithm+"' is not valid", MediaType.TEXT_PLAIN).build(); // return createOkResponse("Algorithm '"+layoutAlgorithm+"' is not valid", MediaType.TEXT_PLAIN_TYPE); resp.setData("Algorithm '" + layoutAlgorithm + "' is not valid"); resp.setType("text"); return resp; } else { // return Response.ok("Algorithm '"+layoutAlgorithm+"' is not valid", MediaType.TEXT_PLAIN).header("content-disposition","attachment; filename = "+filename+".err.log").build(); // return createOkResponse("Algorithm '"+layoutAlgorithm+"' is not valid", MediaType.TEXT_PLAIN_TYPE, filename+".err.log"); resp.setData("Algorithm '" + layoutAlgorithm + "' is not valid"); resp.setType("text"); resp.setFileName(filename + ".err.log"); return resp; } } } public LayoutResp coordinates(String layoutAlgorithm, String dotData, String jsonpCallback) { LayoutResp resp = new LayoutResp(); logger.debug("LayoutWSServer: coordinates() method"); if (graphvizLayoutAlgorithms.contains(layoutAlgorithm)) { StringBuilder sb = new StringBuilder("{"); try { Path randomFolder = Paths.get(properties.getProperty("TMP.FOLDER") + "/" + StringUtils.randomString(20) + "_layout"); logger.debug("Creating output folder: " + randomFolder); Files.createDirectory(randomFolder); // FileUtils.createDirectory(randomFolder); String inputFile = randomFolder + "/input.dot"; String outputFile = randomFolder + "/output.plain"; Path inputPath = Paths.get(inputFile); Path outputPath = Paths.get(outputFile); logger.debug("Writting dot data file: " + inputFile); // IOUtils.write(inputFile, dotData); Files.write(inputPath, dotData.getBytes(), StandardOpenOption.CREATE_NEW); int exitValue = executeGraphviz(new File(inputFile), layoutAlgorithm, "plain", new File(outputFile)); if (exitValue == 0 && Files.exists(outputPath)) { // FileUtils.checkFile(outputFile); // getting the coords form the file // Grep the file BufferedReader br = Files.newBufferedReader(outputPath, Charset.defaultCharset()); final Pattern pattern = Pattern.compile("^node.+"); String currentLine; List<String> lines = new ArrayList<>(); while ((currentLine = br.readLine()) != null) { if(pattern.matcher(currentLine).matches()) { lines.add(currentLine); } } // List<String> lines = IOUtils.grep(new File(outputFile), "^node.+"); String[] fields; double min = Double.POSITIVE_INFINITY; double max = Double.NEGATIVE_INFINITY; String[] ids = new String[lines.size()]; double[][] coords = new double[lines.size()][2]; for (int i = 0; i < lines.size(); i++) { fields = lines.get(i).split(" "); ids[i] = fields[1]; coords[i][0] = Double.parseDouble(fields[2]); coords[i][1] = Double.parseDouble(fields[3]); min = Math.min(min, Math.min(coords[i][0], coords[i][1])); max = Math.max(max, Math.max(coords[i][0], coords[i][1])); } // max needs to be calculated after subtract min max -= min; for (int i = 0; i < ids.length; i++) { sb.append("\"" + ids[i] + "\"").append(": {").append("\"id\":\"").append(ids[i]).append("\", \"x\": ").append((coords[i][0] - min) / max).append(", \"y\": ").append((coords[i][1] - min) / max).append("}"); if (i < ids.length - 1) { sb.append(", "); } } sb.append("}"); if (jsonpCallback != null && !jsonpCallback.equals("")) { // return Response.ok("var " + jsonpCallback + " = (" + sb.toString() + ")", MediaType.APPLICATION_JSON_TYPE).build(); // return createOkResponse("var " + jsonpCallback + " = (" + sb.toString() + ")", MediaType.APPLICATION_JSON_TYPE); resp.setData("var " + jsonpCallback + " = (" + sb.toString() + ")"); resp.setType("json"); return resp; } else { // return Response.ok(sb.toString(), MediaType.TEXT_PLAIN).build(); // return createOkResponse(sb.toString(), MediaType.TEXT_PLAIN_TYPE); resp.setData(sb.toString()); resp.setType("text"); return resp; } } else { // return Response.ok("Graphviz exit status not 0: '"+exitValue+"'", MediaType.TEXT_PLAIN).build(); // return createOkResponse("Graphviz exit status not 0: '"+exitValue+"'", MediaType.TEXT_PLAIN_TYPE); resp.setData("Graphviz exit status not 0: '" + exitValue + "'"); resp.setType("text"); return resp; } } catch (Exception e) { logger.error("Error in LayoutWSServer, layout() method: " + e); // return Response.ok("Error in LayoutWSServer, coordinates() method:\n"+StringUtils.getStackTrace(e), MediaType.TEXT_PLAIN).build(); // return createOkResponse("Error in LayoutWSServer, coordinates() method:\n"+StringUtils.getStackTrace(e), MediaType.TEXT_PLAIN_TYPE); resp.setData("Error in LayoutWSServer, coordinates() method:\n" + e); resp.setType("text"); return resp; } } else { // return Response.ok("Algorithm '"+layoutAlgorithm+"' is not valid", MediaType.TEXT_PLAIN).build(); // return createOkResponse("Algorithm '"+layoutAlgorithm+"' is not valid", MediaType.TEXT_PLAIN_TYPE); resp.setData("Algorithm '" + layoutAlgorithm + "' is not valid"); resp.setType("text"); return resp; } } private int executeGraphviz(File inputFile, String layoutAlgorithm, String outputFormat, File outputFile) throws IOException, InterruptedException { // FileUtils.checkFile(inputFile); // FileUtils.checkDirectory(outputFile.getParent()); if (inputFile.exists()) { throw new IOException("input file not exists"); } if (outputFile.getParentFile().exists()) { throw new IOException("output parent file not exists"); } String command = "dot -K" + layoutAlgorithm + " -T" + outputFormat + " -o" + outputFile + " " + inputFile; logger.debug("Graphviz command line: " + command); Process process = Runtime.getRuntime().exec(command); process.waitFor(); logger.debug("Graphviz exit status: " + process.exitValue()); return process.exitValue(); } protected byte[] toByteArray(InputStream inputStream) throws IOException { byte[] buffer = new byte[1024 * 4]; ByteArrayOutputStream output = new ByteArrayOutputStream(); int n = 0; while ((n = inputStream.read(buffer)) != -1) { output.write(buffer, 0, n); } output.flush(); return output.toByteArray(); } public class LayoutResp { private Object data; private String type, fileName; public LayoutResp() { } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } } }