initial implementation
authorRoman Shevchenko <roman.shevchenko@jetbrains.com>
Thu, 15 Sep 2016 16:37:02 +0000 (19:37 +0300)
committerRoman Shevchenko <roman.shevchenko@jetbrains.com>
Fri, 23 Sep 2016 14:17:14 +0000 (17:17 +0300)
update-server-mock/build.gradle
update-server-mock/src/main/java/org/jetbrains/updater/mock/Generator.kt [new file with mode: 0644]
update-server-mock/src/main/java/org/jetbrains/updater/mock/Server.kt [new file with mode: 0644]
update-server-mock/src/main/java/org/jetbrains/updater/mock/main.kt [new file with mode: 0644]
update-server-mock/src/main/resources/patch/patch.jar [new file with mode: 0644]
update-server-mock/src/test/java/org/jetbrains/updater/mock/GeneratorTest.kt [new file with mode: 0644]

index b04b75f3abf37042d23ca3b447a81e2cbfe7df23..63d67a579143df3ba4c1b02fe69631f2ba870e9b 100644 (file)
@@ -11,4 +11,14 @@ repositories { jcenter() }
 dependencies {
   compile "org.jetbrains.kotlin:kotlin-stdlib:1.0.+"
   testCompile "junit:junit:4.+"
+  testCompile "org.assertj:assertj-core:3.+"
+}
+
+jar {
+  manifest {
+    attributes "Main-Class" : "org.jetbrains.updater.mock.MainKt"
+  }
+  from {
+    configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
+  }
 }
