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