2 * Copyright 2011-2012 GRNET S.A. All rights reserved.
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the following
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
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.
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.
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.
36 package gr.grnet.aquarium
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
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
50 * Create a tailored Aquarium.
54 * @author Christos KK Loverdos <loverdos@gmail.com>
57 final class AquariumBuilder(
58 val originalProps: Props,
59 val defaultPolicyModel: PolicyModel
62 if(originalProps eq null) {
63 throw new AquariumInternalError("props is null")
66 import Aquarium.EnvKeys
68 private[this] var _env = Env()
70 private[this] val eventBus = new EventBusService
73 private[this] var _aquarium: Aquarium = _
75 @throws(classOf[AquariumInternalError])
76 private def propsGetEx(key: String): String = {
78 originalProps.getEx(key)
81 throw new AquariumInternalError("Could not locate %s in Aquarium properties".format(key))
85 @throws(classOf[AquariumInternalError])
86 private def envGetEx[T: Manifest](key: TypedKey[T]): T = {
91 throw new AquariumInternalError("Could not locate %s in Aquarium environment".format(key))
95 def update[T: Manifest](keyvalue: (TypedKey[T], T)): this.type = {
96 assert(keyvalue ne null, "keyvalue ne null")
102 def update[T : Manifest](key: TypedKey[T], value: T): this.type = {
103 assert(key ne null, "key ne null")
105 this update (key -> value)
108 private[this] def newInstanceFromKey[T <: AnyRef : Manifest](envKey: TypedKey[T]): T = {
109 newInstance(envKey.keyType, envKey.keyType.erasure.getName)
113 * Reflectively provide a new instance of a class and configure it appropriately.
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])
119 case Just(instance) ⇒
120 eventBus.addSubscriber(instance)
123 case configurable: Configurable if (originalProps ne null) ⇒
124 val localProps = configurable.propertyPrefix match {
125 case somePrefix @ Some(prefix) ⇒
126 if(prefix.length == 0) {
128 "Property prefix for %s is %s. Consider using None".format(instance, somePrefix))
131 originalProps.subsetForKeyPrefix(prefix)
137 logger.debug("Configuring {} with props (prefix={})", configurable.getClass.getName, configurable.propertyPrefix)
138 MaybeEither(configurable configure localProps) match {
140 logger.info("Configured {} with props (prefix={})", configurable.getClass.getName, configurable.propertyPrefix)
144 throw new AquariumInternalError("Could not configure instance of %s".format(className), e)
152 throw new AquariumInternalError("Could not instantiate %s".format(className), e)
156 private[this] def checkStoreProviderOverride: Unit = {
157 val envKey = EnvKeys.storeProvider
158 if(_env.contains(envKey)) {
162 if(originalProps eq null) {
163 throw new AquariumInternalError("Cannot locate store provider, since no properties have been defined")
166 val propName = envKey.name
167 originalProps.get(propName) match {
168 case Just(storeProviderClassName) ⇒
169 update(envKey, newInstance(envKey.keyType, storeProviderClassName))
172 throw new AquariumInternalError("No store provider is given in properties")
175 throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
179 private[this] def checkEventsStoreFolderOverride: Unit = {
180 val propName = EnvKeys.eventsStoreFolder.name
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)
188 throw new AquariumInternalError(e, "While obtaining value for env key %s".format(propName))
191 if(originalProps eq null) {
192 update(EnvKeys.eventsStoreFolder, None)
197 for(folderName ← originalProps.get(propName)) {
198 logger.info("{} = {}", propName, folderName)
200 update(EnvKeys.eventsStoreFolder, Some(new File(folderName)))
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
212 logger.info("{} is not absolute, making it relative to Aquarium Home", propName)
213 new File(ResourceLocator.Homes.Folders.AquariumHome, folder.getPath).getCanonicalFile
217 val canonicalPath = canonicalFolder.getCanonicalPath
219 if(canonicalFolder.exists() && !canonicalFolder.isDirectory) {
220 throw new AquariumInternalError("%s = %s is not a folder".format(propName, canonicalFolder))
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(
235 canonicalFolder.mkdirs()
237 update(EnvKeys.eventsStoreFolder, Some(canonicalFolder))
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)
250 update(envKey, false)
253 throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
258 checkVar(EnvKeys.eventsStoreSaveRCEvents)
259 checkVar(EnvKeys.eventsStoreSaveIMEvents)
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)
268 newInstance(envKey.keyType, propValue)
272 private[this] def checkNoPropsOverride[T <: AnyRef : Manifest](envKey: TypedKey[T])
273 (f: TypedKey[T] ⇒ T): Unit = {
275 if(_env.contains(envKey)) {
279 update(envKey, f(envKey))
282 private[this] def checkPropsOverride[T: Manifest](envKey: TypedKey[T])(f: (TypedKey[T], String) ⇒ T): Unit = {
283 if(_env.contains(envKey)) {
287 val propName = envKey.name
288 originalProps.get(propName) match {
289 case Just(propValue) ⇒
290 update(envKey, f(envKey, propValue))
293 throw new AquariumInternalError("No value for key %s in properties".format(propName))
296 throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
300 private[this] def checkOptionalPropsOverride[T: Manifest]
301 (envKey: TypedKey[Option[T]])
302 (f: (TypedKey[Option[T]], String) ⇒ Option[T]): Unit = {
304 if(_env.contains(envKey)) {
308 val propName = envKey.name
309 originalProps.get(propName) match {
310 case Just(propValue) ⇒
311 update(envKey, f(envKey, propValue))
317 throw new AquariumInternalError(e, "While obtaining value for key %s in properties".format(propName))
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.
325 * This is a very delicate procedure, so always change with care.
327 * @return A fresh instance of [[gr.grnet.aquarium.Aquarium]].
329 def build(): Aquarium = {
330 if(this._aquarium ne null) {
331 return this._aquarium
334 checkPropsOverride(EnvKeys.version) { (envKey, propValue) ⇒ propValue }
336 checkNoPropsOverride(EnvKeys.eventBus) { _ ⇒ eventBus }
338 checkNoPropsOverride(EnvKeys.originalProps) { _ ⇒ originalProps }
340 checkNoPropsOverride(EnvKeys.defaultClassLoader) { _ ⇒ Thread.currentThread().getContextClassLoader }
342 checkNoPropsOverride(EnvKeys.converters) { _ ⇒ StdConverters.AllConverters }
344 checkStoreProviderOverride
346 checkEventsStoreFolderOverride
347 checkEventsStoreFolderExistence
348 checkEventsStoreFolderVariablesOverrides
350 checkRestServiceOverride
352 checkNoPropsOverride(EnvKeys.timerService) { newInstanceFromKey(_) }
354 checkNoPropsOverride(EnvKeys.chargingService) { newInstanceFromKey(_) }
356 checkNoPropsOverride(EnvKeys.akkaService) { newInstanceFromKey(_) }
358 checkNoPropsOverride(EnvKeys.rabbitMQService) { newInstanceFromKey(_) }
360 checkNoPropsOverride(EnvKeys.rabbitMQProducer) { newInstanceFromKey(_) }
362 checkNoPropsOverride(EnvKeys.storeWatcherService) { newInstanceFromKey(_) }
364 checkPropsOverride(EnvKeys.userStateTimestampThreshold) { (envKey, propValue) ⇒
368 checkPropsOverride(EnvKeys.restPort) { (envKey, propValue) ⇒
372 checkPropsOverride(EnvKeys.restShutdownTimeoutMillis) { (envKey, propValue) ⇒
376 checkOptionalPropsOverride(EnvKeys.adminCookie) { (envKey, propValue) ⇒
380 update(EnvKeys.defaultPolicyModel, defaultPolicyModel)
382 this._aquarium = new Aquarium(_env)
384 this._aquarium.eventBus.syncPost(AquariumCreatedEvent(this._aquarium))