001/*
002 *  Copyright 2012 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.runtime.servlet;
017
018import java.io.File;
019import java.text.ParseException;
020import java.text.SimpleDateFormat;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.Map;
026
027import org.apache.avalon.framework.configuration.Configuration;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031import org.ametys.runtime.util.AmetysHomeHelper;
032
033/**
034 * Java representation of the WEB-INF/param/runtime.xml file.<br>
035 * Contains all runtime configuration values.
036 */
037public final class RuntimeConfig
038{
039    // shared instance
040    private static RuntimeConfig __config;
041    
042    private boolean _safeMode;
043
044    private String _defaultWorkspace;
045    private String _initClass;
046    private final Collection<String> _pluginsLocations = new ArrayList<>();
047    private final Collection<String> _excludedPlugins = new ArrayList<>();
048    private final Collection<String> _excludedFeatures = new ArrayList<>();
049    private final Collection<String> _excludedWorkspaces = new ArrayList<>();
050    private final Map<String, String> _components = new HashMap<>();
051
052    private Logger _logger = LoggerFactory.getLogger(RuntimeConfig.class);
053    
054    private String _contextPath;
055
056    private String _version;
057    private Date _buildDate;
058    
059    /* External location of the kernel, if any */
060    private File _externalKernel;
061    
062    /* Locations of external plugins */
063    private Map<String, File> _externalPlugins = new HashMap<>();
064    
065    /* Locations of external workspaces */
066    private Map<String, File> _externalWorkspaces = new HashMap<>();
067
068    private File _ametysHome;
069    
070    private RuntimeConfig()
071    {
072        // empty constructor
073    }
074
075    /**
076     * Returns the shared instance of the <code>RuntimeConfig</code>
077     * @return the shared instance of the <code>RuntimeConfig</code>
078     */
079    public static RuntimeConfig getInstance()
080    {
081        if (__config == null)
082        {
083            throw new IllegalStateException("RuntimeConfig has not been initialized.");
084        }
085
086        return __config;
087    }
088    
089    /**
090     * Returns true if the Runtime has been configured (ie. if the {@link #configure(Configuration, Configuration, File, String)} method has been called.
091     * @return true if the Runtime has been configured.
092     */
093    public static boolean isConfigured()
094    {
095        return __config != null;
096    }
097    
098    /**
099     * Configures the Runtime kernel.<br>
100     * This method must be called <i>before</i> getting the RuntimConfig instance.<br><br>
101     * <b>Warning : the implementation allows this method to be called twice or more. This is only to allow the Runtime to be re-started dynamically.<br>
102     * Be aware that this can cause the application to become unstable.</b>
103     * @param runtimeConf the Configuration of the Runtime kernel (ie the contents of the WEB-INF/param/runtime.xml file)
104     * @param externalConf the Configuration of external locations (ie the contents of the WEB-INF/param/external-locations.xml file)
105     * @param ametysHome The ametys home directory
106     * @param contextPath the application context path
107     */
108    public static synchronized void configure(Configuration runtimeConf, Configuration externalConf, File ametysHome, String contextPath)
109    {
110        __config = new RuntimeConfig();
111        
112        __config._contextPath = contextPath;
113        __config._ametysHome = ametysHome;
114
115        // create home directories
116        File dataHome = new File(ametysHome, AmetysHomeHelper.AMETYS_HOME_DATA_DIR);
117        dataHome.mkdirs();
118        
119        File configHome = new File(ametysHome, AmetysHomeHelper.AMETYS_HOME_CONFIG_DIR);
120        configHome.mkdirs();
121        
122        File tmpDir = new File(ametysHome, AmetysHomeHelper.AMETYS_HOME_TMP_DIR);
123        tmpDir.mkdirs();
124
125        // runtimeConfig is null if the runtime.xml could not be read for any reason
126        if (runtimeConf != null)
127        {
128            __config._safeMode = false;
129            __config._initClass = runtimeConf.getChild("initClass").getValue(null);
130    
131            __config._configureWorkspaces(runtimeConf.getChild("workspaces"));
132            __config._configurePlugins(runtimeConf.getChild("plugins"));
133            __config._configureComponents(runtimeConf.getChild("components"));
134            __config._configureApplication(runtimeConf.getChild("application"));
135        }
136        else
137        {
138            __config._safeMode = true;
139            __config._pluginsLocations.add("plugins/");
140        }
141        
142        if (externalConf != null)
143        {
144            __config._configureExternal(externalConf);
145        }
146    }
147    
148    private void _configureWorkspaces(Configuration config)
149    {
150        _defaultWorkspace = config.getAttribute("default", null);
151
152        for (Configuration excluded : config.getChild("exclude").getChildren("workspace"))
153        {
154            String workspace = excluded.getValue(null);
155
156            if (workspace != null)
157            {
158                _excludedWorkspaces.add(workspace);
159            }
160        }
161    }
162
163    private void _configurePlugins(Configuration config)
164    {
165        for (Configuration excluded : config.getChild("exclude").getChildren("plugin"))
166        {
167            String plugin = excluded.getValue(null);
168
169            if (plugin != null)
170            {
171                _excludedPlugins.add(plugin);
172            }
173        }
174
175        for (Configuration excluded : config.getChild("exclude").getChildren("feature"))
176        {
177            String feature = excluded.getValue(null);
178
179            if (feature != null)
180            {
181                _excludedFeatures.add(feature);
182            }
183        }
184
185        for (Configuration locationConf : config.getChild("locations").getChildren("location"))
186        {
187            String location = locationConf.getValue(null);
188
189            if (location != null)
190            {
191                _pluginsLocations.add(location);
192            }
193        }
194
195        // On ajoute aux emplacements de plugins le répertoire "plugins"
196        if (!_pluginsLocations.contains("plugins") && !_pluginsLocations.contains("plugins/"))
197        {
198            _pluginsLocations.add("plugins/");
199        }
200
201    }
202
203    private void _configureComponents(Configuration config)
204    {
205        for (Configuration extension : config.getChildren())
206        {
207            String point = extension.getName();
208            String id = extension.getValue(null);
209
210            if (id != null)
211            {
212                __config._components.put(point, id);
213            }
214        }
215    }
216
217    private void _configureApplication(Configuration config)
218    {
219        String version = config.getChild("version").getValue("");
220        if (!"@VERSION@".equals(version) && !"VERSION".equals(version))
221        {
222            _version = version;
223        }
224
225        String strDate = config.getChild("date").getValue(null);
226
227        if (strDate != null && !"".equals(strDate) && !"@DATE@".equals(strDate) && !"DATE".equals(strDate))
228        {
229            try
230            {
231                _buildDate = new SimpleDateFormat("yyyyMMdd'T'HHmm z").parse(strDate);
232            }
233            catch (ParseException e)
234            {
235                _logger.warn("Unable to parse date '" + strDate + "' with format \"yyyyMMdd'T'HHmm z\". It will be ignored.");
236            }
237        }
238    }
239    
240    private void _configureExternal(Configuration config)
241    {
242        String externalKernel = config.getChild("kernel").getValue(null);
243        _externalKernel = _getFile(externalKernel);
244        
245        for (Configuration pluginConf : config.getChild("plugins").getChildren("plugin"))
246        {
247            String name = pluginConf.getAttribute("name", null);
248            String location = pluginConf.getValue(null);
249            
250            if (name != null && location != null)
251            {
252                _externalPlugins.put(name, _getFile(location));
253            }
254        }
255        
256        for (Configuration workspaceConf : config.getChild("workspaces").getChildren("workspace"))
257        {
258            String name = workspaceConf.getAttribute("name", null);
259            String location = workspaceConf.getValue(null);
260            
261            if (location != null)
262            {
263                _externalWorkspaces.put(name, _getFile(location));
264            }
265        }
266    }
267    
268    /*
269     * Returns the correspoding file, either absolute or relative to the context path
270     */
271    private File _getFile(String path)
272    {
273        File file = path == null ? null : new File(path);
274        File result = file == null ? null : file.isAbsolute() ? file : new File(_contextPath, path);
275        return result;
276    }
277    
278    /**
279     * Returns true if safe mode is needed (ie. if there's been an error reading runtime.xml). 
280     * @return true if safe mode is needed.
281     */
282    public boolean isSafeMode()
283    {
284        return _safeMode;
285    }
286
287    /**
288     * Returns the name of the default workspace. Null if none.
289     * @return the name of the default workspace
290     */
291    public String getDefaultWorkspace()
292    {
293        return _defaultWorkspace;
294    }
295
296    /**
297     * Returns the name of the class to be excuted at the end of the initialization process, if any.<br>
298     * May be null.
299     * @return Returns the name of the class to be excuted at the end of the initialization process, if any
300     */
301    public String getInitClassName()
302    {
303        return _initClass;
304    }
305
306    /**
307     * Returns a Collection containing the locations of the plugins
308     * @return a Collection containing the locations of the plugins
309     */
310    public Collection<String> getPluginsLocations()
311    {
312        return _pluginsLocations;
313    }
314    
315    /**
316     * Returns the declared external plugins (ie. not located in the webapp context).
317     * @return the declared external plugins
318     */
319    public Map<String, File> getExternalPlugins()
320    {
321        return _externalPlugins;
322    }
323
324    /**
325     * Returns the declared external workspaces (ie. not located in the webapp context).
326     * @return the declared external workspaces
327     */
328    public Map<String, File> getExternalWorkspaces()
329    {
330        return _externalWorkspaces;
331    }
332
333    /**
334     * Returns the absolute external location of the kernel, if any.<br>
335     * Returns null if the kernel is not externalized.
336     * @return the absolute external location of the kernel, if any.
337     */
338    public File getExternalKernel()
339    {
340        return _externalKernel;
341    }
342
343    /**
344     * Returns a Collection containing the names of the excluded (deactivated) plugins
345     * @return a Collection containing the names of the excluded (deactivated) plugins
346     */
347    public Collection<String> getExcludedPlugins()
348    {
349        return _excludedPlugins;
350    }
351
352    /**
353     * Returns a Collection containing the names of the excluded (deactivated) features
354     * @return a Collection containing the names of the excluded (deactivated) features
355     */
356    public Collection<String> getExcludedFeatures()
357    {
358        return _excludedFeatures;
359    }
360
361    /**
362     * Returns a Collection containing the names of the excluded (deactivated) workspaces
363     * @return a Collection containing the names of the excluded (deactivated) workspaces
364     */
365    public Collection<String> getExcludedWorkspaces()
366    {
367        return _excludedWorkspaces;
368    }
369
370    /**
371     * Returns a Map&lt;role, component id&gt; containing the choosen implementation for the given component role
372     * @return a Map&lt;role, component id&gt; containing the choosen implementation for the given component role
373     */
374    public Map<String, String> getComponents()
375    {
376        return _components;
377    }
378
379    /**
380     * Returns the application version name
381     * @return the application version name
382     */
383    public String getApplicationVersion()
384    {
385        return _version;
386    }
387
388    /**
389     * Returns the application build date, if provided. May be null.
390     * @return the application build date.
391     */
392    public Date getApplicationBuildDate()
393    {
394        return _buildDate;
395    }
396    
397    /**
398     * Returns the Ametys home directory. Cannot be null.
399     * @return The Ametys home directory.
400     */
401    public File getAmetysHome()
402    {
403        return _ametysHome;
404    }
405}