\ No newline at end of file
diff --git a/update-server-mock/src/main/java/org/jetbrains/updater/mock/Generator.kt b/update-server-mock/src/main/java/org/jetbrains/updater/mock/Generator.kt
new file mode 100644 (file)
index 0000000..7a5a90e
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2016 JetBrains s.r.o.
+ *
+ * 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.jetbrains.updater.mock
+
+object Generator {
+  fun generateXml(productCode: String, buildId: String, eap: Boolean): String {
+    val status = if (eap) "eap" else "release"
+    return """
+      <!DOCTYPE products SYSTEM "updates.dtd">
+      <products>
+        <product name="Mock Product">
+          <code>$productCode</code>
+          <channel id="MOCK" status="$status" licensing="$status">
+            <build number="9999.0.0" version="Mock">
+              <message><![CDATA[A mock update. For testing purposes only.]]></message>
+              <button name="Download" url="https://www.jetbrains.com/" download="true"/>
+              <patch from="$buildId" size="from 0 to ∞"/>
+            </build>
+          </channel>
+        </product>
+      </products>""".trimIndent()
+  }
+
+  fun generatePatch(): ByteArray =
+    this.javaClass.classLoader.getResourceAsStream("patch/patch.jar").use { it.readBytes() }
+}
\ No newline at end of file
diff --git a/update-server-mock/src/main/java/org/jetbrains/updater/mock/Server.kt b/update-server-mock/src/main/java/org/jetbrains/updater/mock/Server.kt
new file mode 100644 (file)
index 0000000..9184974
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2016 JetBrains s.r.o.
+ *
+ * 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.jetbrains.updater.mock
+
+import com.sun.net.httpserver.Filter
+import com.sun.net.httpserver.HttpExchange
+import com.sun.net.httpserver.HttpHandler
+import com.sun.net.httpserver.HttpServer
+import java.io.OutputStream
+import java.net.HttpURLConnection.HTTP_BAD_REQUEST
+import java.net.HttpURLConnection.HTTP_OK
+import java.net.InetSocketAddress
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+
+class Server(private val port: Int) {
+  private val server = HttpServer.create()
+
+  fun start() {
+    server.bind(InetSocketAddress("localhost", port), 0)
+    server.handle("/updates/updates.xml", HttpHandler { sendUpdatesXml(it) })
+    server.handle("/patches/", HttpHandler { sendPatch(it) })
+    server.handle("/", HttpHandler { sendText(it, "Mock Update Server") })
+    server.start()
+  }
+
+  private fun sendText(ex: HttpExchange, data: String, type: String = "text/plain", code: Int = HTTP_OK) {
+    val bytes = data.toByteArray()
+    ex.responseHeaders.add("Content-Type", "$type; charset=utf-8")
+    ex.sendResponseHeaders(code, bytes.size.toLong())
+    ex.responseBody.write(bytes)
+    ex.close()
+  }
+
+  private fun sendUpdatesXml(ex: HttpExchange) {
+    var build: String? = null
+    var eap = false
+    ex.requestURI.query?.splitToSequence('&')?.forEach {
+      val p = it.split('=', limit = 2)
+      when (p[0]) {
+        "build" -> build = if (p.size > 1) p[1] else null
+        "eap" -> eap = true
+      }
+    }
+    if (build == null) {
+      sendText(ex, "Parameter missing", code = HTTP_BAD_REQUEST)
+      return
+    }
+
+    val result = "([A-Z]+)-([0-9.]+)".toRegex().find(build!!)
+    val productCode = result?.groups?.get(1)?.value
+    val buildId = result?.groups?.get(2)?.value
+    if (productCode == null || buildId == null) {
+      sendText(ex, "Parameter malformed", code = HTTP_BAD_REQUEST)
+      return
+    }
+
+    val xml = Generator.generateXml(productCode, buildId, eap)
+    sendText(ex, xml, "text/xml")
+  }
+
+  private fun sendPatch(ex: HttpExchange) {
+    if (!ex.requestURI.path.endsWith(".jar")) {
+      sendText(ex, "Request malformed", code = HTTP_BAD_REQUEST)
+      return
+    }
+
+    val patch = Generator.generatePatch()
+    ex.responseHeaders.add("Content-Type", "binary/octet-stream")
+    ex.sendResponseHeaders(HTTP_OK, patch.size.toLong())
+    ex.responseBody.write(patch)
+    ex.close()
+  }
+}
+
+private fun HttpServer.handle(path: String, handler: HttpHandler) {
+  val ctx = createContext(path, handler)
+  ctx.filters += AccessLogFilter()
+}
+
+private class AccessLogFilter : Filter() {
+  companion object {
+    private val DTF = DateTimeFormatter.ofPattern("dd/MMM/yyyy:kk:mm:ss ZZ")
+  }
+
+  override fun description() = "Access Log Filter"
+
+  override fun doFilter(ex: HttpExchange, chain: Chain) {
+    val out = CountingOutputStream(ex.responseBody)
+    ex.setStreams(ex.requestBody, out)
+
+    try {
+      chain.doFilter(ex)
+      println("${ex.remoteAddress.address.hostAddress} - - [${DTF.format(ZonedDateTime.now())}]" +
+        " \"${ex.requestMethod} ${ex.requestURI}\" ${ex.responseCode} ${out.count}")
+    }
+    catch(e: Exception) {
+      e.printStackTrace()
+      ex.close()
+    }
+  }
+}
+
+private class CountingOutputStream(private val stream: OutputStream) : OutputStream() {
+  var count: Int = 0
+    private set(v) { field = v }
+
+  override fun write(b: Int) {
+    stream.write(b)
+    count += 1
+  }
+
+  override fun write(b: ByteArray, off: Int, len: Int) {
+    stream.write(b, off, len)
+    count += len
+  }
+}
\ No newline at end of file
diff --git a/update-server-mock/src/main/java/org/jetbrains/updater/mock/main.kt b/update-server-mock/src/main/java/org/jetbrains/updater/mock/main.kt
new file mode 100644 (file)
index 0000000..ee1e7c9
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2016 JetBrains s.r.o.
+ *
+ * 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.jetbrains.updater.mock
+
+fun main(args: Array<String>) {
+  if (args.size != 1) {
+    println("usage: java -jar update-server-mock <port>")
+    System.exit(1)
+  }
+
+  val port = args[0].toInt()
+  Server(port).start()
+}
\ No newline at end of file
diff --git a/update-server-mock/src/main/resources/patch/patch.jar b/update-server-mock/src/main/resources/patch/patch.jar
new file mode 100644 (file)
index 0000000..24f84df
Binary files /dev/null and b/update-server-mock/src/main/resources/patch/patch.jar differ
diff --git a/update-server-mock/src/test/java/org/jetbrains/updater/mock/GeneratorTest.kt b/update-server-mock/src/test/java/org/jetbrains/updater/mock/GeneratorTest.kt
new file mode 100644 (file)
index 0000000..f44c2dc
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2016 JetBrains s.r.o.
+ *
+ * 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.jetbrains.updater.mock
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+
+class GeneratorTest {
+  @Test fun generateXml() {
+    val xml = Generator.generateXml("IC", "163.11.22", false)
+    assertThat(xml).contains("patch from=\"163.11.22\"").contains("status=\"release\"")
+  }
+
+  @Test fun generatePatch() {
+    val patch = Generator.generatePatch()
+    assertThat(patch).isNotEmpty()
+  }
+}
\ No newline at end of file