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