com.tuplejump.plugin.CassandraPlugin.scala Maven / Gradle / Ivy
The newest version!
/*
* Licensed to Tuplejump Software Pvt. Ltd. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Tuplejump Software Pvt. Ltd. licenses this file
* to you 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 com.tuplejump.plugin
import java.util.Date
import com.datastax.driver.core.exceptions.NoHostAvailableException
import com.datastax.driver.core.{Cluster, Session, ResultSet}
import play.api.Play.current
import play.api.{Application, Play, Plugin}
import scala.io.Source
import scala.util.{Failure, Success, Try}
class CassandraPlugin(app: Application) extends Plugin {
private var _helper: Option[CassandraConnection] = None
def helper = _helper.getOrElse(throw new RuntimeException("CassandraPlugin error: CassandraHelper initialization failed"))
override def onStart() = {
val appConfig = app.configuration.getConfig("cassandraPlugin").get
val appName: String = appConfig.getString("appName").getOrElse("appWithCassandraPlugin")
val isEvolutionEnabled: Boolean = appConfig.getBoolean("evolution.enabled").getOrElse(true)
val scriptSource: String = appConfig.getString("evolution.directory").getOrElse("evolutions/cassandra/")
val hosts: Array[java.lang.String] = appConfig.getString("host").getOrElse("localhost").split(",").map(_.trim)
val port: Int = appConfig.getInt("port").getOrElse(9042)
val cluster = Cluster.builder()
.addContactPoints(hosts: _*)
.withPort(port).build()
_helper = try {
val session = cluster.connect()
Util.loadScript("cassandraPlugin.cql", session)
if (isEvolutionEnabled) {
Evolutions.applyEvolution(session, appName, scriptSource)
}
Some(CassandraConnection(hosts, port, cluster, session))
} catch {
case e: NoHostAvailableException =>
val msg =
s"""Failed to initialize CassandraPlugin.
|Please check if Cassandra is accessible at
| ${hosts.head}:$port or update configuration""".stripMargin
throw app.configuration.globalError(msg)
}
}
override def onStop() = {
helper.session.close()
helper.cluster.close()
}
override def enabled = true
}
private[plugin] object Util {
private def isComment(statement: String): Boolean = {
statement.startsWith("#")
}
private def isValidStatement(str: String): Boolean = {
val line = str.trim
line.length == 0 || isComment(line)
}
def getValidStatements(lines: Iterator[String]): Array[String] = {
lines.filterNot(isValidStatement).mkString("").split(";")
}
private def getValidStatementsFromFile(fileName: String): Array[String] = {
val lines = Source.fromURL(getClass.getClassLoader.getResource(fileName)).getLines()
getValidStatements(lines)
}
def executeStmnts(stmnts: Array[String], session: Session) = {
stmnts.foreach {
line =>
try {
session.execute(line)
} catch {
case ex: Throwable =>
throw new RuntimeException(s"Failed to execute $line", ex)
}
}
}
def loadScript(fileName: String, session: Session): Unit = {
val stmnts = getValidStatementsFromFile(fileName)
executeStmnts(stmnts, session)
}
}
private[plugin] object Evolutions {
import com.datastax.driver.core.querybuilder.{QueryBuilder => QB}
import scala.collection.JavaConversions._
private val Keyspace = "cassandra_play_plugin"
private val Table = "revision_history"
private val AppIDColumn = "app_id"
private val RevisionColumn = "revision"
private val TimeColumn = "applied_at"
private def getLastUpdateRevision(session: Session, appName: String): Int = {
val query = QB.select(RevisionColumn)
.from(Keyspace, Table)
.where(QB.eq(AppIDColumn, appName))
val row = session.execute(query).toIterable.headOption
val result = row.map(_.getInt(RevisionColumn))
result.getOrElse(0)
}
private def updateRevision(session: Session,
appName: String,
revision: Int): ResultSet = {
val query = QB.update(Keyspace, Table)
.`with`(QB.set(RevisionColumn, revision))
.and(QB.set(TimeColumn, new Date()))
.where(QB.eq(AppIDColumn, appName))
session.execute(query)
}
private def updateDBFromRevision(session: Session,
appName: String,
dirName: String,
revision: Int): Boolean = {
val currentRevision = revision + 1
val fileName: String = s"$dirName$currentRevision.cql"
val mayBeLines = Try(Source.fromURL(getClass.getClassLoader.getResource(fileName)).getLines())
mayBeLines match {
case Success(lines) =>
val stmt = Util.getValidStatements(lines)
Util.executeStmnts(stmt, session)
updateRevision(session, appName, currentRevision)
updateDBFromRevision(session, appName, dirName, currentRevision)
case Failure(e: NullPointerException) =>
//no more cql files to run
false
case Failure(e) =>
throw e
}
}
def applyEvolution(session: Session, appName: String, dirName: String) = {
val lastUpdatedRevision = getLastUpdateRevision(session, appName)
updateDBFromRevision(session, appName, dirName, lastUpdatedRevision)
}
}
object Cassandra {
private val casPlugin = Play.application.plugin[CassandraPlugin].get
private val cassandraHelper = casPlugin.helper
/**
* gets the Cassandra hosts provided in the configuration
*/
def hosts: Array[java.lang.String] = cassandraHelper.hosts
/**
* gets the port number on which Cassandra is running from the configuration
*/
def port: Int = cassandraHelper.port
/**
* gets a reference of the started Cassandra cluster
* The cluster is built with the configured set of initial contact points
* and policies at startup
*/
def cluster: Cluster = cassandraHelper.cluster
/**
* gets a reference of the started Cassandra session
* A new session is created on the cluster at startup
*/
def session: Session = cassandraHelper.session
/**
* executes CQL statements available in given file.
* Empty lines or lines starting with `#` are ignored.
* Each statement can extend over multiple lines and must end with a semi-colon.
* @param fileName - name of the file
*/
def loadCQLFile(fileName: String): Unit = {
Util.loadScript(fileName, cassandraHelper.session)
}
}
private[plugin] case class CassandraConnection(hosts: Array[java.lang.String],
port: Int,
cluster: Cluster,
session: Session)
© 2015 - 2024 Weber Informatics LLC | Privacy Policy