Skip to content

Commit

Permalink
Update libs for java11
Browse files Browse the repository at this point in the history
  • Loading branch information
lindseydew committed Feb 28, 2024
1 parent 35946ea commit b668734
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 72 deletions.
127 changes: 91 additions & 36 deletions common/src/main/scala/com/gu/sfl/model/model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,47 @@ package com.gu.sfl.model
import java.io.IOException
import java.time.format.DateTimeFormatter
import java.time.{Instant, LocalDateTime, ZoneOffset}

import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.core.{JsonGenerator, JsonParser, JsonProcessingException}
import com.fasterxml.jackson.databind.annotation.{JsonDeserialize, JsonSerialize}
import com.fasterxml.jackson.core.{
JsonGenerator,
JsonParser,
JsonProcessingException
}
import com.fasterxml.jackson.databind.annotation.{
JsonDeserialize,
JsonSerialize
}
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.databind.{DeserializationContext, JsonNode, SerializerProvider}
import com.fasterxml.jackson.databind.{
DeserializationContext,
JsonNode,
SerializerProvider
}
import com.gu.sfl.lib.Jackson.mapper

object SavedArticle {
implicit val localDateOrdering: Ordering[LocalDateTime] = Ordering.by(_.toEpochSecond(ZoneOffset.UTC))
implicit val ordering: Ordering[SavedArticle] = Ordering.by[SavedArticle, LocalDateTime](_.date)
implicit val localDateOrdering: Ordering[LocalDateTime] =
Ordering.by(_.toEpochSecond(ZoneOffset.UTC))
implicit val ordering: Ordering[SavedArticle] =
Ordering.by[SavedArticle, LocalDateTime](_.date)
}


@JsonSerialize(using = classOf[SavedArticleSerializer])
case class SavedArticle(id: String, shortUrl: String, date: LocalDateTime, read: Boolean)
case class SavedArticle(
id: String,
shortUrl: String,
date: LocalDateTime,
read: Boolean
)

@JsonDeserialize(using = classOf[DirtySavedArticleDeserializer])
case class DirtySavedArticle(id: Option[String], shortUrl: Option[String], date: Option[LocalDateTime], read: Boolean)
case class DirtySavedArticle(
id: Option[String],
shortUrl: Option[String],
date: Option[LocalDateTime],
read: Boolean
)

case class SyncedPrefsResponse(status: String, syncedPrefs: SyncedPrefs)

