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