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 '{}' ({}) information at '{}' url.", _label, _id, _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}