ReportBuilder.scala 8.03 KB
Newer Older
Peter van 't Hof's avatar
Peter van 't Hof committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * Biopet is built on top of GATK Queue for building bioinformatic
 * pipelines. It is mainly intended to support LUMC SHARK cluster which is running
 * SGE. But other types of HPC that are supported by GATK Queue (such as PBS)
 * should also be able to execute Biopet tools and pipelines.
 *
 * Copyright 2014 Sequencing Analysis Support Core - Leiden University Medical Center
 *
 * Contact us at: sasc@lumc.nl
 *
 * A dual licensing mode is applied. The source code within this project that are
 * not part of GATK Queue is freely available for non-commercial use under an AGPL
 * license; For commercial users or users who do not want to follow the AGPL
 * license, please contact us to obtain a separate license.
 */
16
17
package nl.lumc.sasc.biopet.core.report

18
import java.io._
19
20

import nl.lumc.sasc.biopet.core.summary.Summary
21
import nl.lumc.sasc.biopet.core.{ Logging, ToolCommand, ToolCommandFuntion }
22
import nl.lumc.sasc.biopet.utils.IoUtils
Peter van 't Hof's avatar
Peter van 't Hof committed
23
24
import org.broadinstitute.gatk.utils.commandline.Input
import org.fusesource.scalate.{ TemplateEngine, TemplateSource }
Peter van 't Hof's avatar
Peter van 't Hof committed
25
import scala.collection.mutable
26
27

/**
28
29
30
 * This trait is meant to make an extension for a report object
 *
 * @author pjvan_thof
31
 */
32
trait ReportBuilderExtension extends ToolCommandFuntion {
33

34
  /** Report builder object */
35
36
37
38
39
  val builder: ReportBuilder

  @Input(required = true)
  var summaryFile: File = _

40
  /** OutputDir for the report  */
41
42
  var outputDir: File = _

43
  /** Arguments that are passed on the commandline */
44
45
  var args: Map[String, String] = Map()

Peter van 't Hof's avatar
Peter van 't Hof committed
46
  override def defaultCoreMemory = 3.0
47

48
49
  override def beforeGraph(): Unit = {
    super.beforeGraph()
50
51
52
53
    jobOutputFile = new File(outputDir, ".report.log.out")
    javaMainClass = builder.getClass.getName.takeWhile(_ != '$')
  }

54
  /** Command to generate the report */
55
56
57
58
  override def commandLine: String = {
    super.commandLine +
      required("--summary", summaryFile) +
      required("--outputDir", outputDir) +
Peter van 't Hof's avatar
Peter van 't Hof committed
59
      args.map(x => required("-a", x._1 + "=" + x._2)).mkString
60
61
62
  }
}

63
64
trait ReportBuilder extends ToolCommand {

Peter van 't Hof's avatar
Peter van 't Hof committed
65
  case class Args(summary: File = null, outputDir: File = null, pageArgs: mutable.Map[String, Any] = mutable.Map()) extends AbstractArgs
66
67

