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.BacklogTracerEventMessage; 036import org.apache.camel.api.management.mbean.ManagedBacklogDebuggerMBean; 037import org.apache.camel.impl.debugger.BacklogDebugger; 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.StopWatch; 043import org.apache.camel.util.StringHelper; 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> getBreakpoints() { 126 return breakpoints(); 127 } 128 129 @Override 130 public Set<String> breakpoints() { 131 return backlogDebugger.getBreakpoints(); 132 } 133 134 @Override 135 public void resumeBreakpoint(String nodeId) { 136 backlogDebugger.resumeBreakpoint(nodeId); 137 } 138 139 @Override 140 public void setMessageBodyOnBreakpoint(String nodeId, Object body) { 141 backlogDebugger.setMessageBodyOnBreakpoint(nodeId, body); 142 } 143 144 @Override 145 public void setMessageBodyOnBreakpoint(String nodeId, Object body, String type) { 146 try { 147 Class<?> classType = camelContext.getClassResolver().resolveMandatoryClass(type); 148 backlogDebugger.setMessageBodyOnBreakpoint(nodeId, body, classType); 149 } catch (ClassNotFoundException e) { 150 throw RuntimeCamelException.wrapRuntimeCamelException(e); 151 } 152 } 153 154 @Override 155 public void removeMessageBodyOnBreakpoint(String nodeId) { 156 backlogDebugger.removeMessageBodyOnBreakpoint(nodeId); 157 } 158 159 @Override 160 public void setMessageHeaderOnBreakpoint(String nodeId, String headerName, Object value) { 161 try { 162 backlogDebugger.setMessageHeaderOnBreakpoint(nodeId, headerName, value); 163 } catch (NoTypeConversionAvailableException e) { 164 throw RuntimeCamelException.wrapRuntimeCamelException(e); 165 } 166 } 167 168 @Override 169 public void setMessageHeaderOnBreakpoint(String nodeId, String headerName, Object value, String type) { 170 try { 171 Class<?> classType = camelContext.getClassResolver().resolveMandatoryClass(type); 172 backlogDebugger.setMessageHeaderOnBreakpoint(nodeId, headerName, value, classType); 173 } catch (Exception e) { 174 throw RuntimeCamelException.wrapRuntimeCamelException(e); 175 } 176 } 177 178 @Override 179 public void removeMessageHeaderOnBreakpoint(String nodeId, String headerName) { 180 backlogDebugger.removeMessageHeaderOnBreakpoint(nodeId, headerName); 181 } 182 183 @Override 184 public void resumeAll() { 185 backlogDebugger.resumeAll(); 186 } 187 188 @Override 189 public void stepBreakpoint(String nodeId) { 190 backlogDebugger.stepBreakpoint(nodeId); 191 } 192 193 @Override 194 public boolean isSingleStepMode() { 195 return backlogDebugger.isSingleStepMode(); 196 } 197 198 @Override 199 public void step() { 200 backlogDebugger.step(); 201 } 202 203 @Override 204 public Set<String> getSuspendedBreakpointNodeIds() { 205 return suspendedBreakpointNodeIds(); 206 } 207 208 @Override 209 public Set<String> suspendedBreakpointNodeIds() { 210 return backlogDebugger.getSuspendedBreakpointNodeIds(); 211 } 212 213 @Override 214 public void disableBreakpoint(String nodeId) { 215 backlogDebugger.disableBreakpoint(nodeId); 216 } 217 218 @Override 219 public void enableBreakpoint(String nodeId) { 220 backlogDebugger.enableBreakpoint(nodeId); 221 } 222 223 @Override 224 public int getBodyMaxChars() { 225 return backlogDebugger.getBodyMaxChars(); 226 } 227 228 @Override 229 public void setBodyMaxChars(int bodyMaxChars) { 230 backlogDebugger.setBodyMaxChars(bodyMaxChars); 231 } 232 233 @Override 234 public boolean isBodyIncludeStreams() { 235 return backlogDebugger.isBodyIncludeStreams(); 236 } 237 238 @Override 239 public void setBodyIncludeStreams(boolean bodyIncludeStreams) { 240 backlogDebugger.setBodyIncludeStreams(bodyIncludeStreams); 241 } 242 243 @Override 244 public boolean isBodyIncludeFiles() { 245 return backlogDebugger.isBodyIncludeFiles(); 246 } 247 248 @Override 249 public void setBodyIncludeFiles(boolean bodyIncludeFiles) { 250 backlogDebugger.setBodyIncludeFiles(bodyIncludeFiles); 251 } 252 253 @Override 254 public String dumpTracedMessagesAsXml(String nodeId) { 255 return dumpTracedMessagesAsXml(nodeId, false); 256 } 257 258 @Override 259 public String dumpTracedMessagesAsXml(String nodeId, boolean includeExchangeProperties) { 260 String messageAsXml = backlogDebugger.dumpTracedMessagesAsXml(nodeId); 261 if (messageAsXml != null && includeExchangeProperties) { 262 String closingTag = "</" + BacklogTracerEventMessage.ROOT_TAG + ">"; 263 String exchangePropertiesAsXml = dumpExchangePropertiesAsXml(nodeId); 264 messageAsXml = messageAsXml.replace(closingTag, exchangePropertiesAsXml) + "\n" + closingTag; 265 } 266 return messageAsXml; 267 } 268 269 @Override 270 public long getDebugCounter() { 271 return backlogDebugger.getDebugCounter(); 272 } 273 274 @Override 275 public void resetDebugCounter() { 276 backlogDebugger.resetDebugCounter(); 277 } 278 279 @Override 280 public String validateConditionalBreakpoint(String language, String predicate) { 281 Language lan = null; 282 try { 283 lan = camelContext.resolveLanguage(language); 284 lan.createPredicate(predicate); 285 return null; 286 } catch (Exception e) { 287 if (lan == null) { 288 return e.getMessage(); 289 } else { 290 return "Invalid syntax " + predicate + " due: " + e.getMessage(); 291 } 292 } 293 } 294 295 @Override 296 public long getFallbackTimeout() { 297 return backlogDebugger.getFallbackTimeout(); 298 } 299 300 @Override 301 public void setFallbackTimeout(long fallbackTimeout) { 302 backlogDebugger.setFallbackTimeout(fallbackTimeout); 303 } 304 305 @Override 306 public String evaluateExpressionAtBreakpoint(String nodeId, String language, String expression) { 307 return evaluateExpressionAtBreakpoint(nodeId, language, expression, "java.lang.String").toString(); 308 } 309 310 @Override 311 public void setExchangePropertyOnBreakpoint(String nodeId, String exchangePropertyName, Object value) { 312 Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId); 313 if (suspendedExchange != null) { 314 suspendedExchange.setProperty(exchangePropertyName, value); 315 } 316 } 317 318 @Override 319 public void removeExchangePropertyOnBreakpoint(String nodeId, String exchangePropertyName) { 320 Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId); 321 if (suspendedExchange != null) { 322 suspendedExchange.removeProperty(exchangePropertyName); 323 } 324 } 325 326 @Override 327 public void setExchangePropertyOnBreakpoint(String nodeId, String exchangePropertyName, Object value, String type) { 328 try { 329 Class<?> classType = camelContext.getClassResolver().resolveMandatoryClass(type); 330 if (type != null) { 331 Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId); 332 if (suspendedExchange != null) { 333 value = suspendedExchange.getContext().getTypeConverter().mandatoryConvertTo(classType, suspendedExchange, 334 value); 335 suspendedExchange.setProperty(exchangePropertyName, value); 336 } 337 } else { 338 this.setExchangePropertyOnBreakpoint(nodeId, exchangePropertyName, value); 339 } 340 } catch (Exception e) { 341 throw RuntimeCamelException.wrapRuntimeCamelException(e); 342 } 343 } 344 345 @Override 346 public Object evaluateExpressionAtBreakpoint(String nodeId, String language, String expression, String resultType) { 347 Exchange suspendedExchange; 348 try { 349 Language lan = camelContext.resolveLanguage(language); 350 suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId); 351 if (suspendedExchange != null) { 352 Object result; 353 Class<?> resultClass = camelContext.getClassResolver().resolveMandatoryClass(resultType); 354 if (!Boolean.class.isAssignableFrom(resultClass)) { 355 Expression expr = lan.createExpression(expression); 356 expr.init(camelContext); 357 result = expr.evaluate(suspendedExchange, resultClass); 358 } else { 359 Predicate pred = lan.createPredicate(expression); 360 pred.init(camelContext); 361 result = pred.matches(suspendedExchange); 362 } 363 //Test if result is serializable 364 if (!isSerializable(result)) { 365 String resultStr = suspendedExchange.getContext().getTypeConverter().tryConvertTo(String.class, result); 366 if (resultStr != null) { 367 result = resultStr; 368 } 369 } 370 return result; 371 } 372 } catch (Exception e) { 373 return e.getMessage(); 374 } 375 return null; 376 } 377 378 @Override 379 public String messageHistoryOnBreakpointAsXml(String nodeId) { 380 StringBuffer messageHistoryBuffer = new StringBuffer(); 381 messageHistoryBuffer.append("<messageHistory>\n"); 382 383 Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(nodeId); 384 if (suspendedExchange != null) { 385 List<MessageHistory> list = suspendedExchange.getProperty(ExchangePropertyKey.MESSAGE_HISTORY, List.class); 386 if (list != null) { 387 // add incoming origin of message on the top 388 String routeId = suspendedExchange.getFromRouteId(); 389 Route route = suspendedExchange.getContext().getRoute(routeId); 390 String loc = route != null ? route.getSourceLocationShort() : ""; 391 String id = routeId; 392 String label = ""; 393 if (suspendedExchange.getFromEndpoint() != null) { 394 label = "from[" 395 + URISupport 396 .sanitizeUri( 397 StringHelper.limitLength(suspendedExchange.getFromEndpoint().getEndpointUri(), 100)) 398 + "]"; 399 } 400 401 long elapsed = StopWatch.elapsedMillisSince(suspendedExchange.getCreated()); 402 403 messageHistoryBuffer 404 .append(" <messageHistoryEntry") 405 .append(" location=\"").append(StringHelper.xmlEncode(loc)).append("\"") 406 .append(" routeId=\"").append(StringHelper.xmlEncode(routeId)).append("\"") 407 .append(" processorId=\"").append(StringHelper.xmlEncode(id)).append("\"") 408 .append(" processor=\"").append(StringHelper.xmlEncode(label)).append("\"") 409 .append(" elapsed=\"").append(elapsed).append("\"") 410 .append("/>\n"); 411 412 for (MessageHistory history : list) { 413 // and then each history 414 loc = LoggerHelper.getLineNumberLoggerName(history.getNode()); 415 if (loc == null) { 416 loc = ""; 417 } 418 routeId = history.getRouteId() != null ? history.getRouteId() : ""; 419 id = history.getNode().getId(); 420 // we need to avoid leak the sensible information here 421 // the sanitizeUri takes a very long time for very long string 422 // and the format cuts this to 423 // 78 characters, anyway. Cut this to 100 characters. This will 424 // give enough space for removing 425 // characters in the sanitizeUri method and will be reasonably 426 // fast 427 label = URISupport.sanitizeUri(StringHelper.limitLength(history.getNode().getLabel(), 100)); 428 elapsed = history.getElapsed(); 429 430 messageHistoryBuffer 431 .append(" <messageHistoryEntry") 432 .append(" location=\"").append(StringHelper.xmlEncode(loc)).append("\"") 433 .append(" routeId=\"").append(StringHelper.xmlEncode(routeId)).append("\"") 434 .append(" processorId=\"").append(StringHelper.xmlEncode(id)).append("\"") 435 .append(" processor=\"").append(StringHelper.xmlEncode(label)).append("\"") 436 .append(" elapsed=\"").append(elapsed).append("\"") 437 .append("/>\n"); 438 } 439 } 440 } 441 messageHistoryBuffer.append("</messageHistory>\n"); 442 return messageHistoryBuffer.toString(); 443 } 444 445 @Override 446 public void attach() { 447 backlogDebugger.attach(); 448 } 449 450 @Override 451 public void detach() { 452 backlogDebugger.detach(); 453 } 454 455 private String dumpExchangePropertiesAsXml(String id) { 456 StringBuilder sb = new StringBuilder(); 457 sb.append(" <exchangeProperties>\n"); 458 Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(id); 459 if (suspendedExchange != null) { 460 Map<String, Object> properties = suspendedExchange.getAllProperties(); 461 properties.forEach((propertyName, propertyValue) -> { 462 String type = ObjectHelper.classCanonicalName(propertyValue); 463 sb.append(" <exchangeProperty name=\"").append(propertyName).append("\""); 464 if (type != null) { 465 sb.append(" type=\"").append(type).append("\""); 466 } 467 sb.append(">"); 468 // dump property value as XML, use Camel type converter to convert 469 // to String 470 if (propertyValue != null) { 471 try { 472 String xml = suspendedExchange.getContext().getTypeConverter().tryConvertTo(String.class, 473 suspendedExchange, propertyValue); 474 if (xml != null) { 475 // must always xml encode 476 sb.append(StringHelper.xmlEncode(xml)); 477 } 478 } catch (Throwable e) { 479 // ignore as the body is for logging purpose 480 } 481 } 482 sb.append("</exchangeProperty>\n"); 483 }); 484 } 485 sb.append(" </exchangeProperties>"); 486 return sb.toString(); 487 } 488 489 private static boolean isSerializable(Object obj) { 490 final ByteArrayOutputStream baos = new ByteArrayOutputStream(512); 491 try (ObjectOutputStream out = new ObjectOutputStream(baos)) { 492 out.writeObject(obj); 493 return true; 494 } catch (Exception e) { 495 return false; 496 } 497 } 498 499}