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