Play 2.4 + i18n: Using a database instead of property files for internationalization - scala

What I did works fine (at least it looks look it does), but I am not convinced this is the best way to do it...
Basically, I wanted to have my i18n translations in a database instead of property files so users can easily edit those translations and a cache can serve them to the other users within a short period of time - I use an Akka Actor to read from the database and create a cache used by the messageApi (before I always needed to redeploy with the changes in the property files).
Basically, is what I did totally the wrong way to do it?
TranslationActor.scala :
class TranslationActor extends Actor {
def receive = {
case _ => {
Logger.info("Starting to cache the translations")
TranslationActor.tempCache = ListMap.empty
var translations: ListMap[String, String] = ListMap.empty
for (acceptedLanguage <- TranslationActor.acceptedLanguages) {
val translationLanguageId: Long = TranslationLanguage.findByCode(acceptedLanguage).get.id
val languageTranslations: Seq[Translation] = Translation.findAllByLanguageId(translationLanguageId)
translations = new ListMap[String, String]
for (languageTranslation <- languageTranslations) {
val tag = EnglishTranslation.findById(languageTranslation.englishTranslationId).get.tag
var returnedTranslation: String = languageTranslation.translation
if (returnedTranslation.isEmpty) {
returnedTranslation = tag
}
translations += tag -> new CacheValue(new Locale(acceptedLanguage), returnedTranslation).stringVar
}
TranslationActor.tempCache += acceptedLanguage -> translations
}
TranslationActor.cache = TranslationActor.tempCache
Logger.info("Finished to cache the translations")
}
}
}
object TranslationActor {
var acceptedLanguages: Seq[String] = Seq("fr", "en")
var cache: ListMap[String, ListMap[String, String]] = ListMap.empty
var tempCache: ListMap[String, ListMap[String, String]] = ListMap.empty
}
class CacheValue(locale: Locale, string: String) {
val created: Long = System.currentTimeMillis
var messageFormat: MessageFormat = null
var localeVar: Locale = locale
var stringVar: String = string
def isOlderThan(period: Long): Boolean = {
(System.currentTimeMillis - created) > (period * 1000)
}
def getMessageFormat: MessageFormat = {
if (messageFormat == null) {
if (stringVar != null) {
messageFormat = new MessageFormat(stringVar, localeVar)
} else {
messageFormat = new MessageFormat("", localeVar)
}
}
messageFormat
}
}
ManageTranslationDaemon.scala :
#Singleton
class ManageTranslationDaemon #Inject() (actorSystem: ActorSystem, applicationLifecycle: ApplicationLifecycle) {
Logger.info("Scheduling the translation daemon")
val translationActor = actorSystem.actorOf(Props(new TranslationActor()))
actorSystem.scheduler.schedule(1 seconds, 30 minutes, translationActor, "translationDaemon")
applicationLifecycle.addStopHook { () =>
Logger.info("Shutting down translation daemon")
Future.successful(actorSystem.shutdown())
}
}
TranslationGuiceConfiguration.scala : (from an com.google.inject.AbstractModule)
class TranslationGuiceConfiguration extends AbstractModule {
def configure() : Unit = {
bind(classOf[ManageTranslationDaemon]).asEagerSingleton()
}
}
Then I extended parts of the DefaultMessagesApi (by looking at the code of MessagesApi) in
MessagesPersoApi.scala :
class MessagesPersoApi #Inject() (environment: Environment, configuration: Configuration, langs: Langs) extends DefaultMessagesApi(environment: Environment, configuration: Configuration, langs: Langs) {
private def joinPaths(first: Option[String], second: String) = first match {
case Some(parent) => new java.io.File(parent, second).getPath
case None => second
}
override protected def loadMessages(langCode: String): Map[String, String] = {
TranslationActor.cache.getOrElse(langCode, loadMessagesFromFile("messages." + langCode))
}
protected def loadMessagesFromFile(langCode: String): Map[String, String] = {
import scala.collection.JavaConverters._
environment.classLoader.getResources(joinPaths(messagesPrefix, langCode)).asScala.toList
.filterNot(url => Resources.isDirectory(environment.classLoader, url)).reverse
.map { messageFile =>
Messages.parse(Messages.UrlMessageSource(messageFile), messageFile.toString).fold(e => throw e, identity)
}.foldLeft(Map.empty[String, String]) {_ ++ _}
}
override protected def loadAllMessages: Map[String, Map[String, String]] = {
langs.availables.map(_.code).map { lang =>
(lang, loadMessages(lang))
}.toMap
.+("default" -> loadMessagesFromFile("messages"))
.+("default.play" -> loadMessagesFromFile("messages.default"))
}
}
And finally created a module (play.api.inject.Module)
MessagesPersoModule.scala :
class MessagesPersoModule extends Module {
def bindings(environment: Environment, configuration: Configuration) = {
Seq(
bind[Langs].to[DefaultLangs],
bind[MessagesApi].to[MessagesPersoApi]
)
}
}
And at the end, used it in my application.conf :
play.modules.disabled += "play.api.i18n.I18nModule"
play.modules.enabled += "modules.MessagesPersoModule"
play.modules.enabled += "modules.TranslationGuiceConfiguration"
Does that actually make sense? It seemed to me that it was a bit "complicated" to write. Is there an easier way to do that same logic with less code/classes ?
Thanks,
Yoann

