001/*
002 *  Copyright 2016 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.data;
017
018import java.io.IOException;
019import java.util.Collection;
020import java.util.Enumeration;
021import java.util.HashMap;
022import java.util.Map;
023import java.util.Map.Entry;
024
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.cocoon.ProcessingException;
028import org.apache.cocoon.environment.ObjectModelHelper;
029import org.apache.cocoon.environment.Request;
030import org.apache.cocoon.generation.ServiceableGenerator;
031import org.apache.cocoon.xml.AttributesImpl;
032import org.apache.cocoon.xml.XMLUtils;
033import org.apache.commons.lang.StringUtils;
034import org.xml.sax.SAXException;
035
036import org.ametys.web.repository.page.Page;
037import org.ametys.web.repository.page.ZoneItem;
038
039/**
040 * Generator for external data search service
041 *
042 */
043public class ExternalSearchGenerator extends ServiceableGenerator
044{
045    private static final String __SEARCH_CRITERIA_PREFIX = "external-data-search-";
046    
047    /** The Query DAO. */
048    protected QueryDao _queryDao;
049    /** Datasource factory handler */
050    protected DataSourceFactoryExtensionPoint _dataSourceFactoryEP;
051    
052    @Override
053    public void service(ServiceManager smanager) throws ServiceException
054    {
055        super.service(smanager);
056        _queryDao = (QueryDao) smanager.lookup(QueryDao.ROLE);
057        _dataSourceFactoryEP = (DataSourceFactoryExtensionPoint) smanager.lookup(DataSourceFactoryExtensionPoint.ROLE);
058    }
059    
060    @Override
061    public void generate() throws IOException, SAXException, ProcessingException
062    {
063        Request request = ObjectModelHelper.getRequest(objectModel);
064        
065        String currentSiteName = null;
066        String lang = null;
067        Page page = (Page) request.getAttribute(Page.class.getName());
068        if (page != null)
069        {
070            currentSiteName = page.getSiteName();
071            lang = page.getSitemapName();
072        }
073        
074        String siteName = (String) request.getAttribute("site");
075        ZoneItem zoneItem = (ZoneItem) request.getAttribute(ZoneItem.class.getName());
076        
077        String idQuery = zoneItem.getServiceParameters().getValue("datasource-query");
078        int limit = Math.toIntExact(zoneItem.getServiceParameters().getValue("limit"));
079        int pageNum = getPageIndex(request);
080        
081        contentHandler.startDocument();
082        XMLUtils.startElement(contentHandler, "search");
083        
084        // The search url
085        XMLUtils.createElement(contentHandler, "url", page != null ? lang + "/" + page.getPathInSitemap() + ".html" : lang + "/_plugins/" + currentSiteName + "/" + lang + "/service/search.html");
086
087        try
088        {
089            Query query = _queryDao.getQuery(siteName, idQuery);
090            Map<String, String> parameterValues = getParameterValues(request, query);
091            
092            saxFormParameters (request, query, parameterValues);
093            
094            boolean submit = request.getParameter("external-data-search-submit") != null;
095            if (submit)
096            {
097                
098                XMLUtils.startElement(contentHandler, "search-result");
099                executeQuery(query, parameterValues, limit, pageNum);
100                XMLUtils.endElement(contentHandler, "search-result");
101            }
102            
103        }
104        catch (DataInclusionException e)
105        {
106            getLogger().error("An error occurred : impossible to get query with id : " + idQuery, e);
107        }
108        XMLUtils.endElement(contentHandler, "search");
109        contentHandler.endDocument();
110    }
111    
112    /**
113     * Get the search criteria values from request
114     * @param request The request
115     * @param query The LDAP query
116     * @return the values
117     */
118    protected Map<String, String> getParameterValues (Request request, Query query)
119    {
120        Map<String, String> paramValues = new HashMap<>();
121        
122        Map<String, String> parametersName = query.getParameters();
123        for (String paramName : parametersName.keySet())
124        {
125            String value = request.getParameter(__SEARCH_CRITERIA_PREFIX + paramName); 
126            if (StringUtils.isNotBlank(value))
127            {
128                paramValues.put(paramName, value);
129            }
130        }
131        return paramValues;
132    }
133    
134    /**
135     * SAX the search parameters from the request parameters and query
136     * @param request The request
137     * @param query The LDAP query
138     * @param values The extracted form values
139     * @throws SAXException If an error occurs while SAXing
140     */
141    protected void saxFormParameters (Request request, Query query, Map<String, String> values)  throws SAXException
142    {
143        XMLUtils.startElement(contentHandler, "form");
144        
145        XMLUtils.startElement(contentHandler, "fields");
146        saxFormFields (query);
147        XMLUtils.endElement(contentHandler, "fields");
148        
149        boolean submit = request.getParameter("external-data-search-submit") != null;
150        if (submit)
151        {
152            XMLUtils.startElement(contentHandler, "values");
153            saxFormValues(request, values);
154            XMLUtils.endElement(contentHandler, "values");
155        }
156        
157        XMLUtils.endElement(contentHandler, "form");
158    }
159    
160    /**
161     * SAX the form search criteria
162     * @param query The LDAP query
163     * @throws SAXException if an error occurs while SAXing
164     */
165    protected void saxFormFields (Query query) throws SAXException
166    {
167        Map<String, String> parametersName = query.getParameters();
168        for (Entry<String, String> entry : parametersName.entrySet())
169        {
170            String parameterName = entry.getKey();
171            String paramaterLabel = entry.getValue();
172            AttributesImpl att = new AttributesImpl();
173            att.addCDATAAttribute("id", parameterName);
174
175            XMLUtils.createElement(contentHandler, "field", att, paramaterLabel);
176        }
177    }
178    
179    /**
180     * SAX the form search criteria values
181     * @param request The request
182     * @param values The extracted form values
183     * @throws SAXException if an error occurs while SAXing
184     */
185    protected void saxFormValues (Request request, Map<String, String> values) throws SAXException
186    {
187        for (Entry<String, String> entry : values.entrySet())
188        {
189            String paramName = entry.getKey();
190            String paramValue = entry.getValue();
191            
192            XMLUtils.createElement(contentHandler, paramName, paramValue);
193        }
194    }
195    
196    /** 
197     * Execute ldap query
198     * @param query the ldap query
199     * @param parameterValues the search criteria
200     * @param nbResultsPerPage the number of result per page
201     * @param page the page number
202     * @throws DataInclusionException if an error occurred
203     * @throws SAXException If an error occurred
204     */
205    protected void executeQuery(Query query, Map<String, String> parameterValues, int nbResultsPerPage, int page) throws DataInclusionException, SAXException
206    {
207        String factoryId = query.getFactory();
208        DataSourceFactory<Query, QueryResult> dsFactory = _dataSourceFactoryEP.getExtension(factoryId);
209        
210        int offset = (page - 1) * nbResultsPerPage;
211        
212        QueryResult result = dsFactory.execute(query, parameterValues, offset, nbResultsPerPage);
213        
214        saxResult(query, result);
215        
216        boolean hasMoreElmts = false;
217        int nbResults = result.getSize();
218        if (nbResults >= nbResultsPerPage)
219        {
220            // Test if there has more elements
221            hasMoreElmts = dsFactory.execute(query, parameterValues, page * nbResultsPerPage, 1).getSize() > 0;
222        }
223        
224        saxPagination(page, offset, nbResultsPerPage, result.getSize(), hasMoreElmts);
225    }
226    
227    /**
228     * SAX the pagination
229     * @param page the current page
230     * @param offset the offset
231     * @param limit the number of results per page
232     * @param nbResult the number result of the page
233     * @param hasMoreResult true if there are less one page more
234     * @throws SAXException if an error occurred
235     */
236    protected void saxPagination(int page, int offset, int limit, int nbResult, boolean hasMoreResult) throws SAXException
237    {
238        AttributesImpl atts = new AttributesImpl();
239        atts.addCDATAAttribute("start", String.valueOf(offset));
240        atts.addCDATAAttribute("end", String.valueOf(offset + nbResult));
241        XMLUtils.startElement(contentHandler, "pagination", atts);
242        
243        int nbPage = page;
244        if (hasMoreResult)
245        {
246            nbPage++;
247        }
248        
249        for (int i = 1; i <= nbPage; i++)
250        {
251            AttributesImpl attPage = new AttributesImpl();
252            attPage.addCDATAAttribute("index", String.valueOf(i));
253            attPage.addCDATAAttribute("start", String.valueOf((i - 1) * limit));
254            XMLUtils.createElement(contentHandler, "page", attPage);
255        }
256        
257        XMLUtils.endElement(contentHandler, "pagination");
258    }
259
260    /**
261     * Sax a query result.
262     * @param query the query.
263     * @param result the result to generate.
264     * @throws SAXException if an error occurs while saxing
265     */
266    protected void saxResult(Query query, QueryResult result) throws SAXException
267    {
268        String queryId = query.getId();
269        try
270        {
271            // Others enhancement handlers should not touch the generated HTML
272            contentHandler.processingInstruction("ametys-unmodifiable", "start");
273            
274            AttributesImpl atts = new AttributesImpl();
275            atts.addCDATAAttribute("class", "data " + queryId);
276            XMLUtils.startElement(contentHandler, "table", atts);
277            Collection<String> colNames = result.getColumnNames();
278            
279            if (!colNames.isEmpty())
280            {
281                XMLUtils.startElement(contentHandler, "tr");
282                for (String columnName : colNames)
283                {
284                    XMLUtils.createElement(contentHandler, "th", columnName);
285                }
286                XMLUtils.endElement(contentHandler, "tr");
287            }
288            
289            int index = 0;
290            for (QueryResultRow row : result)
291            {
292                AttributesImpl attr = new AttributesImpl();
293                if (index % 2 != 0)
294                {
295                    attr.addAttribute("", "class", "class", "CDATA", "odd");
296                }
297                else
298                {
299                    attr.addAttribute("", "class", "class", "CDATA", "even");
300                }
301                XMLUtils.startElement(contentHandler, "tr", attr);
302                
303                for (String columnName : colNames)
304                {
305                    String value = row.get(columnName);
306                    XMLUtils.createElement(contentHandler, "td", StringUtils.defaultString(value));
307                }
308                XMLUtils.endElement(contentHandler, "tr");
309                index++;
310            }
311            
312            XMLUtils.endElement(contentHandler, "table");
313            
314            contentHandler.processingInstruction("ametys-unmodifiable", "end");
315        }
316        catch (DataInclusionException e)
317        {
318            String message = "Unable to get the query results (query ID : " + queryId + ")";
319            getLogger().error(message, e);
320            throw new SAXException(message, e);
321        }
322        finally
323        {
324            result.close();
325        }
326    }
327    /**
328     * Get the page index
329     * @param request The request
330     * @return The page index
331     */
332    protected int getPageIndex (Request request)
333    {
334        Enumeration paramNames = request.getParameterNames();
335        while (paramNames.hasMoreElements())
336        {
337            String param = (String) paramNames.nextElement();
338            if (param.startsWith("page-"))
339            {
340                return Integer.parseInt(param.substring("page-".length()));
341            }
342        }
343        return 1;
344    }
345}