Skip to content

Commit

Permalink
Merge pull request #356 from gicmo/etag
Browse files Browse the repository at this point in the history
Initial eTag support
  • Loading branch information
mpsonntag committed Mar 21, 2016
2 parents f7157bf + 72956aa commit 268243e
Show file tree
Hide file tree
Showing 12 changed files with 175 additions and 40 deletions.
35 changes: 26 additions & 9 deletions app/controllers/api/Abstracts.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package controllers.api

import org.apache.commons.codec.digest.DigestUtils
import org.joda.time.format.DateTimeFormat
import play.api._
import play.api.libs.functional.syntax._
Expand Down Expand Up @@ -45,7 +46,18 @@ extends Silhouette[Login, CachedCookieAuthenticator] {

val newAbs = abstractService.create(abs, conference, request.identity.account)

Created(Json.toJson(newAbs))
Created(Json.toJson(newAbs)).withHeaders(ETAG -> newAbs.eTag)
}

def resultWithETag[A](abstracts: Seq[Abstract])(implicit request: Request[A]) = {
val theirs = request.headers.get("If-None-Match")
val eTag = abstracts.map(_.eTag).reduce((a, b) => DigestUtils.md5Hex(a + b))

if (theirs.contains(eTag)) {
NotModified
} else {
Ok(Json.toJson(abstracts)).withHeaders(ETAG -> eTag)
}
}

/**
Expand All @@ -58,7 +70,7 @@ extends Silhouette[Login, CachedCookieAuthenticator] {
val conference = conferenceService.get(id)
val abstracts = abstractService.list(conference)

Ok(Json.toJson(abstracts))
resultWithETag(abstracts)
}

/**
Expand All @@ -75,7 +87,7 @@ extends Silhouette[Login, CachedCookieAuthenticator] {
}

val abstracts = abstractService.listAll(conference)
Ok(Json.toJson(abstracts))
resultWithETag(abstracts)
}

/**
Expand All @@ -85,8 +97,7 @@ extends Silhouette[Login, CachedCookieAuthenticator] {
*/
def listByAccount(id: String) = SecuredAction { implicit request =>
val ownAbstracts = abstractService.listOwn(request.identity.account)

Ok(Json.toJson(ownAbstracts))
resultWithETag(ownAbstracts)
}


Expand All @@ -100,15 +111,15 @@ extends Silhouette[Login, CachedCookieAuthenticator] {
val conference = conferenceService.get(conferenceId)
val abstracts = abstractService.listOwn(conference, request.identity.account)

Ok(Json.toJson(abstracts))
resultWithETag(abstracts)
}

/**
* An abstract info by id.
*
* @param id The id of the abstract.
*
* @return An abstract as JSON / abstract page.
* @return An abstract as JSON / abstract page.
*/
def get(id: String) = UserAwareAction { implicit request =>
Logger.debug(s"Getting abstract with uuid: [$id]")
Expand All @@ -118,7 +129,13 @@ extends Silhouette[Login, CachedCookieAuthenticator] {
case _ => abstractService.get(id)
}

Ok(Json.toJson(abs)).withHeaders(LAST_MODIFIED -> rfcDateFormatter.print(abs.mtime))
if (request.headers.get("If-None-Match").contains(abs.eTag)) {
NotModified
} else {
Ok(Json.toJson(abs)).withHeaders(
LAST_MODIFIED -> rfcDateFormatter.print(abs.mtime),
ETAG -> abs.eTag)
}
}

/**
Expand Down Expand Up @@ -148,7 +165,7 @@ extends Silhouette[Login, CachedCookieAuthenticator] {

val newAbstract = abstractService.update(abs, request.identity.account)

Ok(Json.toJson(newAbstract))
Ok(Json.toJson(newAbstract)).withHeaders(ETAG -> newAbstract.eTag)
}

/**
Expand Down
33 changes: 28 additions & 5 deletions app/controllers/api/Conferences.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package controllers.api

import org.apache.commons.codec.digest.DigestUtils
import play.api.mvc._
import play.api.libs.json._
import utils.serializer.{AccountFormat, ConferenceFormat}
Expand Down Expand Up @@ -32,7 +33,18 @@ class Conferences(implicit val env: Environment[Login, CachedCookieAuthenticator
val conference = request.body.as[Conference]
val resp = conferenceService.create(conference, request.identity.account)

Created(confFormat.writes(resp))
Created(confFormat.writes(resp)).withHeaders(ETAG -> conference.eTag)
}

def resultWithETag[A](conferences: Seq[Conference])(implicit request: Request[A]) = {
val theirs = request.headers.get("If-None-Match")
val eTag = conferences.map(_.eTag).reduce((a, b) => DigestUtils.md5Hex(a + b))

if (theirs.contains(eTag)) {
NotModified
} else {
Ok(Json.toJson(conferences)).withHeaders(ETAG -> eTag)
}
}

/**
Expand All @@ -46,7 +58,8 @@ class Conferences(implicit val env: Environment[Login, CachedCookieAuthenticator
} else {
conferenceService.list()
}
Ok(Json.toJson(conferences))

resultWithETag(conferences)
}

/**
Expand All @@ -57,7 +70,7 @@ class Conferences(implicit val env: Environment[Login, CachedCookieAuthenticator
*/
def listWithOwnAbstracts = SecuredAction { implicit request =>
val conferences = conferenceService.listWithAbstractsOfAccount(request.identity.account)
Ok(Json.toJson(conferences))
resultWithETag(conferences)
}