Related

Download an archive file on the fly using play framework 2.3

I'am trying to create and download an archive file without relying on memory(to avoid out of memory exception for large file) I'am using for that play framework 2.3 with Scala, after some research I found an example: https://gist.github.com/kirked/412b5156f94419e71ce4a84ec1d54761, I made some modification on it. My problem is when I download the file and try to open it I get this exception: An error occurred while loading the archive.
here all the code:
def zip() = Action {
implicit request: Request[AnyContent] =>
val buffer = new ZipBuffer(10000)
val writeCentralDirectory = Enumerator.generateM(Future{
if (buffer.isClosed) {
None
}
else {
buffer.flush
buffer.close
Some(buffer.bytes)
}
})
val test = Enumerator.apply(ResolvedSource2("test.txt", "helllo"))
Ok.chunked(test &> zipeach2(buffer) andThen writeCentralDirectory >>> Enumerator.eof) as withCharset("application/zip") withHeaders(
CONTENT_DISPOSITION -> s"attachment; filename=aa.zip")
}
case class ResolvedSource2(filepath: String, stream: String)
def zipeach2(buffer: ZipBuffer)(implicit ec: ExecutionContext): Enumeratee[ResolvedSource2, Array[Byte]] = {
Enumeratee.mapConcat[ResolvedSource2] { source =>
buffer.zipStream.putNextEntry(new ZipEntry(source.filepath))
var done = false
def entryDone: Unit = {
done = true
buffer.zipStream.closeEntry
}
def restOfStream: Stream[Array[Byte]] = {
if (done) Stream.empty
else {
while (!done && !buffer.full) {
try {
val byte = source.stream
buffer.zipStream.write(byte.getBytes)
entryDone
}
catch {
case e: IOException =>
println(s"reading/zipping stream [${source.filepath}]", e)
}
}
buffer.bytes #:: restOfStream
}
}
restOfStream
}
}
}
class ZipBuffer(capacity: Int) {
private val buf = new ByteArrayOutputStream(capacity)
private var closed = false
val zipStream = new ZipOutputStream(buf)
def close(): Unit = {
if (!closed) {
closed = true
reset
zipStream.finish()
zipStream.close // writes central directory
}
}
def flush() = {
zipStream.flush()
}
def isClosed = closed
def reset: Unit = buf.reset
def full: Boolean = buf.size >= capacity
def bytes: Array[Byte] = {
val result = buf.toByteArray
reset
result
}
}

Play (Scala) Configuration Map Object

