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.ByteArrayInputStream; 020import java.io.InputStream; 021import java.net.URLDecoder; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Comparator; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import java.util.concurrent.TimeUnit; 029 030import javax.management.MBeanServer; 031import javax.management.ObjectName; 032 033import org.w3c.dom.Document; 034 035import org.apache.camel.CamelContext; 036import org.apache.camel.CatalogCamelContext; 037import org.apache.camel.Endpoint; 038import org.apache.camel.ExtendedCamelContext; 039import org.apache.camel.ManagementStatisticsLevel; 040import org.apache.camel.Producer; 041import org.apache.camel.ProducerTemplate; 042import org.apache.camel.Route; 043import org.apache.camel.TimerListener; 044import org.apache.camel.api.management.ManagedResource; 045import org.apache.camel.api.management.mbean.ManagedCamelContextMBean; 046import org.apache.camel.api.management.mbean.ManagedProcessorMBean; 047import org.apache.camel.api.management.mbean.ManagedRouteMBean; 048import org.apache.camel.api.management.mbean.ManagedStepMBean; 049import org.apache.camel.model.Model; 050import org.apache.camel.model.RouteDefinition; 051import org.apache.camel.model.RoutesDefinition; 052import org.apache.camel.model.rest.RestDefinition; 053import org.apache.camel.model.rest.RestsDefinition; 054import org.apache.camel.spi.ManagementStrategy; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057 058@ManagedResource(description = "Managed CamelContext") 059public class ManagedCamelContext extends ManagedPerformanceCounter implements TimerListener, ManagedCamelContextMBean { 060 061 private static final Logger LOG = LoggerFactory.getLogger(ManagedCamelContext.class); 062 063 private final CamelContext context; 064 private final LoadTriplet load = new LoadTriplet(); 065 private final String jmxDomain; 066 067 public ManagedCamelContext(CamelContext context) { 068 this.context = context; 069 this.jmxDomain = context.getManagementStrategy().getManagementAgent().getMBeanObjectDomainName(); 070 } 071 072 @Override 073 public void init(ManagementStrategy strategy) { 074 super.init(strategy); 075 boolean enabled = context.getManagementStrategy().getManagementAgent() != null && context.getManagementStrategy().getManagementAgent().getStatisticsLevel() != ManagementStatisticsLevel.Off; 076 setStatisticsEnabled(enabled); 077 } 078 079 public CamelContext getContext() { 080 return context; 081 } 082 083 @Override 084 public String getCamelId() { 085 return context.getName(); 086 } 087 088 @Override 089 public String getManagementName() { 090 return context.getManagementName(); 091 } 092 093 @Override 094 public String getCamelVersion() { 095 return context.getVersion(); 096 } 097 098 @Override 099 public String getState() { 100 return context.getStatus().name(); 101 } 102 103 @Override 104 public String getUptime() { 105 return context.getUptime(); 106 } 107 108 @Override 109 public long getUptimeMillis() { 110 return context.getUptimeMillis(); 111 } 112 113 @Override 114 public String getManagementStatisticsLevel() { 115 if (context.getManagementStrategy().getManagementAgent() != null) { 116 return context.getManagementStrategy().getManagementAgent().getStatisticsLevel().name(); 117 } else { 118 return null; 119 } 120 } 121 122 @Override 123 public String getClassResolver() { 124 return context.getClassResolver().getClass().getName(); 125 } 126 127 @Override 128 public String getPackageScanClassResolver() { 129 return context.adapt(ExtendedCamelContext.class).getPackageScanClassResolver().getClass().getName(); 130 } 131 132 @Override 133 public String getApplicationContextClassName() { 134 if (context.getApplicationContextClassLoader() != null) { 135 return context.getApplicationContextClassLoader().getClass().getName(); 136 } else { 137 return null; 138 } 139 } 140 141 @Override 142 public String getHeadersMapFactoryClassName() { 143 return context.adapt(ExtendedCamelContext.class).getHeadersMapFactory().getClass().getName(); 144 } 145 146 @Override 147 public Map<String, String> getGlobalOptions() { 148 if (context.getGlobalOptions().isEmpty()) { 149 return null; 150 } 151 return context.getGlobalOptions(); 152 } 153 154 @Override 155 public String getGlobalOption(String key) throws Exception { 156 return context.getGlobalOption(key); 157 } 158 159 @Override 160 public void setGlobalOption(String key, String value) throws Exception { 161 context.getGlobalOptions().put(key, value); 162 } 163 164 @Override 165 public Boolean getTracing() { 166 return context.isTracing(); 167 } 168 169 @Override 170 public void setTracing(Boolean tracing) { 171 context.setTracing(tracing); 172 } 173 174 public Integer getInflightExchanges() { 175 return (int) super.getExchangesInflight(); 176 } 177 178 @Override 179 public Integer getTotalRoutes() { 180 return context.getRoutesSize(); 181 } 182 183 @Override 184 public Integer getStartedRoutes() { 185 int started = 0; 186 for (Route route : context.getRoutes()) { 187 if (context.getRouteController().getRouteStatus(route.getId()).isStarted()) { 188 started++; 189 } 190 } 191 return started; 192 } 193 194 @Override 195 public void setTimeout(long timeout) { 196 context.getShutdownStrategy().setTimeout(timeout); 197 } 198 199 @Override 200 public long getTimeout() { 201 return context.getShutdownStrategy().getTimeout(); 202 } 203 204 @Override 205 public void setTimeUnit(TimeUnit timeUnit) { 206 context.getShutdownStrategy().setTimeUnit(timeUnit); 207 } 208 209 @Override 210 public TimeUnit getTimeUnit() { 211 return context.getShutdownStrategy().getTimeUnit(); 212 } 213 214 @Override 215 public void setShutdownNowOnTimeout(boolean shutdownNowOnTimeout) { 216 context.getShutdownStrategy().setShutdownNowOnTimeout(shutdownNowOnTimeout); 217 } 218 219 @Override 220 public boolean isShutdownNowOnTimeout() { 221 return context.getShutdownStrategy().isShutdownNowOnTimeout(); 222 } 223 224 @Override 225 public String getLoad01() { 226 double load1 = load.getLoad1(); 227 if (Double.isNaN(load1)) { 228 // empty string if load statistics is disabled 229 return ""; 230 } else { 231 return String.format("%.2f", load1); 232 } 233 } 234 235 @Override 236 public String getLoad05() { 237 double load5 = load.getLoad5(); 238 if (Double.isNaN(load5)) { 239 // empty string if load statistics is disabled 240 return ""; 241 } else { 242 return String.format("%.2f", load5); 243 } 244 } 245 246 @Override 247 public String getLoad15() { 248 double load15 = load.getLoad15(); 249 if (Double.isNaN(load15)) { 250 // empty string if load statistics is disabled 251 return ""; 252 } else { 253 return String.format("%.2f", load15); 254 } 255 } 256 257 @Override 258 public boolean isUseBreadcrumb() { 259 return context.isUseBreadcrumb(); 260 } 261 262 @Override 263 public boolean isAllowUseOriginalMessage() { 264 return context.isAllowUseOriginalMessage(); 265 } 266 267 @Override 268 public boolean isMessageHistory() { 269 return context.isMessageHistory() != null ? context.isMessageHistory() : false; 270 } 271 272 @Override 273 public boolean isLogMask() { 274 return context.isLogMask() != null ? context.isLogMask() : false; 275 } 276 277 @Override 278 public boolean isUseMDCLogging() { 279 return context.isUseMDCLogging(); 280 } 281 282 @Override 283 public boolean isUseDataType() { 284 return context.isUseDataType(); 285 } 286 287 @Override 288 public void onTimer() { 289 load.update(getInflightExchanges()); 290 } 291 292 @Override 293 public void start() throws Exception { 294 if (context.isSuspended()) { 295 context.resume(); 296 } else { 297 context.start(); 298 } 299 } 300 301 @Override 302 public void stop() throws Exception { 303 context.stop(); 304 } 305 306 @Override 307 public void restart() throws Exception { 308 context.stop(); 309 context.start(); 310 } 311 312 @Override 313 public void suspend() throws Exception { 314 context.suspend(); 315 } 316 317 @Override 318 public void resume() throws Exception { 319 if (context.isSuspended()) { 320 context.resume(); 321 } else { 322 throw new IllegalStateException("CamelContext is not suspended"); 323 } 324 } 325 326 @Override 327 public void startAllRoutes() throws Exception { 328 context.getRouteController().startAllRoutes(); 329 } 330 331 @Override 332 public boolean canSendToEndpoint(String endpointUri) { 333 try { 334 Endpoint endpoint = context.getEndpoint(endpointUri); 335 if (endpoint != null) { 336 Producer producer = endpoint.createProducer(); 337 return producer != null; 338 } 339 } catch (Exception e) { 340 // ignore 341 } 342 343 return false; 344 } 345 346 @Override 347 public void sendBody(String endpointUri, Object body) throws Exception { 348 ProducerTemplate template = context.createProducerTemplate(); 349 try { 350 template.sendBody(endpointUri, body); 351 } finally { 352 template.stop(); 353 } 354 } 355 356 @Override 357 public void sendStringBody(String endpointUri, String body) throws Exception { 358 sendBody(endpointUri, body); 359 } 360 361 @Override 362 public void sendBodyAndHeaders(String endpointUri, Object body, Map<String, Object> headers) throws Exception { 363 ProducerTemplate template = context.createProducerTemplate(); 364 try { 365 template.sendBodyAndHeaders(endpointUri, body, headers); 366 } finally { 367 template.stop(); 368 } 369 } 370 371 @Override 372 public Object requestBody(String endpointUri, Object body) throws Exception { 373 ProducerTemplate template = context.createProducerTemplate(); 374 Object answer = null; 375 try { 376 answer = template.requestBody(endpointUri, body); 377 } finally { 378 template.stop(); 379 } 380 return answer; 381 } 382 383 @Override 384 public Object requestStringBody(String endpointUri, String body) throws Exception { 385 return requestBody(endpointUri, body); 386 } 387 388 @Override 389 public Object requestBodyAndHeaders(String endpointUri, Object body, Map<String, Object> headers) throws Exception { 390 ProducerTemplate template = context.createProducerTemplate(); 391 Object answer = null; 392 try { 393 answer = template.requestBodyAndHeaders(endpointUri, body, headers); 394 } finally { 395 template.stop(); 396 } 397 return answer; 398 } 399 400 @Override 401 public String dumpRestsAsXml() throws Exception { 402 return dumpRestsAsXml(false); 403 } 404 405 @Override 406 public String dumpRestsAsXml(boolean resolvePlaceholders) throws Exception { 407 List<RestDefinition> rests = context.getExtension(Model.class).getRestDefinitions(); 408 if (rests.isEmpty()) { 409 return null; 410 } 411 412 // use a routes definition to dump the rests 413 RestsDefinition def = new RestsDefinition(); 414 def.setRests(rests); 415 416 ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class); 417 return ecc.getModelToXMLDumper().dumpModelAsXml(context, def, resolvePlaceholders, false); 418 } 419 420 @Override 421 public String dumpRoutesAsXml() throws Exception { 422 return dumpRoutesAsXml(false, false); 423 } 424 425 @Override 426 public String dumpRoutesAsXml(boolean resolvePlaceholders) throws Exception { 427 return dumpRoutesAsXml(resolvePlaceholders, false); 428 } 429 430 @Override 431 public String dumpRoutesAsXml(boolean resolvePlaceholders, boolean resolveDelegateEndpoints) throws Exception { 432 List<RouteDefinition> routes = context.getExtension(Model.class).getRouteDefinitions(); 433 if (routes.isEmpty()) { 434 return null; 435 } 436 437 // use a routes definition to dump the routes 438 RoutesDefinition def = new RoutesDefinition(); 439 def.setRoutes(routes); 440 441 ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class); 442 return ecc.getModelToXMLDumper().dumpModelAsXml(context, def, resolvePlaceholders, resolveDelegateEndpoints); 443 } 444 445 @Override 446 public void addOrUpdateRoutesFromXml(String xml) throws Exception { 447 // do not decode so we function as before 448 addOrUpdateRoutesFromXml(xml, false); 449 } 450 451 @Override 452 public void addOrUpdateRoutesFromXml(String xml, boolean urlDecode) throws Exception { 453 // decode String as it may have been encoded, from its xml source 454 if (urlDecode) { 455 xml = URLDecoder.decode(xml, "UTF-8"); 456 } 457 458 InputStream is = context.getTypeConverter().mandatoryConvertTo(InputStream.class, xml); 459 try { 460 // add will remove existing route first 461 ExtendedCamelContext ecc = context.adapt(ExtendedCamelContext.class); 462 RoutesDefinition routes = (RoutesDefinition) ecc.getXMLRoutesDefinitionLoader().loadRoutesDefinition(ecc, is); 463 context.getExtension(Model.class).addRouteDefinitions(routes.getRoutes()); 464 } catch (Exception e) { 465 // log the error as warn as the management api may be invoked remotely over JMX which does not propagate such exception 466 String msg = "Error updating routes from xml: " + xml + " due: " + e.getMessage(); 467 LOG.warn(msg, e); 468 throw e; 469 } 470 } 471 472 @Override 473 public String dumpRoutesStatsAsXml(boolean fullStats, boolean includeProcessors) throws Exception { 474 StringBuilder sb = new StringBuilder(); 475 sb.append("<camelContextStat").append(String.format(" id=\"%s\" state=\"%s\"", getCamelId(), getState())); 476 // use substring as we only want the attributes 477 String stat = dumpStatsAsXml(fullStats); 478 sb.append(" exchangesInflight=\"").append(getInflightExchanges()).append("\""); 479 sb.append(" ").append(stat, 7, stat.length() - 2).append(">\n"); 480 481 MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer(); 482 if (server != null) { 483 // gather all the routes for this CamelContext, which requires JMX 484 String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : ""; 485 ObjectName query = ObjectName.getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=routes,*"); 486 Set<ObjectName> routes = server.queryNames(query, null); 487 488 List<ManagedProcessorMBean> processors = new ArrayList<>(); 489 if (includeProcessors) { 490 // gather all the processors for this CamelContext, which requires JMX 491 query = ObjectName.getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=processors,*"); 492 Set<ObjectName> names = server.queryNames(query, null); 493 for (ObjectName on : names) { 494 ManagedProcessorMBean processor = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedProcessorMBean.class); 495 processors.add(processor); 496 } 497 } 498 processors.sort(new OrderProcessorMBeans()); 499 500 // loop the routes, and append the processor stats if needed 501 sb.append(" <routeStats>\n"); 502 for (ObjectName on : routes) { 503 ManagedRouteMBean route = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedRouteMBean.class); 504 sb.append(" <routeStat").append(String.format(" id=\"%s\" state=\"%s\"", route.getRouteId(), route.getState())); 505 // use substring as we only want the attributes 506 stat = route.dumpStatsAsXml(fullStats); 507 sb.append(" exchangesInflight=\"").append(route.getExchangesInflight()).append("\""); 508 sb.append(" ").append(stat, 7, stat.length() - 2).append(">\n"); 509 510 // add processor details if needed 511 if (includeProcessors) { 512 sb.append(" <processorStats>\n"); 513 for (ManagedProcessorMBean processor : processors) { 514 // the processor must belong to this route 515 if (route.getRouteId().equals(processor.getRouteId())) { 516 sb.append(" <processorStat").append(String.format(" id=\"%s\" index=\"%s\" state=\"%s\"", processor.getProcessorId(), processor.getIndex(), processor.getState())); 517 // use substring as we only want the attributes 518 stat = processor.dumpStatsAsXml(fullStats); 519 sb.append(" exchangesInflight=\"").append(processor.getExchangesInflight()).append("\""); 520 sb.append(" ").append(stat, 7, stat.length()).append("\n"); 521 } 522 } 523 sb.append(" </processorStats>\n"); 524 } 525 sb.append(" </routeStat>\n"); 526 } 527 sb.append(" </routeStats>\n"); 528 } 529 530 sb.append("</camelContextStat>"); 531 return sb.toString(); 532 } 533 534 @Override 535 public String dumpStepStatsAsXml(boolean fullStats) throws Exception { 536 StringBuilder sb = new StringBuilder(); 537 sb.append("<camelContextStat").append(String.format(" id=\"%s\" state=\"%s\"", getCamelId(), getState())); 538 // use substring as we only want the attributes 539 String stat = dumpStatsAsXml(fullStats); 540 sb.append(" exchangesInflight=\"").append(getInflightExchanges()).append("\""); 541 sb.append(" ").append(stat, 7, stat.length() - 2).append(">\n"); 542 543 MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer(); 544 if (server != null) { 545 // gather all the routes for this CamelContext, which requires JMX 546 String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : ""; 547 ObjectName query = ObjectName.getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=routes,*"); 548 Set<ObjectName> routes = server.queryNames(query, null); 549 550 List<ManagedProcessorMBean> steps = new ArrayList<>(); 551 // gather all the steps for this CamelContext, which requires JMX 552 query = ObjectName.getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=steps,*"); 553 Set<ObjectName> names = server.queryNames(query, null); 554 for (ObjectName on : names) { 555 ManagedStepMBean step = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedStepMBean.class); 556 steps.add(step); 557 } 558 steps.sort(new OrderProcessorMBeans()); 559 560 // loop the routes, and append the processor stats if needed 561 sb.append(" <routeStats>\n"); 562 for (ObjectName on : routes) { 563 ManagedRouteMBean route = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedRouteMBean.class); 564 sb.append(" <routeStat").append(String.format(" id=\"%s\" state=\"%s\"", route.getRouteId(), route.getState())); 565 // use substring as we only want the attributes 566 stat = route.dumpStatsAsXml(fullStats); 567 sb.append(" exchangesInflight=\"").append(route.getExchangesInflight()).append("\""); 568 sb.append(" ").append(stat, 7, stat.length() - 2).append(">\n"); 569 570 // add steps details if needed 571 sb.append(" <stepStats>\n"); 572 for (ManagedProcessorMBean processor : steps) { 573 // the step must belong to this route 574 if (route.getRouteId().equals(processor.getRouteId())) { 575 sb.append(" <stepStat").append(String.format(" id=\"%s\" index=\"%s\" state=\"%s\"", processor.getProcessorId(), processor.getIndex(), processor.getState())); 576 // use substring as we only want the attributes 577 stat = processor.dumpStatsAsXml(fullStats); 578 sb.append(" exchangesInflight=\"").append(processor.getExchangesInflight()).append("\""); 579 sb.append(" ").append(stat, 7, stat.length()).append("\n"); 580 } 581 sb.append(" </stepStats>\n"); 582 } 583 sb.append(" </stepStat>\n"); 584 } 585 sb.append(" </routeStats>\n"); 586 } 587 588 sb.append("</camelContextStat>"); 589 return sb.toString(); 590 } 591 592 @Override 593 public String dumpRoutesCoverageAsXml() throws Exception { 594 StringBuilder sb = new StringBuilder(); 595 sb.append("<camelContextRouteCoverage") 596 .append(String.format(" id=\"%s\" exchangesTotal=\"%s\" totalProcessingTime=\"%s\"", getCamelId(), getExchangesTotal(), getTotalProcessingTime())) 597 .append(">\n"); 598 599 String xml = dumpRoutesAsXml(); 600 if (xml != null) { 601 // use the coverage xml parser to dump the routes and enrich with coverage stats 602 Document dom = RouteCoverageXmlParser.parseXml(context, new ByteArrayInputStream(xml.getBytes())); 603 // convert dom back to xml 604 String converted = context.getTypeConverter().convertTo(String.class, dom); 605 sb.append(converted); 606 } 607 608 sb.append("\n</camelContextRouteCoverage>"); 609 return sb.toString(); 610 } 611 612 @Override 613 public boolean createEndpoint(String uri) throws Exception { 614 if (context.hasEndpoint(uri) != null) { 615 // endpoint already exists 616 return false; 617 } 618 619 Endpoint endpoint = context.getEndpoint(uri); 620 if (endpoint != null) { 621 // ensure endpoint is registered, as the management strategy could have been configured to not always 622 // register new endpoints in JMX, so we need to check if its registered, and if not register it manually 623 ObjectName on = context.getManagementStrategy().getManagementObjectNameStrategy().getObjectNameForEndpoint(endpoint); 624 if (on != null && !context.getManagementStrategy().getManagementAgent().isRegistered(on)) { 625 // register endpoint as mbean 626 Object me = context.getManagementStrategy().getManagementObjectStrategy().getManagedObjectForEndpoint(context, endpoint); 627 context.getManagementStrategy().getManagementAgent().register(me, on); 628 } 629 return true; 630 } else { 631 return false; 632 } 633 } 634 635 @Override 636 public int removeEndpoints(String pattern) throws Exception { 637 // endpoints is always removed from JMX if removed from context 638 Collection<Endpoint> removed = context.removeEndpoints(pattern); 639 return removed.size(); 640 } 641 642 @Override 643 public String componentParameterJsonSchema(String componentName) throws Exception { 644 return context.adapt(CatalogCamelContext.class).getComponentParameterJsonSchema(componentName); 645 } 646 647 @Override 648 public String dataFormatParameterJsonSchema(String dataFormatName) throws Exception { 649 return context.adapt(CatalogCamelContext.class).getDataFormatParameterJsonSchema(dataFormatName); 650 } 651 652 @Override 653 public String languageParameterJsonSchema(String languageName) throws Exception { 654 return context.adapt(CatalogCamelContext.class).getLanguageParameterJsonSchema(languageName); 655 } 656 657 @Override 658 public String eipParameterJsonSchema(String eipName) throws Exception { 659 return context.adapt(CatalogCamelContext.class).getEipParameterJsonSchema(eipName); 660 } 661 662 @Override 663 public void reset(boolean includeRoutes) throws Exception { 664 reset(); 665 666 // and now reset all routes for this route 667 if (includeRoutes) { 668 MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer(); 669 if (server != null) { 670 String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : ""; 671 ObjectName query = ObjectName.getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=routes,*"); 672 Set<ObjectName> names = server.queryNames(query, null); 673 for (ObjectName name : names) { 674 server.invoke(name, "reset", new Object[]{true}, new String[]{"boolean"}); 675 } 676 } 677 } 678 } 679 680 /** 681 * Used for sorting the processor mbeans accordingly to their index. 682 */ 683 private static final class OrderProcessorMBeans implements Comparator<ManagedProcessorMBean> { 684 685 @Override 686 public int compare(ManagedProcessorMBean o1, ManagedProcessorMBean o2) { 687 return o1.getIndex().compareTo(o2.getIndex()); 688 } 689 } 690 691}