001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.management.mbean; 018 019import java.io.ByteArrayOutputStream; 020import java.io.ObjectOutputStream; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024 025import org.apache.camel.CamelContext; 026import org.apache.camel.Exchange; 027import org.apache.camel.ExchangePropertyKey; 028import org.apache.camel.Expression; 029import org.apache.camel.MessageHistory; 030import org.apache.camel.NoTypeConversionAvailableException; 031import org.apache.camel.Predicate; 032import org.apache.camel.Route; 033import org.apache.camel.RuntimeCamelException; 034import org.apache.camel.api.management.ManagedResource; 035import org.apache.camel.api.management.mbean.ManagedBacklogDebuggerMBean; 036import org.apache.camel.impl.debugger.BacklogDebugger; 037import org.apache.camel.spi.BacklogTracerEventMessage; 038import org.apache.camel.spi.Language; 039import org.apache.camel.spi.ManagementStrategy; 040import org.apache.camel.support.LoggerHelper; 041import org.apache.camel.util.ObjectHelper; 042import org.apache.camel.util.StringHelper; 043import org.apache.camel.util.TimeUtils; 044import org.apache.camel.util.URISupport; 045 046@ManagedResource(description = "Managed BacklogDebugger") 047public class ManagedBacklogDebugger implements ManagedBacklogDebuggerMBean { 048 049 private final CamelContext camelContext; 050 private final BacklogDebugger backlogDebugger; 051 052 public ManagedBacklogDebugger(CamelContext camelContext, BacklogDebugger backlogDebugger) { 053 this.camelContext = camelContext; 054 this.backlogDebugger = backlogDebugger; 055 } 056 057 public void init(ManagementStrategy strategy) { 058 // do nothing 059 } 060 061 public CamelContext getContext() { 062 return camelContext; 063 } 064 065 public BacklogDebugger getBacklogDebugger() { 066 return backlogDebugger; 067 } 068 069 @Override 070 public String getCamelId() { 071 return camelContext.getName(); 072 } 073 074 @Override 075 public String getCamelManagementName() { 076 return camelContext.getManagementName(); 077 } 078 079 @Override 080 public String getLoggingLevel() { 081 return backlogDebugger.getLoggingLevel(); 082 } 083 084 @Override 085 public void setLoggingLevel(String level) { 086 backlogDebugger.setLoggingLevel(level); 087 } 088 089 @Override 090 public boolean isEnabled() { 091 return backlogDebugger.isEnabled(); 092 } 093 094 @Override 095 public void enableDebugger() { 096 backlogDebugger.enableDebugger(); 097 } 098 099 @Override 100 public void disableDebugger() { 101 backlogDebugger.disableDebugger(); 102 } 103 104 @Override 105 public void addBreakpoint(String nodeId) { 106 backlogDebugger.addBreakpoint(nodeId); 107 } 108 109 @Override 110 public void addConditionalBreakpoint(String nodeId, String language, String predicate) { 111 backlogDebugger.addConditionalBreakpoint(nodeId, language, predicate); 112 } 113 114 @Override 115 public void removeBreakpoint(String nodeId) { 116 backlogDebugger.removeBreakpoint(nodeId); 117 } 118 119 @Override 120 public void removeAllBreakpoints() { 121 backlogDebugger.removeAllBreakpoints(); 122 } 123 124 @Override 125 public Set<String> breakpoints() { 126 return backlogDebugger.getBreakpoints(); 127 } 128 129 @Override 130 public void resumeBreakpoint(String nodeId) { 131 backlogDebugger.resumeBreakpoint(nodeId); 132 } 133 134 @Override 135 public void setMessageBodyOnBreakpoint(String nodeId, Object body) { 136 backlogDebugger.setMessageBodyOnBreakpoint(nodeId, body); 137 } 138 139 @Override 140 public void setMessageBodyOnBreakpoint(String nodeId, Object body, String type) { 141 try { 142 Class<?> classType = camelContext.getClassResolver().resolveMandatoryClass(type); 143 backlogDebugger.setMessageBodyOnBreakpoint(nodeId, body, classType); 144 } catch (ClassNotFoundException e) { 145 throw RuntimeCamelException.wrapRuntimeCamelException(e); 146 } 147 } 148 149 @Override 150 public void removeMessageBodyOnBreakpoint(String nodeId) { 151 backlogDebugger.removeMessageBodyOnBreakpoint(nodeId); 152 } 153 154 @Override 155 public void setMessageHeaderOnBreakpoint(String nodeId, String headerName, Object value) { 156 try { 157 backlogDebugger.setMessageHeaderOnBreakpoint(nodeId, headerName, value); 158 } catch (NoTypeConversionAvailableException e) { 159 throw RuntimeCamelException.wrapRuntimeCamelException(e); 160 } 161 } 162 163 @Override 164 public void setMessageHeaderOnBreakpoint(String nodeId, String headerName, Object value, String type) { 165 try { 166 Class<?> classType = camelContext.getClassResolver().resolveMandatoryClass(type); 167 backlogDebugger.setMessageHeaderOnBreakpoint(nodeId, headerName, value, classType); 168 } catch (Exception e) { 169 throw RuntimeCamelException.wrapRuntimeCamelException(e); 170 } 171 } 172 173 @Override 174 public void removeMessageHeaderOnBreakpoint(String nodeId, String headerName) { 175 backlogDebugger.removeMessageHeaderOnBreakpoint(nodeId, headerName); 176 } 177 178 @Override 179 public void resumeAll() { 180 backlogDebugger.resumeAll(); 181 } 182 183 @Override 184 public void stepBreakpoint(String nodeId) { 185 backlogDebugger.stepBreakpoint(nodeId); 186 } 187 188 @Override 189 public boolean isSingleStepMode() { 190 return backlogDebugger.isSingleStepMode(); 191 } 192 193 @Override 194 public void step() { 195 backlogDebugger.step(); 196 } 197 198 @Override 199 public Set<String> suspendedBreakpointNodeIds() { 200 return backlogDebugger.getSuspendedBreakpointNodeIds(); 201 } 202 203 @Override 204 public void disableBreakpoint(String nodeId) { 205 backlogDebugger.disableBreakpoint(nodeId); 206 } 207 208 @Override 209 public void enableBreakpoint(String nodeId) { 210 backlogDebugger.enableBreakpoint(nodeId); 211 } 212 213 @Override 214 public int getBodyMaxChars() { 215 return backlogDebugger.getBodyMaxChars(); 216 } 217 218 @Override 219 public void setBodyMaxChars(int bodyMaxChars) { 220 backlogDebugger.setBodyMaxChars(bodyMaxChars); 221 } 222 223 @Override 224 public boolean isBodyIncludeStreams() { 225 return backlogDebugger.isBodyIncludeStreams(); 226 } 227 228 @Override 229 public void setBodyIncludeStreams(boolean bodyIncludeStreams) { 230 backlogDebugger.setBodyIncludeStreams(bodyIncludeStreams); 231 } 232 233 @Override 234 public boolean isBodyIncludeFiles() { 235 return backlogDebugger.isBodyIncludeFiles(); 236 } 237 238 @Override 239 public void setBodyIncludeFiles(boolean bodyIncludeFiles) { 240 backlogDebugger.setBodyIncludeFiles(bodyIncludeFiles); 241 } 242 243 @Override 244 public String dumpTracedMessagesAsXml(String nodeId, boolean includeExchangeProperties) { 245 String messageAsXml = backlogDebugger.dumpTracedMessagesAsXml(nodeId); 246 if (messageAsXml != null && includeExchangeProperties) { 247 String closingTag = "</" + BacklogTracerEventMessage.ROOT_TAG + ">"; 248 String exchangePropertiesAsXml = dumpExchangePropertiesAsXml(nodeId); 249 messageAsXml = messageAsXml.replace(closingTag, exchangePropertiesAsXml) + "\n" + closingTag; 250 } 251 return messageAsXml; 252 } 253 254 @Override 255 public long getDebugCounter() { 256 return backlogDebugger.getDebugCounter(); 257 } 258 259 @Override 260 public void resetDebugCounter() { 261 backlogDebugger.resetDebugCounter(); 262 } 263 264 @Override 265 public String validateConditionalBreakpoint(String language, String predicate) { 266 Language lan = null; 267 try { 268 lan = camelContext.resolveLanguage(language); 269 lan.createPredicate(predicate); 270 return null; 271 } catch (Exception e) { 272 if (lan == null) { 273 return e.getMessage(); 274 } else { 275 return "Invalid syntax " + predicate + " due: " + e.getMessage(); 276 } 277 } 278 } 279 280 @Override 281 public long getFallbackTimeout() { 282 return backlogDebugger.getFallbackTimeout(); 283 } 284 285 @Override 286 public void setFallbackTimeout(long fallbackTimeout) { 287 backlogDebugger.setFallbackTimeout(fallbackTimeout); 288 } 289 290 @Override 291 public String evaluateExpressionAtBreakpoint(String nodeId, String language, String expression) { 292 return evaluateExpressionAtBreakpoint(nodeId, language, expression, "java.lang.String").toString(); 293 } 294 295 @Override 296 public void setExchangePropertyOnBreakpoint(String nodeId, String exchangePropertyName, Object value) { 297 Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId); 298 if (suspendedExchange != null) { 299 suspendedExchange.setProperty(exchangePropertyName, value); 300 } 301 } 302 303 @Override 304 public void removeExchangePropertyOnBreakpoint(String nodeId, String exchangePropertyName) { 305 Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId); 306 if (suspendedExchange != null) { 307 suspendedExchange.removeProperty(exchangePropertyName); 308 } 309 } 310 311 @Override 312 public void setExchangePropertyOnBreakpoint(String nodeId, String exchangePropertyName, Object value, String type) { 313 try { 314 Class<?> classType = camelContext.getClassResolver().resolveMandatoryClass(type); 315 if (type != null) { 316 Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId); 317 if (suspendedExchange != null) { 318 value = suspendedExchange.getContext().getTypeConverter().mandatoryConvertTo(classType, suspendedExchange, 319 value); 320 suspendedExchange.setProperty(exchangePropertyName, value); 321 } 322 } else { 323 this.setExchangePropertyOnBreakpoint(nodeId, exchangePropertyName, value); 324 } 325 } catch (Exception e) { 326 throw RuntimeCamelException.wrapRuntimeCamelException(e); 327 } 328 } 329 330 @Override 331 public Object evaluateExpressionAtBreakpoint(String nodeId, String language, String expression, String resultType) { 332 Exchange suspendedExchange; 333 try { 334 Language lan = camelContext.resolveLanguage(language); 335 suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId); 336 if (suspendedExchange != null) { 337 Object result; 338 Class<?> resultClass = camelContext.getClassResolver().resolveMandatoryClass(resultType); 339 if (!Boolean.class.isAssignableFrom(resultClass)) { 340 Expression expr = lan.createExpression(expression); 341 expr.init(camelContext); 342 result = expr.evaluate(suspendedExchange, resultClass); 343 } else { 344 Predicate pred = lan.createPredicate(expression); 345 pred.init(camelContext); 346 result = pred.matches(suspendedExchange); 347 } 348 //Test if result is serializable 349 if (!isSerializable(result)) { 350 String resultStr = suspendedExchange.getContext().getTypeConverter().tryConvertTo(String.class, result); 351 if (resultStr != null) { 352 result = resultStr; 353 } 354 } 355 return result; 356 } 357 } catch (Exception e) { 358 return e.getMessage(); 359 } 360 return null; 361 } 362 363 @Override 364 public String messageHistoryOnBreakpointAsXml(String nodeId) { 365 StringBuilder messageHistoryBuilder = new StringBuilder(); 366 messageHistoryBuilder.append("<messageHistory>\n"); 367 368 Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId); 369 if (suspendedExchange != null) { 370 List<MessageHistory> list = suspendedExchange.getProperty(ExchangePropertyKey.MESSAGE_HISTORY, List.class); 371 if (list != null) { 372 // add incoming origin of message on the top 373 String routeId = suspendedExchange.getFromRouteId(); 374 Route route = suspendedExchange.getContext().getRoute(routeId); 375 String loc = route != null ? route.getSourceLocationShort() : ""; 376 String id = routeId; 377 String label = ""; 378 if (suspendedExchange.getFromEndpoint() != null) { 379 label = "from[" 380 + URISupport 381 .sanitizeUri( 382 StringHelper.limitLength(suspendedExchange.getFromEndpoint().getEndpointUri(), 100)) 383 + "]"; 384 } 385 386 long elapsed = TimeUtils.elapsedMillisSince(suspendedExchange.getCreated()); 387 388 messageHistoryBuilder 389 .append(" <messageHistoryEntry") 390 .append(" location=\"").append(StringHelper.xmlEncode(loc)).append("\"") 391 .append(" routeId=\"").append(StringHelper.xmlEncode(routeId)).append("\"") 392 .append(" processorId=\"").append(StringHelper.xmlEncode(id)).append("\"") 393 .append(" processor=\"").append(StringHelper.xmlEncode(label)).append("\"") 394 .append(" elapsed=\"").append(elapsed).append("\"") 395 .append("/>\n"); 396 397 for (MessageHistory history : list) { 398 // and then each history 399 loc = LoggerHelper.getLineNumberLoggerName(history.getNode()); 400 if (loc == null) { 401 loc = ""; 402 } 403 routeId = history.getRouteId() != null ? history.getRouteId() : ""; 404 id = history.getNode().getId(); 405 // we need to avoid leak the sensible information here 406 // the sanitizeUri takes a very long time for very long string 407 // and the format cuts this to 408 // 78 characters, anyway. Cut this to 100 characters. This will 409 // give enough space for removing 410 // characters in the sanitizeUri method and will be reasonably 411 // fast 412 label = URISupport.sanitizeUri(StringHelper.limitLength(history.getNode().getLabel(), 100)); 413 elapsed = history.getElapsed(); 414 415 messageHistoryBuilder 416 .append(" <messageHistoryEntry") 417 .append(" location=\"").append(StringHelper.xmlEncode(loc)).append("\"") 418 .append(" routeId=\"").append(StringHelper.xmlEncode(routeId)).append("\"") 419 .append(" processorId=\"").append(StringHelper.xmlEncode(id)).append("\"") 420 .append(" processor=\"").append(StringHelper.xmlEncode(label)).append("\"") 421 .append(" elapsed=\"").append(elapsed).append("\"") 422 .append("/>\n"); 423 } 424 } 425 } 426 messageHistoryBuilder.append("</messageHistory>\n"); 427 return messageHistoryBuilder.toString(); 428 } 429 430 @Override 431 public void attach() { 432 backlogDebugger.attach(); 433 } 434 435 @Override 436 public void detach() { 437 backlogDebugger.detach(); 438 } 439 440 private String dumpExchangePropertiesAsXml(String id) { 441 StringBuilder sb = new StringBuilder(); 442 sb.append(" <exchangeProperties>\n"); 443 Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(id); 444 if (suspendedExchange != null) { 445 Map<String, Object> properties = suspendedExchange.getAllProperties(); 446 properties.forEach((propertyName, propertyValue) -> { 447 String type = ObjectHelper.classCanonicalName(propertyValue); 448 sb.append(" <exchangeProperty name=\"").append(propertyName).append("\""); 449 if (type != null) { 450 sb.append(" type=\"").append(type).append("\""); 451 } 452 sb.append(">"); 453 // dump property value as XML, use Camel type converter to convert 454 // to String 455 if (propertyValue != null) { 456 try { 457 String xml = suspendedExchange.getContext().getTypeConverter().tryConvertTo(String.class, 458 suspendedExchange, propertyValue); 459 if (xml != null) { 460 // must always xml encode 461 sb.append(StringHelper.xmlEncode(xml)); 462 } 463 } catch (Throwable e) { 464 // ignore as the body is for logging purpose 465 } 466 } 467 sb.append("</exchangeProperty>\n"); 468 }); 469 } 470 sb.append(" </exchangeProperties>"); 471 return sb.toString(); 472 } 473 474 private static boolean isSerializable(Object obj) { 475 final ByteArrayOutputStream baos = new ByteArrayOutputStream(512); 476 try (ObjectOutputStream out = new ObjectOutputStream(baos)) { 477 out.writeObject(obj); 478 return true; 479 } catch (Exception e) { 480 return false; 481 } 482 } 483 484}