42305b30f4e7a313a42aa728a12576ffb294781e
[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.{Failed, MaybeEither, Just, NoVal}
42 import gr.grnet.aquarium.util.Loggable
43 import java.io.File
44 import gr.grnet.aquarium.service.EventBusService
45 import gr.grnet.aquarium.converter.StdConverters
46 import gr.grnet.aquarium.service.event.AquariumCreatedEvent
47 import gr.grnet.aquarium.policy.PolicyModel
48
49 /**
50  * Create a tailored Aquarium.
51  *
52  * Thread-unsafe.
53  *
54  * @author Christos KK Loverdos <loverdos@gmail.com>
55  */
56
57 final class AquariumBuilder(
58     val originalProps: Props,
59     val defaultPolicyModel: PolicyModel
60 ) extends Loggable {
61
62   if(originalProps eq null) {
63     throw new AquariumInternalError("props is null")
64   }
65
66   import Aquarium.EnvKeys
67
68   private[this] var _env = Env()
69   // This is special
70   private[this] val eventBus = new EventBusService
71
72   @volatile
73   private[this] var _aquarium: Aquarium = _
74
75   @throws(classOf[AquariumInternalError])
76   private def propsGetEx(key: String): String = {
77     try {
78      originalProps.getEx(key)
79     } catch {
80       case e: Exception ⇒
81         throw new AquariumInternalError("Could not locate %s in Aquarium properties".format(key))
82     }
83   }
84
85   @throws(classOf[AquariumInternalError])
86   private def envGetEx[T: Manifest](key: TypedKey[T]): T = {
87     try {
88      _env.getEx(key)
89     } catch {
90       case e: Exception ⇒
91         throw new AquariumInternalError("Could not locate %s in Aquarium environment".format(key))
92     }
93   }
94
95   def update[T: Manifest](keyvalue: (TypedKey[T], T)): this.type = {
96     assert(keyvalue ne null, "keyvalue ne null")
97
98     _env += keyvalue
99     this
100   }
101
102   def update[T : Manifest](key: TypedKey[T], value: T): this.type = {
103     assert(key ne null, "key ne null")
104
105     this update (key -> value)
106   }
107
108   private[this] def newInstanceFromKey[T <: AnyRef : Manifest](envKey: TypedKey[T]): T = {
109     newInstance(envKey.keyType, envKey.keyType.erasure.getName)
110   }
111
112   /**
113    * Reflectively provide a new instance of a class and configure it appropriately.
114    */
115   private[this] def newInstance[C <: AnyRef](manifest: Manifest[C], className: String): C = {
116     val defaultClassLoader = Thread.currentThread().getContextClassLoader
117     val instanceM = MaybeEither(defaultClassLoader.loadClass(className).newInstance().asInstanceOf[C])
118     instanceM match {
119       case Just(instance) ⇒
120         eventBus.addSubscriber(instance)
121
122         instance match {
123           case configurable: Configurable if (originalProps ne null) ⇒
124             val localProps = configurable.propertyPrefix match {
125               case somePrefix @ Some(prefix) ⇒
126                 if(prefix.length == 0) {
127                   logger.warn(
128                     "Property prefix for %s is %s. Consider using None".format(instance, somePrefix))
129                 }
130
131                 originalProps.subsetForKeyPrefix(prefix)
132
133               case None ⇒
134                 originalProps
135             }
136
137             logger.debug("Configuring {} with props (prefix={})", configurable.getClass.getName, configurable.propertyPrefix)
138             MaybeEither(configurable configure localProps) match {
139               case Just(_) ⇒
140                 logger.info("Configured {} with props (prefix={})", configurable.getClass.getName, configurable.propertyPrefix)
141                 instance
142
143               case Failed(e) ⇒
144                 throw new AquariumInternalError("Could not configure instance of %s".format(className), e)
145             }
146
147           case _ ⇒
148             instance
149         }
150
151       case Failed(e) ⇒
152         throw new AquariumInternalError("Could not instantiate %s".format(className), e)
153     }
154   }
155
156   private[this] def checkStoreProviderOverride: Unit = {
157     val envKey = EnvKeys.storeProvider
158     if(_env.contains(envKey)) {
159       return
160     }
161
162     if(originalProps eq null) {
163       throw new AquariumInternalError("Cannot locate store provider, since no properties have been defined")
164     }
165
166     val propName = envKey.name
167     originalProps.get(propName) match {
168       case Just(storeProviderClassName) ⇒
169         update(envKey, newInstance(envKey.keyType, storeProviderClassName))
170
171       case NoVal ⇒
172         throw new AquariumInternalError("No store provider is given in properties")
173
174       case Failed(e) ⇒
175         throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
176     }
177   }
178
179   private[this] def checkEventsStoreFolderOverride: Unit = {
180     val propName = EnvKeys.eventsStoreFolder.name
181
182     _env.get(EnvKeys.eventsStoreFolder) match {
183       case Just(storeFolderOption) ⇒
184         // Some value has been set, even a None, so do nothing more
185         logger.info("{} = {}", propName, storeFolderOption)
186
187       case Failed(e) ⇒
188         throw new AquariumInternalError(e, "While obtaining value for env key %s".format(propName))
189
190       case NoVal ⇒
191         if(originalProps eq null) {
192           update(EnvKeys.eventsStoreFolder, None)
193           return
194         }
195
196         // load from props
197         for(folderName ← originalProps.get(propName)) {
198           logger.info("{} = {}", propName, folderName)
199
200           update(EnvKeys.eventsStoreFolder, Some(new File(folderName)))
201         }
202     }
203   }
204
205   private[this] def checkEventsStoreFolderExistence: Unit = {
206     val propName = EnvKeys.eventsStoreFolder.name
207     for(folder ← this.envGetEx(EnvKeys.eventsStoreFolder)) {
208       val canonicalFolder = {
209         if(folder.isAbsolute) {
210           folder.getCanonicalFile
211         } else {
212           logger.info("{} is not absolute, making it relative to Aquarium Home", propName)
213           new File(ResourceLocator.Homes.Folders.AquariumHome, folder.getPath).getCanonicalFile
214         }
215       }
216
217       val canonicalPath = canonicalFolder.getCanonicalPath
218
219       if(canonicalFolder.exists() && !canonicalFolder.isDirectory) {
220         throw new AquariumInternalError("%s = %s is not a folder".format(propName, canonicalFolder))
221       }
222
223       // Now, events folder must be outside AQUARIUM_HOME, since AQUARIUM_HOME can be wiped out for an upgrade but
224       // we still want to keep the events.
225       val ahCanonicalPath = ResourceLocator.Homes.Folders.AquariumHome.getCanonicalPath
226       if(canonicalPath.startsWith(ahCanonicalPath)) {
227         throw new AquariumInternalError(
228           "%s = %s is under Aquarium Home = %s".format(
229             propName,
230             canonicalFolder,
231             ahCanonicalPath
232           ))
233       }
234
235       canonicalFolder.mkdirs()
236
237       update(EnvKeys.eventsStoreFolder, Some(canonicalFolder))
238     }
239   }
240
241   private[this] def checkEventsStoreFolderVariablesOverrides: Unit = {
242     def checkVar(envKey: BooleanKey): Unit = {
243       if(!_env.contains(envKey)) {
244         val propName = envKey.name
245         originalProps.getBoolean(propName) match {
246           case Just(propValue) ⇒
247             update(envKey, propValue)
248
249           case NoVal ⇒
250             update(envKey, false)
251
252           case Failed(e) ⇒
253             throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
254         }
255       }
256     }
257
258     checkVar(EnvKeys.eventsStoreSaveRCEvents)
259     checkVar(EnvKeys.eventsStoreSaveIMEvents)
260   }
261
262   private[this] def checkRestServiceOverride: Unit = {
263     checkNoPropsOverride(EnvKeys.restService) { envKey ⇒
264       val envKey    = EnvKeys.restService
265       val propName  = envKey.name
266       val propValue = propsGetEx(propName)
267
268       newInstance(envKey.keyType, propValue)
269     }
270   }
271
272   private[this] def checkNoPropsOverride[T <: AnyRef : Manifest](envKey: TypedKey[T])
273                                                                 (f: TypedKey[T] ⇒ T): Unit = {
274
275     if(_env.contains(envKey)) {
276       return
277     }
278
279     update(envKey, f(envKey))
280   }
281
282   private[this] def checkPropsOverride[T: Manifest](envKey: TypedKey[T])(f: (TypedKey[T], String) ⇒ T): Unit = {
283     if(_env.contains(envKey)) {
284       return
285     }
286
287     val propName = envKey.name
288     originalProps.get(propName) match {
289       case Just(propValue) ⇒
290         update(envKey, f(envKey, propValue))
291
292       case NoVal ⇒
293         throw new AquariumInternalError("No value for key %s in properties".format(propName))
294
295       case Failed(e) ⇒
296         throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
297     }
298   }
299
300   private[this] def checkOptionalPropsOverride[T: Manifest]
301       (envKey: TypedKey[Option[T]])
302       (f: (TypedKey[Option[T]], String) ⇒ Option[T]): Unit = {
303
304     if(_env.contains(envKey)) {
305       return
306     }
307
308     val propName = envKey.name
309     originalProps.get(propName) match {
310       case Just(propValue) ⇒
311         update(envKey, f(envKey, propValue))
312
313       case NoVal ⇒
314         update(envKey, None)
315
316       case Failed(e) ⇒
317         throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
318     }
319   }
320
321   /**
322    * Builds a fresh instance of [[gr.grnet.aquarium.Aquarium]]. The services needed by [[gr.grnet.aquarium.Aquarium]]
323    * are configured but not started yet.
324    *
325    * This is a very delicate procedure, so always change with care.
326    *
327    * @return A fresh instance of [[gr.grnet.aquarium.Aquarium]].
328    */
329   def build(): Aquarium = {
330     if(this._aquarium ne null) {
331       return this._aquarium
332     }
333
334     checkPropsOverride(EnvKeys.version) { (envKey, propValue) ⇒ propValue }
335
336     checkNoPropsOverride(EnvKeys.eventBus) { _ ⇒ eventBus }
337
338     checkNoPropsOverride(EnvKeys.originalProps) { _ ⇒ originalProps }
339
340     checkNoPropsOverride(EnvKeys.defaultClassLoader) { _ ⇒  Thread.currentThread().getContextClassLoader }
341
342     checkNoPropsOverride(EnvKeys.converters) { _ ⇒ StdConverters.AllConverters }
343
344     checkStoreProviderOverride
345
346     checkEventsStoreFolderOverride
347     checkEventsStoreFolderExistence
348     checkEventsStoreFolderVariablesOverrides
349
350     checkRestServiceOverride
351
352     checkNoPropsOverride(EnvKeys.timerService) { newInstanceFromKey(_) }
353
354     checkNoPropsOverride(EnvKeys.chargingService) { newInstanceFromKey(_) }
355
356     checkNoPropsOverride(EnvKeys.akkaService) { newInstanceFromKey(_) }
357
358     checkNoPropsOverride(EnvKeys.rabbitMQService) { newInstanceFromKey(_) }
359
360     checkNoPropsOverride(EnvKeys.rabbitMQProducer) { newInstanceFromKey(_) }
361
362     checkNoPropsOverride(EnvKeys.storeWatcherService) { newInstanceFromKey(_) }
363
364     checkPropsOverride(EnvKeys.userStateTimestampThreshold) { (envKey, propValue) ⇒
365       propValue.toLong
366     }
367
368     checkPropsOverride(EnvKeys.restPort) { (envKey, propValue) ⇒
369       propValue.toInt
370     }
371
372     checkPropsOverride(EnvKeys.restShutdownTimeoutMillis) { (envKey, propValue) ⇒
373         propValue.toLong
374     }
375
376     checkOptionalPropsOverride(EnvKeys.adminCookie) { (envKey, propValue) ⇒
377       Some(propValue)
378     }
379
380     update(EnvKeys.defaultPolicyModel, defaultPolicyModel)
381
382     this._aquarium = new Aquarium(_env)
383
384     this._aquarium.eventBus.syncPost(AquariumCreatedEvent(this._aquarium))
385
386     this._aquarium
387   }
388 }