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}