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