c029d76353edb62080a14b634a6640c9f7fd0253
[aquarium] / src / main / scala / gr / grnet / aquarium / MasterConf.scala
1 /*
2  * Copyright 2011 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 actor.{ActorProvider}
39 import com.ckkloverdos.resource._
40 import com.ckkloverdos.sys.SysProp
41 import com.ckkloverdos.props.Props
42 import com.ckkloverdos.maybe.{Maybe, Failed, Just, NoVal}
43 import com.ckkloverdos.convert.Converters.{DefaultConverters => TheDefaultConverters}
44 import processor.actor.{ResourceEventProcessorService}
45 import store._
46 import util.{Lifecycle, Loggable}
47
48 /**
49  * The master configurator. Responsible to load all of application configuration and provide the relevant services.
50  *
51  * @author Christos KK Loverdos <loverdos@gmail.com>.
52  */
53 class MasterConf(val props: Props) extends Loggable {
54   import MasterConf.Keys
55
56   /**
57    * Reflectively provide a new instance of a class and configure it appropriately.
58    */
59   private[this] def newInstance[C : Manifest](className: String): C = {
60     val instanceM = Maybe(defaultClassLoader.loadClass(className).newInstance().asInstanceOf[C])
61     instanceM match {
62       case Just(instance) ⇒ instance match {
63         case configurable: Configurable ⇒
64           Maybe(configurable configure props) match {
65             case Just(_) ⇒
66               instance
67             case Failed(e, _) ⇒
68               throw new Exception("Could not configure instance of %s".format(className), e)
69             case NoVal ⇒
70               throw new Exception("Could not configure instance of %s".format(className))
71           }
72         case _ ⇒
73           instance
74       }
75       case Failed(e, _) ⇒
76         throw new Exception("Could not instantiate %s".format(className), e)
77       case NoVal ⇒
78         throw new Exception("Could not instantiate %s".format(className))
79     }
80
81   }
82
83   private[this] lazy val _actorProvider: ActorProvider = {
84     val instance = newInstance[ActorProvider](props.getEx(Keys.actor_provider_class))
85     logger.info("Loaded ActorProvider: %s".format(instance.getClass))
86     instance
87   }
88
89   private[this] lazy val _storeProvider: StoreProvider = {
90     val instance = newInstance[StoreProvider](props.getEx(Keys.store_provider_class))
91     logger.info("Loaded StoreProvider: %s".format(instance.getClass))
92     instance
93   }
94   
95   private[this] lazy val _restService: Lifecycle = {
96     val instance = newInstance[Lifecycle](props.getEx(Keys.rest_service_class))
97     logger.info("Loaded RESTService: %s".format(instance.getClass))
98     instance
99   }
100
101   private[this] lazy val _userStoreM: Maybe[UserStore] = {
102     // If there is a specific `UserStore` implementation specified in the
103     // properties, then this implementation overrides the user store given by
104     // `StoreProvider`.
105     props.get(Keys.userstore_class) map { className ⇒
106       val instance = newInstance[UserStore](className)
107       logger.info("Overriding UserStore provisioning. Implementation given by: %s".format(instance.getClass))
108       instance
109     }
110   }
111
112   private[this] lazy val _eventStoreM: Maybe[EventStore] = {
113     // If there is a specific `EventStore` implementation specified in the
114     // properties, then this implementation overrides the event store given by
115     // `StoreProvider`.
116     props.get(Keys.eventstore_class) map { className ⇒
117       val instance = newInstance[EventStore](className)
118       logger.info("Overriding EventStore provisioning. Implementation given by: %s".format(instance.getClass))
119       instance
120     }
121   }
122
123   private[this] lazy val _IMEventStoreM: Maybe[IMStore] = {
124     // If there is a specific `IMStore` implementation specified in the
125     // properties, then this implementation overrides the event store given by
126     // `IMProvider`.
127     props.get(Keys.im_eventstore_class) map { className ⇒
128       val instance = newInstance[IMStore](className)
129       logger.info("Overriding IMStore provisioning. Implementation given by: %s".format(instance.getClass))
130       instance
131     }
132   }
133
134   private[this] lazy val _WalletEventStoreM: Maybe[WalletStore] = {
135     // If there is a specific `IMStore` implementation specified in the
136     // properties, then this implementation overrides the event store given by
137     // `IMProvider`.
138     props.get(Keys.walletstore_class) map { className ⇒
139       val instance = newInstance[WalletStore](className)
140       logger.info("Overriding WalletStore provisioning. Implementation given by: %s".format(instance.getClass))
141       instance
142     }
143   }
144
145   private[this] lazy val _resEventProc: ResourceEventProcessorService = {
146     new ResourceEventProcessorService()
147   }
148
149   def get(prop: String): String =
150     props.get(prop) match {
151       case Just(y) => y
152       case _ => ""
153     }
154
155   def defaultClassLoader = Thread.currentThread().getContextClassLoader
156
157   def startServices(): Unit = {
158     _restService.start()
159     _actorProvider.start()
160     _resEventProc.start()
161   }
162
163   def stopServices(): Unit = {
164     _resEventProc.stop()
165     _restService.stop()
166     _actorProvider.stop()
167
168
169 //    akka.actor.Actor.registry.shutdownAll()
170   }
171
172   def stopServicesWithDelay(millis: Long) {
173     Thread sleep millis
174     stopServices()
175   }
176   
177   def actorProvider = _actorProvider
178
179   def userStore = {
180     _userStoreM match {
181       case Just(us) ⇒ us
182       case _        ⇒ storeProvider.userStore
183     }
184   }
185
186   def eventStore = {
187     _eventStoreM match {
188       case Just(es) ⇒ es
189       case _        ⇒ storeProvider.eventStore
190     }
191   }
192
193   def IMStore = {
194     _IMEventStoreM match {
195       case Just(es) ⇒ es
196       case _        ⇒ storeProvider.imStore
197     }
198   }
199
200   def WalletStore = {
201     _WalletEventStoreM match {
202       case Just(es) ⇒ es
203       case _        ⇒ storeProvider.walletStore
204     }
205   }
206
207   def storeProvider = _storeProvider
208 }
209
210 object MasterConf {
211   implicit val DefaultConverters = TheDefaultConverters
212
213   val MasterConfName = "aquarium.properties"
214
215   /**
216    * Current directory resource context.
217    * Normally this should be the application installation directory.
218    *
219    * It takes priority over `ClasspathBaseResourceContext`.
220    */
221   val AppBaseResourceContext = new FileStreamResourceContext(".")
222
223   /**
224    * The venerable /etc resource context
225    */
226   val SlashEtcResourceContext = new FileStreamResourceContext("/etc")
227
228   /**
229    * Class loader resource context.
230    * This has the lowest priority.
231    */
232   val ClasspathBaseResourceContext = new ClassLoaderStreamResourceContext(Thread.currentThread().getContextClassLoader)
233
234   /**
235    * Use this property to override the place where aquarium configuration resides.
236    *
237    * The value of this property is a folder that defines the highest-priority resource context.
238    */
239   val ConfBaseFolderSysProp = SysProp("aquarium.conf.base.folder")
240
241   /**
242    * The resource context used in the application.
243    */
244   lazy val MasterResourceContext = {
245     val rc0 = ClasspathBaseResourceContext
246     val rc1 = AppBaseResourceContext
247     val rc2 = SlashEtcResourceContext
248     val basicContext = new CompositeStreamResourceContext(NoVal, rc2, rc1, rc0)
249     
250     ConfBaseFolderSysProp.value match {
251       case Just(value) ⇒
252         // We have a system override for the configuration location
253         new CompositeStreamResourceContext(Just(basicContext), new FileStreamResourceContext(value))
254       case NoVal ⇒
255         basicContext
256       case Failed(e, m) ⇒
257         throw new RuntimeException(m , e)
258     }
259   }
260
261   lazy val MasterConfResource = {
262     val maybeMCResource = MasterResourceContext getResource MasterConfName
263     maybeMCResource match {
264       case Just(masterConfResource) ⇒
265         masterConfResource
266       case NoVal ⇒
267         throw new RuntimeException("Could not find master configuration file: %s".format(MasterConfName))
268       case Failed(e, m) ⇒
269         throw new RuntimeException(m, e)
270     }
271   }
272
273   lazy val MasterConfProps = {
274     val maybeProps = Props apply MasterConfResource
275     maybeProps match {
276       case Just(props) ⇒
277         props
278       case NoVal ⇒
279         throw new RuntimeException("Could not load master configuration file: %s".format(MasterConfName))
280       case Failed(e, m) ⇒
281         throw new RuntimeException(m, e)
282     }
283   }
284
285   lazy val MasterConf = {
286     Maybe(new MasterConf(MasterConfProps)) match {
287       case Just(masterConf) ⇒
288         masterConf
289       case NoVal ⇒
290         throw new RuntimeException("Could not initialize master configuration file: %s".format(MasterConfName))
291       case Failed(e, m) ⇒
292         throw new RuntimeException(m, e)
293     }
294   }
295
296   /**
297    * Defines the names of all the known keys inside the master properties file.
298    */
299   final object Keys {
300     /**
301      * The Aquarium version. Will be reported in any due occasion.
302      */
303     final val version = "version"
304
305     /**
306      * The fully qualified name of the class that implements the `ActorProvider`.
307      * Will be instantiated reflectively and should have a public default constructor.
308      */
309     final val actor_provider_class = "actor.provider.class"
310
311     /**
312      * The class that initializes the REST service
313      */
314     final val rest_service_class = "rest.service.class"
315
316     /**
317      * The fully qualified name of the class that implements the `StoreProvider`.
318      * Will be instantiated reflectively and should have a public default constructor.
319      */
320     final val store_provider_class = "store.provider.class"
321
322     /**
323      * The class that implements the User store
324      */
325     final val userstore_class = "user.store.class"
326
327     /**
328      * The class that implements the event store
329      */
330     final val eventstore_class = "event.store.class"
331
332     /**
333      * The class that implements the IM event store
334      */
335     final val im_eventstore_class = "imevent.store.class"
336
337     /**
338      * The class that implements the wallet store
339      */
340     final val walletstore_class = "wallet.store.class"
341
342     /** The lower mark for the UserActors' LRU, managed by UserActorManager.
343      *
344      * The terminology is borrowed from the (also borrowed) Apache-lucene-solr-based implementation.
345      *
346      */
347     final val user_actors_lru_lower_mark = "user.actors.LRU.lower.mark"
348
349     /**
350      * The upper mark for the UserActors' LRU, managed by UserActorManager.
351      *
352      * The terminology is borrowed from the (also borrowed) Apache-lucene-solr-based implementation.
353      */
354     final val user_actors_lru_upper_mark = "user.actors.LRU.upper.mark"
355
356     /**
357      * Comma separated list of amqp servers running in active-active
358      * configuration.
359      */
360     final val amqp_servers = "amqp.servers"
361
362     /**
363      * Comma separated list of amqp servers running in active-active
364      * configuration.
365      */
366     final val amqp_port = "amqp.port"
367
368     /**
369      * User name for connecting with the AMQP server
370      */
371     final val amqp_username = "amqp.username"
372
373     /**
374      * Passwd for connecting with the AMQP server
375      */
376     final val amqp_password = "amqp.passwd"
377
378     /**
379      * Virtual host on the AMQP server
380      */
381     final val amqp_vhost = "amqp.vhost"
382
383     /**
384      * Comma separated list of exchanges known to aquarium
385      */
386     final val amqp_exchanges = "amqp.exchanges"
387
388     /**
389      * REST service listening port.
390      *
391      * Default is 8080.
392      */
393     final val rest_port = "rest.port"
394
395     /*
396      * Provider for persistence services
397      */
398     final val persistence_provider = "persistence.provider"
399
400     /**
401      * Hostname for the persistence service
402      */
403     final val persistence_host = "persistence.host"
404
405     /**
406      * Username for connecting to the persistence service
407      */
408     final val persistence_username = "persistence.username"
409
410     /**
411      *  Password for connecting to the persistence service
412      */
413     final val persistence_password = "persistence.password"
414
415     /**
416      *  Password for connecting to the persistence service
417      */
418     final val persistence_port = "persistence.port"
419
420     /**
421      *  The DB schema to use
422      */
423     final val persistence_db = "persistence.db"
424   }
425 }