IpPhoneBridgeApplication.kt 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. package co.id.datacomsolusindo.ipphonebridge
  2. import kotlinx.coroutines.*
  3. import kotlinx.coroutines.sync.Mutex
  4. import kotlinx.coroutines.sync.withLock
  5. import org.apache.logging.log4j.Level
  6. import org.apache.logging.log4j.LogManager
  7. import org.apache.logging.log4j.Logger
  8. import org.apache.logging.log4j.core.Filter
  9. import org.apache.logging.log4j.core.config.Configurator
  10. import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory
  11. import org.apache.logging.log4j.core.config.builder.api.FilterComponentBuilder
  12. import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder
  13. import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder
  14. import org.ini4j.Wini
  15. import org.springframework.boot.SpringApplication
  16. import org.springframework.boot.autoconfigure.SpringBootApplication
  17. import org.springframework.context.event.EventListener
  18. import org.springframework.messaging.simp.stomp.StompHeaderAccessor
  19. import org.springframework.messaging.support.GenericMessage
  20. import org.springframework.scheduling.annotation.EnableScheduling
  21. import org.springframework.stereotype.Component
  22. import org.springframework.web.socket.messaging.SessionConnectEvent
  23. import org.springframework.web.socket.messaging.SessionDisconnectEvent
  24. import org.springframework.web.socket.messaging.SessionSubscribeEvent
  25. import java.io.File
  26. import java.io.Serializable
  27. import java.time.LocalDateTime
  28. import java.time.format.DateTimeFormatter
  29. import java.util.*
  30. import kotlin.system.exitProcess
  31. import kotlin.system.measureTimeMillis
  32. @SpringBootApplication
  33. @EnableScheduling
  34. class IpPhoneBridgeApplication
  35. fun main(args: Array<String>) {
  36. TimeZone.setDefault(TimeZone.getTimeZone("Asia/Jakarta"))
  37. val properties = Properties()
  38. val confFile = File("conf/general.ini")
  39. if (confFile.exists()) {
  40. val ini = Wini(File("conf/general.ini"))
  41. properties["server.port"] = ini.get("server", "port")
  42. } else {
  43. properties["server.port"] = System.getenv("PORT")
  44. }
  45. if (properties["server.port"] == null) {
  46. println("undefined port")
  47. exitProcess(1)
  48. }
  49. val sApp = SpringApplication(IpPhoneBridgeApplication::class.java)
  50. sApp.setDefaultProperties(properties)
  51. sApp.run(*args)
  52. LogManager.getLogger("co.id.datacomsolusindo.ipphonebridge.IpPhoneBridgeApplication").info("Bridge Start")
  53. val hostName = System.getenv("HOSTNAME")
  54. println("hostname $hostName")
  55. }
  56. class Client(
  57. val number: String,
  58. var connectAt: String,
  59. var lastRequest: String
  60. ) : Serializable {
  61. var reqSuccess = 0
  62. var reqFailed = 0
  63. var avgReqSuccessTime = 0.0
  64. }
  65. object ClientHolder {
  66. private val mutexSucReq = Mutex()
  67. private val clientMap: MutableMap<String, Client> = mutableMapOf()
  68. private suspend fun massiveRun(action: suspend () -> Unit) {
  69. measureTimeMillis {
  70. coroutineScope { // sc
  71. launch {
  72. action()
  73. }
  74. }
  75. }
  76. }
  77. fun put(key: String, client: Client) {
  78. clientMap[key] = client
  79. }
  80. fun get() = clientMap.toMap()
  81. fun addFailedRequest(clNum: String) {
  82. runBlocking {
  83. withContext(Dispatchers.Default) {
  84. massiveRun {
  85. mutexSucReq.withLock {
  86. val cl = clientMap[clNum]
  87. if (cl != null) {
  88. cl.lastRequest =
  89. LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
  90. cl.reqFailed += 1
  91. }
  92. }
  93. }
  94. }
  95. }
  96. }
  97. fun addSuccessRequest(clNum: String, duration: Double) {
  98. runBlocking {
  99. withContext(Dispatchers.Default) {
  100. massiveRun {
  101. // protect each increment with lock
  102. mutexSucReq.withLock {
  103. val cl = clientMap[clNum]
  104. if (cl != null) {
  105. cl.lastRequest =
  106. LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
  107. cl.avgReqSuccessTime =
  108. ((cl.avgReqSuccessTime * cl.reqSuccess) + duration) / (cl.reqSuccess + 1)
  109. cl.reqSuccess += 1
  110. }
  111. }
  112. }
  113. }
  114. }
  115. }
  116. }
  117. @Component
  118. class WebSocketEventListener {
  119. fun logBuilder(clNum: String) {
  120. val builder = ConfigurationBuilderFactory.newConfigurationBuilder()
  121. val console = builder.newAppender("stdout", "Console")
  122. val appLog = builder.newAppender("appLog", "RollingFile")
  123. appLog.addAttribute("fileName", "log/app/app.log")
  124. appLog.addAttribute("filePattern", "log/app/\$\${date:yyyy - MM}/app-%d{yyyy-MM-dd}-%i.log.zip")
  125. val flow: FilterComponentBuilder = builder.newFilter(
  126. "MarkerFilter",
  127. Filter.Result.ACCEPT,
  128. Filter.Result.DENY
  129. )
  130. flow.addAttribute("marker", "FLOW")
  131. console.add(flow)
  132. val standard = builder.newLayout("PatternLayout")
  133. standard.addAttribute("pattern", "%d %p [%c{1}] %m%n")
  134. val policies = builder.newLayout("Policies")
  135. val timeBasedTriggeringPolicy = builder.newLayout("TimeBasedTriggeringPolicy")
  136. val sizeBasedTriggeringPolicy = builder.newLayout("SizeBasedTriggeringPolicy")
  137. sizeBasedTriggeringPolicy.addAttribute("size", "10 MB")
  138. policies.addComponent(timeBasedTriggeringPolicy)
  139. policies.addComponent(sizeBasedTriggeringPolicy)
  140. console.add(standard)
  141. builder.add(console)
  142. appLog.add(standard)
  143. appLog.addComponent(policies)
  144. builder.add(appLog)
  145. val rootLogger: RootLoggerComponentBuilder = builder.newRootLogger(Level.INFO)
  146. rootLogger.add(builder.newAppenderRef("stdout"))
  147. builder.add(rootLogger)
  148. val logger: LoggerComponentBuilder = builder.newLogger("co.id.datacomsolusindo.ipphonebridge", Level.DEBUG)
  149. logger.add(builder.newAppenderRef("appLog"))
  150. logger.addAttribute("additivity", false)
  151. builder.add(logger)
  152. rootLogger.add(builder.newAppenderRef("appLog"))
  153. ClientHolder.get().entries.forEach {
  154. val clientLog = builder.newAppender("client-${it.key}", "RollingFile")
  155. clientLog.addAttribute("fileName", "log/${it.key}/client-${it.key}.log")
  156. clientLog.addAttribute(
  157. "filePattern",
  158. "log/${it.key}/\$\${date:yyyy - MM}/app-%d{yyyy-MM-dd}-%i.client-${it.key}.zip"
  159. )
  160. clientLog.add(standard)
  161. clientLog.addComponent(policies)
  162. builder.add(clientLog)
  163. val loggerClient: LoggerComponentBuilder = builder.newLogger("client.${it.key}", Level.DEBUG)
  164. loggerClient.add(builder.newAppenderRef("client-${it.key}"))
  165. loggerClient.addAttribute("additivity", false)
  166. builder.add(loggerClient)
  167. rootLogger.add(builder.newAppenderRef("client-${it.key}"))
  168. }
  169. Configurator.reconfigure(builder.build())
  170. }
  171. // @EventListener
  172. // private void handleSessionConnected(SessionConnectEvent event) {
  173. // ...
  174. // }
  175. //
  176. // @EventListener
  177. // private void handleSessionDisconnect(SessionDisconnectEvent event) {
  178. // ...
  179. // }
  180. @EventListener
  181. fun onConnect(event: SessionConnectEvent) {
  182. val accessor = StompHeaderAccessor.wrap(event.message)
  183. val sessionId = accessor.sessionId
  184. println("connect with session id $sessionId")
  185. }
  186. @EventListener
  187. fun onDisconnect(event: SessionDisconnectEvent) {
  188. println("disconnect with session id ${event.sessionId}")
  189. }
  190. @EventListener
  191. fun handleSessionSubscribeEvent(event: SessionSubscribeEvent) {
  192. val message = event.message as GenericMessage<*>
  193. val simDestination = message.headers["simpDestination"] as String?
  194. val accessor = StompHeaderAccessor.wrap(event.message)
  195. val sessionId = accessor.sessionId
  196. println("subscribe to $simDestination with session id $sessionId")
  197. if (!(simDestination!!.startsWith("/topic/healthCheck") || simDestination.startsWith("/topic/notification"))) {
  198. // do stuff
  199. val clNum = simDestination.split("/")[3]
  200. ClientHolder.put(
  201. clNum, Client(
  202. clNum,
  203. LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")),
  204. LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
  205. )
  206. )
  207. LogManager.getLogger(this.javaClass).info("clientConnected $simDestination")
  208. logBuilder(clNum)
  209. LogManager.getLogger("client.$clNum").info("Client $clNum Connected")
  210. }
  211. }
  212. }
  213. object AppLog {
  214. fun write(cls: Class<*>): Logger {
  215. return LogManager.getLogger(cls)
  216. }
  217. }