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 |
} |