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 */ 016package org.ametys.plugins.externaldata.transformation; 017 018import java.util.Collection; 019import java.util.HashMap; 020import java.util.Map; 021 022import org.apache.avalon.framework.component.Component; 023import org.apache.avalon.framework.context.Context; 024import org.apache.avalon.framework.context.ContextException; 025import org.apache.avalon.framework.context.Contextualizable; 026import org.apache.avalon.framework.logger.Logger; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.avalon.framework.service.Serviceable; 030import org.apache.cocoon.components.ContextHelper; 031import org.apache.cocoon.environment.Request; 032import org.apache.cocoon.xml.AttributesImpl; 033import org.apache.cocoon.xml.XMLUtils; 034import org.apache.commons.lang.StringUtils; 035import org.xml.sax.Attributes; 036import org.xml.sax.SAXException; 037 038import org.ametys.cms.repository.Content; 039import org.ametys.cms.transformation.AbstractEnhancementHandler; 040import org.ametys.plugins.externaldata.data.DataInclusionException; 041import org.ametys.plugins.externaldata.data.DataSourceFactory; 042import org.ametys.plugins.externaldata.data.DataSourceFactoryExtensionPoint; 043import org.ametys.plugins.externaldata.data.Query; 044import org.ametys.plugins.externaldata.data.Query.ResultType; 045import org.ametys.plugins.externaldata.data.QueryDao; 046import org.ametys.plugins.externaldata.data.QueryResult; 047import org.ametys.plugins.externaldata.data.QueryResultRow; 048import org.ametys.runtime.i18n.I18nizableText; 049import org.ametys.runtime.plugin.component.PluginAware; 050import org.ametys.web.URIPrefixHandler; 051import org.ametys.web.renderingcontext.RenderingContext; 052import org.ametys.web.renderingcontext.RenderingContextHandler; 053import org.ametys.web.repository.content.WebContent; 054 055/** 056 * DataInclusion enhancement handler : transform a query tag into its results. 057 */ 058public class DataInclusionEnhancementHandler extends AbstractEnhancementHandler implements Component, Serviceable, Contextualizable, PluginAware 059{ 060 /** The Avalon role. */ 061 public static final String ROLE = DataInclusionEnhancementHandler.class.getName(); 062 063 private static final String _QUERY_TAG = "dataquery"; 064 065 private static final String _ID_ATTR = "id"; 066 067 private static final String _PARAMETER_PREFIX = "param-"; 068 069 /** The Query DAO. */ 070 protected QueryDao _queryDao; 071 072 /** The avalon context. */ 073 protected Context _context; 074 075 /** The plugin name. */ 076 protected String _pluginName; 077 078 /** The logger */ 079 protected Logger _logger; 080 081 private RenderingContextHandler _renderingContextHandler; 082 private URIPrefixHandler _prefixHandler; 083 084 private DataSourceFactoryExtensionPoint _dataSourceFactoryEP; 085 086 @Override 087 public void service(ServiceManager serviceManager) throws ServiceException 088 { 089 _queryDao = (QueryDao) serviceManager.lookup(QueryDao.ROLE); 090 _dataSourceFactoryEP = (DataSourceFactoryExtensionPoint) serviceManager.lookup(DataSourceFactoryExtensionPoint.ROLE); 091 _renderingContextHandler = (RenderingContextHandler) serviceManager.lookup(RenderingContextHandler.ROLE); 092 _prefixHandler = (URIPrefixHandler) serviceManager.lookup(URIPrefixHandler.ROLE); 093 } 094 095 @Override 096 public void contextualize(Context context) throws ContextException 097 { 098 _context = context; 099 } 100 101 @Override 102 public void setPluginInfo(String pluginName, String featureName, String id) 103 { 104 _pluginName = pluginName; 105 } 106 107 @Override 108 public void enableLogging(final Logger logger) 109 { 110 _logger = logger; 111 } 112 113 @Override 114 public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException 115 { 116 if (_QUERY_TAG.equals(localName)) 117 { 118 Request request = ContextHelper.getRequest(_context); 119 WebContent content = (WebContent) request.getAttribute(Content.class.getName()); 120 String contentTitle = content.getTitle(); 121 String id = atts.getValue(_ID_ATTR); 122 123 String siteName = (String) ContextHelper.getRequest(_context).getAttribute("site"); 124 if (siteName == null) 125 { 126 siteName = content.getSiteName(); 127 } 128 129 try 130 { 131 if (StringUtils.isNotBlank(id)) 132 { 133 Query query = _queryDao.getQuery(siteName, id); 134 if (query != null) 135 { 136 Map<String, String> parameters = getParameters(query, atts); 137 138 String factoryId = query.getFactory(); 139 DataSourceFactory<Query, QueryResult> dsFactory = _dataSourceFactoryEP.getExtension(factoryId); 140 QueryResult result = dsFactory.execute(query, parameters); 141 142 if (result != null) 143 { 144 if (query.getResultType().equals(ResultType.MULTIPLE)) 145 { 146 _saxResult(query, result); 147 } 148 else 149 { 150 _saxSingleResult(query, result); 151 } 152 } 153 } 154 else 155 { 156 _logger.error("The query of id '" + id + "' is included in the content of title '" + contentTitle + "' but was deleted."); 157 158 RenderingContext currentContext = _renderingContextHandler.getRenderingContext(); 159 if (currentContext == RenderingContext.BACK) 160 { 161 String iconPath = _prefixHandler.getUriPrefix() + "/plugins/" + _pluginName + "/resources/img/content/edition/data-error.png"; 162 saxError(new I18nizableText("plugin." + _pluginName, "PLUGINS_EXTERNAL_DATA_ERROR_TITLE"), new I18nizableText("plugin." + _pluginName, "PLUGINS_EXTERNAL_DATA_ERROR_NONEXISTING_QUERY"), iconPath); 163 } 164 } 165 } 166 else 167 { 168 _logger.error("Query ID must be provided."); 169 RenderingContext currentContext = _renderingContextHandler.getRenderingContext(); 170 if (currentContext == RenderingContext.BACK) 171 { 172 String iconPath = _prefixHandler.getUriPrefix() + "/plugins/" + _pluginName + "/resources/img/content/edition/data-error.png"; 173 saxError(new I18nizableText("plugin." + _pluginName, "PLUGINS_EXTERNAL_DATA_ERROR_TITLE"), new I18nizableText("plugin." + _pluginName, "PLUGINS_EXTERNAL_DATA_ERROR_NO_ID"), iconPath); 174 } 175 } 176 } 177 catch (DataInclusionException e) 178 { 179 _logger.error("Error executing the query " + id, e); 180 RenderingContext currentContext = _renderingContextHandler.getRenderingContext(); 181 if (currentContext == RenderingContext.BACK) 182 { 183 String iconPath = _prefixHandler.getUriPrefix() + "/plugins/" + _pluginName + "/resources/img/content/edition/data-error.png"; 184 saxError(new I18nizableText("plugin." + _pluginName, "PLUGINS_EXTERNAL_DATA_ERROR_TITLE"), new I18nizableText("plugin." + _pluginName, "PLUGINS_EXTERNAL_DATA_QUERY_EXECUTION_ERROR"), iconPath); 185 } 186 } 187 } 188 else 189 { 190 super.startElement(uri, localName, qName, atts); 191 } 192 } 193 194 @Override 195 public void endElement(String uri, String localName, String qName) throws SAXException 196 { 197 if (!_QUERY_TAG.equals(localName)) 198 { 199 super.endElement(uri, localName, qName); 200 } 201 } 202 203 /** 204 * Sax a query result. 205 * @param query the query. 206 * @param result the result to generate. 207 * @throws SAXException if an error occurs while saxing 208 */ 209 protected void _saxResult(Query query, QueryResult result) throws SAXException 210 { 211 String queryId = query.getId(); 212 try 213 { 214 // Others enhancement handlers should not touch the generated HTML 215 _contentHandler.processingInstruction("ametys-unmodifiable", "start"); 216 217 AttributesImpl atts = new AttributesImpl(); 218 atts.addCDATAAttribute("class", "data " + queryId); 219 XMLUtils.startElement(_contentHandler, "table", atts); 220 Collection<String> colNames = result.getColumnNames(); 221 222 if (!colNames.isEmpty()) 223 { 224 XMLUtils.startElement(_contentHandler, "tr"); 225 for (String columnName : colNames) 226 { 227 XMLUtils.createElement(_contentHandler, "th", columnName); 228 } 229 XMLUtils.endElement(_contentHandler, "tr"); 230 } 231 232 int index = 0; 233 for (QueryResultRow row : result) 234 { 235 AttributesImpl attr = new AttributesImpl(); 236 if (index % 2 != 0) 237 { 238 attr.addAttribute("", "class", "class", "CDATA", "odd"); 239 } 240 else 241 { 242 attr.addAttribute("", "class", "class", "CDATA", "even"); 243 } 244 XMLUtils.startElement(_contentHandler, "tr", attr); 245 246 for (String columnName : colNames) 247 { 248 String value = row.get(columnName); 249 XMLUtils.createElement(_contentHandler, "td", StringUtils.defaultString(value)); 250 } 251 XMLUtils.endElement(_contentHandler, "tr"); 252 index++; 253 } 254 255 XMLUtils.endElement(_contentHandler, "table"); 256 257 _contentHandler.processingInstruction("ametys-unmodifiable", "end"); 258 } 259 catch (DataInclusionException e) 260 { 261 String message = "Unable to get the query results (query ID : " + queryId + ")"; 262 _logger.error(message, e); 263 throw new SAXException(message, e); 264 } 265 finally 266 { 267 result.close(); 268 } 269 } 270 271 /** 272 * Sax a single query result. 273 * @param query the query 274 * @param result the query's result 275 * @throws SAXException if an error occurs while saxing 276 */ 277 protected void _saxSingleResult(Query query, QueryResult result) throws SAXException 278 { 279 String queryId = query.getId(); 280 try 281 { 282 Collection<String> colNames = result.getColumnNames(); 283 if (colNames.size() > 0) 284 { 285 String colName = colNames.iterator().next(); 286 287 if (result.iterator().hasNext()) 288 { 289 QueryResultRow row = result.iterator().next(); 290 291 String value = row.get(colName); 292 293 if (StringUtils.isNotBlank(value)) 294 { 295 AttributesImpl atts = new AttributesImpl(); 296 atts.addCDATAAttribute("class", "query " + queryId); 297 298 XMLUtils.createElement(_contentHandler, "span", atts, value); 299 } 300 } 301 } 302 } 303 catch (DataInclusionException e) 304 { 305 String message = "Unable to get the query results (query ID : " + queryId + ")"; 306 _logger.error(message, e); 307 throw new SAXException(message, e); 308 } 309 finally 310 { 311 result.close(); 312 } 313 } 314 315 /** 316 * Get the query parameter values from the query node attributes. 317 * @param query the query. 318 * @param atts the query node attributes. 319 * @return the parameters as a Map of parameter name -> value. 320 */ 321 protected Map<String, String> getParameters(Query query, Attributes atts) 322 { 323 Map<String, String> params = new HashMap<>(); 324 325 for (String param : query.getParameters().keySet()) 326 { 327 String value = atts.getValue(_PARAMETER_PREFIX + param.toLowerCase()); 328 if (StringUtils.isNotBlank(value)) 329 { 330 params.put(param, value); 331 } 332 } 333 334 return params; 335 } 336 337}