Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit 2c8c678

Browse files
committed
Separate GUI into its own jar and add a servlet to serve the jar's content
1 parent 6015ca5 commit 2c8c678

File tree

8 files changed

+113
-26
lines changed

8 files changed

+113
-26
lines changed

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,3 @@ project/plugins/project/
3737

3838
# Plugin Data
3939
data/
40-
41-
# Built gui
42-
/src/main/resources/chatoverflow-gui

build.sbt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ bs := BootstrapUtility.bootstrapGenTask(streams.value.log, s"$scalaMajorVersion$
9999
deploy := BootstrapUtility.prepareDeploymentTask(streams.value.log, scalaMajorVersion)
100100
gui := BuildUtility(streams.value.log).guiTask(guiProjectPath.value, streams.value.cacheDirectory / "gui")
101101

102+
Compile / packageBin := {
103+
BuildUtility(streams.value.log).packageGUITask(guiProjectPath.value, scalaMajorVersion, crossTarget.value)
104+
(Compile / packageBin).value
105+
}
106+
107+
Compile / unmanagedJars := (crossTarget.value ** "chatoverflow-gui*.jar").classpath
108+
102109
// ---------------------------------------------------------------------------------------------------------------------
103110
// UTIL
104111
// ---------------------------------------------------------------------------------------------------------------------
@@ -117,4 +124,3 @@ lazy val getDependencyList = Def.task[List[ModuleID]] {
117124

118125
// Clears the built GUI dirs on clean
119126
cleanFiles += baseDirectory.value / guiProjectPath.value / "dist"
120-
cleanFiles += baseDirectory.value / "src" / "main" / "resources" / "chatoverflow-gui"

project/BootstrapUtility.scala

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -165,19 +165,15 @@ object BootstrapUtility {
165165
}
166166

167167
/**
168-
* Copies ONE jar file from the source to all target directories. Useful for single packaged jar files.
169-
*/
168+
* Copies all jar files from the source to all target directories.
169+
*/
170170
private def copyJars(sourceDirectory: String, targetDirectories: List[String], logger: ManagedLogger): Unit = {
171171
val candidates = new File(sourceDirectory)
172172
.listFiles().filter(f => f.isFile && f.getName.toLowerCase.endsWith(".jar"))
173-
if (candidates.length != 1) {
174-
logger warn s"Unable to identify jar file in $sourceDirectory"
175-
} else {
176-
for (targetDirectory <- targetDirectories) {
177-
Files.copy(Paths.get(candidates.head.getAbsolutePath),
178-
Paths.get(s"$targetDirectory/${candidates.head.getName}"))
179-
logger info s"Finished copying file '${candidates.head.getAbsolutePath}' to '$targetDirectory'."
180-
}
173+
for (targetDirectory <- targetDirectories; file <- candidates) {
174+
Files.copy(Paths.get(file.getAbsolutePath),
175+
Paths.get(s"$targetDirectory/${file.getName}"))
176+
logger info s"Finished copying file '${file.getAbsolutePath}' to '$targetDirectory'."
181177
}
182178
}
183179
}

project/BuildUtility.scala

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import java.io.{File, IOException}
22
import java.nio.file.{Files, StandardCopyOption}
3+
import java.util.jar.Manifest
34

5+
import com.fasterxml.jackson.databind.ObjectMapper
46
import sbt.internal.util.ManagedLogger
57
import sbt.util.{FileFunction, FilesInfo}
68

9+
import scala.io.Source
10+
711
/**
812
* A build utility instance handles build tasks and prints debug information using the managed logger.
913
*
@@ -151,15 +155,10 @@ class BuildUtility(logger: ManagedLogger) {
151155
val srcFiles = recursiveFileListing(new File(guiDir, "src"))
152156
val outDir = new File(guiDir, "dist")
153157

154-
if(!executeNpmCommand(guiDir, cacheDir, srcFiles + packageJson, "run build",
158+
executeNpmCommand(guiDir, cacheDir, srcFiles + packageJson, "run build",
155159
() => logger error "GUI couldn't be built, please check above log for further details.",
156160
() => outDir
157-
)) {
158-
return // again early return on failure
159-
}
160-
161-
// copy built gui into resources, will be included in the classpath on execution of the framework
162-
sbt.IO.copyDirectory(outDir, new File("src/main/resources/chatoverflow-gui"))
161+
)
163162
}
164163
}
165164

@@ -212,6 +211,30 @@ class BuildUtility(logger: ManagedLogger) {
212211
}
213212
}
214213

214+
def packageGUITask(guiProjectPath: String, scalaMajorVersion: String, crossTargetDir: File): Unit = {
215+
val dir = new File(guiProjectPath, "dist")
216+
if (!dir.exists()) {
217+
return
218+
}
219+
220+
val files = recursiveFileListing(dir)
221+
222+
// contains tuples with the actual file as the first value and the name with directory in the jar as the second value
223+
val jarEntries = files.map(file => (file, "/chatoverflow-gui/" + dir.toURI.relativize(file.toURI)))
224+
225+
val guiVersion = getGUIVersion(guiProjectPath)
226+
227+
sbt.IO.jar(jarEntries, new File(crossTargetDir, s"chatoverflow-gui_$scalaMajorVersion-$guiVersion.jar"), new Manifest())
228+
}
229+
230+
private def getGUIVersion(guiProjectPath: String): String = {
231+
val packageJson = Source.fromFile(s"$guiProjectPath/package.json")
232+
val version = new ObjectMapper().reader().readTree(packageJson.mkString).get("version").asText()
233+
234+
packageJson.close()
235+
version
236+
}
237+
215238
/**
216239
* Creates a file listing with all files including files in any sub-dir.
217240
*

project/plugins.sbt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")
1+
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")
2+
3+
// JSON Lib (Jackson)
4+
libraryDependencies += "org.json4s" %% "json4s-jackson" % "3.5.2"

src/main/scala/ScalatraBootstrap.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import org.codeoverflow.chatoverflow.ui.web.rest.connector.ConnectorController
44
import org.codeoverflow.chatoverflow.ui.web.rest.events.{EventsController, EventsDispatcher}
55
import org.codeoverflow.chatoverflow.ui.web.rest.plugin.PluginInstanceController
66
import org.codeoverflow.chatoverflow.ui.web.rest.types.TypeController
7-
import org.codeoverflow.chatoverflow.ui.web.{CodeOverflowSwagger, OpenAPIServlet}
7+
import org.codeoverflow.chatoverflow.ui.web.{CodeOverflowSwagger, GUIServlet, OpenAPIServlet}
88
import org.scalatra._
99

1010
/**
@@ -30,5 +30,7 @@ class ScalatraBootstrap extends LifeCycle {
3030
context.mount(new PluginInstanceController(), "/instances/*", "instances")
3131
context.mount(new ConnectorController(), "/connectors/*", "connectors")
3232
context.mount(new OpenAPIServlet(), "/api-docs")
33+
34+
context.mount(new GUIServlet(), "/*")
3335
}
3436
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.codeoverflow.chatoverflow.ui.web
2+
3+
import java.io.File
4+
import java.net.URI
5+
import java.util.jar.JarFile
6+
7+
import org.codeoverflow.chatoverflow.WithLogger
8+
import org.eclipse.jetty.http.MimeTypes
9+
import org.eclipse.jetty.util.Loader
10+
import org.scalatra.{ActionResult, ScalatraServlet}
11+
12+
import scala.io.Source
13+
14+
/**
15+
* A servlet to serve the GUI files of the chatoverflow-gui dir from the classpath.
16+
* This directory is provided if the gui jar is added on the classpath.
17+
* Responds with an error if the gui jar isn't on the classpath.
18+
*/
19+
class GUIServlet extends ScalatraServlet with WithLogger {
20+
21+
private val jarFilePath = {
22+
val res = Loader.getResource(s"/chatoverflow-gui/")
23+
24+
// directory couldn't be found
25+
if (res == null) {
26+
logger error "GUI couldn't be found on the classpath! Has the GUI been built?"
27+
null
28+
} else {
29+
// remove the path inside the jar and only keep the file path to the jar file
30+
val jarPath = res.getFile.split("!").head
31+
logger info s"GUI jar file found at ${new File(".").toURI.relativize(new URI(jarPath))}"
32+
33+
Some(jarPath)
34+
}
35+
}
36+
37+
get("/*") {
38+
if (jarFilePath.isEmpty) {
39+
ActionResult(500, "GUI couldn't be found on the classpath! Has the GUI been built?", Map())
40+
} else {
41+
val jarFile = new JarFile(new File(new URI(jarFilePath.get)))
42+
43+
val path = if (requestPath == "/")
44+
"/index.html"
45+
else
46+
requestPath
47+
48+
val entry = jarFile.getEntry(s"/chatoverflow-gui$path")
49+
50+
val res = if (entry == null) {
51+
ActionResult(404, s"Requested file '$path' couldn't be found in the GUI jar!", Map())
52+
} else {
53+
contentType = MimeTypes.getDefaultMimeByExtension(entry.getName)
54+
Source.fromInputStream(jarFile.getInputStream(entry)).mkString
55+
}
56+
57+
response.setHeader("Cache-Control", "no-cache,no-store")
58+
jarFile.close()
59+
res
60+
}
61+
}
62+
}

src/main/scala/org/codeoverflow/chatoverflow/ui/web/Server.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.codeoverflow.chatoverflow.ui.web
22

33
import org.codeoverflow.chatoverflow.{ChatOverflow, WithLogger}
4-
import org.eclipse.jetty.util.resource.Resource
54
import org.eclipse.jetty.webapp.WebAppContext
65
import org.scalatra.servlet.ScalatraListener
76

@@ -16,9 +15,8 @@ class Server(val chatOverflow: ChatOverflow, val port: Int) extends WithLogger {
1615
private val server = new org.eclipse.jetty.server.Server(port)
1716
private val context = new WebAppContext()
1817
context.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false")
19-
context.setInitParameter("org.eclipse.jetty.servlet.Default.cacheControl", "no-cache,no-store")
2018
context setContextPath "/"
21-
context.setBaseResource(Resource.newClassPathResource("/chatoverflow-gui/"))
19+
context setResourceBase "/"
2220
context.addEventListener(new ScalatraListener)
2321

2422
server.setHandler(context)

0 commit comments

Comments
 (0)