  class OptParser extends AbstractOptParser {
Peter van 't Hof's avatar
Peter van 't Hof committed
68
    opt[File]('s', "summary") unbounded () required () maxOccurs 1 valueName "<file>" action { (x, c) =>
69
70
      c.copy(summary = x)
    }
Peter van 't Hof's avatar
Peter van 't Hof committed
71
    opt[File]('o', "outputDir") unbounded () required () maxOccurs 1 valueName "<file>" action { (x, c) =>
72
73
      c.copy(outputDir = x)
    }
Peter van 't Hof's avatar
Peter van 't Hof committed
74
    opt[Map[String, String]]('a', "args") unbounded () action { (x, c) =>
75
76
      c.copy(pageArgs = c.pageArgs ++ x)
    }
77
78
  }

79
  /** summary object internaly */
80
81
  private var setSummary: Summary = _

82
  /** Retrival of summary, read only */
83
84
  final def summary = setSummary

85
  /** default args that are passed to all page withing the report */
86
87
  def pageArgs: Map[String, Any] = Map()

Peter van 't Hof's avatar
Peter van 't Hof committed
88
89
90
  private var done = 0
  private var total = 0

Peter van 't Hof's avatar
Peter van 't Hof committed
91
92
93
94
95
  private var _sampleId: Option[String] = None
  protected def sampleId = _sampleId
  private var _libId: Option[String] = None
  protected def libId = _libId

96
  /** Main function to for building the report */
97
98
99
100
101
102
103
104
105
  def main(args: Array[String]): Unit = {
    logger.info("Start")

    val argsParser = new OptParser
    val cmdArgs: Args = argsParser.parse(args, Args()) getOrElse sys.exit(1)

    require(cmdArgs.outputDir.exists(), "Output dir does not exist")
    require(cmdArgs.outputDir.isDirectory, "Output dir is not a directory")

Peter van 't Hof's avatar
Peter van 't Hof committed
106
    cmdArgs.pageArgs.get("sampleId") match {
107
      case Some(s: String) =>
Peter van 't Hof's avatar
Peter van 't Hof committed
108
109
        cmdArgs.pageArgs += "sampleId" -> Some(s)
        _sampleId = Some(s)
Peter van 't Hof's avatar
Peter van 't Hof committed
110
      case _ =>
Peter van 't Hof's avatar
Peter van 't Hof committed
111
112
113
    }

    cmdArgs.pageArgs.get("libId") match {
114
      case Some(l: String) =>
Peter van 't Hof's avatar
Peter van 't Hof committed
115
116
        cmdArgs.pageArgs += "libId" -> Some(l)
        _libId = Some(l)
Peter van 't Hof's avatar
Peter van 't Hof committed
117
      case _ =>
Peter van 't Hof's avatar
Peter van 't Hof committed
118
119
    }

120
121
    logger.info("Copy Base files")

Peter van 't Hof's avatar
Peter van 't Hof committed
122
    // Static files that will be copied to the output folder, then file is added to [resourceDir] it's need to be added here also
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
    val extOutputDir: File = new File(cmdArgs.outputDir, "ext")
    val resourceDir: String = "/nl/lumc/sasc/biopet/core/report/ext/"
    val extFiles = List(
      "css/bootstrap_dashboard.css",
      "css/bootstrap.min.css",
      "css/bootstrap-theme.min.css",
      "css/sortable-theme-bootstrap.css",
      "js/jquery.min.js",
      "js/sortable.min.js",
      "js/bootstrap.min.js",
      "fonts/glyphicons-halflings-regular.woff",
      "fonts/glyphicons-halflings-regular.ttf",
      "fonts/glyphicons-halflings-regular.woff2"
    )

    for (resource <- extFiles.par) {
139
      IoUtils.copyStreamToFile(getClass.getResourceAsStream(resourceDir + resource), new File(extOutputDir, resource), createDirs = true)
140
    }
141

Peter van 't Hof's avatar
Peter van 't Hof committed
142
    logger.info("Parsing summary")
143
144
    setSummary = new Summary(cmdArgs.summary)

145
    total = ReportBuilder.countPages(indexPage)
Peter van 't Hof's avatar
Peter van 't Hof committed
146
147
    logger.info(total + " pages to be generated")

Peter van 't Hof's avatar
Peter van 't Hof committed
148
    logger.info("Generate pages")
Peter van 't Hof's avatar
Peter van 't Hof committed
149
    val jobs = generatePage(summary, indexPage, cmdArgs.outputDir,
Peter van 't Hof's avatar
Peter van 't Hof committed
150
      args = pageArgs ++ cmdArgs.pageArgs.toMap ++
151
        Map("summary" -> summary, "reportName" -> reportName, "indexPage" -> indexPage))
152

Peter van 't Hof's avatar
Peter van 't Hof committed
153
    logger.info(jobs + " Done")
154
155
  }

156
  /** This must be implemented, this will be the root page of the report */
157
158
  def indexPage: ReportPage

159
  /** This must be implemented, this will because the title of the report */
160
161
  def reportName: String

162
163
164
165
166
167
168
169
170
  /**
   * This method will render the page and the subpages recursivly
   * @param summary The summary object
   * @param page Page to render
   * @param outputDir Root output dir of the report
   * @param path Path from root to current page
   * @param args Args to add to this sub page, are args from current page are passed automaticly
   * @return Number of pages including all subpages that are rendered
   */
171
172
173
174
  def generatePage(summary: Summary,
                   page: ReportPage,
                   outputDir: File,
                   path: List[String] = Nil,
Peter van 't Hof's avatar
Peter van 't Hof committed
175
                   args: Map[String, Any] = Map()): Int = {
176

Peter van 't Hof's avatar
Peter van 't Hof committed
177
178
    val pageOutputDir = new File(outputDir, path.mkString(File.separator))
    pageOutputDir.mkdirs()
179
180
181
182
183
184
185
    val rootPath = "./" + Array.fill(path.size)("../").mkString("")
    val pageArgs = args ++ page.args ++
      Map("page" -> page,
        "path" -> path,
        "outputDir" -> pageOutputDir,
        "rootPath" -> rootPath
      )
186

187
188
189
190
191
    // Generating subpages
    val jobs = for ((name, subPage) <- page.subPages.par) yield {
      generatePage(summary, subPage, outputDir, path ::: name :: Nil, pageArgs)
    }

Peter van 't Hof's avatar
Peter van 't Hof committed
192
193
194
195
    val output = ReportBuilder.renderTemplate("/nl/lumc/sasc/biopet/core/report/main.ssp",
      pageArgs ++ Map("args" -> pageArgs))

    val file = new File(pageOutputDir, "index.html")
196
197
198
199
    val writer = new PrintWriter(file)
    writer.println(output)
    writer.close()

Peter van 't Hof's avatar
Peter van 't Hof committed
200
201
    done += 1
    if (done % 100 == 0) logger.info(done + " Done, " + (done.toDouble / total * 100) + "%")
202
    jobs.sum + 1
203
204
  }
}
Peter van 't Hof's avatar
Peter van 't Hof committed
205
206
207

