001/*
002 *  Copyright 2017 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.plugins.linkdirectory.dynamic;
017
018import java.io.ByteArrayInputStream;
019import java.io.InputStream;
020import java.net.URL;
021import java.util.regex.Matcher;
022import java.util.regex.Pattern;
023
024import javax.xml.XMLConstants;
025import javax.xml.parsers.SAXParserFactory;
026import javax.xml.transform.stream.StreamSource;
027import javax.xml.validation.Schema;
028import javax.xml.validation.SchemaFactory;
029import javax.xml.validation.Validator;
030
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.service.ServiceException;
035import org.apache.avalon.framework.service.ServiceManager;
036import org.apache.avalon.framework.service.Serviceable;
037import org.apache.cocoon.xml.AttributesImpl;
038import org.apache.cocoon.xml.XMLUtils;
039import org.apache.commons.compress.utils.IOUtils;
040import org.apache.commons.lang.StringUtils;
041import org.apache.excalibur.source.SourceResolver;
042import org.xml.sax.ContentHandler;
043import org.xml.sax.InputSource;
044import org.xml.sax.XMLReader;
045
046import org.ametys.core.util.URLEncoder;
047import org.ametys.runtime.config.Config;
048import org.ametys.runtime.i18n.I18nizableText;
049import org.ametys.runtime.plugin.component.AbstractLogEnabled;
050import org.ametys.runtime.plugin.component.PluginAware;
051import org.ametys.web.site.SiteConfigurationExtensionPoint;
052
053
054/**
055 * Implementation of {@link DynamicInformationProvider} allowing to SAX information from a confiured URL.
056 */
057public class URLBasedDynamicInformationProvider extends AbstractLogEnabled implements DynamicInformationProvider, Configurable, PluginAware, Serviceable
058{
059    private static final Pattern __CONFIG_PARAM_PATTERN = Pattern.compile("\\$\\{config:([^}]+)\\}");
060    private static final Pattern __SITE_CONFIG_PARAM_PATTERN = Pattern.compile("\\$\\{site-config:([^}]+)\\}");
061    
062    private String _url;
063    
064    private SiteConfigurationExtensionPoint _siteConf;
065
066    private String _pluginName;
067
068    private I18nizableText _label;
069
070    private String _id;
071
072    private SourceResolver _sourceResolver;
073
074    public void service(ServiceManager manager) throws ServiceException
075    {
076        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
077        _siteConf = (SiteConfigurationExtensionPoint) manager.lookup(SiteConfigurationExtensionPoint.ROLE);
078    }
079
080    @Override
081    public void setPluginInfo(String pluginName, String featureName, String id)
082    {
083        _pluginName = pluginName;
084        _id = id;
085    }
086
087    @Override
088    public void configure(Configuration configuration) throws ConfigurationException
089    {
090        _url = configuration.getChild("url").getValue();
091        _label = I18nizableText.parseI18nizableText(configuration.getChild("label"), "plugin." + _pluginName, "");
092    }
093
094    @Override
095    public void saxDynamicInformation(ContentHandler contentHandler, String linkId, String siteName, String lang, int nbItems) throws Exception
096    {
097        String url = _handleVariable(_url, siteName, lang, nbItems);
098        
099        try (InputStream is = _sourceResolver.resolveURI(url).getInputStream())
100        {
101            byte[] asByteArray = IOUtils.toByteArray(is);
102            
103            if (_xsdValidation(new ByteArrayInputStream(asByteArray)))
104            {
105                AttributesImpl attrs = new AttributesImpl();
106                attrs.addCDATAAttribute("id", linkId);
107                XMLUtils.startElement(contentHandler, "dynamic-information", attrs);
108                _saxInformation(contentHandler, new ByteArrayInputStream(asByteArray));
109                XMLUtils.endElement(contentHandler, "dynamic-information");
110            }
111        }
112    }
113    
114    private String _handleVariable(String url, String siteName, String lang, int nbItems)
115    {
116        int startParamIndex = url.indexOf("?");
117        
118        String convertUrl = url;
119        convertUrl = StringUtils.replace(convertUrl, "${lang}", lang);
120        convertUrl = StringUtils.replace(convertUrl, "${site}", siteName);
121        convertUrl = StringUtils.replace(convertUrl, "${maxItems}", String.valueOf(nbItems));
122        
123        // Resolve configuration parameters
124        Matcher confMatcher = __CONFIG_PARAM_PATTERN.matcher(convertUrl);
125        while (confMatcher.find())
126        {
127            String paramName = confMatcher.group(1);
128            String paramValue = Config.getInstance().getValueAsString(paramName);
129            
130            String encodedValue = confMatcher.start() > startParamIndex ? URLEncoder.encodeParameter(paramValue) : URLEncoder.encodePath(paramValue);
131            convertUrl = StringUtils.replace(convertUrl, confMatcher.group(0), encodedValue);
132        }
133        
134        // Resolve site configuration parameters
135        Matcher siteConfMatcher = __SITE_CONFIG_PARAM_PATTERN.matcher(convertUrl);
136        while (siteConfMatcher.find())
137        {
138            String paramName = confMatcher.group(1);
139            String paramValue = _siteConf.getUntypedValue(siteName, paramName);
140            
141            String encodedValue = siteConfMatcher.start() > startParamIndex ? URLEncoder.encodeParameter(paramValue) : URLEncoder.encodePath(paramValue);
142            convertUrl = StringUtils.replace(convertUrl, siteConfMatcher.group(0), encodedValue);
143        }
144        
145        return convertUrl;
146    }
147
148    /**
149     * Verify if an XML document is an instance of a specified XML schema.
150     * 
151     * @param xml The XML feed.
152     * @return True if XML verify the xml schema and false if not.
153     */
154    private boolean _xsdValidation(InputStream xml)
155    {
156        try
157        {
158            SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
159            URL schemaURL = getClass().getResource("dynamic-information.xsd");
160            Schema schema = factory.newSchema(schemaURL);
161            Validator validator = schema.newValidator();
162            validator.validate(new StreamSource(xml));
163            return true;
164        }
165        catch (Exception e)
166        {
167            getLogger().error("Unable to validate XSD for '" + _label + "' (" + _id + ") information at '" + _url + "' url.", e);
168            return false;
169        }
170    }
171
172    /**
173     * Sax xml feed returned by URL provider.
174     * @param contentHandler The content handler to sax into
175     * @param xml The xml feed.
176     * @throws Exception if exception error occurs.
177     */
178    private void _saxInformation(ContentHandler contentHandler, InputStream xml) throws Exception
179    {
180        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
181        saxFactory.setNamespaceAware(true);
182        XMLReader reader = saxFactory.newSAXParser().getXMLReader();
183        DynamicInformationHandler dynamicInfoHandler = new DynamicInformationHandler(contentHandler);
184        reader.setContentHandler(dynamicInfoHandler);
185        reader.parse(new InputSource(xml));
186    }
187
188    /**
189     * Get the url of the WebService
190     * @return url of the WebService
191     */
192    public String getUrl()
193    {
194        return _url;
195    }
196
197    @Override
198    public String getId()
199    {
200        return _id;
201    }
202
203    @Override
204    public I18nizableText getLabel()
205    {
206        return _label;
207    }
208
209}