001/*
002 *  Copyright 2018 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.web.frontoffice.search.requesttime.impl;
017
018import java.util.Map;
019import java.util.Optional;
020import java.util.function.Function;
021import java.util.stream.Stream;
022
023import org.apache.avalon.framework.activity.Initializable;
024import org.apache.avalon.framework.configuration.Configurable;
025import org.apache.avalon.framework.configuration.Configuration;
026import org.apache.avalon.framework.configuration.ConfigurationException;
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.xml.AttributesImpl;
031import org.apache.cocoon.xml.XMLUtils;
032import org.slf4j.Logger;
033import org.xml.sax.ContentHandler;
034
035import org.ametys.cms.search.SearchResults;
036import org.ametys.cms.search.solr.SearcherFactory.Searcher;
037import org.ametys.plugins.repository.AmetysObject;
038import org.ametys.plugins.repository.AmetysObjectResolver;
039import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
040import org.ametys.web.frontoffice.search.SearchService;
041import org.ametys.web.frontoffice.search.requesttime.SearchComponent;
042import org.ametys.web.frontoffice.search.requesttime.SearchComponentArguments;
043import org.ametys.web.frontoffice.search.requesttime.SearchServiceDebugModeHelper;
044import org.ametys.web.frontoffice.search.requesttime.SearchServiceDebugModeHelper.DebugMode;
045import org.ametys.web.repository.page.ZoneItem;
046
047import com.google.gson.Gson;
048import com.google.gson.GsonBuilder;
049import com.google.gson.JsonElement;
050import com.google.gson.JsonParser;
051
052/**
053 * {@link SearchComponent} for debugging.
054 */
055public class DebugSearchComponent implements SearchComponent, Configurable, Serviceable, Initializable
056{
057    private int _part;
058    private AmetysObjectResolver _resolver;
059    private Gson _gson;
060    
061    @Override
062    public void configure(Configuration configuration) throws ConfigurationException
063    {
064        _part = configuration.getChild("part").getValueAsInteger();
065    }
066
067    @Override
068    public void service(ServiceManager manager) throws ServiceException
069    {
070        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
071    }
072    
073    @Override
074    public void initialize() throws Exception
075    {
076        if (_part == 2)
077        {
078            _gson = new GsonBuilder()
079                    .setPrettyPrinting()
080                    .disableHtmlEscaping()
081                    .create();
082        }
083    }
084    
085    @Override
086    public int priority()
087    {
088        return _part == 1
089                ? SEARCH_PRIORITY - 20000
090                : SEARCH_PRIORITY + 20000;
091    }
092
093    @Override
094    public boolean supports(SearchComponentArguments args)
095    {
096        return args.isDebug();
097    }
098    
099    static String appendDebugRequestParameters(String url, SearchComponentArguments args)
100    {
101        if (args.debugMode().orElse(null) == DebugMode.DEBUG_VIEW_AFTER_VALIDATE)
102        {
103            StringBuilder modifiedUrl = new StringBuilder(url)
104                    .append(url.contains("?") ? "" : "?")
105                    .append("&cocoon-view=service.debug")
106                    .append("&").append(SearchServiceDebugModeHelper.DEBUG_MODE).append("=").append(DebugMode.NORMAL.getInt());
107            return modifiedUrl.toString();
108        }
109        else
110        {
111            return url;
112        }
113    }
114
115    @Override
116    public void execute(SearchComponentArguments args) throws Exception
117    {
118        if (_part == 1)
119        {
120            _executePart1(args);
121        }
122        else
123        {
124            _executePart2(args);
125        }
126    }
127    
128    private void _executePart1(SearchComponentArguments args)
129    {
130        _setSolrDebug(args.searcher());
131    }
132    
133    private void _executePart2(SearchComponentArguments args) throws Exception
134    {
135        if (_mustSaxDebug(args))
136        {
137            _saxDebug(args);
138        }
139    }
140    
141    private void _setSolrDebug(Searcher searcher)
142    {
143        searcher.setDebugOn();
144    }
145    
146    private boolean _mustSaxDebug(SearchComponentArguments args)
147    {
148        return args.debugMode().get() == DebugMode.NORMAL;
149    }
150    
151    private void _saxDebug(SearchComponentArguments args) throws Exception
152    {
153        Logger logger = args.logger();
154        ContentHandler contentHandler = args.contentHandler();
155        SearchService service = args.service();
156        String id = args.serviceInstance().getId();
157        ZoneItem zoneItem = _resolver.resolveById(id);
158        ModelAwareDataHolder serviceParameters = zoneItem.getServiceParameters();
159        XMLUtils.startElement(contentHandler, "debug");
160        _saxServiceParameters(logger, contentHandler, service, serviceParameters);
161        _saxUserCriteria(contentHandler, args.userInputs().criteria());
162        _saxDebugMap(contentHandler, args.results());
163        XMLUtils.endElement(contentHandler, "debug");
164    }
165    
166    private void _saxServiceParameters(Logger logger, ContentHandler contentHandler, SearchService service, ModelAwareDataHolder serviceParameters) throws Exception
167    {
168        XMLUtils.startElement(contentHandler, "parameters");
169        XMLUtils.data(contentHandler, "The valued parameters are:");
170        
171        for (String serviceParam : service.getParameters().keySet())
172        {
173            if (!serviceParameters.hasValue(serviceParam))
174            {
175                logger.warn("No value stored for service parameter with name '{}'", serviceParam);
176                continue;
177            }
178            
179            XMLUtils.startElement(contentHandler, "parameter");
180            XMLUtils.createElement(contentHandler, "name", serviceParam);
181            
182            boolean isMultiple = serviceParameters.isMultiple(serviceParam); 
183            if (isMultiple)
184            {
185                Object[] values = serviceParameters.getValue(serviceParam);
186                String[] strVals = Stream.of(values)
187                        .map(Object::toString)
188                        .toArray(String[]::new);
189                AttributesImpl atts = new AttributesImpl();
190                atts.addCDATAAttribute("multiple", "true");
191                XMLUtils.startElement(contentHandler, "value", atts);
192                for (int i = 0; i < strVals.length; i++)
193                {
194                    String val = _prettifyJson(strVals[i], logger);
195                    XMLUtils.createElement(contentHandler, String.valueOf(i), val);
196                }
197                XMLUtils.endElement(contentHandler, "value");
198            }
199            else
200            {
201                Object value = serviceParameters.getValue(serviceParam);
202                String strVal = _prettifyJson(value.toString(), logger);
203                XMLUtils.createElement(contentHandler, "value", strVal);
204            }
205            
206            XMLUtils.endElement(contentHandler, "parameter");
207        }
208        
209        XMLUtils.endElement(contentHandler, "parameters");
210    }
211    
212    private String _prettifyJson(String input, Logger logger)
213    {
214        if (_seemsLikeJson(input))
215        {
216            JsonParser parser = new JsonParser();
217            try
218            {
219                JsonElement parsedElement = parser.parse(input);
220                String prettyJson = _gson.toJson(parsedElement);
221                return prettyJson.contains("\n") ? "\n" + prettyJson + "\n" : prettyJson;
222            }
223            catch (Exception e)
224            {
225                logger.warn("Cannot jsonify input '{}'", input, e);
226            }
227        }
228        return input;
229    }
230    
231    private boolean _seemsLikeJson(String input)
232    {
233        return input.startsWith("{") && input.endsWith("}");
234    }
235    
236    private void _saxUserCriteria(ContentHandler contentHandler, Map<String, Object> userCriteria) throws Exception
237    {
238        XMLUtils.startElement(contentHandler, "userInputCriteria");
239        for (String userInputKey : userCriteria.keySet())
240        {
241            XMLUtils.startElement(contentHandler, "userInputCriterion");
242            XMLUtils.createElement(contentHandler, "name", userInputKey);
243            XMLUtils.createElement(contentHandler, "value", userCriteria.get(userInputKey).toString());
244            XMLUtils.endElement(contentHandler, "userInputCriterion");
245        }
246        XMLUtils.endElement(contentHandler, "userInputCriteria");
247    }
248    
249    private void _saxDebugMap(ContentHandler contentHandler, Optional<SearchResults<AmetysObject>> results) throws Exception
250    {
251        Optional<Map<String, Object>> debugMap = results
252            .map(SearchResults::getDebugMap)
253            .flatMap(Function.identity());
254        
255        if (debugMap.isPresent())
256        {
257            String prettyMap = "\n" + _gson.toJson(debugMap.get()) + "\n";
258            XMLUtils.createElement(contentHandler, "debugMap", prettyMap);
259        }
260    }
261}