/**
Expand All @@ -67,7 +80,17 @@ class Conferences(implicit val env: Environment[Login, CachedCookieAuthenticator
* @return OK with conference in JSON / NotFound
*/
def get(id: String) = Action { implicit request =>
Ok(confFormat.writes(conferenceService.get(id)))

val conference = conferenceService.get(id)

val theirs = request.headers.get("If-None-Match")
val eTag = conference.eTag

if (theirs.contains(eTag)) {
NotModified
} else {
Ok(confFormat.writes(conference)).withHeaders(ETAG -> eTag)
}
}

/**
Expand All @@ -81,7 +104,7 @@ class Conferences(implicit val env: Environment[Login, CachedCookieAuthenticator
conference.uuid = id
val resp = conferenceService.update(conference, request.identity.account)

Ok(confFormat.writes(resp))
Ok(confFormat.writes(resp)).withHeaders(ETAG -> conference.eTag)
}

/**
Expand Down
5 changes: 4 additions & 1 deletion app/models/Abstract.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import models.Model._
import java.util.{Set => JSet, TreeSet => JTreeSet}
import javax.persistence._
import models.util.DateTimeConverter
import org.apache.commons.codec.digest.DigestUtils
import org.joda.time.{DateTimeZone, DateTime}


/**
* A model class for abstracts
*/
@Entity
class Abstract extends Model with Owned {
class Abstract extends Model with Owned with Tagged {

var title: String = _
var topic: String = _
Expand Down Expand Up @@ -64,6 +65,8 @@ class Abstract extends Model with Owned {
override def canRead(account: Account): Boolean = {
isOwner(account) || account.isAdmin || conference.isOwner(account)
}

def eTag = DigestUtils.md5Hex(uuid + mtime.toString())
}


Expand Down
11 changes: 9 additions & 2 deletions app/models/Conference.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ package models
import models.Model._
import java.util.{Set => JSet, TreeSet => JTreeSet}
import javax.persistence._
import org.joda.time.DateTime

import org.joda.time.{DateTimeZone, DateTime}
import org.apache.commons.codec.digest.DigestUtils

import org.joda.time.format.DateTimeFormat
import models.util.DateTimeConverter

Expand All @@ -24,7 +27,7 @@ import models.util.DateTimeConverter
* maybe we should keep it simple for now.
*/
@Entity
class Conference extends Model with Owned {
class Conference extends Model with Owned with Tagged {

def dateFormatter = DateTimeFormat.forPattern("d MMMM, yyyy")

Expand Down Expand Up @@ -106,6 +109,8 @@ class Conference extends Model with Owned {
}
}

def eTag : String = DigestUtils.md5Hex(uuid + mtime.toString())

}

object Conference extends Model {
Expand Down Expand Up @@ -166,6 +171,8 @@ object Conference extends Model {
conference.schedule = unwrapRef(schedule)
conference.info = unwrapRef(info)

conference.mtime = new DateTime(DateTimeZone.UTC)

conference
}

Expand Down
15 changes: 15 additions & 0 deletions app/models/Tagged.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright © 2016, German Neuroinformatics Node (G-Node)
// Christian Kellner <christian@kellner.me>
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted under the terms of the BSD License. See
// LICENSE file in the root of the Project.

package models


trait Tagged {
def eTag: String
}
42 changes: 19 additions & 23 deletions app/service/AbstractService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import service.util.PermissionsBase
import scala.collection.JavaConversions._
import scala.collection.mutable.{Map => MMap}

import org.joda.time.{DateTimeZone, DateTime}


//for the patch method
abstract class PatchOp
case class PatchAddSortId(id: Int) extends PatchOp
Expand All @@ -35,8 +38,7 @@ class AbstractService(figPath: String) extends PermissionsBase {
* List all published abstracts that belong to a conference.
*
* @param conference The conference for which to list the abstracts.
*
* @return All published abstracts that are associated with a
* @return All published abstracts that are associated with a
* certain conference.
*/
def list(conference: Conference) : Seq[Abstract] = {
Expand All @@ -63,8 +65,7 @@ class AbstractService(figPath: String) extends PermissionsBase {
* List all abstracts (independent of the state) that belong to a conference.
*
* @param conference The conference for which to list the abstracts.
*
* @return All published abstracts that are associated with a
* @return All published abstracts that are associated with a
* certain conference.
*/
def listAll(conference: Conference) : Seq[Abstract] = {
Expand All @@ -85,8 +86,7 @@ class AbstractService(figPath: String) extends PermissionsBase {
* List all published and unpublished abstracts that belong to an account.
*
* @param account The account for which to list the abstracts.
*
* @return All abstracts that belong to an account.
* @return All abstracts that belong to an account.
*/
def listOwn(account: Account) : Seq[Abstract] = {
query { em =>
Expand Down Expand Up @@ -131,10 +131,8 @@ class AbstractService(figPath: String) extends PermissionsBase {
* Return a published (= Accepted && Conference.isPublished) abstract by id.
*
* @param id The id of the abstract.
*
* @return The abstract with the specified id.
*
* @throws NoResultException If the conference was not found
* @return The abstract with the specified id.
* @throws NoResultException If the conference was not found
*/
def get(id: String) : Abstract= {
query { em =>
Expand Down Expand Up @@ -162,10 +160,8 @@ class AbstractService(figPath: String) extends PermissionsBase {
*
* @param id The id of the abstract.
* @param account The account who wants to request the abstract.
*
* @return The abstract with the specified id.
*
* @throws EntityNotFoundException If the account does not exist
* @return The abstract with the specified id.
* @throws EntityNotFoundException If the account does not exist
* @throws IllegalAccessException if not accessible
* @throws NoResultException If was not found
*/
Expand Down Expand Up @@ -207,12 +203,10 @@ class AbstractService(figPath: String) extends PermissionsBase {
* Create a new abstract.
* This is only permitted if the account is one of the owners.
*
*
* @param abstr The Abstract to create.
* @param abstr The Abstract to create.
* @param conference The the id of the conference.
* @param account The account who wants to perform the creation.
*
* @return The created and persisted abstract.
* @return The created and persisted abstract.
*/
def create(abstr : Abstract, conference: Conference, account: Account) : Abstract = {
val abstrCreated = transaction { (em, tx) =>
Expand Down Expand Up @@ -240,6 +234,8 @@ class AbstractService(figPath: String) extends PermissionsBase {
abstr.stateLog.add(StateLogEntry(abstr, AbstractState.InPreparation,
account, Some("Initial abstract creation")))

abstr.ctime = new DateTime(DateTimeZone.UTC)

em.merge(abstr)
}

Expand All @@ -252,8 +248,7 @@ class AbstractService(figPath: String) extends PermissionsBase {
*
* @param abstr The Abstract to update.
* @param account The account who wants to perform the update.
*
* @return The updated and persisted abstract.
* @return The updated and persisted abstract.
*/
def update(abstr : Abstract, account: Account) : Abstract = {
val abstrUpdated = transaction { (em, tx) =>
Expand Down Expand Up @@ -289,6 +284,7 @@ class AbstractService(figPath: String) extends PermissionsBase {
abstr.owners = abstrChecked.owners
abstr.conference = abstrChecked.conference
abstr.figures = abstrChecked.figures
abstr.ctime = abstrChecked.ctime

val merged = em.merge(abstr)

Expand Down Expand Up @@ -319,8 +315,7 @@ class AbstractService(figPath: String) extends PermissionsBase {
*
* @param id The id of the abstract to delete.
* @param account The account who wants to perform the delete.
*
* @throws IllegalArgumentException If the conference has no uuid
* @throws IllegalArgumentException If the conference has no uuid
* @throws EntityNotFoundException If the conference or the user does not exist
* @throws IllegalAccessException If account is not an owner.
*/
Expand Down Expand Up @@ -360,7 +355,8 @@ class AbstractService(figPath: String) extends PermissionsBase {

/**
* List all state logs of a given abstract
* @param id abstract id
*
* @param id abstract id
* @param account account (for permission)
* @return Sorted sequence of log entries (newest first)
*/
Expand Down
Loading

0 comments on commit 268243e

Please sign in to comment.