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 -&gt; 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}