I would like to access the following configuration
customers {
"cust1" {
clientId: "id1"
attrs {
att1: "str1"
att2: "str2"
att3: "str3"
att4: "str4"
}
}
"cust2" {
clientId: "id2"
attrs: {
att1: "faldfjalfj"
att2: "reqwrewrqrq"
}
}
"cust3" {
clientId: "id3"
attrs {
att2: "xvcbzxbv"
}
}
}
as a Map[String, CustomerConfig] where CustomerConfig is
package models
import play.api.ConfigLoader
import com.typesafe.config.Config // I added this import in order to make the documentation snippet compile to the best of my knowledge.
case class CustomerConfig(clientId: String, attrs: Map[String, String])
object CustomerConfig {
implicit val configLoader: ConfigLoader[CustomerConfig] = new ConfigLoader[CustomerConfig] {
def load(rootConfig: Config, path: String): CustomerConfig = {
val config = rootConfig.getConfig(path)
CustomerConfig(
clientId = config.getString("clientId"),
attrs = config.get[Map[String, String]]("attrs").map { case (attr, attrVal) =>
(attr, attrVal)
})
}
}
}
For reference, this is how I am currently attempting to reference it:
val resellerEnvMap = conf.get[Map[String, CustomerConfig]]("customers").map {
case (customer, customerConfig) =>
customer -> customerConfig.attrs.map {
case (attr, attrVal) =>
attr -> new Obj(attrVal, customerConfig.clientId)
}
}
based on custom config loader documentation.
The problem is that config.get[A] does not exist (neither does config.getMap[K, V]) per the what I believe to be the API docs. I would like a way to populate that map from the configuration file. My end goal is to populate anything in the functional vicinity of Map[String, Map[String, (String, String)]] where the first String is the customer name, the 2nd is attribute name, the 3rd is the attribute value, and lastly, the 4th is the client ID.
Play 2.6 uses com.typesafe:config 1.3.2 while the link you posted seems to be from version 2? Here's one way to do it:
object CustomerConfig {
implicit val configLoader: ConfigLoader[CustomerConfig] = new ConfigLoader[CustomerConfig] {
def load(rootConfig: Config, path: String): CustomerConfig = {
val config = rootConfig.getConfig(path)
import scala.collection.JavaConverters._
val attrConfig = config.getConfig("attrs")
CustomerConfig(
clientId = config.getString("clientId"),
attrs = attrConfig.entrySet().asScala.map { entry =>
(entry.getKey, attrConfig.getString(entry.getKey))
}.toMap
)
}
}
}

How to list out all the files in the public directory in a Play Framework 2.X Scala application?

Here is my controller
class Proguard extends Controller {
val proguardFolder = "/public/proguards/"
val proguardFolderFix = "/public/proguards"
val proguardSuffix = "proguard-"
val proguardExtension = ".pro"
val title = "# Created by https://www.proguard.io/api/%s\n\n%s"
def proguard(libraryName: String) = Action {
val libraries = libraryName.split(',')
val availableLibs = listInDir(proguardFolderFix)
val result = availableLibs.filter(libraries.contains).map(readFile).mkString
Ok(title.format(libraryName, result))
}
def list() = Action {
Ok(Json.toJson(listInDir(proguardFolder)))
}
private def listInDir(filePath: String): List[String] = {
getListOfFiles(Play.getFile(filePath)).map(_.getName.replace(proguardExtension, "").replace(proguardSuffix, ""))
}
def getListOfFiles(dir: File): List[File] = {
dir.listFiles.toList
}
def readFile(string: String): String = {
val source = scala.io.Source.fromFile(Play.getFile(s"$proguardFolder$proguardSuffix$string$proguardExtension"))
val lines = try source.mkString finally source.close()
lines
}
}
It worked totally okay in debug mode, but in production at Heroku dir.listFiles. is giving me NPE
I've tried different ways, but looks like only solution is move my files to s3 or database.

Slick code generation for only a single schema

