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.cocoon.environment.Request;
044import org.apache.commons.lang.StringUtils;
045import org.apache.excalibur.source.Source;
046import org.apache.excalibur.source.SourceResolver;
047import org.apache.excalibur.source.TraversableSource;
048
049import org.ametys.plugins.repository.AmetysObjectResolver;
050import org.ametys.runtime.i18n.I18nizableText;
051import org.ametys.runtime.model.Enumerator;
052import org.ametys.runtime.plugin.component.PluginAware;
053import org.ametys.web.WebConstants;
054import org.ametys.web.repository.page.ServicesAssignmentHandler;
055import org.ametys.web.repository.page.SitemapElement;
056import org.ametys.web.repository.page.ZoneItem;
057import org.ametys.web.source.ServiceSourceFactory;
058
059/**
060 * This enuerator return the list of available files for services.
061 * The files are searched in the skin and in the plugin
062 * Configuration is
063 * <path> to specify the path where file are stored
064 * <file> an optionnal regexp to list files in the path (default is ^.*\.xsl$ to list all xsl files)
065 */
066public class ServiceXSLTEnumerator extends AbstractLogEnabled implements Enumerator<String>, Component, Configurable, PluginAware, Serviceable, Contextualizable
067{
068    /** The relative path to search */
069    protected String _path;
070    
071    /** The file pattern */
072    protected Pattern _fileFilter;
073    /** The plugin declaring the enumerator */
074    protected String _pluginName;
075    /** The feature declaring the enumerator */
076    protected String _featureName;
077    /** The excalibur source resolver */
078    protected SourceResolver _resolver;
079    /** The default value of the xsl to use. Can be null. */
080    protected String _defaultValue;
081    /** The plugin xsl to use. Can be empty. */
082    protected Set<String> _values;
083    /** The avalon context */
084    protected Context _context;
085    /** The service assignement handler instance */
086    protected ServicesAssignmentHandler _servicesAssignmentHandler;
087    /** The ametys object resolver instance */
088    protected AmetysObjectResolver _ametysObjectResolver;
089    
090    @Override
091    public void configure(Configuration configuration) throws ConfigurationException
092    {
093        _defaultValue = configuration.getChild("default-value").getValue(null);
094        
095        Configuration enumConf = configuration.getChild("enumeration").getChild("custom-enumerator");
096        
097        _values = _getPluginOtherXSL (enumConf);
098        
099        _path = enumConf.getChild("path").getValue("");
100        if (StringUtils.isBlank(_path))
101        {
102            throw new ConfigurationException("The configuration of the path is mandatory", configuration);
103        }
104        
105        String regexp = enumConf.getChild("file").getValue("^.*\\.xsl$");
106        _fileFilter = Pattern.compile(regexp);
107    }
108    
109    @Override
110    public void contextualize(Context context) throws ContextException
111    {
112        _context = context;
113    }
114    
115    @Override
116    public void service(ServiceManager manager) throws ServiceException
117    {
118        _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
119        _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
120        _servicesAssignmentHandler = (ServicesAssignmentHandler) manager.lookup(ServicesAssignmentHandler.ROLE);
121    }
122    
123    @Override
124    public void setPluginInfo(String pluginName, String featureName, String id)
125    {
126        _pluginName = pluginName;
127        _featureName = featureName;
128    }
129    
130    @Override
131    public Map<String, Object> getConfiguration()
132    {
133        Map<String, Object> config = new HashMap<>();
134        
135        config.put("defaultValue", _defaultValue);
136        config.put("values", _values);
137        config.put("path", _path);
138        config.put("fileFilter", _fileFilter.pattern());
139        
140        return config;
141    }
142    
143    /**
144     * Filter a list of sources to return thoses matching the _filter
145     * @param files A non null list of files to filter
146     * @return The non null filtered list of names
147     */
148    protected List<String> _filterNames(Collection<TraversableSource> files)
149    {
150        List<String> matched = new ArrayList<>();
151        
152        for (TraversableSource f : files)
153        {
154            if (!f.isCollection() && _fileFilter.matcher(f.getName()).matches())
155            {
156                matched.add(f.getName());
157            }
158        }
159        
160        return matched;
161    }
162    
163    public Map<String, I18nizableText> getEntries() throws Exception
164    {
165        // 1 - Compute the list of files
166        Set<String> files = new HashSet<>();
167        
168        TraversableSource source = null;
169        try
170        {
171            source = (TraversableSource) _resolver.resolveURI("skin://services/" + _pluginName + "/" + _path);
172            if (source.exists())
173            {
174                if (source.isCollection())
175                {
176                    @SuppressWarnings("unchecked")
177                    List<String> sourceFiles = _filterNames(source.getChildren());
178                    files.addAll(sourceFiles);
179                }
180                else
181                {
182                    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");
183                }
184            }
185        }
186        finally
187        {
188            _resolver.release(source);
189        }
190
191        if (_defaultValue != null)
192        {
193            Source pluginSource = null;
194            try
195            {
196                pluginSource = _resolver.resolveURI("plugin:" + _pluginName + "://" + _defaultValue);
197                if (pluginSource.exists())
198                {
199                    int i = _defaultValue.lastIndexOf('/');
200                    String name = _defaultValue;
201                    if (i > 0)
202                    {
203                        name = _defaultValue.substring(i + 1);
204                    }
205                    files.add(name);
206                }
207            }
208            finally
209            {
210                _resolver.release(pluginSource);
211            }
212        }
213        
214        if (_values != null)
215        {
216            for (String value : _values)
217            {
218                Source pluginSource = null;
219                try
220                {
221                    pluginSource = _resolver.resolveURI("plugin:" + _pluginName + "://" + value);
222                    if (pluginSource.exists())
223                    {
224                        int i = value.lastIndexOf('/');
225                        String name = value;
226                        if (i > 0)
227                        {
228                            name = value.substring(i + 1);
229                        }
230                        files.add(name);
231                    }
232                }
233                finally
234                {
235                    _resolver.release(pluginSource);
236                }
237            }
238        }
239        
240        // 2 - Limit to authorized values
241        Request request = ContextHelper.getRequest(_context);
242        
243        String pageId = (String) request.getAttribute(WebConstants.REQUEST_ATTR_PAGE_ID);
244        if (StringUtils.isNotBlank(pageId)) // Specific places like forms preview, may need the enumerator with no restriction possible
245        {
246            SitemapElement page = _ametysObjectResolver.resolveById(pageId);
247            
248            String zoneName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_ZONE_NAME);
249            if (zoneName == null)
250            {
251                String zoneItemId = (String) request.getAttribute(WebConstants.REQUEST_ATTR_ZONEITEM_ID);
252                ZoneItem zoneItem = _ametysObjectResolver.resolveById(zoneItemId);
253                zoneName = zoneItem.getZone().getName();
254            }
255            
256            String serviceId = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SERVICE_ID);
257            files = _servicesAssignmentHandler.limitAvailableServiceViews(files, page, zoneName, serviceId);
258        }
259        
260        // 3 - Convert the files into entries
261        Map<String, I18nizableText> entries = new HashMap<>();
262        for (String filename : files)
263        {
264            String s = _path + "/" + filename;
265            
266            I18nizableText i18ntext = getEntry(s);
267            if (i18ntext != null)
268            {
269                entries.put(s, i18ntext);
270            }
271        }
272        
273        return entries;
274    }
275    
276    @Override
277    public I18nizableText getEntry(String value) throws Exception
278    {
279        String valueWithNoExtension = value.substring(0, value.length() - 4);
280        String url = "service:" + _pluginName + "://" + valueWithNoExtension + ".xml";
281        
282        Source source = null;
283        try
284        {
285            source = _resolver.resolveURI(url);
286            if (source.exists())
287            {
288                String defaultCatalogue = "plugin." + _pluginName;
289                
290                if (source instanceof ServiceSourceFactory.ServiceSource)
291                {
292                    if (((ServiceSourceFactory.ServiceSource) source).getSourceType() != ServiceSourceFactory.SourceType.PLUGIN)
293                    {
294                        String skinName = (String) ContextHelper.getRequest(_context).getAttribute("skin");
295                        defaultCatalogue = "skin." + skinName;
296                    }
297                }
298                
299                try (InputStream inputStream = source.getInputStream())
300                {
301                    Configuration conf = new DefaultConfigurationBuilder().build(inputStream);
302                    
303                    if (!conf.getAttributeAsBoolean("exclude", false))
304                    {
305                        getLogger().warn("The 'exclude' attribute in service view definition file (" + url + ") is obsolete. Consider using the services-views-xxx.xml file : search for \"services-views-default.xml\" in the Ametys documentation to know more about it.");
306                        
307                        Configuration node = conf.getChild("label", false);
308                        if (node != null)
309                        {
310                            return I18nizableText.parseI18nizableText(node, defaultCatalogue);
311                        }
312                        else
313                        {
314                            // No label defined
315                            int i = valueWithNoExtension.lastIndexOf('/');
316                            String shortFilename = valueWithNoExtension.substring(i + 1);
317                            return new I18nizableText(shortFilename);
318                        }
319                    }
320                    else
321                    {
322                        return null;
323                    }
324                }
325            }
326        }
327        catch (IOException e)
328        {
329            // Nothing
330        }
331        finally
332        {
333            _resolver.release(source);
334        }
335        
336        int i = valueWithNoExtension.lastIndexOf('/');
337        String shortFilename = valueWithNoExtension.substring(i + 1);
338        return new I18nizableText(shortFilename);
339    }
340    
341    private Set<String> _getPluginOtherXSL (Configuration configuration)
342    {
343        Set<String> otherFiles = new HashSet<>();
344        
345        Configuration[] filesConf = configuration.getChild("values", true).getChildren("value");
346        for (Configuration fileConf : filesConf)
347        {
348            otherFiles.add(fileConf.getValue(null));
349        }
350        
351        return otherFiles;
352    }
353
354}