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}