Is there a way to have Slick's code generation generate code for only a single schema? Say, public? I have extensions that create a whole ton of tables (eg postgis, pg_jobman) that make the code that slick generates gigantic.
Use this code with appropriate values and schema name,
object CodeGenerator {
def outputDir :String =""
def pkg:String =""
def schemaList:String = "schema1, schema2"
def url:String = "dburl"
def fileName:String =""
val user = "dbUsername"
val password = "dbPassword"
val slickDriver="scala.slick.driver.PostgresDriver"
val JdbcDriver = "org.postgresql.Driver"
val container = "Tables"
def generate() = {
val driver: JdbcProfile = buildJdbcProfile
val schemas = createSchemaList
var model = createModel(driver,schemas)
val codegen = new SourceCodeGenerator(model){
// customize Scala table name (table class, table values, ...)
override def tableName = dbTableName => dbTableName match {
case _ => dbTableName+"Table"
}
override def code = {
//imports is copied right out of
//scala.slick.model.codegen.AbstractSourceCodeGenerator
val imports = {
"import scala.slick.model.ForeignKeyAction\n" +
(if (tables.exists(_.hlistEnabled)) {
"import scala.slick.collection.heterogenous._\n" +
"import scala.slick.collection.heterogenous.syntax._\n"
} else ""
) +
(if (tables.exists(_.PlainSqlMapper.enabled)) {
"import scala.slick.jdbc.{GetResult => GR}\n" +
"// NOTE: GetResult mappers for plain SQL are only generated for tables where Slick knows how to map the types of all columns.\n"
} else ""
) + "\n\n" //+ tables.map(t => s"implicit val ${t.model.name.table}Format = Json.format[${t.model.name.table}]").mkString("\n")+"\n\n"
}
val bySchema = tables.groupBy(t => {
t.model.name.schema
})
val schemaFor = (schema: Option[String]) => {
bySchema(schema).sortBy(_.model.name.table).map(
_.code.mkString("\n")
).mkString("\n\n")
}
}
val joins = tables.flatMap( _.foreignKeys.map{ foreignKey =>
import foreignKey._
val fkt = referencingTable.TableClass.name
val pkt = referencedTable.TableClass.name
val columns = referencingColumns.map(_.name) zip
referencedColumns.map(_.name)
s"implicit def autojoin${fkt + name.toString} = (left:${fkt} ,right:${pkt}) => " +
columns.map{
case (lcol,rcol) =>
"left."+lcol + " === " + "right."+rcol
}.mkString(" && ")
})
override def entityName = dbTableName => dbTableName match {
case _ => dbTableName
}
override def Table = new Table(_) {
table =>
// customize table value (TableQuery) name (uses tableName as a basis)
override def TableValue = new TableValue {
override def rawName = super.rawName.uncapitalize
}
// override generator responsible for columns
override def Column = new Column(_){
// customize Scala column names
override def rawName = (table.model.name.table,this.model.name) match {
case _ => super.rawName
}
}
}
}
println(outputDir+"\\"+fileName)
(new File(outputDir)).mkdirs()
val fw = new FileWriter(outputDir+File.separator+fileName)
fw.write(codegen.packageCode(slickDriver, pkg, container))
fw.close()
}
def createModel(driver: JdbcProfile, schemas:Set[Option[String]]): Model = {
driver.simple.Database
.forURL(url, user = user, password = password, driver = JdbcDriver)
.withSession { implicit session =>
val filteredTables = driver.defaultTables.filter(
(t: MTable) => schemas.contains(t.name.schema)
)
PostgresDriver.createModel(Some(filteredTables))
}
}
def createSchemaList: Set[Option[String]] = {
schemaList.split(",").map({
case "" => None
case (name: String) => Some(name)
}).toSet
}
def buildJdbcProfile: JdbcProfile = {
val module = currentMirror.staticModule(slickDriver)
val reflectedModule = currentMirror.reflectModule(module)
val driver = reflectedModule.instance.asInstanceOf[JdbcProfile]
driver
}
}
I encountered the same problem and I found this question. The answer by S.Karthik sent me in the right direction. However, the code in the answer is slightly outdated. And I think a bit over-complicated. So I crafted my own solution:
import slick.codegen.SourceCodeGenerator
import slick.driver.JdbcProfile
import slick.model.Model
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, ExecutionContext}
val slickDriver = "slick.driver.PostgresDriver"
val jdbcDriver = "org.postgresql.Driver"
val url = "jdbc:postgresql://localhost:5432/mydb"
val outputFolder = "/path/to/src/test/scala"
val pkg = "com.mycompany"
val user = "user"
val password = "password"
object MySourceCodeGenerator {
def run(slickDriver: String, jdbcDriver: String, url: String, outputDir: String,
pkg: String, user: Option[String], password: Option[String]): Unit = {
val driver: JdbcProfile =
Class.forName(slickDriver + "$").getField("MODULE$").get(null).asInstanceOf[JdbcProfile]
val dbFactory = driver.api.Database
val db = dbFactory.forURL(url, driver = jdbcDriver, user = user.orNull,
password = password.orNull, keepAliveConnection = true)
try {
// **1**
val allSchemas = Await.result(db.run(
driver.createModel(None, ignoreInvalidDefaults = false)(ExecutionContext.global).withPinnedSession), Duration.Inf)
// **2**
val publicSchema = new Model(allSchemas.tables.filter(_.name.schema.isEmpty), allSchemas.options)
// **3**
new SourceCodeGenerator(publicSchema).writeToFile(slickDriver, outputDir, pkg)
} finally db.close
}
}
MySourceCodeGenerator.run(slickDriver, jdbcDriver, url, outputFolder, pkg, Some(user), Some(password))
I'll explain what's going on here:
I copied the run function from the SourceCodeGenerator class that's in the slick-codegen library. (I used version slick-codegen_2.10-3.1.1.)
// **1**: In the origninal code, the generated Model was referenced in a val called m. I renamed that to allSchemas.
// **2**: I created a new Model (publicSchema), using the options from the original model, and using a filtered version of the tables set from the original model. It turns out tables from the public schema don't get a schema name in the model. Hence the isEmpty. Should you need tables from one or more other schemas, you can easily create a different filter expression.
// **3**: I create a SourceCodeGenerator with the created publicSchema model.
Of course, it would even be better if the Slick codegenerator could incorporate an option to select one or more schemas.

