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