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.tags;
017
018import java.io.InputStream;
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.avalon.framework.configuration.Configuration;
025import org.apache.avalon.framework.configuration.ConfigurationException;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.excalibur.source.Source;
029import org.apache.excalibur.source.SourceResolver;
030
031import org.ametys.cms.tag.CMSTag;
032import org.ametys.cms.tag.CMSTag.TagVisibility;
033import org.ametys.cms.tag.StaticTagProvider;
034import org.ametys.cms.tag.Tag;
035import org.ametys.cms.tag.TagTargetType;
036import org.ametys.runtime.i18n.I18nizableText;
037import org.ametys.web.repository.site.Site;
038import org.ametys.web.repository.site.SiteManager;
039import org.ametys.web.skin.Skin;
040import org.ametys.web.skin.SkinConfigurationHelper;
041import org.ametys.web.skin.SkinsManager;
042
043/**
044 * This class represents the tags provide by the skin
045 */
046public class SkinTagProvider extends StaticTagProvider
047{
048    /** The source resolver */
049    protected SourceResolver _resolver;
050    
051    /** The tags */
052    protected Map<String, List<String>> _skinLocalIds;
053    /** The tags */
054    protected Map<String, Map<String, CMSTag>> _skinTags;
055    
056    private SiteManager _siteManager;
057    private SkinsManager _skinsManager;
058
059    private SkinConfigurationHelper _skinConfigurationHelper;
060    
061    @Override
062    public void service(ServiceManager smanager) throws ServiceException
063    {
064        super.service(smanager);
065        _resolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE);
066        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
067        _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE);
068        _skinConfigurationHelper = (SkinConfigurationHelper) smanager.lookup(SkinConfigurationHelper.ROLE);
069    }
070    
071    @Override
072    public void configure(Configuration configuration) throws ConfigurationException
073    {
074        _skinLocalIds = new HashMap<>();
075        
076        _id = configuration.getAttribute("id");
077        _label = configureLabel(configuration, "plugin." + _pluginName);
078        _description = configureDescription(configuration, "plugin." + _pluginName);
079        
080        if (_skinTags == null)
081        {
082            _skinTags = new HashMap<>();
083        }
084        
085        for (String skinName : _skinsManager.getSkins())
086        {
087            try
088            {
089                initializeTags(skinName);
090            }
091            catch (Exception e)
092            {
093                throw new ConfigurationException("Unable to load tags configuration values for skin " + skinName, e);
094            }
095        }
096    }
097    
098    @Override
099    public Map<String, CMSTag> getTags(Map<String, Object> contextualParameters)
100    {
101        if (contextualParameters.get("siteName") == null)
102        {
103            // contextualParameters#containsKey not sufficient here because the in some case the siteName key can be set to null
104            return null;
105        }
106        
107        Site site = _siteManager.getSite((String) contextualParameters.get("siteName"));
108        
109        if (site == null)
110        {
111            String errorMessage = "Unable to load tags configuration values for site " + (String) contextualParameters.get("siteName");
112            getLogger().error(errorMessage);
113            return null;
114        }
115        
116        String skin = site.getSkinId();
117        
118        if (!_skinTags.containsKey(skin))
119        {
120            try
121            {
122                initializeTags(skin);
123            }
124            catch (Exception e)
125            {
126                String errorMessage = "Unable to load tags configuration values for skin " + skin;
127                getLogger().error(errorMessage, e);
128            }
129        }
130        
131        return _skinTags.get(skin);
132    }
133    
134    /**
135     * Initialize a skin's tags from the tags file.
136     * @param skinName the name of the skin to initialize tags.
137     * @throws Exception if an error occurs.
138     */
139    protected void initializeTags(String skinName) throws Exception
140    {
141        // Obsolete location
142        Source otherSrc = null;
143        try
144        {
145            otherSrc = _resolver.resolveURI("skin:" + skinName + "://tags/tags.xml");
146            if (otherSrc.exists())
147            {
148                getLogger().error("In skin '" + skinName + "' (or one of its parent skin) the 'tags.xml' file location is obsolete AND NOT SUPPORTED ANYMORE. Move the file tags/tags.xml to conf/tags.xml");
149            }
150        }
151        finally 
152        {
153            _resolver.release(otherSrc);
154        }
155        
156        
157        // Right inheritable location
158        Skin skin = _skinsManager.getSkin(skinName);
159        try (InputStream xslIs = getClass().getResourceAsStream("skin-tag-merge.xsl"))
160        {
161            Configuration configuration = _skinConfigurationHelper.getInheritanceMergedConfiguration(skin, "conf/tags.xml", xslIs);
162            
163            if (!_skinLocalIds.containsKey(skinName))
164            {
165                _skinLocalIds.put(skinName, new ArrayList<String>());
166            }
167            
168            Map<String, CMSTag> tags = configureTags(configuration, skinName, null, "skin." + skinName);
169            _skinTags.put(skinName, tags);
170        }
171    }
172    
173    /**
174     * Configure tag from the passed configuration
175     * @param configuration The configuration
176     * @param skinName the skin name
177     * @param parent The parent tag if any
178     * @param defaultCatalogue The default catalog for i18n
179     * @return a Set of {@link CMSTag}
180     * @throws ConfigurationException if configuration is invalid
181     */
182    protected Map<String, CMSTag> configureTags (Configuration configuration, String skinName, CMSTag parent, String defaultCatalogue)  throws ConfigurationException
183    {
184        Map<String, CMSTag> tags = new HashMap<>();
185        
186        Configuration[] tagsConfiguration = configuration.getChildren("tag");
187        for (Configuration tagConfiguration : tagsConfiguration)
188        {
189            String id = tagConfiguration.getAttribute("id", null);
190            if (id == null)
191            {
192                getLogger().error("Missing attributed named \"id\" for tag configuration at " + tagConfiguration.getLocation() + ". Tag is ignored.");
193            }
194            else if (!Tag.NAME_PATTERN.matcher(id).matches())
195            {
196                getLogger().error("Invalid tag ID \"" + id + "\" for tag configuration at " + tagConfiguration.getLocation() + ". It must match the pattern " + Tag.NAME_PATTERN.toString() + ". Tag is ignored.");
197            }
198            else if (_skinLocalIds.get(skinName).contains(id))
199            {
200                getLogger().error("A tag with the ID \"" + id + "\" for tag configuration at " + tagConfiguration.getLocation() + " already exists. Tag is ignored.");
201            }
202            else
203            {
204                _skinLocalIds.get(skinName).add(id);
205                
206                String typeName = tagConfiguration.getAttribute("target", null);
207                if (typeName != null)
208                {
209                    TagTargetType targetType = _targetTypeEP.getTagTargetType(typeName);
210                    
211                    TagVisibility visibility = TagVisibility.PUBLIC;
212                    if (tagConfiguration.getAttribute("private", "").equals("true"))
213                    {
214                        visibility = TagVisibility.PRIVATE;
215                    }
216                    I18nizableText label = configureLabel (tagConfiguration, defaultCatalogue);
217                    I18nizableText description = configureDescription (tagConfiguration, defaultCatalogue);
218                    CMSTag tag = new CMSTag(id, id, parent, label, description, visibility, targetType);
219                    tags.put(id, tag);
220                    
221                    // Recursive configuration
222                    Map<String, CMSTag> childTags = configureTags(tagConfiguration, skinName, tag, defaultCatalogue);
223                    tag.setTags(childTags);
224                }
225                else
226                {
227                    getLogger().error("Missing attributed named \"target\" for tag configuration of id \"" + id + "\" at " + tagConfiguration.getLocation() + ". Tag is ignored.");
228                }
229            }
230            
231        }
232        
233        return tags;
234    }
235}