Simple Example for Scala React

Based on the paper Deprecating the Observer Pattern with Scala.React I tried to set up a simple example from the paper, but it throwed an exception Exception in thread "main" java.lang.AssertionError: assertion failed: This method must be run on its domain scala.react.NilDebug#1502c065
A related question is Running a simple Scala.React expression.
How do I set up everything to use the great power of the scala react library?
Besides the library scala-react, I used the following example:
object MyFirstReact extends App {
object MyDomain extends scala.react.Domain {
protected val scheduler: Scheduler = new ManualScheduler
protected val engine: Engine = new Engine
}
import MyDomain._
case class MouseEvent(position: (Int, Int))
class Path(var positions: Seq[(Int, Int)]) {
def this(pos: (Int, Int)) = this(Seq(pos))
def lineTo(pos: (Int, Int)) { positions = positions :+ pos }
def close { positions = positions :+ positions.head }
}
val mouseDown: Events[MouseEvent] = Events.once(MouseEvent((0, 0)))
val mouseMove: Events[MouseEvent] = Events.once(MouseEvent((1, 1)))
val mouseUp: Events[MouseEvent] = Events.once(MouseEvent((2, 2)))
def draw(path: Path) { /* ... */ }
Reactor.loop { self =>
// step 1
val path = new Path((self await mouseDown).position)
self.loopUntil(mouseUp) { // step 2
val m = self awaitNext mouseMove
path.lineTo(m.position)
draw(path)
}
path.close // step 3
draw(path)
}
}
You got the exception because Reactor.loop must be wrapped with a schedule { ... } block. But fixing that is not enough (I got an java.lang.StackOverflowError).
Anyhow, I prepared a full working example using a scala-swing in which you can actually draw a line with the very same reactor code that you have. I put a sbt-buildable copy with scala-react included to github: https://github.com/zsoltdonca/scala-react-line-drawing
package zsd
import scala.swing._
import java.awt.event.{MouseMotionAdapter, MouseEvent, MouseAdapter}
import java.awt.{Color, Point}
import scala.react.Domain
object MyDomain extends Domain {
val scheduler = new SwingScheduler()
val engine = new Engine
}
import MyDomain._
object ScalaReactLineDrawing extends SimpleSwingApplication with Observing {
override def main(args: Array[String]) {
schedule { startup(args) }
start() // starts the scala-react engine
}
override def top: Frame = new MainFrame() {
contents = new FlowPanel() {
val mouseDown = EventSource[Point]
val mouseMove = EventSource[Point]
val mouseUp = EventSource[Point]
val mainProgramFlow = Reactor.loop {
self =>
// step 1
val path = new Path(self await mouseDown)
self.loopUntil(mouseUp) {
// step 2
val m = self awaitNext mouseMove
path.lineTo(m)
draw(path)
}
path.close() // step 3
draw(path)
}
peer.addMouseListener(new MouseAdapter {
override def mousePressed(e: MouseEvent): Unit = mouseDown << e.getPoint
override def mouseReleased(e: MouseEvent): Unit = mouseUp << e.getPoint
})
peer.addMouseMotionListener(new MouseMotionAdapter {
override def mouseDragged(e: MouseEvent): Unit = mouseMove << e.getPoint
})
class Path(var positions: Seq[Point]) {
def this(pos: Point) = this(Seq(pos))
def lineTo(pos: Point) {
positions = positions :+ pos
}
def close() {
positions = positions :+ positions.head
}
}
var pathDrawn = new Path(new Point(0, 0))
def draw(path: Path) {
pathDrawn = path
repaint()
}
override protected def paintComponent(g: swing.Graphics2D): Unit = {
super.paintComponent(g)
val xPoints = pathDrawn.positions.map(pos => pos.x).toArray
val yPoints = pathDrawn.positions.map(pos => pos.y).toArray
g.setColor(Color.BLACK)
g.drawPolyline(xPoints, yPoints, pathDrawn.positions.length)
}
}
preferredSize = new Dimension(400, 300)
pack()
}
}