Expand All @@ -30,9 +52,9 @@ case class SavedArticlesResponse(status: String, savedArticles: SavedArticles)
/*This is cribbed from a historic version of the Identity model: https://github.com/guardian/identity/blob/main/identity-model/src/main/scala/com/gu/identity/model/Model.scala
This service was designed to sync various categories of data for a signed in user of which saved articles were one flavour - hence synced prefs. Because we need to preserve integrity with
existing clients (ie apps) we need to maintain this model in order to render the same json
*/
case class SyncedPrefs(userId: String, savedArticles: Option[SavedArticles]) {
def ordered: SyncedPrefs = copy( savedArticles = savedArticles.map(_.ordered) )
*/
case class SyncedPrefs(userId: String, savedArticles: Option[SavedArticles]) {
def ordered: SyncedPrefs = copy(savedArticles = savedArticles.map(_.ordered))
}

sealed trait SyncedPrefsData {
Expand All @@ -43,38 +65,55 @@ sealed trait SyncedPrefsData {
}

object SavedArticles {
private val oldDate = LocalDateTime.of(2010,1,1,0,0,0)
private val oldDate = LocalDateTime.of(2010, 1, 1, 0, 0, 0)
def nextVersion() = Instant.now().toEpochMilli.toString
def apply(articles: List[SavedArticle]) : SavedArticles = SavedArticles(nextVersion(), articles)
def apply(dirtySavedArticles: DirtySavedArticles) : SavedArticles = SavedArticles(dirtySavedArticles.version, buildArticlesWithDates(dirtySavedArticles))
def apply(articles: List[SavedArticle]): SavedArticles =
SavedArticles(nextVersion(), articles)
def apply(dirtySavedArticles: DirtySavedArticles): SavedArticles =
SavedArticles(
dirtySavedArticles.version,
buildArticlesWithDates(dirtySavedArticles)
)
private def buildArticlesWithDates(dirtySavedArticles: DirtySavedArticles) = {
val startingDate = dirtySavedArticles.articles.flatMap(_.date).headOption.map(_.minusDays(1)).getOrElse(oldDate)
dirtySavedArticles.articles.foldLeft((startingDate, List.empty[SavedArticle])) {
case ((lastGoodDate, clean), dirtySavedArticle) => {
dirtySavedArticle match {
case DirtySavedArticle(Some(id), Some(shortUrl), date, read) => {
val thisDate = date.getOrElse(lastGoodDate.plusSeconds(1))
(thisDate, SavedArticle(id, shortUrl, thisDate, read) :: clean)
val startingDate = dirtySavedArticles.articles
.flatMap(_.date)
.headOption
.map(_.minusDays(1))
.getOrElse(oldDate)
dirtySavedArticles.articles
.foldLeft((startingDate, List.empty[SavedArticle])) {
case ((lastGoodDate, clean), dirtySavedArticle) => {
dirtySavedArticle match {
case DirtySavedArticle(Some(id), Some(shortUrl), date, read) => {
val thisDate = date.getOrElse(lastGoodDate.plusSeconds(1))
(thisDate, SavedArticle(id, shortUrl, thisDate, read) :: clean)
}
case _ => (lastGoodDate, clean)
}
case _ => (lastGoodDate, clean)
}
}
}._2.reverse
._2
.reverse
}

val empty = SavedArticles("1", List.empty)
}

case class SavedArticles(version: String, articles: List[SavedArticle]) extends SyncedPrefsData {
case class SavedArticles(version: String, articles: List[SavedArticle])
extends SyncedPrefsData {
override def advanceVersion: SavedArticles = copy(version = nextVersion)
@JsonIgnore
lazy val numberOfArticles = articles.length
def ordered: SavedArticles = copy(articles = articles.sorted)
def deduped: SavedArticles = copy( articles = articles.groupBy(_.id).map(_._2.max).toList.sorted )
def mostRecent(limit: Int) = copy( articles = articles.sorted.takeRight(limit) )
def deduped: SavedArticles =
copy(articles = articles.groupBy(_.id).map(_._2.max).toList.sorted)
def mostRecent(limit: Int) = copy(articles = articles.sorted.takeRight(limit))
}

case class DirtySavedArticles(version: String, articles: List[DirtySavedArticle])
case class DirtySavedArticles(
version: String,
articles: List[DirtySavedArticle]
)
case class ErrorResponse(status: String = "error", errors: List[Error])

case class Error(message: String, description: String)
Expand All @@ -83,33 +122,49 @@ object SavedArticleDateSerializer {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
}

class DirtySavedArticleDeserializer(t: Class[DirtySavedArticle]) extends StdDeserializer[DirtySavedArticle](t) {
def this () = this(null)
class DirtySavedArticleDeserializer(t: Class[DirtySavedArticle])
extends StdDeserializer[DirtySavedArticle](t) {
def this() = this(null)
@Override
@throws(classOf[IOException])
@throws(classOf[JsonProcessingException])
override def deserialize(p: JsonParser, ctxt: DeserializationContext): DirtySavedArticle = {
override def deserialize(
p: JsonParser,
ctxt: DeserializationContext
): DirtySavedArticle = {
val node: JsonNode = p.readValueAsTree()
val id = Option(node.get("id")).filter(_.isTextual).map(_.asText())
val shortUrl = Option(node.get("shortUrl")).filter(_.isTextual).map(_.asText())
val shortUrl =
Option(node.get("shortUrl")).filter(_.isTextual).map(_.asText())
val read = Option(node.get("read")).filter(_.isBoolean).map(_.asBoolean())
val date = Option(node.get("date")).filter(_.isTextual).map(_.asText()).map(LocalDateTime.parse(_, SavedArticleDateSerializer.formatter))
val date = Option(node.get("date"))
.filter(_.isTextual)
.map(_.asText())
.map(LocalDateTime.parse(_, SavedArticleDateSerializer.formatter))
DirtySavedArticle(id, shortUrl, date, read.getOrElse(false))
}
}

class SavedArticleSerializer(t:Class[SavedArticle]) extends StdSerializer[SavedArticle](t) {
class SavedArticleSerializer(t: Class[SavedArticle])
extends StdSerializer[SavedArticle](t) {

def this() = this(null)

@Override
@throws(classOf[IOException])
@throws(classOf[JsonProcessingException])
def serialize(value: SavedArticle, gen: JsonGenerator, serializers: SerializerProvider) = {
def serialize(
value: SavedArticle,
gen: JsonGenerator,
serializers: SerializerProvider
) = {
gen.writeStartObject()
gen.writeStringField("id", value.id)
gen.writeStringField("shortUrl", value.shortUrl)
gen.writeStringField("date", SavedArticleDateSerializer.formatter.format(value.date))
gen.writeStringField(
"date",
SavedArticleDateSerializer.formatter.format(value.date)
)
gen.writeBooleanField("read", value.read)
gen.writeEndObject()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,66 @@
package com.gu.sfl.persistence

import com.amazonaws.auth.DefaultAWSCredentialsProviderChain
import com.amazonaws.services.dynamodbv2.{AmazonDynamoDBAsync, AmazonDynamoDBAsyncClient}
import org.scanamo.{Scanamo, Table}
import org.scanamo.auto._
import org.scanamo.syntax._
import org.scanamo.{PutReturn, Scanamo, Table}
import com.gu.sfl.Logging
import com.gu.sfl.lib.Jackson._
import com.gu.sfl.model._
import software.amazon.awssdk.services.dynamodb.DynamoDbClient

import scala.util.{Failure, Success, Try}

case class PersistenceConfig(app: String, stage: String) {
val tableName = s"$app-$stage-articles"
}

case class DynamoSavedArticles(userId: String, version: String, articles: String)
trait SavedArticlesPersistence {
def read(userId: String): Try[Option[SavedArticles]]

object DynamoSavedArticles {
def apply(userId: String, savedArticles: SavedArticles): DynamoSavedArticles = DynamoSavedArticles(userId, savedArticles.nextVersion, mapper.writeValueAsString(savedArticles.articles))
}
def update(
userId: String,
savedArticles: SavedArticles
): Try[Option[SavedArticles]]

trait SavedArticlesPersistence {
def read(userId: String) : Try[Option[SavedArticles]]
def write(
userId: String,
savedArticles: SavedArticles
): Try[Option[SavedArticles]]
}

def update(userId: String, savedArticles: SavedArticles): Try[Option[SavedArticles]]
case class DynamoSavedArticles(
userId: String,
version: String,
articles: String
)

def write(userId: String, savedArticles: SavedArticles) : Try[Option[SavedArticles]]
object DynamoSavedArticles {
def apply(userId: String, savedArticles: SavedArticles): DynamoSavedArticles =
DynamoSavedArticles(
userId,
savedArticles.nextVersion,
mapper.writeValueAsString(savedArticles.articles)
)
}

class SavedArticlesPersistenceImpl(persistanceConfig: PersistenceConfig) extends SavedArticlesPersistence with Logging {
implicit def toSavedArticles(dynamoSavedArticles: DynamoSavedArticles): SavedArticles = {
val articles = mapper.readValue[List[SavedArticle]](dynamoSavedArticles.articles)
class SavedArticlesPersistenceImpl(persistanceConfig: PersistenceConfig)
extends SavedArticlesPersistence
with Logging {
implicit def toSavedArticles(
dynamoSavedArticles: DynamoSavedArticles
): SavedArticles = {
val articles =
mapper.readValue[List[SavedArticle]](dynamoSavedArticles.articles)
SavedArticles(dynamoSavedArticles.version, articles)
}

private val client: AmazonDynamoDBAsync = AmazonDynamoDBAsyncClient.asyncBuilder().withCredentials(DefaultAWSCredentialsProviderChain.getInstance()).build()
// TODO - validate if default instance creds are needed here
private val client = DynamoDbClient.builder().build()
//TODO confirm that it's ok to share the same client concurrently in all requests.. I guess if this is a lambda there won't be concurrent requests anyway ?
private val scanamo = Scanamo(client)
private val table = Table[DynamoSavedArticles](persistanceConfig.tableName)

import org.scanamo.syntax._
import org.scanamo.generic.auto._

val table = Table[DynamoSavedArticles](persistanceConfig.tableName)

override def read(userId: String): Try[Option[SavedArticles]] = {
scanamo.exec(table.get("userId" -> userId)) match {
Expand All @@ -55,14 +77,24 @@ class SavedArticlesPersistenceImpl(persistanceConfig: PersistenceConfig) extends
}
}

override def write(userId: String, savedArticles: SavedArticles): Try[Option[SavedArticles]] = {
scanamo.exec(table.put(DynamoSavedArticles(userId, savedArticles))) match {
override def write(
userId: String,
savedArticles: SavedArticles
): Try[Option[SavedArticles]] = {
scanamo.exec(
table.putAndReturn(PutReturn.NewValue)(
DynamoSavedArticles(userId, savedArticles)
)
) match {
case Some(Right(articles)) =>
logger.debug(s"Succcesfully saved articles for $userId")
Success(Some(articles.ordered))
case Some(Left(error)) =>
val exception = new IllegalArgumentException(s"$error")
logger.debug(s"Exception Thrown saving articles for $userId:", exception)
logger.debug(
s"Exception Thrown saving articles for $userId:",
exception
)
Failure(exception)
case None => {
logger.debug(s"Successfully saved but none retrieved for $userId")
Expand All @@ -71,17 +103,25 @@ class SavedArticlesPersistenceImpl(persistanceConfig: PersistenceConfig) extends
}
}

override def update(userId: String, savedArticles: SavedArticles): Try[Option[SavedArticles]] = {
scanamo.exec(table.update("userId" -> userId,
override def update(
userId: String,
savedArticles: SavedArticles
): Try[Option[SavedArticles]] = {
scanamo.exec(
table.update(
"userId" -> userId,
set("version" -> savedArticles.nextVersion) and
set("articles" -> mapper.writeValueAsString(savedArticles.articles)))
set("articles" -> mapper.writeValueAsString(savedArticles.articles))
)
) match {
case Right(articles) =>
logger.debug("Updated articles")
Success(Some(articles.ordered))
case Left(error) =>
val ex = new IllegalStateException(s"${error}")
logger.error(s"Error updating articles for userId ${userId}: ${ex.getMessage} ")
logger.error(
s"Error updating articles for userId ${userId}: ${ex.getMessage} "
)
Failure(ex)
}
}
Expand Down
29 changes: 18 additions & 11 deletions project/dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,35 @@ object Dependencies {
val specsVersion = "4.0.3"

val awsLambda = "com.amazonaws" % "aws-lambda-java-core" % "1.2.0"
val awsDynamo ="com.amazonaws" % "aws-java-sdk-dynamodb" % awsSdkVersion
val awsDynamo = "software.amazon.awssdk" % "dynamodb" % "2.24.11"
val awsLambdaLog = "com.amazonaws" % "aws-lambda-java-log4j2" % "1.5.0"
val awsJavaSdk ="com.amazonaws" % "aws-java-sdk-ec2" % awsSdkVersion
val awsSqs ="com.amazonaws" % "aws-java-sdk-sqs" % awsSdkVersion
val awsJavaSdk = "com.amazonaws" % "aws-java-sdk-ec2" % awsSdkVersion
val awsSqs = "com.amazonaws" % "aws-java-sdk-sqs" % awsSdkVersion
val awsLambdaEvent = "com.amazonaws" % "aws-lambda-java-events" % "2.2.2"

val jackson = "com.fasterxml.jackson.module" %% "jackson-module-scala" % jacksonVersion
val jacksonDataFormat = "com.fasterxml.jackson.dataformat" % "jackson-dataformat-cbor" % jacksonVersion
val jacksonJdk8DataType = "com.fasterxml.jackson.datatype" % "jackson-datatype-jdk8" % jacksonVersion
val jacksonJsrDataType = "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % jacksonVersion
val jackson =
"com.fasterxml.jackson.module" %% "jackson-module-scala" % jacksonVersion
val jacksonDataFormat =
"com.fasterxml.jackson.dataformat" % "jackson-dataformat-cbor" % jacksonVersion
val jacksonJdk8DataType =
"com.fasterxml.jackson.datatype" % "jackson-datatype-jdk8" % jacksonVersion
val jacksonJsrDataType =
"com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % jacksonVersion
val log4j = "org.apache.logging.log4j" % "log4j-slf4j-impl" % log4j2Version
val commonsIo = "commons-io" % "commons-io" % "2.6"
val scanamo = "org.scanamo" %% "scanamo" % "1.0.0-M11"
val scanamo = "org.scanamo" %% "scanamo" % "1.0.0-M30"
val okHttp = "com.squareup.okhttp3" % "okhttp" % "4.9.2"
val specsCore = "org.specs2" %% "specs2-core" % specsVersion % "test"
val specsScalaCheck = "org.specs2" %% "specs2-scalacheck" % specsVersion % "test"
val specsScalaCheck =
"org.specs2" %% "specs2-scalacheck" % specsVersion % "test"
val specsMock = "org.specs2" %% "specs2-mock" % specsVersion % "test"
val identityAuthCore = "com.gu.identity" %% "identity-auth-core" % "4.17"

//DependencyOverride
val commonsLogging = "commons-logging" % "commons-logging" % "1.2"
val slf4jApi = "org.slf4j" % "slf4j-api" % "1.7.25"
val apacheLog4JCore = "org.apache.logging.log4j" % "log4j-core" % log4j2Version
val apacheLog$jApi = "org.apache.logging.log4j" % "log4j-api" % log4j2Version % "provided"
val apacheLog4JCore =
"org.apache.logging.log4j" % "log4j-core" % log4j2Version
val apacheLog$jApi =
"org.apache.logging.log4j" % "log4j-api" % log4j2Version % "provided"
}

0 comments on commit b668734

Please sign in to comment.