Fix initialization order error for DefaultContext
[aquarium] / src / main / scala / gr / grnet / aquarium / ResourceLocator.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.convert.Converters
39 import com.ckkloverdos.maybe.Failed
40 import com.ckkloverdos.maybe.Just
41 import com.ckkloverdos.maybe.{Maybe, NoVal}
42 import com.ckkloverdos.props.Props
43 import com.ckkloverdos.resource.{FileStreamResource, StreamResource, CompositeStreamResourceContext, ClassLoaderStreamResourceContext, FileStreamResourceContext}
44 import com.ckkloverdos.sys.{SysEnv, SysProp}
45 import gr.grnet.aquarium.message.avro.AvroHelpers
46 import gr.grnet.aquarium.message.avro.gen.PolicyMsg
47 import gr.grnet.aquarium.util.justForSure
48 import java.io.File
49
50
51 /**
52  * Locates resources.
53  *
54  * @author Christos KK Loverdos <loverdos@gmail.com>
55  */
56
57 object ResourceLocator {
58   final object ResourceNames {
59     final val CONF_FODLER               = "conf"
60     final val SLASH_ETC_AQUARIUM_FOLDER = "/etc/aquarium"
61
62     final val LOGBACK_XML         = "logback.xml"
63     final val AQUARIUM_PROPERTIES = "aquarium.properties"
64     final val POLICY_JSON         = "policy.json"
65   }
66
67   final object Homes {
68     final object Names {
69       final val AKKA_HOME     = "AKKA_HOME"
70 //      final val AQUARIUM_HOME = "AQUARIUM_HOME"
71     }
72
73     final object Folders {
74       private[this] def checkFolder(name: String, file: File): File = {
75         if(!file.isDirectory) {
76           throw new AquariumInternalError(
77             "%s=%s is not a folder".format(
78               name,
79               if(file.isAbsolute)
80                 file.getAbsolutePath
81               else
82                 "%s [=%s]".format(file, file.getCanonicalFile)
83             )
84           )
85         }
86         file
87       }
88
89       /**
90        * This is normally exported from the shell script (AQUARIUM_HOME) that starts Aquarium or given in the command
91        * line (aquarium.home).
92        *
93        * TODO: Make this searchable for resources (ie put it in the resource context)
94        */
95       final lazy val AquariumHome = {
96         SysProps.AquariumHome.value match {
97           case Just(aquariumHome) ⇒
98             checkFolder(SysProps.Names.AquariumHome, new File(aquariumHome))
99
100           case Failed(e) ⇒
101             throw new AquariumInternalError("Error regarding %s".format(SysProps.Names.AquariumHome), e)
102
103           case NoVal ⇒
104             SysEnvs.AQUARIUM_HOME.value match {
105               case Just(aquarium_home) ⇒
106                 val folder = new File(aquarium_home)
107                 checkFolder(SysEnvs.Names.AQUARIUM_HOME, folder)
108                 SysProps.AquariumHome.update(folder.getPath) // needed for logback configuration
109                 folder
110
111               case Failed(e) ⇒
112                 throw new AquariumInternalError("Error regarding %s".format(SysEnvs.Names.AQUARIUM_HOME), e)
113
114               case NoVal ⇒
115                 val folder = new File(".")
116                 SysProps.AquariumHome.update(folder.getPath) // needed for logback configuration
117                 folder
118             }
119         }
120       }
121     }
122   }
123
124   final object SysEnvs {
125     final object Names {
126       final val AKKA_HOME     = Homes.Names.AKKA_HOME
127       final val AQUARIUM_HOME = "AQUARIUM_HOME"
128     }
129
130     final val AKKA_HOME     = SysEnv(Names.AKKA_HOME)
131     final val AQUARIUM_HOME = SysEnv(Names.AQUARIUM_HOME)
132   }
133
134   final object SysProps {
135     final object Names {
136       final val AquariumHome           = "aquarium.home"
137       final val AquariumPropertiesPath = "aquarium.properties.path"
138       final val AquariumConfFolder     = "aquarium.conf.folder"
139     }
140
141     final val AquariumHome = SysProp(Names.AquariumHome)
142
143     /**
144      * Use this property to override the place of aquarium.properties.
145      * If this is set, then it override any other way to specify the aquarium.properties location.
146      */
147     final val AquariumPropertiesPath = SysProp(Names.AquariumPropertiesPath)
148
149     /**
150      * Use this property to override the place where aquarium configuration resides.
151      *
152      * The value of this property is a folder that defines the highest-priority resource context.
153      */
154     final val AquariumConfFolder = SysProp(Names.AquariumConfFolder)
155   }
156
157   final object ResourceContexts {
158     /**
159      * AQUARIUM_HOME/conf resource context.
160      */
161     private[this] final lazy val HomeConfResourceContext = new FileStreamResourceContext(AQUARIUM_HOME_CONF_FOLDER)
162
163     /**
164      * The venerable /etc resource context. Applicable in Unix environments
165      */
166     private[this] final lazy val SlashEtcResourceContext = new FileStreamResourceContext(ResourceNames.SLASH_ETC_AQUARIUM_FOLDER)
167
168     /**
169      * Class loader resource context.
170      * This has the lowest priority.
171      */
172     private[this] final lazy val ClasspathBaseResourceContext = new ClassLoaderStreamResourceContext(
173       Thread.currentThread().getContextClassLoader)
174
175     private[this] final lazy val BasicResourceContext = new CompositeStreamResourceContext(
176       NoVal,
177       SlashEtcResourceContext,
178       HomeConfResourceContext,
179       ClasspathBaseResourceContext)
180
181     /**
182      * The resource context used in the application.
183      */
184     final lazy val MasterResourceContext = {
185       SysProps.AquariumConfFolder.value match {
186         case Just(value) ⇒
187           // We have a system override for the configuration location
188           new CompositeStreamResourceContext(
189             Just(BasicResourceContext),
190             new FileStreamResourceContext(value))
191
192         case NoVal ⇒
193           BasicResourceContext
194
195         case Failed(e) ⇒
196           throw new AquariumInternalError(e)
197       }
198     }
199   }
200
201   final lazy val AQUARIUM_HOME_CONF_FOLDER = new File(Homes.Folders.AquariumHome, ResourceNames.CONF_FODLER)
202
203   /**
204    * This exists in order to have a feeling of where we are.
205    */
206   final lazy val HERE = justForSure(getResource(".")).get.url.toExternalForm
207
208   final object Resources {
209     final lazy val AquariumPropertiesResource = {
210       ResourceLocator.SysProps.AquariumPropertiesPath.value match {
211         case Just(aquariumPropertiesPath) ⇒
212           // If we have a command-line override, prefer that
213           new FileStreamResource(new File(aquariumPropertiesPath))
214
215         case Failed(e) ⇒
216           // On error, fail
217           throw new AquariumInternalError(
218             "Could not find %s".format(ResourceLocator.SysProps.Names.AquariumPropertiesPath), e)
219
220         case NoVal ⇒
221           // Otherwise try other locations
222           val aquariumPropertiesRCM = ResourceLocator getResource ResourceLocator.ResourceNames.AQUARIUM_PROPERTIES
223           aquariumPropertiesRCM match {
224             case Just(aquariumProperties) ⇒
225               aquariumProperties
226
227             case NoVal ⇒
228               // No luck
229               throw new AquariumInternalError(
230                 "Could not find %s".format(ResourceLocator.ResourceNames.AQUARIUM_PROPERTIES))
231
232             case Failed(e) ⇒
233               // Bad luck
234               throw new AquariumInternalError(
235                 "Could not find %s".format(ResourceLocator.ResourceNames.AQUARIUM_PROPERTIES), e)
236           }
237       }
238     }
239
240     final lazy val LogbackXMLResource = {
241       getResource(ResourceNames.LOGBACK_XML) match {
242         case Just(logbackXML) ⇒
243           logbackXML
244
245         case NoVal ⇒
246           throw new AquariumInternalError(
247             "Could not find %s".format(ResourceLocator.ResourceNames.LOGBACK_XML))
248
249         case Failed(e) ⇒
250           throw new AquariumInternalError(
251             "Could not find %s".format(ResourceLocator.ResourceNames.LOGBACK_XML), e)
252
253       }
254     }
255
256     final lazy val PolicyJSONResource = {
257       ResourceLocator.getResource(ResourceLocator.ResourceNames.POLICY_JSON) match {
258         case Just(policyJSON) ⇒
259           policyJSON
260
261         case NoVal ⇒
262           throw new AquariumInternalError(
263             "Could not find %s".format(ResourceLocator.ResourceNames.POLICY_JSON))
264
265         case Failed(e) ⇒
266           throw new AquariumInternalError(
267             "Could not find %s".format(ResourceLocator.ResourceNames.POLICY_JSON), e)
268       }
269     }
270   }
271
272   final lazy val AquariumProperties = {
273     implicit val DefaultConverters = Converters.DefaultConverters
274     val maybeProps = Props(Resources.AquariumPropertiesResource)
275     maybeProps match {
276       case Just(props) ⇒
277         props
278
279       case NoVal ⇒
280         throw new AquariumInternalError(
281           "Could not load %s from %s".format(
282             ResourceLocator.ResourceNames.AQUARIUM_PROPERTIES,
283             Resources.AquariumPropertiesResource))
284
285
286       case Failed(e) ⇒
287         throw new AquariumInternalError(
288           "Could not load %s from %s".format(
289             ResourceLocator.ResourceNames.AQUARIUM_PROPERTIES,
290             Resources.AquariumPropertiesResource),
291           e)
292     }
293   }
294
295   final lazy val DefaultPolicyMsg = {
296     val maybePolicyJSON = Resources.PolicyJSONResource.stringContent
297     maybePolicyJSON match {
298       case NoVal ⇒
299         throw new AquariumInternalError(
300           "Could not load %s from %s".format(
301             ResourceLocator.ResourceNames.POLICY_JSON,
302             Resources.PolicyJSONResource))
303
304       case Failed(e) ⇒
305         throw new AquariumInternalError(e,
306           "Could not load %s from %s".format(
307             ResourceLocator.ResourceNames.POLICY_JSON,
308             Resources.PolicyJSONResource))
309
310       case Just(jsonString) ⇒
311         try AvroHelpers.specificRecordOfJsonString(jsonString, new PolicyMsg)
312         catch {
313           case error: Error ⇒
314             throw error.asInstanceOf[Error]
315
316           case t: Throwable ⇒
317             throw new AquariumInternalError(t, "Cannot load default policy %s", Resources.PolicyJSONResource)
318         }
319     }
320   }
321
322   def getResource(what: String): Maybe[StreamResource] = {
323     ResourceContexts.MasterResourceContext.getResource(what)
324   }
325 }