Refactor Aquarium to make it more configurable
[aquarium] / src / main / scala / gr / grnet / aquarium / AquariumBuilder.scala
1 /*
2  * Copyright 2011-2012 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above
13  *      copyright notice, this list of conditions and the following
14  *      disclaimer in the documentation and/or other materials
15  *      provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * The views and conclusions contained in the software and
31  * documentation are those of the authors and should not be
32  * interpreted as representing official policies, either expressed
33  * or implied, of GRNET S.A.
34  */
35
36 package gr.grnet.aquarium
37
38 import com.ckkloverdos.key.{BooleanKey, TypedKey}
39 import com.ckkloverdos.env.Env
40 import com.ckkloverdos.props.Props
41 import com.ckkloverdos.maybe.{MaybeOption, Failed, MaybeEither, Just, NoVal}
42 import gr.grnet.aquarium.util.Loggable
43 import java.io.File
44 import gr.grnet.aquarium.store.StoreProvider
45 import gr.grnet.aquarium.logic.accounting.algorithm.SimpleCostPolicyAlgorithmCompiler
46 import gr.grnet.aquarium.computation.UserStateComputations
47 import gr.grnet.aquarium.service.{StoreWatcherService, RabbitMQService, AkkaService, SimpleTimerService, EventBusService}
48 import gr.grnet.aquarium.converter.StdConverters
49 import gr.grnet.aquarium.service.event.AquariumCreatedEvent
50
51 /**
52  * Create a tailored Aquarium.
53  *
54  * Thread-unsafe.
55  *
56  * @author Christos KK Loverdos <loverdos@gmail.com>
57  */
58
59 final class AquariumBuilder(val originalProps: Props) extends Loggable {
60   if(originalProps eq null) {
61     throw new AquariumInternalError("props is null")
62   }
63
64   import Aquarium.EnvKeys
65
66   private[this] var _env = Env()
67   // This is special
68   private[this] val eventBus = new EventBusService
69
70   @volatile
71   private[this] var _aquarium: Aquarium = _
72
73   @throws(classOf[AquariumInternalError])
74   private def propsGetEx(key: String): String = {
75     try {
76      originalProps.getEx(key)
77     } catch {
78       case e: Exception ⇒
79         throw new AquariumInternalError("Could not locate %s in Aquarium properties".format(key))
80     }
81   }
82
83   @throws(classOf[AquariumInternalError])
84   private def envGetEx[T: Manifest](key: TypedKey[T]): T = {
85     try {
86      _env.getEx(key)
87     } catch {
88       case e: Exception ⇒
89         throw new AquariumInternalError("Could not locate %s in Aquarium environment".format(key))
90     }
91   }
92
93   def update[T: Manifest](keyvalue: (TypedKey[T], T)): this.type = {
94     assert(keyvalue ne null, "keyvalue ne null")
95
96     _env += keyvalue
97     this
98   }
99
100   def update[T : Manifest](key: TypedKey[T], value: T): this.type = {
101     assert(key ne null, "key ne null")
102
103     this update (key -> value)
104   }
105
106   /**
107    * Reflectively provide a new instance of a class and configure it appropriately.
108    */
109   private[this] def newInstance[C <: AnyRef](manifest: Manifest[C], className: String): C = {
110     val defaultClassLoader = Thread.currentThread().getContextClassLoader
111     val instanceM = MaybeEither(defaultClassLoader.loadClass(className).newInstance().asInstanceOf[C])
112     instanceM match {
113       case Just(instance) ⇒
114         eventBus.addSubscriber(instance)
115
116         instance match {
117           case configurable: Configurable if (originalProps ne null) ⇒
118             val localProps = configurable.propertyPrefix match {
119               case somePrefix @ Some(prefix) ⇒
120                 if(prefix.length == 0) {
121                   logger.warn(
122                     "Property prefix for %s is %s. Consider using None".format(instance, somePrefix))
123                 }
124
125                 originalProps.subsetForKeyPrefix(prefix)
126
127               case None ⇒
128                 originalProps
129             }
130
131             logger.debug("Configuring {} with props", configurable.getClass.getName)
132             MaybeEither(configurable configure localProps) match {
133               case Just(_) ⇒
134                 logger.info("Configured {} with props", configurable.getClass.getName)
135                 instance
136
137               case Failed(e) ⇒
138                 throw new AquariumInternalError("Could not configure instance of %s".format(className), e)
139             }
140
141           case _ ⇒
142             instance
143         }
144
145       case Failed(e) ⇒
146         throw new AquariumInternalError("Could not instantiate %s".format(className), e)
147     }
148   }
149
150   private[this] def checkStoreProviderOverride: Unit = {
151     val envKey = EnvKeys.storeProvider
152     if(_env.contains(envKey)) {
153       return
154     }
155
156     if(originalProps eq null) {
157       throw new AquariumInternalError("Cannot locate store provider, since no properties have been defined")
158     }
159
160     val propName = envKey.name
161     originalProps.get(propName) match {
162       case Just(propValue) ⇒
163         update(envKey, newInstance(envKey.keyType, propValue))
164
165       case NoVal ⇒
166         throw new AquariumInternalError("No store provider is given in properties")
167
168       case Failed(e) ⇒
169         throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
170     }
171   }
172
173   private[this] def checkStoreOverrides: Unit = {
174     if(originalProps eq null) {
175       return
176     }
177
178     def checkOverride[S <: AnyRef : Manifest](envKey: TypedKey[S], f: StoreProvider ⇒ S): Unit = {
179       if(!_env.contains(envKey)) {
180         val propName = envKey.name
181
182         originalProps.get(propName) match {
183           case Just(propValue) ⇒
184             // Create the store reflectively
185             update(envKey, newInstance(envKey.keyType, propValue))
186
187           case NoVal ⇒
188             // Get the store from the store provider
189             val storeProvider = this.envGetEx(EnvKeys.storeProvider)
190             val propValue = f(storeProvider)
191             update(envKey, propValue)
192
193           case Failed(e) ⇒
194             throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
195         }
196       }
197     }
198
199     // If a store has not been specifically overridden, we load it from the properties
200     checkOverride(EnvKeys.resourceEventStore, _.resourceEventStore)
201     checkOverride(EnvKeys.imEventStore,       _.imEventStore)
202     checkOverride(EnvKeys.userStateStore,     _.userStateStore)
203     checkOverride(EnvKeys.policyStore,        _.policyStore)
204   }
205
206   private[this] def checkEventsStoreFolderOverride: Unit = {
207     val propName = EnvKeys.eventsStoreFolder.name
208
209     _env.get(EnvKeys.eventsStoreFolder) match {
210       case Just(storeFolderOption) ⇒
211         // Some value has been set, even a None, so do nothing more
212         logger.info("{} = {}", propName, storeFolderOption)
213
214       case Failed(e) ⇒
215         throw new AquariumInternalError(e, "While obtaining value for env key %s".format(propName))
216
217       case NoVal ⇒
218         if(originalProps eq null) {
219           update(EnvKeys.eventsStoreFolder, None)
220           return
221         }
222
223         // load from props
224         for(folderName ← originalProps.get(propName)) {
225           logger.info("{} = {}", propName, folderName)
226
227           update(EnvKeys.eventsStoreFolder, Some(new File(folderName)))
228         }
229
230     }
231   }
232
233   private[this] def checkEventsStoreFolderExistence: Unit = {
234     val propName = EnvKeys.eventsStoreFolder.name
235     for(folder ← this.envGetEx(EnvKeys.eventsStoreFolder)) {
236       val canonicalFolder = {
237         if(folder.isAbsolute) {
238           folder.getCanonicalFile
239         } else {
240           logger.info("{} is not absolute, making it relative to Aquarium Home", propName)
241           new File(ResourceLocator.Homes.Folders.AquariumHome, folder.getPath).getCanonicalFile
242         }
243       }
244
245       val canonicalPath = canonicalFolder.getCanonicalPath
246
247       if(canonicalFolder.exists() && !canonicalFolder.isDirectory) {
248         throw new AquariumInternalError("%s = %s is not a folder".format(propName, canonicalFolder))
249       }
250
251       // Now, events folder must be outside AQUARIUM_HOME, since AQUARIUM_HOME can be wiped out for an upgrade but
252       // we still want to keep the events.
253       val ahCanonicalPath = ResourceLocator.Homes.Folders.AquariumHome.getCanonicalPath
254       if(canonicalPath.startsWith(ahCanonicalPath)) {
255         throw new AquariumInternalError(
256           "%s = %s is under Aquarium Home = %s".format(
257             propName,
258             canonicalFolder,
259             ahCanonicalPath
260           ))
261       }
262
263       canonicalFolder.mkdirs()
264
265       update(EnvKeys.eventsStoreFolder, Some(canonicalFolder))
266     }
267   }
268
269   private[this] def checkEventsStoreFolderVariablesOverrides: Unit = {
270     def checkVar(envKey: BooleanKey): Unit = {
271       if(!_env.contains(envKey)) {
272         val propName = envKey.name
273         originalProps.getBoolean(propName) match {
274           case Just(propValue) ⇒
275             update(envKey, propValue)
276
277           case NoVal ⇒
278             update(envKey, false)
279
280           case Failed(e) ⇒
281             throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
282         }
283       }
284     }
285
286     checkVar(EnvKeys.eventsStoreSaveRCEvents)
287     checkVar(EnvKeys.eventsStoreSaveIMEvents)
288   }
289
290   private[this] def checkRestServiceOverride: Unit = {
291     checkNoPropsOverride(EnvKeys.restService) { envKey ⇒
292       val envKey    = EnvKeys.restService
293       val propName  = envKey.name
294       val propValue = propsGetEx(propName)
295
296       newInstance(envKey.keyType, propValue)
297     }
298   }
299
300   private[this] def checkNoPropsOverride[T: Manifest](envKey: TypedKey[T])(f: TypedKey[T] ⇒ T): Unit = {
301     if(_env.contains(envKey)) {
302       return
303     }
304
305     update(envKey, f(envKey))
306   }
307
308   private[this] def checkPropsOverride[T: Manifest](envKey: TypedKey[T])(f: (TypedKey[T], String) ⇒ T): Unit = {
309     if(_env.contains(envKey)) {
310       return
311     }
312
313     val propName = envKey.name
314     originalProps.get(propName) match {
315       case Just(propValue) ⇒
316         update(envKey, f(envKey, propValue))
317
318       case NoVal ⇒
319         throw new AquariumInternalError("No value for key %s in properties".format(propName))
320
321       case Failed(e) ⇒
322         throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
323     }
324   }
325
326   private[this] def checkOptionalPropsOverride[T: Manifest]
327       (envKey: TypedKey[Option[T]])
328       (f: (TypedKey[Option[T]], String) ⇒ Option[T]): Unit = {
329
330     if(_env.contains(envKey)) {
331       return
332     }
333
334     val propName = envKey.name
335     originalProps.get(propName) match {
336       case Just(propValue) ⇒
337         update(envKey, f(envKey, propValue))
338
339       case NoVal ⇒
340         update(envKey, None)
341
342       case Failed(e) ⇒
343         throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
344     }
345   }
346
347   def build(): Aquarium = {
348     if(this._aquarium ne null) {
349       return this._aquarium
350     }
351
352     checkPropsOverride(EnvKeys.version) { (envKey, propValue) ⇒ propValue }
353
354     checkNoPropsOverride(EnvKeys.eventBus) { _ ⇒ eventBus }
355
356     checkNoPropsOverride(EnvKeys.originalProps) { _ ⇒ originalProps }
357
358     checkNoPropsOverride(EnvKeys.defaultClassLoader) { _ ⇒  Thread.currentThread().getContextClassLoader }
359
360     checkNoPropsOverride(EnvKeys.converters) { _ ⇒ StdConverters.AllConverters }
361
362     checkStoreProviderOverride
363     checkStoreOverrides
364
365     checkEventsStoreFolderOverride
366     checkEventsStoreFolderExistence
367     checkEventsStoreFolderVariablesOverrides
368
369     checkRestServiceOverride
370
371     checkNoPropsOverride(EnvKeys.timerService) { envKey ⇒
372       newInstance(envKey.keyType, classOf[SimpleTimerService].getName)
373     }
374
375     checkNoPropsOverride(EnvKeys.algorithmCompiler) { _ ⇒ SimpleCostPolicyAlgorithmCompiler }
376
377     checkNoPropsOverride(EnvKeys.userStateComputations) { envKey ⇒
378       newInstance(envKey.keyType, classOf[UserStateComputations].getName)
379     }
380
381     checkNoPropsOverride(EnvKeys.akkaService) { envKey ⇒
382       newInstance(envKey.keyType, classOf[AkkaService].getName)
383     }
384
385     checkNoPropsOverride(EnvKeys.rabbitMQService) { envKey ⇒
386       newInstance(envKey.keyType, classOf[RabbitMQService].getName)
387     }
388
389     checkNoPropsOverride(EnvKeys.storeWatcherService) { envKey ⇒
390       newInstance(envKey.keyType, classOf[StoreWatcherService].getName)
391     }
392
393     checkPropsOverride(EnvKeys.actorProvider) { (envKey, propValue) ⇒
394       newInstance(envKey.keyType, propValue)
395     }
396
397     checkPropsOverride(EnvKeys.userStateTimestampThreshold) { (envKey, propValue) ⇒
398       propValue.toLong
399     }
400
401     checkPropsOverride(EnvKeys.restPort) { (envKey, propValue) ⇒
402       propValue.toInt
403     }
404
405     checkOptionalPropsOverride(EnvKeys.adminCookie) { (envKey, propValue) ⇒
406       Some(propValue)
407     }
408
409     this._aquarium = new Aquarium(_env)
410
411     this._aquarium.eventBus.syncPost(AquariumCreatedEvent(this._aquarium))
412
413     this._aquarium
414   }
415 }