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 */
016
017package org.ametys.web.service;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import java.util.regex.Pattern;
029
030import org.apache.avalon.framework.component.Component;
031import org.apache.avalon.framework.configuration.Configurable;
032import org.apache.avalon.framework.configuration.Configuration;
033import org.apache.avalon.framework.configuration.ConfigurationException;
034import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
035import org.apache.avalon.framework.context.Context;
036import org.apache.avalon.framework.context.ContextException;
037import org.apache.avalon.framework.context.Contextualizable;
038import org.apache.avalon.framework.logger.AbstractLogEnabled;
039import org.apache.avalon.framework.service.ServiceException;
040import org.apache.avalon.framework.service.ServiceManager;
041import org.apache.avalon.framework.service.Serviceable;
042import org.apache.cocoon.components.ContextHelper;
043import org.apache.commons.lang.StringUtils;
044import org.apache.excalibur.source.Source;
045import org.apache.excalibur.source.SourceResolver;
046import org.apache.excalibur.source.TraversableSource;
047
048import org.ametys.runtime.i18n.I18nizableText;
049import org.ametys.runtime.parameter.Enumerator;
050import org.ametys.runtime.plugin.component.PluginAware;
051import org.ametys.web.source.ServiceSourceFactory;
052
053/**
054 * This enuerator return the list of available files for services.
055 * The files are searched in the skin and in the plugin
056 * Configuration is
057 * <path> to specify the path where file are stored
058 * <file> an optionnal regexp to list files in the path (default is ^.*\.xsl$ to list all xsl files)
059 */
060public class ServiceXSLTEnumerator extends AbstractLogEnabled implements Enumerator, Component, Configurable, PluginAware, Serviceable, Contextualizable
061{
062    /** The relative path to search */
063    protected String _path;
064    
065    /** The file pattern */
066    protected Pattern _fileFilter;
067    /** The plugin declaring the enumerator */
068    protected String _pluginName;
069    /** The feature declaring the enumerator */
070    protected String _featureName;
071    /** The excalibur source resolver */
072    protected SourceResolver _resolver;
073    /** The default value of the xsl to use. Can be null. */
074    protected String _defaultValue;
075    /** The plugin xsl to use. Can be empty. */
076    protected Set<String> _values;
077    /** The avalon context */
078    protected Context _context;
079    
080    @Override
081    public void configure(Configuration configuration) throws ConfigurationException
082    {
083        _defaultValue = configuration.getChild("default-value").getValue(null);
084        
085        Configuration enumConf = configuration.getChild("enumeration").getChild("custom-enumerator");
086        
087        _values = _getPluginOtherXSL (enumConf);
088        
089        _path = enumConf.getChild("path").getValue("");
090        if (StringUtils.isBlank(_path))
091        {
092            throw new ConfigurationException("The configuration of the path is mandatory", configuration);
093        }
094        
095        String regexp = enumConf.getChild("file").getValue("^.*\\.xsl$");
096        _fileFilter = Pattern.compile(regexp);
097    }
098    
099    @Override
100    public void contextualize(Context context) throws ContextException
101    {
102        _context = context;
103    }
104    
105    @Override
106    public void service(ServiceManager manager) throws ServiceException
107    {
108        _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
109    }
110    
111    @Override
112    public void setPluginInfo(String pluginName, String featureName, String id)
113    {
114        _pluginName = pluginName;
115        _featureName = featureName;
116    }
117    
118    @Override
119    public Map<String, Object> getConfiguration()
120    {
121        Map<String, Object> config = new HashMap<>();
122        
123        config.put("defaultValue", _defaultValue);
124        config.put("values", _values);
125        config.put("path", _path);
126        config.put("fileFilter", _fileFilter.pattern());
127        
128        return config;
129    }
130    
131    /**
132     * Filter a list of sources to return thoses matching the _filter
133     * @param files A non null list of files to filter
134     * @return The non null filtered list of names
135     */
136    protected List<String> _filterNames(Collection<TraversableSource> files)
137    {
138        List<String> matched = new ArrayList<>();
139        
140        for (TraversableSource f : files)
141        {
142            if (!f.isCollection() && _fileFilter.matcher(f.getName()).matches())
143            {
144                matched.add(f.getName());
145            }
146        }
147        
148        return matched;
149    }
150    
151    @Override
152    public Map<Object, I18nizableText> getEntries() throws Exception
153    {
154        // 1 - Compute the list of files
155        Set<String> files = new HashSet<>();
156        
157        TraversableSource source = null;
158        try
159        {
160            source = (TraversableSource) _resolver.resolveURI("skin://services/" + _pluginName + "/" + _path);
161            if (source.exists())
162            {
163                if (source.isCollection())
164                {
165                    @SuppressWarnings("unchecked")
166                    List<String> sourceFiles = _filterNames(source.getChildren());
167                    files.addAll(sourceFiles);
168                }
169                else
170                {
171                    getLogger().warn("Can not enumerate files for service defined in '" + _pluginName + "/" + _featureName + "' on the path '" + _path + "', because it exists on the skin but is not a directory in the skin");
172                }
173            }
174        }
175        finally
176        {
177            _resolver.release(source);
178        }
179
180        if (_defaultValue != null)
181        {
182            Source pluginSource = null; 
183            try
184            {
185                pluginSource = _resolver.resolveURI("plugin:" + _pluginName + "://" + _defaultValue);
186                if (pluginSource.exists())
187                {
188                    int i = _defaultValue.lastIndexOf('/');
189                    String name = _defaultValue;
190                    if (i > 0)
191                    {
192                        name = _defaultValue.substring(i + 1);
193                    }
194                    files.add(name);
195                }
196            }
197            finally
198            {
199                _resolver.release(pluginSource);
200            }
201        }
202        
203        if (_values != null)
204        {
205            for (String value : _values)
206            {
207                Source pluginSource = null; 
208                try
209                {
210                    pluginSource = _resolver.resolveURI("plugin:" + _pluginName + "://" + value);
211                    if (pluginSource.exists())
212                    {
213                        int i = value.lastIndexOf('/');
214                        String name = value;
215                        if (i > 0)
216                        {
217                            name = value.substring(i + 1);
218                        }
219                        files.add(name);
220                    }
221                }
222                finally
223                {
224                    _resolver.release(pluginSource);
225                }
226            }
227        }
228
229        // 2 - Convert the files into entries
230        Map<Object, I18nizableText> entries = new HashMap<>();
231        for (String filename : files)
232        {
233            String s = _path + "/" + filename;
234            
235            I18nizableText i18ntext = getEntry(s); 
236            if (i18ntext != null)
237            {
238                entries.put(s, i18ntext);
239            }
240        }
241        
242        return entries;
243    }
244    
245    @Override
246    public I18nizableText getEntry(String value) throws Exception
247    {
248        String valueWithNoExtension = value.substring(0, value.length() - 4);
249        String url = "service:" + _pluginName + "://" + valueWithNoExtension + ".xml";
250        
251        Source source = null;
252        try
253        {
254            source = _resolver.resolveURI(url);
255            if (source.exists())
256            {
257                String defaultCatalogue = "plugin." + _pluginName;
258                
259                if (source instanceof ServiceSourceFactory.ServiceSource)
260                {
261                    if (((ServiceSourceFactory.ServiceSource) source).getSourceType() != ServiceSourceFactory.SourceType.PLUGIN)
262                    {
263                        String skinName = (String) ContextHelper.getRequest(_context).getAttribute("skin");
264                        defaultCatalogue = "skin." + skinName;
265                    }
266                }
267                
268                try (InputStream inputStream = source.getInputStream())
269                {
270                    Configuration conf = new DefaultConfigurationBuilder().build(inputStream);
271                    
272                    if (!conf.getAttributeAsBoolean("exclude", false))
273                    {
274                        Configuration node = conf.getChild("label");
275                        return I18nizableText.parseI18nizableText(node, defaultCatalogue);
276                    }
277                    else
278                    {
279                        return null;
280                    }
281                }
282            }
283        }
284        catch (IOException e) 
285        {
286            // Nothing
287        }
288        finally
289        {
290            _resolver.release(source);
291        }
292        
293        int i = valueWithNoExtension.lastIndexOf('/');
294        String shortFilename = valueWithNoExtension.substring(i + 1);
295        return new I18nizableText(shortFilename);
296    }
297    
298    private Set<String> _getPluginOtherXSL (Configuration configuration)
299    {
300        Set<String> otherFiles = new HashSet<>();
301        
302        Configuration[] filesConf = configuration.getChild("values", true).getChildren("value");
303        for (Configuration fileConf : filesConf)
304        {
305            otherFiles.add(fileConf.getValue(null));
306        }
307        
308        return otherFiles;
309    }
310
311}