24
24
package org .hatdex .hat .api .service .applications
25
25
26
26
import akka .Done
27
- import javax . inject . Inject
27
+ import akka . actor . ActorSystem
28
28
import com .mohiva .play .silhouette .api .Silhouette
29
+ import javax .inject .Inject
29
30
import org .hatdex .hat .api .models .applications .{ Application , ApplicationStatus , HatApplication , Version }
30
31
import org .hatdex .hat .api .models .{ AccessToken , DataDebit , EndpointQuery }
31
32
import org .hatdex .hat .api .service .richData .{ DataDebitService , RichDataDuplicateDebitException , RichDataService }
@@ -35,7 +36,7 @@ import org.hatdex.hat.authentication.models.HatUser
35
36
import org .hatdex .hat .dal .Tables
36
37
import org .hatdex .hat .dal .Tables .ApplicationStatusRow
37
38
import org .hatdex .hat .resourceManagement .HatServer
38
- import org .hatdex .hat .utils .FutureTransformations
39
+ import org .hatdex .hat .utils .{ FutureTransformations , Utils }
39
40
import org .hatdex .libs .dal .HATPostgresProfile .api ._
40
41
import org .joda .time .DateTime
41
42
import play .api .Logger
@@ -46,6 +47,7 @@ import play.api.mvc.RequestHeader
46
47
47
48
import scala .concurrent .Future
48
49
import scala .concurrent .duration ._
50
+ import scala .util .Success
49
51
50
52
class ApplicationStatusCheckService @ Inject () (wsClient : WSClient )(implicit val rec : RemoteExecutionContext ) {
51
53
@@ -56,11 +58,13 @@ class ApplicationStatusCheckService @Inject() (wsClient: WSClient)(implicit val
56
58
}
57
59
}
58
60
59
- protected def status (statusCheck : ApplicationStatus .External , token : String ): Future [Boolean ] =
61
+ protected def status (statusCheck : ApplicationStatus .External , token : String ): Future [Boolean ] = {
60
62
wsClient.url(statusCheck.statusUrl)
61
63
.withHttpHeaders(" x-auth-token" → token)
64
+ .withRequestTimeout(5000 .millis)
62
65
.get()
63
66
.map(_.status == statusCheck.expectedStatus)
67
+ }
64
68
}
65
69
66
70
class ApplicationsService @ Inject () (
@@ -69,15 +73,20 @@ class ApplicationsService @Inject() (
69
73
dataDebitService : DataDebitService ,
70
74
statusCheckService : ApplicationStatusCheckService ,
71
75
trustedApplicationProvider : TrustedApplicationProvider ,
72
- silhouette : Silhouette [HatApiAuthEnvironment ])(implicit val ec : DalExecutionContext ) {
76
+ silhouette : Silhouette [HatApiAuthEnvironment ],
77
+ system : ActorSystem )(implicit val ec : DalExecutionContext ) {
73
78
74
79
private val logger = Logger (this .getClass)
75
80
private val applicationsCacheDuration : FiniteDuration = 30 .minutes
76
81
77
82
def applicationStatus (id : String , bustCache : Boolean = false )(implicit hat : HatServer , user : HatUser , requestHeader : RequestHeader ): Future [Option [HatApplication ]] = {
83
+ val eventuallyCleanedCache = if (bustCache) {
84
+ cache.remove(s " apps: ${hat.domain}" )
85
+ cache.remove(appCacheKey(id))
86
+ }
87
+ else { Future .successful(Done ) }
78
88
for {
79
- _ ← if (bustCache) { cache.remove(appCacheKey(id)) } else { Future .successful(Done ) }
80
- _ ← if (bustCache) { cache.remove(s " apps: ${hat.domain}" ) } else { Future .successful(Done ) }
89
+ _ ← eventuallyCleanedCache
81
90
application ← cache.get[HatApplication ](appCacheKey(id))
82
91
.flatMap {
83
92
case Some (application) ⇒ Future .successful(Some (application))
@@ -86,24 +95,33 @@ class ApplicationsService @Inject() (
86
95
for {
87
96
maybeApp <- trustedApplicationProvider.application(id)
88
97
setup <- applicationSetupStatus(id)(hat.db)
89
- status <- FutureTransformations .transform(maybeApp.map(collectStatus (_, setup)))
90
- _ ← status.map(cache.set(appCacheKey(id), _ , applicationsCacheDuration)).getOrElse(Future .successful(Done ))
91
- } yield status
98
+ status <- FutureTransformations .transform(maybeApp.map(refetchApplicationsStatus (_, Seq ( setup).flatten )))
99
+ _ ← status.map(s ⇒ cache.set(appCacheKey(id), s._1 , applicationsCacheDuration)).getOrElse(Future .successful(Done ))
100
+ } yield status.map(_._1)
92
101
}
93
102
} yield application
94
103
}
95
104
96
105
def applicationStatus ()(implicit hat : HatServer , user : HatUser , requestHeader : RequestHeader ): Future [Seq [HatApplication ]] = {
97
- cache.getOrElseUpdate(s " apps: ${hat.domain}" , applicationsCacheDuration) {
98
- for {
99
- apps <- trustedApplicationProvider.applications // potentially caching
100
- setup <- applicationSetupStatus()(hat.db) // database
101
- statuses <- Future .sequence(apps
102
- .map(a => (a, setup.find(_.id == a.id)))
103
- .map(as => collectStatus(as._1, as._2)))
104
- _ ← Future .sequence(statuses.map(app ⇒ cache.set(appCacheKey(app.application.id), app, applicationsCacheDuration))) // reinject all fetched items as individual cached items
105
- } yield statuses
106
- }
106
+ cache.get[Seq [HatApplication ]](s " apps: ${hat.domain}" )
107
+ .flatMap({
108
+ case Some (applications) ⇒ Future .successful(applications)
109
+ case None ⇒
110
+ for {
111
+ apps ← trustedApplicationProvider.applications // potentially caching
112
+ setup ← applicationSetupStatus()(hat.db) // database
113
+ statuses ← Future .sequence(apps.map(refetchApplicationsStatus(_, setup)))
114
+ _ ← if (statuses.forall(_._2)) { cache.set(s " apps: ${hat.domain}" , statuses.unzip._1, applicationsCacheDuration) } else { Future .successful(Done ) }
115
+ } yield statuses.unzip._1
116
+ })
117
+ }
118
+
119
+ private def refetchApplicationsStatus (app : Application , setup : Seq [Tables .ApplicationStatusRow ])(implicit hat : HatServer , user : HatUser , requestHeader : RequestHeader ) = {
120
+ val aSetup = setup.find(_.id == app.id)
121
+ Utils .timeFuture(s " ${hat.domain} ${app.id} status " , logger)(collectStatus(app, aSetup))
122
+ .andThen {
123
+ case Success ((a, true )) ⇒ cache.set(appCacheKey(a.application.id), a, applicationsCacheDuration)
124
+ }
107
125
}
108
126
109
127
def setup (application : HatApplication )(implicit hat : HatServer , user : HatUser , requestHeader : RequestHeader ): Future [HatApplication ] = {
@@ -197,26 +215,33 @@ class ApplicationsService @Inject() (
197
215
}
198
216
}
199
217
218
+ private def fastOrDefault [T ](timeout : FiniteDuration , default : T )(block : => Future [T ]): Future [(T , Boolean )] = {
219
+ val fallback = akka.pattern.after(timeout, using = system.scheduler)(Future .successful((default, false )))
220
+ Future .firstCompletedOf(Seq (block.map((_, true )), fallback))
221
+ }
222
+
200
223
private def collectStatus (app : Application , setup : Option [ApplicationStatusRow ])(
201
224
implicit
202
- hat : HatServer , user : HatUser , requestHeader : RequestHeader ): Future [HatApplication ] = {
225
+ hat : HatServer , user : HatUser , requestHeader : RequestHeader ): Future [( HatApplication , Boolean ) ] = { // return status as well as flag indicating if it is successfully generated
203
226
204
227
setup match {
205
228
case Some (ApplicationStatusRow (_, version, true )) =>
229
+ val eventualStatus = fastOrDefault(5 .seconds, (false , " " ))(checkStatus(app))
230
+ val eventualMostRecentData = fastOrDefault(5 .seconds, Option [DateTime ](null ))(mostRecentDataTime(app))
206
231
for {
207
- (status, _) <- checkStatus(app)
208
- mostRecentData <- mostRecentDataTime(app)
232
+ (( status, _), canCacheStatus) <- eventualStatus
233
+ ( mostRecentData, canCacheData) <- eventualMostRecentData
209
234
} yield {
210
235
logger.debug(s " Check compatibility between $version and new ${app.status}: ${Version (version).greaterThan(app.status.compatibility)}" )
211
- HatApplication (app, setup = true , enabled = true , active = status,
236
+ ( HatApplication (app, setup = true , enabled = true , active = status,
212
237
Some (app.status.compatibility.greaterThan(Version (version))), // Needs updating if setup version beyond compatible
213
- mostRecentData)
238
+ mostRecentData), canCacheStatus && canCacheData)
214
239
}
215
240
case Some (ApplicationStatusRow (_, _, false )) =>
216
241
// If application has been disabled, reflect in status
217
- Future .successful(HatApplication (app, setup = true , enabled = false , active = false , needsUpdating = None , mostRecentData = None ))
242
+ Future .successful(( HatApplication (app, setup = true , enabled = false , active = false , needsUpdating = None , mostRecentData = None ), true ))
218
243
case None =>
219
- Future .successful(HatApplication (app, setup = false , enabled = false , active = false , needsUpdating = None , mostRecentData = None ))
244
+ Future .successful(( HatApplication (app, setup = false , enabled = false , active = false , needsUpdating = None , mostRecentData = None ), true ))
220
245
}
221
246
}
222
247
0 commit comments