001/*
002 *  Copyright 2010 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.web.repository.page;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.util.Date;
021import java.util.HashSet;
022import java.util.Set;
023
024import org.apache.avalon.framework.activity.Initializable;
025import org.apache.avalon.framework.configuration.Configuration;
026import org.apache.avalon.framework.configuration.ConfigurationException;
027import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
028import org.apache.avalon.framework.logger.AbstractLogEnabled;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.excalibur.source.Source;
033import org.apache.excalibur.source.SourceResolver;
034import org.xml.sax.SAXException;
035
036import org.ametys.core.cache.AbstractCacheManager;
037import org.ametys.runtime.i18n.I18nizableText;
038import org.ametys.web.repository.site.Site;
039import org.ametys.web.repository.site.SiteType;
040import org.ametys.web.repository.site.SiteTypesExtensionPoint;
041import org.ametys.web.service.ServiceExtensionPoint;
042
043/**
044 * This implementation of the services handler is based on services declared in the whole application
045 */
046public class DefaultServicesAssignmentHandler extends AbstractLogEnabled  implements ServicesAssignmentHandler, Serviceable, Initializable
047{ 
048    private static final String __CACHE_ID = DefaultServicesAssignmentHandler.class.getName() + "$cache";
049    
050    /** The services manager */
051    protected ServiceExtensionPoint _serviceEP;
052    /** The site type manager */
053    protected SiteTypesExtensionPoint _siteTypeExtensionPoint;
054    /** The source resolver */
055    protected SourceResolver _srcResolver;    
056    /** The cache manager */
057    protected AbstractCacheManager _cacheManager;
058    
059    @Override
060    public void service(ServiceManager manager) throws ServiceException
061    {
062        _serviceEP = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE);
063        _siteTypeExtensionPoint = (SiteTypesExtensionPoint) manager.lookup(SiteTypesExtensionPoint.ROLE);
064        _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
065        _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE);
066    }
067    
068    @Override
069    public void initialize() throws Exception
070    {
071        _cacheManager.createMemoryCache(__CACHE_ID,
072                new I18nizableText("plugin.web", "PLUGINS_WEB_DEFAULT_SERVICES_ASSIGNMENT_HANDLER_CACHE_LABEL"),
073                new I18nizableText("plugin.web", "PLUGINS_WEB_DEFAULT_SERVICES_ASSIGNMENT_HANDLER_CACHE_DESCRIPTION"),
074                true,
075                null);
076    }
077    
078    @Override
079    public Set<String> getAvailableServices(SitemapElement page, String zoneName)
080    {
081        Site site = page.getSite();
082        SiteType siteType = _siteTypeExtensionPoint.getExtension(site.getType());
083        
084        Set<String> services = _getServicesForZone(siteType.getName(), site.getSkinId(), page.getTemplate(), zoneName);
085        if (services != null)
086        {
087            services.retainAll(_getPublicServices());
088            return services;
089        }
090        
091        services = _getServicesForTemplate(siteType.getName(), site.getSkinId(), page.getTemplate());
092        if (services != null)
093        {
094            services.retainAll(_getPublicServices());
095            return services;
096        }
097        
098        services = _getServicesForSkin(siteType.getName(), site.getSkinId());
099        if (services != null)
100        {
101            services.retainAll(_getPublicServices());
102            return services;
103        }
104        
105        services = _getServicesForSiteType(siteType.getName());
106        if (services != null)
107        {
108            services.retainAll(_getPublicServices());
109            return services;
110        }
111        
112        return _getPublicServices();
113    }
114    
115    private Set<String> _getServicesForZone(String siteType, String skinName, String templateName, String zoneName)
116    {
117        String key = siteType + "/" + skinName + "/" + templateName + "/" + zoneName;
118        String file = "skin:" + skinName + "://templates/" + templateName + "/conf/services-" + siteType + ".xml";
119
120        Source configFile = null;
121        try
122        {
123            configFile = _srcResolver.resolveURI(file);
124            if (!configFile.exists())
125            {
126                return null;
127            }
128
129            Cache data = getCache().get(key); 
130            if (data == null || !data.isValid(configFile.getLastModified()))
131            {
132                data = _parseZoneServices(configFile, zoneName);
133                getCache().put(key, data);
134            }
135            return data;
136
137        }
138        catch (IOException e)
139        {
140            getLogger().error("Unable to read the services configuration file", e);
141            return null;
142        }
143        finally
144        {
145            _srcResolver.release(configFile);
146        }
147    }
148    
149    private Set<String> _getServicesForTemplate (String siteType, String skinName, String templateName)
150    {
151        String key = siteType + "/" + skinName + "/" + templateName;
152        String file = "skin:" + skinName + "://templates/" + templateName + "/conf/services-" + siteType + ".xml";
153
154        Source configFile = null;
155        try
156        {
157            configFile = _srcResolver.resolveURI(file);
158            if (!configFile.exists())
159            {
160                return null;
161            }
162
163            Cache data = getCache().get(key); 
164            if (data == null || !data.isValid(configFile.getLastModified()))
165            {
166                data = _parseTemplateServices(configFile);
167                getCache().put(key, data);
168            }
169            return data;
170
171        }
172        catch (IOException e)
173        {
174            getLogger().error("Unable to read the services configuration file", e);
175            return null;
176        }
177        finally
178        {
179            _srcResolver.release(configFile);
180        }
181    }
182    
183    private Set<String> _getServicesForSkin (String siteType, String skinName)
184    {
185        String key = siteType + "/" + skinName;
186        String file = "skin:" + skinName + "://conf/services-" + siteType + ".xml";
187
188        Source configFile = null;
189        try
190        {
191            configFile = _srcResolver.resolveURI(file);
192            if (!configFile.exists())
193            {
194                return null;
195            }
196
197            Cache data = getCache().get(key); 
198            if (data == null || !data.isValid(configFile.getLastModified()))
199            {
200                data = _parseServices(configFile);
201                getCache().put(key, data);
202            }
203            return data;
204
205        }
206        catch (IOException e)
207        {
208            getLogger().error("Unable to read the services configuration file", e);
209            return null;
210        }
211        finally
212        {
213            _srcResolver.release(configFile);
214        }
215    }
216    
217    private Set<String> _getServicesForSiteType (String siteType)
218    {
219        String key = siteType;
220        String file = "context://WEB-INF/param/services-" + siteType + ".xml";
221        
222        Source configFile = null;
223        try
224        {
225            configFile = _srcResolver.resolveURI(file);
226            if (!configFile.exists())
227            {
228                return null;
229            }
230
231            Cache data = getCache().get(key); 
232            if (data == null || !data.isValid(configFile.getLastModified()))
233            {
234                data = _parseServices(configFile);
235                getCache().put(key, data);
236            }
237            return data;
238
239        }
240        catch (IOException e)
241        {
242            getLogger().error("Unable to read the services configuration file", e);
243            return null;
244        }
245        finally
246        {
247            _srcResolver.release(configFile);
248        }        
249    }
250    
251    /**
252     * Get the public services.
253     * @return the public services.
254     */
255    protected Cache _getPublicServices()
256    {
257        Cache publicServices = new Cache(new Date().getTime());
258        
259        for (String id : _serviceEP.getExtensionsIds())
260        {
261            if (!_serviceEP.getExtension(id).isPrivate())
262            {
263                publicServices.add(id);
264            }
265        }
266        
267        return publicServices;
268    }
269    
270    /**
271     * Parse a template configuration file.
272     * @param configFile the template configuration file.
273     * @return the set of available services for the template.
274     */
275    protected Cache _parseTemplateServices(Source configFile)
276    {
277        Cache services = null;
278        
279        if (!configFile.exists())
280        {
281            return null;
282        }
283        
284        try (InputStream is = configFile.getInputStream())
285        {
286            Configuration configuration = new DefaultConfigurationBuilder().build(is);
287            
288            Configuration tplConf = configuration.getChild("template", false);
289            if (tplConf != null)
290            {
291                services = _parseServices(tplConf, configFile.getLastModified());
292            }
293        }
294        catch (IOException e)
295        {
296            getLogger().error("Unable to read the services configuration file", e);
297            return null;
298        }
299        catch (ConfigurationException e)
300        {
301            getLogger().error("Unable to parse the services configuration file", e);
302            return null;
303        }
304        catch (SAXException e)
305        {
306            getLogger().error("Unable parse the services configuration file", e);
307            return null;
308        }
309        
310        return services;
311    }
312    
313    /**
314     * Parse a template configuration file and get the set of available services for a given zone.
315     * @param configFile the template configuration file.
316     * @param zoneName the zone name.
317     * @return the set of available services for the given zone.
318     */
319    protected Cache _parseZoneServices(Source configFile, String zoneName)
320    {
321        Cache services = null;
322        
323        if (!configFile.exists())
324        {
325            return null;
326        }
327
328        try (InputStream is = configFile.getInputStream())
329        {
330            Configuration configuration = new DefaultConfigurationBuilder().build(is);
331            
332            Configuration zoneConfs = configuration.getChild("zones", false);
333            if (zoneConfs != null)
334            {
335                for (Configuration zoneConf : zoneConfs.getChildren("zone"))
336                {
337                    if (zoneConf.getAttribute("id").equals(zoneName))
338                    {
339                        services = _parseServices(zoneConf, configFile.getLastModified());
340                    }
341                }
342            }
343        }
344        catch (IOException e)
345        {
346            getLogger().error("Unable to read the services configuration file", e);
347            return null;
348        }
349        catch (ConfigurationException e)
350        {
351            getLogger().error("Unable to parse the services configuration file", e);
352            return null;
353        }
354        catch (SAXException e)
355        {
356            getLogger().error("Unable parse the services configuration file", e);
357            return null;
358        }
359        
360        return services;
361    }
362    
363    /**
364     * Parses the valid services for the site type
365     * @param configFile the configuration file.
366     * @return the services id in a Set
367     */ 
368    protected Cache _parseServices (Source configFile)
369    {
370        if (!configFile.exists())
371        {
372            return null;
373        }
374        
375        Cache services = null;
376        
377        try (InputStream is = configFile.getInputStream())
378        {
379            Configuration configuration = new DefaultConfigurationBuilder().build(is);
380            
381            services = _parseServices(configuration, configFile.getLastModified());
382        }
383        catch (IOException e)
384        {
385            getLogger().error("Unable to read the services configuration file", e);
386            return null;
387        }
388        catch (ConfigurationException e)
389        {
390            getLogger().error("Unable to parse the services configuration file", e);
391            return null;
392        }
393        catch (SAXException e)
394        {
395            getLogger().error("Unable parse the services configuration file", e);
396            return null;
397        }
398        
399        return services;
400    }
401    
402    /**
403     * Parses the valid services in a configuration.
404     * @param configuration the configuration.
405     * @param lastModificationDade date de dernière modification
406     * @return the services id in a Set
407     * @throws ConfigurationException if configuration is invalid
408     */ 
409    protected Cache _parseServices(Configuration configuration, long lastModificationDade) throws ConfigurationException
410    {
411        Cache services = new Cache(lastModificationDade);
412        
413        String mode = configuration.getAttribute("mode", "include");
414        if ("exclude".equals(mode))
415        {
416            services = _getPublicServices();
417            for (Configuration serviceConf : configuration.getChildren("service"))
418            {
419                String id = serviceConf.getAttribute("id");
420                services.remove(id);
421            }
422        }
423        else
424        {
425            for (Configuration serviceConf : configuration.getChildren("service"))
426            {
427                String id = serviceConf.getAttribute("id");
428                services.add(id);
429            }
430        }
431        
432        return services;
433    }
434    
435    /**
436     * The cache is a HashSet of String + a date 
437     */
438    protected static class Cache extends HashSet<String>
439    {
440        private long _sourceLastModified;
441        
442        /**
443         * Build the cache
444         * @param sourceLastModified The last modification date
445         */
446        public Cache(long sourceLastModified)
447        {
448            super();
449        }
450        
451        /**
452         * Determine if the cache is valid
453         * @param newSourceLastModified The new last modification date
454         * @return true if the cache is still valid
455         */
456        public boolean isValid(long newSourceLastModified)
457        {
458            return newSourceLastModified <= _sourceLastModified;
459        }
460    }
461
462    private org.ametys.core.cache.Cache<String, Cache> getCache()
463    {
464        return _cacheManager.get(__CACHE_ID);
465    }
466}