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.util; 018 019import java.net.URI; 020import java.net.URISyntaxException; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.Iterator; 025import java.util.LinkedHashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.concurrent.atomic.AtomicLong; 029import java.util.regex.Matcher; 030import java.util.regex.Pattern; 031import java.util.regex.PatternSyntaxException; 032 033import org.apache.camel.CamelContext; 034import org.apache.camel.DelegateEndpoint; 035import org.apache.camel.Endpoint; 036import org.apache.camel.Exchange; 037import org.apache.camel.ExchangePattern; 038import org.apache.camel.Message; 039import org.apache.camel.PollingConsumer; 040import org.apache.camel.Processor; 041import org.apache.camel.ResolveEndpointFailedException; 042import org.apache.camel.Route; 043import org.apache.camel.spi.BrowsableEndpoint; 044import org.slf4j.Logger; 045import org.slf4j.LoggerFactory; 046 047/** 048 * Some helper methods for working with {@link Endpoint} instances 049 */ 050public final class EndpointHelper { 051 052 private static final Logger LOG = LoggerFactory.getLogger(EndpointHelper.class); 053 private static final AtomicLong ENDPOINT_COUNTER = new AtomicLong(0); 054 private static final Pattern SYNTAX_PATTERN = Pattern.compile("(\\w+)"); 055 056 private EndpointHelper() { 057 //Utility Class 058 } 059 060 /** 061 * Creates a {@link PollingConsumer} and polls all pending messages on the endpoint 062 * and invokes the given {@link Processor} to process each {@link Exchange} and then closes 063 * down the consumer and throws any exceptions thrown. 064 */ 065 public static void pollEndpoint(Endpoint endpoint, Processor processor, long timeout) throws Exception { 066 PollingConsumer consumer = endpoint.createPollingConsumer(); 067 try { 068 ServiceHelper.startService(consumer); 069 070 while (true) { 071 Exchange exchange = consumer.receive(timeout); 072 if (exchange == null) { 073 break; 074 } else { 075 processor.process(exchange); 076 } 077 } 078 } finally { 079 try { 080 ServiceHelper.stopAndShutdownService(consumer); 081 } catch (Exception e) { 082 LOG.warn("Failed to stop PollingConsumer: " + consumer + ". This example is ignored.", e); 083 } 084 } 085 } 086 087 /** 088 * Creates a {@link PollingConsumer} and polls all pending messages on the 089 * endpoint and invokes the given {@link Processor} to process each 090 * {@link Exchange} and then closes down the consumer and throws any 091 * exceptions thrown. 092 */ 093 public static void pollEndpoint(Endpoint endpoint, Processor processor) throws Exception { 094 pollEndpoint(endpoint, processor, 1000L); 095 } 096 097 /** 098 * Matches the endpoint with the given pattern. 099 * <p/> 100 * The endpoint will first resolve property placeholders using {@link CamelContext#resolvePropertyPlaceholders(String)}. 101 * <p/> 102 * The match rules are applied in this order: 103 * <ul> 104 * <li>exact match, returns true</li> 105 * <li>wildcard match (pattern ends with a * and the uri starts with the pattern), returns true</li> 106 * <li>regular expression match, returns true</li> 107 * <li>otherwise returns false</li> 108 * </ul> 109 * 110 * @param context the Camel context, if <tt>null</tt> then property placeholder resolution is skipped. 111 * @param uri the endpoint uri 112 * @param pattern a pattern to match 113 * @return <tt>true</tt> if match, <tt>false</tt> otherwise. 114 */ 115 public static boolean matchEndpoint(CamelContext context, String uri, String pattern) { 116 if (context != null) { 117 try { 118 uri = context.resolvePropertyPlaceholders(uri); 119 } catch (Exception e) { 120 throw new ResolveEndpointFailedException(uri, e); 121 } 122 } 123 124 // normalize uri so we can do endpoint hits with minor mistakes and parameters is not in the same order 125 try { 126 uri = URISupport.normalizeUri(uri); 127 } catch (Exception e) { 128 throw new ResolveEndpointFailedException(uri, e); 129 } 130 131 // we need to test with and without scheme separators (//) 132 if (uri.contains("://")) { 133 // try without :// also 134 String scheme = ObjectHelper.before(uri, "://"); 135 String path = ObjectHelper.after(uri, "://"); 136 if (matchPattern(scheme + ":" + path, pattern)) { 137 return true; 138 } 139 } else { 140 // try with :// also 141 String scheme = ObjectHelper.before(uri, ":"); 142 String path = ObjectHelper.after(uri, ":"); 143 if (matchPattern(scheme + "://" + path, pattern)) { 144 return true; 145 } 146 } 147 148 // and fallback to test with the uri as is 149 return matchPattern(uri, pattern); 150 } 151 152 /** 153 * Matches the endpoint with the given pattern. 154 * 155 * @see #matchEndpoint(org.apache.camel.CamelContext, String, String) 156 * @deprecated use {@link #matchEndpoint(org.apache.camel.CamelContext, String, String)} instead. 157 */ 158 @Deprecated 159 public static boolean matchEndpoint(String uri, String pattern) { 160 return matchEndpoint(null, uri, pattern); 161 } 162 163 /** 164 * Matches the name with the given pattern. 165 * <p/> 166 * The match rules are applied in this order: 167 * <ul> 168 * <li>exact match, returns true</li> 169 * <li>wildcard match (pattern ends with a * and the name starts with the pattern), returns true</li> 170 * <li>regular expression match, returns true</li> 171 * <li>otherwise returns false</li> 172 * </ul> 173 * 174 * @param name the name 175 * @param pattern a pattern to match 176 * @return <tt>true</tt> if match, <tt>false</tt> otherwise. 177 */ 178 public static boolean matchPattern(String name, String pattern) { 179 if (name == null || pattern == null) { 180 return false; 181 } 182 183 if (name.equals(pattern)) { 184 // exact match 185 return true; 186 } 187 188 if (matchWildcard(name, pattern)) { 189 return true; 190 } 191 192 if (matchRegex(name, pattern)) { 193 return true; 194 } 195 196 // no match 197 return false; 198 } 199 200 /** 201 * Matches the name with the given pattern. 202 * <p/> 203 * The match rules are applied in this order: 204 * <ul> 205 * <li>wildcard match (pattern ends with a * and the name starts with the pattern), returns true</li> 206 * <li>otherwise returns false</li> 207 * </ul> 208 * 209 * @param name the name 210 * @param pattern a pattern to match 211 * @return <tt>true</tt> if match, <tt>false</tt> otherwise. 212 */ 213 private static boolean matchWildcard(String name, String pattern) { 214 // we have wildcard support in that hence you can match with: file* to match any file endpoints 215 if (pattern.endsWith("*") && name.startsWith(pattern.substring(0, pattern.length() - 1))) { 216 return true; 217 } 218 return false; 219 } 220 221 /** 222 * Matches the name with the given pattern. 223 * <p/> 224 * The match rules are applied in this order: 225 * <ul> 226 * <li>regular expression match, returns true</li> 227 * <li>otherwise returns false</li> 228 * </ul> 229 * 230 * @param name the name 231 * @param pattern a pattern to match 232 * @return <tt>true</tt> if match, <tt>false</tt> otherwise. 233 */ 234 private static boolean matchRegex(String name, String pattern) { 235 // match by regular expression 236 try { 237 if (name.matches(pattern)) { 238 return true; 239 } 240 } catch (PatternSyntaxException e) { 241 // ignore 242 } 243 return false; 244 } 245 246 /** 247 * Sets the regular properties on the given bean 248 * 249 * @param context the camel context 250 * @param bean the bean 251 * @param parameters parameters 252 * @throws Exception is thrown if setting property fails 253 */ 254 public static void setProperties(CamelContext context, Object bean, Map<String, Object> parameters) throws Exception { 255 IntrospectionSupport.setProperties(context.getTypeConverter(), bean, parameters); 256 } 257 258 /** 259 * Sets the reference properties on the given bean 260 * <p/> 261 * This is convention over configuration, setting all reference parameters (using {@link #isReferenceParameter(String)} 262 * by looking it up in registry and setting it on the bean if possible. 263 * 264 * @param context the camel context 265 * @param bean the bean 266 * @param parameters parameters 267 * @throws Exception is thrown if setting property fails 268 */ 269 public static void setReferenceProperties(CamelContext context, Object bean, Map<String, Object> parameters) throws Exception { 270 Iterator<Map.Entry<String, Object>> it = parameters.entrySet().iterator(); 271 while (it.hasNext()) { 272 Map.Entry<String, Object> entry = it.next(); 273 String name = entry.getKey(); 274 Object v = entry.getValue(); 275 String value = v != null ? v.toString() : null; 276 if (value != null && isReferenceParameter(value)) { 277 boolean hit = IntrospectionSupport.setProperty(context, context.getTypeConverter(), bean, name, null, value, true); 278 if (hit) { 279 // must remove as its a valid option and we could configure it 280 it.remove(); 281 } 282 } 283 } 284 } 285 286 /** 287 * Is the given parameter a reference parameter (starting with a # char) 288 * 289 * @param parameter the parameter 290 * @return <tt>true</tt> if its a reference parameter 291 */ 292 public static boolean isReferenceParameter(String parameter) { 293 return parameter != null && parameter.trim().startsWith("#"); 294 } 295 296 /** 297 * Resolves a reference parameter by making a lookup in the registry. 298 * 299 * @param <T> type of object to lookup. 300 * @param context Camel context to use for lookup. 301 * @param value reference parameter value. 302 * @param type type of object to lookup. 303 * @return lookup result. 304 * @throws IllegalArgumentException if referenced object was not found in registry. 305 */ 306 public static <T> T resolveReferenceParameter(CamelContext context, String value, Class<T> type) { 307 return resolveReferenceParameter(context, value, type, true); 308 } 309 310 /** 311 * Resolves a reference parameter by making a lookup in the registry. 312 * 313 * @param <T> type of object to lookup. 314 * @param context Camel context to use for lookup. 315 * @param value reference parameter value. 316 * @param type type of object to lookup. 317 * @return lookup result (or <code>null</code> only if 318 * <code>mandatory</code> is <code>false</code>). 319 * @throws IllegalArgumentException if object was not found in registry and 320 * <code>mandatory</code> is <code>true</code>. 321 */ 322 public static <T> T resolveReferenceParameter(CamelContext context, String value, Class<T> type, boolean mandatory) { 323 String valueNoHash = StringHelper.replaceAll(value, "#", ""); 324 if (mandatory) { 325 return CamelContextHelper.mandatoryLookup(context, valueNoHash, type); 326 } else { 327 return CamelContextHelper.lookup(context, valueNoHash, type); 328 } 329 } 330 331 /** 332 * Resolves a reference list parameter by making lookups in the registry. 333 * The parameter value must be one of the following: 334 * <ul> 335 * <li>a comma-separated list of references to beans of type T</li> 336 * <li>a single reference to a bean type T</li> 337 * <li>a single reference to a bean of type java.util.List</li> 338 * </ul> 339 * 340 * @param context Camel context to use for lookup. 341 * @param value reference parameter value. 342 * @param elementType result list element type. 343 * @return list of lookup results, will always return a list. 344 * @throws IllegalArgumentException if any referenced object was not found in registry. 345 */ 346 @SuppressWarnings({"unchecked", "rawtypes"}) 347 public static <T> List<T> resolveReferenceListParameter(CamelContext context, String value, Class<T> elementType) { 348 if (value == null) { 349 return new ArrayList<T>(); 350 } 351 List<String> elements = Arrays.asList(value.split(",")); 352 if (elements.size() == 1) { 353 Object bean = resolveReferenceParameter(context, elements.get(0).trim(), Object.class); 354 if (bean instanceof List) { 355 // The bean is a list 356 return (List) bean; 357 } else { 358 // The bean is a list element 359 return Arrays.asList(elementType.cast(bean)); 360 } 361 } else { // more than one list element 362 List<T> result = new ArrayList<T>(elements.size()); 363 for (String element : elements) { 364 result.add(resolveReferenceParameter(context, element.trim(), elementType)); 365 } 366 return result; 367 } 368 } 369 370 /** 371 * Resolves a parameter, by doing a reference lookup if the parameter is a reference, and converting 372 * the parameter to the given type. 373 * 374 * @param <T> type of object to convert the parameter value as. 375 * @param context Camel context to use for lookup. 376 * @param value parameter or reference parameter value. 377 * @param type type of object to lookup. 378 * @return lookup result if it was a reference parameter, or the value converted to the given type 379 * @throws IllegalArgumentException if referenced object was not found in registry. 380 */ 381 public static <T> T resolveParameter(CamelContext context, String value, Class<T> type) { 382 T result; 383 if (EndpointHelper.isReferenceParameter(value)) { 384 result = EndpointHelper.resolveReferenceParameter(context, value, type); 385 } else { 386 result = context.getTypeConverter().convertTo(type, value); 387 } 388 return result; 389 } 390 391 /** 392 * @deprecated use {@link #resolveParameter(org.apache.camel.CamelContext, String, Class)} 393 */ 394 @Deprecated 395 public static <T> T resloveStringParameter(CamelContext context, String value, Class<T> type) { 396 return resolveParameter(context, value, type); 397 } 398 399 /** 400 * Gets the route id for the given endpoint in which there is a consumer listening. 401 * 402 * @param endpoint the endpoint 403 * @return the route id, or <tt>null</tt> if none found 404 */ 405 public static String getRouteIdFromEndpoint(Endpoint endpoint) { 406 if (endpoint == null || endpoint.getCamelContext() == null) { 407 return null; 408 } 409 410 List<Route> routes = endpoint.getCamelContext().getRoutes(); 411 for (Route route : routes) { 412 if (route.getEndpoint().equals(endpoint) 413 || route.getEndpoint().getEndpointKey().equals(endpoint.getEndpointKey())) { 414 return route.getId(); 415 } 416 } 417 return null; 418 } 419 420 /** 421 * A helper method for Endpoint implementations to create new Ids for Endpoints which also implement 422 * {@link org.apache.camel.spi.HasId} 423 */ 424 public static String createEndpointId() { 425 return "endpoint" + ENDPOINT_COUNTER.incrementAndGet(); 426 } 427 428 /** 429 * Lookup the id the given endpoint has been enlisted with in the {@link org.apache.camel.spi.Registry}. 430 * 431 * @param endpoint the endpoint 432 * @return the endpoint id, or <tt>null</tt> if not found 433 */ 434 public static String lookupEndpointRegistryId(Endpoint endpoint) { 435 if (endpoint == null || endpoint.getCamelContext() == null) { 436 return null; 437 } 438 439 // it may be a delegate endpoint, which we need to match as well 440 Endpoint delegate = null; 441 if (endpoint instanceof DelegateEndpoint) { 442 delegate = ((DelegateEndpoint) endpoint).getEndpoint(); 443 } 444 445 Map<String, Endpoint> map = endpoint.getCamelContext().getRegistry().findByTypeWithName(Endpoint.class); 446 for (Map.Entry<String, Endpoint> entry : map.entrySet()) { 447 if (entry.getValue().equals(endpoint) || entry.getValue().equals(delegate)) { 448 return entry.getKey(); 449 } 450 } 451 452 // not found 453 return null; 454 } 455 456 /** 457 * Browses the {@link BrowsableEndpoint} within the given range, and returns the messages as a XML payload. 458 * 459 * @param endpoint the browsable endpoint 460 * @param fromIndex from range 461 * @param toIndex to range 462 * @param includeBody whether to include the message body in the XML payload 463 * @return XML payload with the messages 464 * @throws IllegalArgumentException if the from and to range is invalid 465 * @see MessageHelper#dumpAsXml(org.apache.camel.Message) 466 */ 467 public static String browseRangeMessagesAsXml(BrowsableEndpoint endpoint, Integer fromIndex, Integer toIndex, Boolean includeBody) { 468 if (fromIndex == null) { 469 fromIndex = 0; 470 } 471 if (toIndex == null) { 472 toIndex = Integer.MAX_VALUE; 473 } 474 if (fromIndex > toIndex) { 475 throw new IllegalArgumentException("From index cannot be larger than to index, was: " + fromIndex + " > " + toIndex); 476 } 477 478 List<Exchange> exchanges = endpoint.getExchanges(); 479 if (exchanges.size() == 0) { 480 return null; 481 } 482 483 StringBuilder sb = new StringBuilder(); 484 sb.append("<messages>"); 485 for (int i = fromIndex; i < exchanges.size() && i <= toIndex; i++) { 486 Exchange exchange = exchanges.get(i); 487 Message msg = exchange.hasOut() ? exchange.getOut() : exchange.getIn(); 488 String xml = MessageHelper.dumpAsXml(msg, includeBody); 489 sb.append("\n").append(xml); 490 } 491 sb.append("\n</messages>"); 492 return sb.toString(); 493 } 494 495 /** 496 * Attempts to resolve if the url has an <tt>exchangePattern</tt> option configured 497 * 498 * @param url the url 499 * @return the exchange pattern, or <tt>null</tt> if the url has no <tt>exchangePattern</tt> configured. 500 * @throws URISyntaxException is thrown if uri is invalid 501 */ 502 public static ExchangePattern resolveExchangePatternFromUrl(String url) throws URISyntaxException { 503 int idx = url.indexOf("?"); 504 if (idx > 0) { 505 url = url.substring(idx + 1); 506 } 507 Map<String, Object> parameters = URISupport.parseQuery(url, true); 508 String pattern = (String) parameters.get("exchangePattern"); 509 if (pattern != null) { 510 return ExchangePattern.asEnum(pattern); 511 } 512 return null; 513 } 514 515 /** 516 * Parses the endpoint uri and builds a map of documentation information for each option which is extracted 517 * from the component json documentation 518 * 519 * @param camelContext the Camel context 520 * @param uri the endpoint uri 521 * @return a map for each option in the uri with the corresponding information from the json 522 * @throws Exception is thrown in case of error 523 */ 524 public static Map<String, Object> endpointProperties(CamelContext camelContext, String uri) throws Exception { 525 // NOTICE: This logic is similar to org.apache.camel.catalog.DefaultCamelCatalog#endpointProperties 526 // as the catalog also offers similar functionality (without having camel-core on classpath) 527 528 // parse the uri 529 URI u = new URI(uri); 530 String scheme = u.getScheme(); 531 532 String json = camelContext.getComponentParameterJsonSchema(u.getScheme()); 533 if (json == null) { 534 throw new IllegalArgumentException("Cannot find endpoint with scheme " + scheme); 535 } 536 537 // grab the syntax 538 String syntax = null; 539 List<Map<String, String>> rows = JsonSchemaHelper.parseJsonSchema("component", json, false); 540 for (Map<String, String> row : rows) { 541 if (row.containsKey("syntax")) { 542 syntax = row.get("syntax"); 543 break; 544 } 545 } 546 if (syntax == null) { 547 throw new IllegalArgumentException("Endpoint with scheme " + scheme + " has no syntax defined in the json schema"); 548 } 549 550 // parse the syntax and find the same group in the uri 551 Matcher matcher = SYNTAX_PATTERN.matcher(syntax); 552 List<String> word = new ArrayList<String>(); 553 while (matcher.find()) { 554 String s = matcher.group(1); 555 if (!scheme.equals(s)) { 556 word.add(s); 557 } 558 } 559 560 String uriPath = stripQuery(uri); 561 562 // if there is only one, then use uriPath as is 563 List<String> word2 = new ArrayList<String>(); 564 565 if (word.size() == 1) { 566 String s = uriPath; 567 s = URISupport.stripPrefix(s, scheme); 568 // strip any leading : or / after the scheme 569 while (s.startsWith(":") || s.startsWith("/")) { 570 s = s.substring(1); 571 } 572 word2.add(s); 573 } else { 574 Matcher matcher2 = SYNTAX_PATTERN.matcher(uriPath); 575 while (matcher2.find()) { 576 String s = matcher2.group(1); 577 if (!scheme.equals(s)) { 578 word2.add(s); 579 } 580 } 581 } 582 583 rows = JsonSchemaHelper.parseJsonSchema("properties", json, true); 584 585 boolean defaultValueAdded = false; 586 587 // now parse the uri to know which part isw what 588 Map<String, String> options = new LinkedHashMap<String, String>(); 589 590 // word contains the syntax path elements 591 Iterator<String> it = word2.iterator(); 592 for (int i = 0; i < word.size(); i++) { 593 String key = word.get(i); 594 595 boolean allOptions = word.size() == word2.size(); 596 boolean required = JsonSchemaHelper.isPropertyRequired(rows, key); 597 String defaultValue = JsonSchemaHelper.getPropertyDefaultValue(rows, key); 598 599 // we have all options so no problem 600 if (allOptions) { 601 String value = it.next(); 602 options.put(key, value); 603 } else { 604 // we have a little problem as we do not not have all options 605 if (!required) { 606 String value = defaultValue; 607 options.put(key, value); 608 defaultValueAdded = true; 609 } else { 610 String value = it.next(); 611 options.put(key, value); 612 } 613 } 614 } 615 616 Map<String, Object> answer = new LinkedHashMap<String, Object>(); 617 618 // remove all options which are using default values and are not required 619 for (Map.Entry<String, String> entry : options.entrySet()) { 620 String key = entry.getKey(); 621 String value = entry.getValue(); 622 623 if (defaultValueAdded) { 624 boolean required = JsonSchemaHelper.isPropertyRequired(rows, key); 625 String defaultValue = JsonSchemaHelper.getPropertyDefaultValue(rows, key); 626 627 if (!required && defaultValue != null) { 628 if (defaultValue.equals(value)) { 629 continue; 630 } 631 } 632 } 633 634 // we should keep this in the answer 635 answer.put(key, value); 636 } 637 638 // now parse the uri parameters 639 Map<String, Object> parameters = URISupport.parseParameters(u); 640 641 // and covert the values to String so its JMX friendly 642 for (Map.Entry<String, Object> entry : parameters.entrySet()) { 643 String key = entry.getKey(); 644 String value = entry.getValue() != null ? entry.getValue().toString() : ""; 645 answer.put(key, value); 646 } 647 648 return answer; 649 } 650 651 /** 652 * Strips the query parameters from the uri 653 * 654 * @param uri the uri 655 * @return the uri without the query parameter 656 */ 657 private static String stripQuery(String uri) { 658 int idx = uri.indexOf('?'); 659 if (idx > -1) { 660 uri = uri.substring(0, idx); 661 } 662 return uri; 663 } 664 665}