object ReportBuilder {

208
  /** Single template render engine, this will have a cache for all compile templates */
Peter van 't Hof's avatar
Peter van 't Hof committed
209
210
  protected val engine = new TemplateEngine()

211
  /** Cache of temp file for templates from the classpath / jar */
212
213
  private var templateCache: Map[String, File] = Map()

214
215
216
217
218
  /** This will give the total number of pages including all nested pages */
  def countPages(page: ReportPage): Int = {
    page.subPages.map(x => countPages(x._2)).fold(1)(_ + _)
  }

Peter van 't Hof's avatar
Peter van 't Hof committed
219
220
221
222
223
224
225
  /**
   * This method will render a template that is located in the classpath / jar
   * @param location location in the classpath / jar
   * @param args Additional arguments, not required
   * @return Rendered result of template
   */
  def renderTemplate(location: String, args: Map[String, Any] = Map()): String = {
Peter van 't Hof's avatar
Peter van 't Hof committed
226
227
    Logging.logger.info("Rendering: " + location)

228
229
    val templateFile: File = templateCache.get(location) match {
      case Some(template) => template
230
      case _ =>
231
        val tempFile = File.createTempFile("ssp-template", new File(location).getName)
232
        IoUtils.copyStreamToFile(getClass.getResourceAsStream(location), tempFile)
233
234
235
236
237
        templateCache += location -> tempFile
        tempFile
    }
    engine.layout(TemplateSource.fromFile(templateFile), args)
  }
Peter van 't Hof's avatar
Peter van 't Hof committed
238
}