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.IOException;
019import java.io.InputStream;
020import java.net.MalformedURLException;
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import javax.xml.parsers.SAXParserFactory;
027
028import org.apache.avalon.framework.configuration.Configuration;
029import org.apache.avalon.framework.configuration.ConfigurationException;
030import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.excalibur.source.Source;
034import org.apache.excalibur.source.SourceResolver;
035import org.xml.sax.XMLReader;
036
037import org.ametys.cms.tag.StaticTagProvider;
038import org.ametys.cms.tag.Tag;
039import org.ametys.cms.tag.Tag.TagVisibility;
040import org.ametys.cms.tag.TagTargetType;
041import org.ametys.runtime.i18n.I18nizableText;
042import org.ametys.web.repository.site.Site;
043import org.ametys.web.repository.site.SiteManager;
044import org.ametys.web.skin.SkinsManager;
045
046/**
047 * This class represents the tags provide by the skin
048 */
049public class SkinTagProvider extends StaticTagProvider
050{
051    /** The source resolver */
052    protected SourceResolver _resolver;
053    
054    /** The tags */
055    protected Map<String, List<String>> _skinLocalIds;
056    /** The tags */
057    protected Map<String, Map<String, Tag>> _skinTags;
058    
059    private SiteManager _siteManager;
060    private SkinsManager _skinsManager;
061    
062    @Override
063    public void service(ServiceManager smanager) throws ServiceException
064    {
065        super.service(smanager);
066        _resolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE);
067        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
068        _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.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, Tag> 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        Source src = null;
142        try
143        {
144            SAXParserFactory factory = SAXParserFactory.newInstance();
145            
146            XMLReader reader = factory.newSAXParser().getXMLReader();
147
148            DefaultConfigurationBuilder confBuilder = new DefaultConfigurationBuilder(reader);
149
150            src = _getTagsFile(skinName);
151            if (src.exists())
152            {
153                try (InputStream is = src.getInputStream())
154                {
155                    Configuration configuration = confBuilder.build(is, src.getURI());
156                    
157                    if (!_skinLocalIds.containsKey(skinName))
158                    {
159                        _skinLocalIds.put(skinName, new ArrayList<String>());
160                    }
161                    
162                    Map<String, Tag> tags = configureTags(configuration, skinName, null, "skin." + skinName);
163                    _skinTags.put(skinName, tags);
164                }
165            }
166        }
167        finally
168        {
169            _resolver.release(src);
170        }
171    }
172    
173    private Source _getTagsFile(String skin) throws MalformedURLException, IOException
174    {
175        Source src; 
176        src = _resolver.resolveURI("context://skins/" + skin + "/conf/tags.xml");
177        if (!src.exists())
178        {
179            Source otherSrc = _resolver.resolveURI("context://skins/" + skin + "/tags/tags.xml");
180            if (otherSrc.exists())
181            {
182                src = otherSrc;
183                getLogger().warn("In skin '" + skin + "' the tags.xml file location is obsolete. It is tags/tags.xml, it should be conf/tags.xml");
184            }
185        }
186        return src;
187    }
188    
189    /**
190     * Configure tag from the passed configuration
191     * @param configuration The configuration
192     * @param skinName the skin name
193     * @param parent The parent tag if any
194     * @param defaultCatalogue The default catalog for i18n
195     * @return a Set of {@link Tag}
196     * @throws ConfigurationException if configuration is invalid
197     */
198    protected Map<String, Tag> configureTags (Configuration configuration, String skinName, Tag parent, String defaultCatalogue)  throws ConfigurationException
199    {
200        Map<String, Tag> tags = new HashMap<>();
201        
202        Configuration[] tagsConfiguration = configuration.getChildren("tag");
203        for (Configuration tagConfiguration : tagsConfiguration)
204        {
205            String id = tagConfiguration.getAttribute("id", null);
206            if (id == null)
207            {
208                getLogger().error("Missing attributed named \"id\" for tag configuration at " + tagConfiguration.getLocation() + ". Tag is ignored.");
209            }
210            else if (!Tag.NAME_PATTERN.matcher(id).matches())
211            {
212                getLogger().error("Invalid tag ID \"" + id + "\" for tag configuration at " + tagConfiguration.getLocation() + ". It must match the pattern " + Tag.NAME_PATTERN.toString() + ". Tag is ignored.");
213            }
214            else if (_skinLocalIds.get(skinName).contains(id))
215            {
216                getLogger().error("A tag with the ID \"" + id + "\" for tag configuration at " + tagConfiguration.getLocation() + " already exists. Tag is ignored.");
217            }
218            else
219            {
220                _skinLocalIds.get(skinName).add(id);
221                
222                String typeName = tagConfiguration.getAttribute("target", null);
223                if (typeName != null)
224                {
225                    TagTargetType targetType = _targetTypeEP.getTagTargetType(typeName);
226                    
227                    TagVisibility visibility = TagVisibility.PUBLIC;
228                    if (tagConfiguration.getAttribute("private", "").equals("true"))
229                    {
230                        visibility = TagVisibility.PRIVATE;
231                    }
232                    I18nizableText label = configureLabel (tagConfiguration, defaultCatalogue);
233                    I18nizableText description = configureDescription (tagConfiguration, defaultCatalogue);
234                    Tag tag = new Tag(id, id, parent, label, description, visibility, targetType);
235                    tags.put(id, tag);
236                    
237                    // Recursive configuration
238                    Map<String, Tag> childTags = configureTags(tagConfiguration, skinName, tag, defaultCatalogue);
239                    tag.setTags(childTags);
240                }
241                else
242                {
243                    getLogger().error("Missing attributed named \"target\" for tag configuration of id \"" + id + "\" at " + tagConfiguration.getLocation() + ". Tag is ignored.");
244                }
245            }
246            
247        }
248        
249        return tags;
250    }
251}