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 long elapsed = new StopWatch(suspendedExchange.getCreated()).taken(); 401 402 messageHistoryBuffer 403 .append(" <messageHistoryEntry") 404 .append(" location=\"").append(StringHelper.xmlEncode(loc)).append("\"") 405 .append(" routeId=\"").append(StringHelper.xmlEncode(routeId)).append("\"") 406 .append(" processorId=\"").append(StringHelper.xmlEncode(id)).append("\"") 407 .append(" processor=\"").append(StringHelper.xmlEncode(label)).append("\"") 408 .append(" elapsed=\"").append(elapsed).append("\"") 409 .append("/>\n"); 410 411 for (MessageHistory history : list) { 412 // and then each history 413 loc = LoggerHelper.getLineNumberLoggerName(history.getNode()); 414 if (loc == null) { 415 loc = ""; 416 } 417 routeId = history.getRouteId() != null ? history.getRouteId() : ""; 418 id = history.getNode().getId(); 419 // we need to avoid leak the sensible information here 420 // the sanitizeUri takes a very long time for very long string 421 // and the format cuts this to 422 // 78 characters, anyway. Cut this to 100 characters. This will 423 // give enough space for removing 424 // characters in the sanitizeUri method and will be reasonably 425 // fast 426 label = URISupport.sanitizeUri(StringHelper.limitLength(history.getNode().getLabel(), 100)); 427 elapsed = history.getElapsed(); 428 429 messageHistoryBuffer 430 .append(" <messageHistoryEntry") 431 .append(" location=\"").append(StringHelper.xmlEncode(loc)).append("\"") 432 .append(" routeId=\"").append(StringHelper.xmlEncode(routeId)).append("\"") 433 .append(" processorId=\"").append(StringHelper.xmlEncode(id)).append("\"") 434 .append(" processor=\"").append(StringHelper.xmlEncode(label)).append("\"") 435 .append(" elapsed=\"").append(elapsed).append("\"") 436 .append("/>\n"); 437 } 438 } 439 } 440 messageHistoryBuffer.append("</messageHistory>\n"); 441 return messageHistoryBuffer.toString(); 442 } 443 444 @Override 445 public void attach() { 446 backlogDebugger.attach(); 447 } 448 449 @Override 450 public void detach() { 451 backlogDebugger.detach(); 452 } 453 454 private String dumpExchangePropertiesAsXml(String id) { 455 StringBuilder sb = new StringBuilder(); 456 sb.append(" <exchangeProperties>\n"); 457 Exchange suspendedExchange = backlogDebugger.getSuspendedExchange(id); 458 if (suspendedExchange != null) { 459 Map<String, Object> properties = suspendedExchange.getAllProperties(); 460 properties.forEach((propertyName, propertyValue) -> { 461 String type = ObjectHelper.classCanonicalName(propertyValue); 462 sb.append(" <exchangeProperty name=\"").append(propertyName).append("\""); 463 if (type != null) { 464 sb.append(" type=\"").append(type).append("\""); 465 } 466 sb.append(">"); 467 // dump property value as XML, use Camel type converter to convert 468 // to String 469 if (propertyValue != null) { 470 try { 471 String xml = suspendedExchange.getContext().getTypeConverter().tryConvertTo(String.class, 472 suspendedExchange, propertyValue); 473 if (xml != null) { 474 // must always xml encode 475 sb.append(StringHelper.xmlEncode(xml)); 476 } 477 } catch (Throwable e) { 478 // ignore as the body is for logging purpose 479 } 480 } 481 sb.append("</exchangeProperty>\n"); 482 }); 483 } 484 sb.append(" </exchangeProperties>"); 485 return sb.toString(); 486 } 487 488 private static boolean isSerializable(Object obj) { 489 final ByteArrayOutputStream baos = new ByteArrayOutputStream(512); 490 try (ObjectOutputStream out = new ObjectOutputStream(baos)) { 491 out.writeObject(obj); 492 return true; 493 } catch (Exception e) { 494 return false; 495 } 496 } 497 498}