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    /**
119     * Filter a list of sources to return thoses matching the _filter
120     * @param files A non null list of files to filter
121     * @return The non null filtered list of names
122     */
123    protected List<String> _filterNames(Collection<TraversableSource> files)
124    {
125        List<String> matched = new ArrayList<>();
126        
127        for (TraversableSource f : files)
128        {
129            if (!f.isCollection() && _fileFilter.matcher(f.getName()).matches())
130            {
131                matched.add(f.getName());
132            }
133        }
134        
135        return matched;
136    }
137    
138    @Override
139    public Map<Object, I18nizableText> getEntries() throws Exception
140    {
141        // 1 - Compute the list of files
142        Set<String> files = new HashSet<>();
143        
144        TraversableSource source = null;
145        try
146        {
147            source = (TraversableSource) _resolver.resolveURI("skin://services/" + _pluginName + "/" + _path);
148            if (source.exists())
149            {
150                if (source.isCollection())
151                {
152                    @SuppressWarnings("unchecked")
153                    List<String> sourceFiles = _filterNames(source.getChildren());
154                    files.addAll(sourceFiles);
155                }
156                else
157                {
158                    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");
159                }
160            }
161        }
162        finally
163        {
164            _resolver.release(source);
165        }
166
167        if (_defaultValue != null)
168        {
169            Source pluginSource = null; 
170            try
171            {
172                pluginSource = _resolver.resolveURI("plugin:" + _pluginName + "://" + _defaultValue);
173                if (pluginSource.exists())
174                {
175                    int i = _defaultValue.lastIndexOf('/');
176                    String name = _defaultValue;
177                    if (i > 0)
178                    {
179                        name = _defaultValue.substring(i + 1);
180                    }
181                    files.add(name);
182                }
183            }
184            finally
185            {
186                _resolver.release(pluginSource);
187            }
188        }
189        
190        if (_values != null)
191        {
192            for (String value : _values)
193            {
194                Source pluginSource = null; 
195                try
196                {
197                    pluginSource = _resolver.resolveURI("plugin:" + _pluginName + "://" + value);
198                    if (pluginSource.exists())
199                    {
200                        int i = value.lastIndexOf('/');
201                        String name = value;
202                        if (i > 0)
203                        {
204                            name = value.substring(i + 1);
205                        }
206                        files.add(name);
207                    }
208                }
209                finally
210                {
211                    _resolver.release(pluginSource);
212                }
213            }
214        }
215
216        // 2 - Convert the files into entries
217        Map<Object, I18nizableText> entries = new HashMap<>();
218        for (String filename : files)
219        {
220            String s = _path + "/" + filename;
221            
222            I18nizableText i18ntext = getEntry(s); 
223            if (i18ntext != null)
224            {
225                entries.put(s, i18ntext);
226            }
227        }
228        
229        return entries;
230    }
231    
232    @Override
233    public I18nizableText getEntry(String value) throws Exception
234    {
235        String valueWithNoExtension = value.substring(0, value.length() - 4);
236        String url = "service:" + _pluginName + "://" + valueWithNoExtension + ".xml";
237        
238        Source source = null;
239        try
240        {
241            source = _resolver.resolveURI(url);
242            if (source.exists())
243            {
244                String defaultCatalogue = "plugin." + _pluginName;
245                
246                if (source instanceof ServiceSourceFactory.ServiceSource)
247                {
248                    if (((ServiceSourceFactory.ServiceSource) source).getSourceType() != ServiceSourceFactory.SourceType.PLUGIN)
249                    {
250                        String skinName = (String) ContextHelper.getRequest(_context).getAttribute("skin");
251                        defaultCatalogue = "skin." + skinName;
252                    }
253                }
254                
255                try (InputStream inputStream = source.getInputStream())
256                {
257                    Configuration conf = new DefaultConfigurationBuilder().build(inputStream);
258                    
259                    if (!conf.getAttributeAsBoolean("exclude", false))
260                    {
261                        Configuration node = conf.getChild("label");
262                        return I18nizableText.parseI18nizableText(node, defaultCatalogue);
263                    }
264                    else
265                    {
266                        return null;
267                    }
268                }
269            }
270        }
271        catch (IOException e) 
272        {
273            // Nothing
274        }
275        finally
276        {
277            _resolver.release(source);
278        }
279        
280        int i = valueWithNoExtension.lastIndexOf('/');
281        String shortFilename = valueWithNoExtension.substring(i + 1);
282        return new I18nizableText(shortFilename);
283    }
284    
285    private Set<String> _getPluginOtherXSL (Configuration configuration)
286    {
287        Set<String> otherFiles = new HashSet<>();
288        
289        Configuration[] filesConf = configuration.getChild("values", true).getChildren("value");
290        for (Configuration fileConf : filesConf)
291        {
292            otherFiles.add(fileConf.getValue(null));
293        }
294        
295        return otherFiles;
296    }
297
298}