Statistics
| Branch: | Tag: | Revision:

root / src / main / scala / gr / grnet / aquarium / AquariumBuilder.scala @ 7dbaeb04

History | View | Annotate | Download (12.3 kB)

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.storeWatcherService) { newInstanceFromKey(_) }
361

    
362
    checkPropsOverride(EnvKeys.userStateTimestampThreshold) { (envKey, propValue) ⇒
363
      propValue.toLong
364
    }
365

    
366
    checkPropsOverride(EnvKeys.restPort) { (envKey, propValue) ⇒
367
      propValue.toInt
368
    }
369

    
370
    checkPropsOverride(EnvKeys.restShutdownTimeoutMillis) { (envKey, propValue) ⇒
371
        propValue.toLong
372
    }
373

    
374
    checkOptionalPropsOverride(EnvKeys.adminCookie) { (envKey, propValue) ⇒
375
      Some(propValue)
376
    }
377

    
378
    update(EnvKeys.defaultPolicyModel, defaultPolicyModel)
379

    
380
    this._aquarium = new Aquarium(_env)
381

    
382
    this._aquarium.eventBus.syncPost(AquariumCreatedEvent(this._aquarium))
383

    
384
    this._aquarium
385
  }
386
}