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.model.rest; 018 019import java.net.URISyntaxException; 020import java.util.ArrayList; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029import javax.xml.bind.annotation.XmlAccessType; 030import javax.xml.bind.annotation.XmlAccessorType; 031import javax.xml.bind.annotation.XmlAttribute; 032import javax.xml.bind.annotation.XmlElement; 033import javax.xml.bind.annotation.XmlElementRef; 034import javax.xml.bind.annotation.XmlRootElement; 035 036import org.apache.camel.CamelContext; 037import org.apache.camel.ExtendedCamelContext; 038import org.apache.camel.RuntimeCamelException; 039import org.apache.camel.model.OptionalIdentifiedDefinition; 040import org.apache.camel.model.ProcessorDefinition; 041import org.apache.camel.model.ProcessorDefinitionHelper; 042import org.apache.camel.model.RouteDefinition; 043import org.apache.camel.model.ToDefinition; 044import org.apache.camel.model.ToDynamicDefinition; 045import org.apache.camel.spi.Metadata; 046import org.apache.camel.spi.RestConfiguration; 047import org.apache.camel.support.CamelContextHelper; 048import org.apache.camel.util.FileUtil; 049import org.apache.camel.util.ObjectHelper; 050import org.apache.camel.util.StringHelper; 051import org.apache.camel.util.URISupport; 052 053/** 054 * Defines a rest service using the rest-dsl 055 */ 056@Metadata(label = "rest") 057@XmlRootElement(name = "rest") 058@XmlAccessorType(XmlAccessType.FIELD) 059public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition> { 060 061 @XmlAttribute 062 private String path; 063 064 @XmlAttribute 065 private String tag; 066 067 @XmlAttribute 068 private String consumes; 069 070 @XmlAttribute 071 private String produces; 072 073 @XmlAttribute 074 @Metadata(defaultValue = "auto") 075 private RestBindingMode bindingMode; 076 077 @XmlAttribute 078 private Boolean skipBindingOnErrorCode; 079 080 @XmlAttribute 081 private Boolean clientRequestValidation; 082 083 @XmlAttribute 084 private Boolean enableCORS; 085 086 @XmlAttribute 087 private Boolean apiDocs; 088 089 @XmlElement(name = "securityDefinitions") // use the name swagger uses 090 private RestSecuritiesDefinition securityDefinitions; 091 092 @XmlElementRef 093 private List<VerbDefinition> verbs = new ArrayList<>(); 094 095 @Override 096 public String getShortName() { 097 return "rest"; 098 } 099 100 @Override 101 public String getLabel() { 102 return "rest"; 103 } 104 105 public String getPath() { 106 return path; 107 } 108 109 /** 110 * Path of the rest service, such as "/foo" 111 */ 112 public void setPath(String path) { 113 this.path = path; 114 } 115 116 public String getTag() { 117 return tag; 118 } 119 120 /** 121 * To configure a special tag for the operations within this rest 122 * definition. 123 */ 124 public void setTag(String tag) { 125 this.tag = tag; 126 } 127 128 public String getConsumes() { 129 return consumes; 130 } 131 132 /** 133 * To define the content type what the REST service consumes (accept as 134 * input), such as application/xml or application/json. This option will 135 * override what may be configured on a parent level 136 */ 137 public void setConsumes(String consumes) { 138 this.consumes = consumes; 139 } 140 141 public String getProduces() { 142 return produces; 143 } 144 145 /** 146 * To define the content type what the REST service produces (uses for 147 * output), such as application/xml or application/json This option will 148 * override what may be configured on a parent level 149 */ 150 public void setProduces(String produces) { 151 this.produces = produces; 152 } 153 154 public RestBindingMode getBindingMode() { 155 return bindingMode; 156 } 157 158 /** 159 * Sets the binding mode to use. This option will override what may be 160 * configured on a parent level 161 * <p/> 162 * The default value is auto 163 */ 164 public void setBindingMode(RestBindingMode bindingMode) { 165 this.bindingMode = bindingMode; 166 } 167 168 public List<VerbDefinition> getVerbs() { 169 return verbs; 170 } 171 172 public RestSecuritiesDefinition getSecurityDefinitions() { 173 return securityDefinitions; 174 } 175 176 /** 177 * Sets the security definitions such as Basic, OAuth2 etc. 178 */ 179 public void setSecurityDefinitions(RestSecuritiesDefinition securityDefinitions) { 180 this.securityDefinitions = securityDefinitions; 181 } 182 183 /** 184 * The HTTP verbs this REST service accepts and uses 185 */ 186 public void setVerbs(List<VerbDefinition> verbs) { 187 this.verbs = verbs; 188 } 189 190 public Boolean getSkipBindingOnErrorCode() { 191 return skipBindingOnErrorCode; 192 } 193 194 /** 195 * Whether to skip binding on output if there is a custom HTTP error code 196 * header. This allows to build custom error messages that do not bind to 197 * json / xml etc, as success messages otherwise will do. This option will 198 * override what may be configured on a parent level 199 */ 200 public void setSkipBindingOnErrorCode(Boolean skipBindingOnErrorCode) { 201 this.skipBindingOnErrorCode = skipBindingOnErrorCode; 202 } 203 204 public Boolean getClientRequestValidation() { 205 return clientRequestValidation; 206 } 207 208 /** 209 * Whether to enable validation of the client request to check whether the 210 * Content-Type and Accept headers from the client is supported by the 211 * Rest-DSL configuration of its consumes/produces settings. 212 * <p/> 213 * This can be turned on, to enable this check. In case of validation error, 214 * then HTTP Status codes 415 or 406 is returned. 215 * <p/> 216 * The default value is false. 217 */ 218 public void setClientRequestValidation(Boolean clientRequestValidation) { 219 this.clientRequestValidation = clientRequestValidation; 220 } 221 222 public Boolean getEnableCORS() { 223 return enableCORS; 224 } 225 226 /** 227 * Whether to enable CORS headers in the HTTP response. This option will 228 * override what may be configured on a parent level 229 * <p/> 230 * The default value is false. 231 */ 232 public void setEnableCORS(Boolean enableCORS) { 233 this.enableCORS = enableCORS; 234 } 235 236 public Boolean getApiDocs() { 237 return apiDocs; 238 } 239 240 /** 241 * Whether to include or exclude the VerbDefinition in API documentation. 242 * This option will override what may be configured on a parent level 243 * <p/> 244 * The default value is true. 245 */ 246 public void setApiDocs(Boolean apiDocs) { 247 this.apiDocs = apiDocs; 248 } 249 250 // Fluent API 251 // ------------------------------------------------------------------------- 252 253 /** 254 * To set the base path of this REST service 255 */ 256 public RestDefinition path(String path) { 257 setPath(path); 258 return this; 259 } 260 261 /** 262 * To set the tag to use of this REST service 263 */ 264 public RestDefinition tag(String tag) { 265 setTag(tag); 266 return this; 267 } 268 269 public RestDefinition get() { 270 return addVerb("get", null); 271 } 272 273 public RestDefinition get(String uri) { 274 return addVerb("get", uri); 275 } 276 277 public RestDefinition post() { 278 return addVerb("post", null); 279 } 280 281 public RestDefinition post(String uri) { 282 return addVerb("post", uri); 283 } 284 285 public RestDefinition put() { 286 return addVerb("put", null); 287 } 288 289 public RestDefinition put(String uri) { 290 return addVerb("put", uri); 291 } 292 293 public RestDefinition patch() { 294 return addVerb("patch", null); 295 } 296 297 public RestDefinition patch(String uri) { 298 return addVerb("patch", uri); 299 } 300 301 public RestDefinition delete() { 302 return addVerb("delete", null); 303 } 304 305 public RestDefinition delete(String uri) { 306 return addVerb("delete", uri); 307 } 308 309 public RestDefinition head() { 310 return addVerb("head", null); 311 } 312 313 public RestDefinition head(String uri) { 314 return addVerb("head", uri); 315 } 316 317 public RestDefinition verb(String verb) { 318 return addVerb(verb, null); 319 } 320 321 public RestDefinition verb(String verb, String uri) { 322 return addVerb(verb, uri); 323 } 324 325 @Override 326 public RestDefinition id(String id) { 327 if (getVerbs().isEmpty()) { 328 super.id(id); 329 } else { 330 // add on last verb as that is how the Java DSL works 331 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 332 verb.id(id); 333 } 334 335 return this; 336 } 337 338 @Override 339 public RestDefinition description(String text) { 340 if (getVerbs().isEmpty()) { 341 super.description(text); 342 } else { 343 // add on last verb as that is how the Java DSL works 344 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 345 verb.description(text); 346 } 347 348 return this; 349 } 350 351 @Override 352 public RestDefinition description(String id, String text, String lang) { 353 if (getVerbs().isEmpty()) { 354 super.description(id, text, lang); 355 } else { 356 // add on last verb as that is how the Java DSL works 357 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 358 verb.description(id, text, lang); 359 } 360 361 return this; 362 } 363 364 public RestDefinition consumes(String mediaType) { 365 if (getVerbs().isEmpty()) { 366 this.consumes = mediaType; 367 } else { 368 // add on last verb as that is how the Java DSL works 369 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 370 verb.setConsumes(mediaType); 371 } 372 373 return this; 374 } 375 376 public RestOperationParamDefinition param() { 377 if (getVerbs().isEmpty()) { 378 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 379 } 380 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 381 return param(verb); 382 } 383 384 public RestDefinition param(RestOperationParamDefinition param) { 385 if (getVerbs().isEmpty()) { 386 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 387 } 388 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 389 verb.getParams().add(param); 390 return this; 391 } 392 393 public RestDefinition params(List<RestOperationParamDefinition> params) { 394 if (getVerbs().isEmpty()) { 395 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 396 } 397 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 398 verb.getParams().addAll(params); 399 return this; 400 } 401 402 public RestOperationParamDefinition param(VerbDefinition verb) { 403 return new RestOperationParamDefinition(verb); 404 } 405 406 public RestDefinition responseMessage(RestOperationResponseMsgDefinition msg) { 407 if (getVerbs().isEmpty()) { 408 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 409 } 410 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 411 verb.getResponseMsgs().add(msg); 412 return this; 413 } 414 415 public RestOperationResponseMsgDefinition responseMessage() { 416 if (getVerbs().isEmpty()) { 417 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 418 } 419 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 420 return responseMessage(verb); 421 } 422 423 public RestOperationResponseMsgDefinition responseMessage(VerbDefinition verb) { 424 return new RestOperationResponseMsgDefinition(verb); 425 } 426 427 public RestDefinition responseMessages(List<RestOperationResponseMsgDefinition> msgs) { 428 if (getVerbs().isEmpty()) { 429 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 430 } 431 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 432 verb.getResponseMsgs().addAll(msgs); 433 return this; 434 } 435 436 /** 437 * To configure security definitions. 438 */ 439 public RestSecuritiesDefinition securityDefinitions() { 440 if (securityDefinitions == null) { 441 securityDefinitions = new RestSecuritiesDefinition(this); 442 } 443 return securityDefinitions; 444 } 445 446 public RestDefinition produces(String mediaType) { 447 if (getVerbs().isEmpty()) { 448 this.produces = mediaType; 449 } else { 450 // add on last verb as that is how the Java DSL works 451 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 452 verb.setProduces(mediaType); 453 } 454 455 return this; 456 } 457 458 public RestDefinition type(Class<?> classType) { 459 // add to last verb 460 if (getVerbs().isEmpty()) { 461 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 462 } 463 464 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 465 verb.setType(classType.getCanonicalName()); 466 return this; 467 } 468 469 public RestDefinition outType(Class<?> classType) { 470 // add to last verb 471 if (getVerbs().isEmpty()) { 472 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 473 } 474 475 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 476 verb.setOutType(classType.getCanonicalName()); 477 return this; 478 } 479 480 public RestDefinition bindingMode(RestBindingMode mode) { 481 if (getVerbs().isEmpty()) { 482 this.bindingMode = mode; 483 } else { 484 // add on last verb as that is how the Java DSL works 485 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 486 verb.setBindingMode(mode); 487 } 488 489 return this; 490 } 491 492 public RestDefinition bindingMode(String mode) { 493 return bindingMode(RestBindingMode.valueOf(mode.toLowerCase())); 494 } 495 496 public RestDefinition skipBindingOnErrorCode(boolean skipBindingOnErrorCode) { 497 if (getVerbs().isEmpty()) { 498 this.skipBindingOnErrorCode = skipBindingOnErrorCode; 499 } else { 500 // add on last verb as that is how the Java DSL works 501 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 502 verb.setSkipBindingOnErrorCode(skipBindingOnErrorCode); 503 } 504 505 return this; 506 } 507 508 public RestDefinition clientRequestValidation(boolean clientRequestValidation) { 509 if (getVerbs().isEmpty()) { 510 this.clientRequestValidation = clientRequestValidation; 511 } else { 512 // add on last verb as that is how the Java DSL works 513 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 514 verb.setClientRequestValidation(clientRequestValidation); 515 } 516 517 return this; 518 } 519 520 public RestDefinition enableCORS(boolean enableCORS) { 521 if (getVerbs().isEmpty()) { 522 this.enableCORS = enableCORS; 523 } else { 524 // add on last verb as that is how the Java DSL works 525 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 526 verb.setEnableCORS(enableCORS); 527 } 528 529 return this; 530 } 531 532 /** 533 * Include or exclude the current Rest Definition in API documentation. 534 * <p/> 535 * The default value is true. 536 */ 537 public RestDefinition apiDocs(Boolean apiDocs) { 538 if (getVerbs().isEmpty()) { 539 this.apiDocs = apiDocs; 540 } else { 541 // add on last verb as that is how the Java DSL works 542 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 543 verb.setApiDocs(apiDocs); 544 } 545 546 return this; 547 } 548 549 /** 550 * Sets the security setting for this verb. 551 */ 552 public RestDefinition security(String key) { 553 return security(key, null); 554 } 555 556 /** 557 * Sets the security setting for this verb. 558 */ 559 public RestDefinition security(String key, String scopes) { 560 // add to last verb 561 if (getVerbs().isEmpty()) { 562 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 563 } 564 565 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 566 SecurityDefinition sd = new SecurityDefinition(); 567 sd.setKey(key); 568 sd.setScopes(scopes); 569 verb.getSecurity().add(sd); 570 return this; 571 } 572 573 /** 574 * Routes directly to the given static endpoint. 575 * <p/> 576 * If you need additional routing capabilities, then use {@link #route()} 577 * instead. 578 * 579 * @param uri the uri of the endpoint 580 * @return this builder 581 */ 582 public RestDefinition to(String uri) { 583 // add to last verb 584 if (getVerbs().isEmpty()) { 585 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 586 } 587 588 ToDefinition to = new ToDefinition(uri); 589 590 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 591 verb.setTo(to); 592 return this; 593 } 594 595 /** 596 * Routes directly to the given dynamic endpoint. 597 * <p/> 598 * If you need additional routing capabilities, then use {@link #route()} 599 * instead. 600 * 601 * @param uri the uri of the endpoint 602 * @return this builder 603 */ 604 public RestDefinition toD(String uri) { 605 // add to last verb 606 if (getVerbs().isEmpty()) { 607 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 608 } 609 610 ToDynamicDefinition to = new ToDynamicDefinition(uri); 611 612 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 613 verb.setToD(to); 614 return this; 615 } 616 617 public RouteDefinition route() { 618 // add to last verb 619 if (getVerbs().isEmpty()) { 620 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 621 } 622 623 // link them together so we can navigate using Java DSL 624 RouteDefinition route = new RouteDefinition(); 625 route.setRestDefinition(this); 626 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 627 verb.setRoute(route); 628 return route; 629 } 630 631 /** 632 * Build the from endpoint uri for the verb 633 */ 634 public String buildFromUri(VerbDefinition verb) { 635 return "rest:" + verb.asVerb() + ":" + buildUri(verb); 636 } 637 638 // Implementation 639 // ------------------------------------------------------------------------- 640 641 private RestDefinition addVerb(String verb, String uri) { 642 VerbDefinition answer; 643 644 if ("get".equals(verb)) { 645 answer = new GetVerbDefinition(); 646 } else if ("post".equals(verb)) { 647 answer = new PostVerbDefinition(); 648 } else if ("delete".equals(verb)) { 649 answer = new DeleteVerbDefinition(); 650 } else if ("head".equals(verb)) { 651 answer = new HeadVerbDefinition(); 652 } else if ("put".equals(verb)) { 653 answer = new PutVerbDefinition(); 654 } else if ("patch".equals(verb)) { 655 answer = new PatchVerbDefinition(); 656 } else { 657 answer = new VerbDefinition(); 658 answer.setMethod(verb); 659 } 660 getVerbs().add(answer); 661 answer.setRest(this); 662 answer.setUri(uri); 663 return this; 664 } 665 666 /** 667 * Transforms this REST definition into a list of 668 * {@link org.apache.camel.model.RouteDefinition} which Camel routing engine 669 * can add and run. This allows us to define REST services using this REST 670 * DSL and turn those into regular Camel routes. 671 * 672 * @param camelContext The Camel context 673 */ 674 public List<RouteDefinition> asRouteDefinition(CamelContext camelContext) { 675 ObjectHelper.notNull(camelContext, "CamelContext"); 676 677 // sanity check this rest definition do not have duplicates 678 validateUniquePaths(); 679 680 List<RouteDefinition> answer = new ArrayList<>(); 681 if (camelContext.getRestConfigurations().isEmpty()) { 682 // make sure to initialize a rest configuration when its empty 683 // lookup a global which may have been setup via camel-spring-boot 684 // etc 685 RestConfiguration conf = CamelContextHelper.lookup(camelContext, RestConstants.DEFAULT_REST_CONFIGURATION_ID, RestConfiguration.class); 686 if (conf == null) { 687 conf = CamelContextHelper.findByType(camelContext, RestConfiguration.class); 688 } 689 if (conf != null) { 690 camelContext.setRestConfiguration(conf); 691 } else { 692 camelContext.setRestConfiguration(new RestConfiguration()); 693 } 694 } 695 for (RestConfiguration config : camelContext.getRestConfigurations()) { 696 addRouteDefinition(camelContext, answer, config.getComponent(), config.getProducerComponent()); 697 } 698 return answer; 699 } 700 701 protected void validateUniquePaths() { 702 Set<String> paths = new HashSet<>(); 703 for (VerbDefinition verb : verbs) { 704 String path = verb.asVerb(); 705 if (verb.getUri() != null) { 706 path += ":" + verb.getUri(); 707 } 708 if (!paths.add(path)) { 709 throw new IllegalArgumentException("Duplicate verb detected in rest-dsl: " + path); 710 } 711 } 712 } 713 714 /** 715 * Transforms the rest api configuration into a 716 * {@link org.apache.camel.model.RouteDefinition} which Camel routing engine 717 * uses to service the rest api docs. 718 */ 719 public static RouteDefinition asRouteApiDefinition(CamelContext camelContext, RestConfiguration configuration) { 720 RouteDefinition answer = new RouteDefinition(); 721 722 // create the from endpoint uri which is using the rest-api component 723 String from = "rest-api:" + configuration.getApiContextPath(); 724 725 // append options 726 Map<String, Object> options = new HashMap<>(); 727 728 String routeId = configuration.getApiContextRouteId(); 729 if (routeId == null) { 730 routeId = answer.idOrCreate(camelContext.adapt(ExtendedCamelContext.class).getNodeIdFactory()); 731 } 732 options.put("routeId", routeId); 733 if (configuration.getComponent() != null && !configuration.getComponent().isEmpty()) { 734 options.put("consumerComponentName", configuration.getComponent()); 735 } 736 if (configuration.getComponent() != null && !configuration.getComponent().isEmpty()) { 737 options.put("producerComponentName", configuration.getProducerComponent()); 738 } 739 if (configuration.getApiContextIdPattern() != null) { 740 options.put("contextIdPattern", configuration.getApiContextIdPattern()); 741 } 742 743 if (!options.isEmpty()) { 744 String query; 745 try { 746 query = URISupport.createQueryString(options); 747 } catch (URISyntaxException e) { 748 throw RuntimeCamelException.wrapRuntimeCamelException(e); 749 } 750 from = from + "?" + query; 751 } 752 753 // we use the same uri as the producer (so we have a little route for 754 // the rest api) 755 String to = from; 756 answer.fromRest(from); 757 answer.id(routeId); 758 answer.to(to); 759 760 return answer; 761 } 762 763 @SuppressWarnings("rawtypes") 764 private void addRouteDefinition(CamelContext camelContext, List<RouteDefinition> answer, String component, String producerComponent) { 765 for (VerbDefinition verb : getVerbs()) { 766 // either the verb has a singular to or a embedded route 767 RouteDefinition route = verb.getRoute(); 768 if (route == null) { 769 // it was a singular to, so add a new route and add the singular 770 // to as output to this route 771 route = new RouteDefinition(); 772 ProcessorDefinition def = verb.getTo() != null ? verb.getTo() : verb.getToD(); 773 route.getOutputs().add(def); 774 } 775 776 // ensure property placeholders is resolved on the verb 777 try { 778 ProcessorDefinitionHelper.resolvePropertyPlaceholders(camelContext, verb); 779 for (RestOperationParamDefinition param : verb.getParams()) { 780 ProcessorDefinitionHelper.resolvePropertyPlaceholders(camelContext, param); 781 } 782 } catch (Exception e) { 783 throw RuntimeCamelException.wrapRuntimeCamelException(e); 784 } 785 786 // add the binding 787 RestBindingDefinition binding = new RestBindingDefinition(); 788 binding.setComponent(component); 789 binding.setType(verb.getType()); 790 binding.setOutType(verb.getOutType()); 791 // verb takes precedence over configuration on rest 792 if (verb.getConsumes() != null) { 793 binding.setConsumes(verb.getConsumes()); 794 } else { 795 binding.setConsumes(getConsumes()); 796 } 797 if (verb.getProduces() != null) { 798 binding.setProduces(verb.getProduces()); 799 } else { 800 binding.setProduces(getProduces()); 801 } 802 if (verb.getBindingMode() != null) { 803 binding.setBindingMode(verb.getBindingMode()); 804 } else { 805 binding.setBindingMode(getBindingMode()); 806 } 807 if (verb.getSkipBindingOnErrorCode() != null) { 808 binding.setSkipBindingOnErrorCode(verb.getSkipBindingOnErrorCode()); 809 } else { 810 binding.setSkipBindingOnErrorCode(getSkipBindingOnErrorCode()); 811 } 812 if (verb.getClientRequestValidation() != null) { 813 binding.setClientRequestValidation(verb.getClientRequestValidation()); 814 } else { 815 binding.setClientRequestValidation(getClientRequestValidation()); 816 } 817 if (verb.getEnableCORS() != null) { 818 binding.setEnableCORS(verb.getEnableCORS()); 819 } else { 820 binding.setEnableCORS(getEnableCORS()); 821 } 822 for (RestOperationParamDefinition param : verb.getParams()) { 823 // register all the default values for the query parameters 824 if (RestParamType.query == param.getType() && ObjectHelper.isNotEmpty(param.getDefaultValue())) { 825 binding.addDefaultValue(param.getName(), param.getDefaultValue()); 826 } 827 // register which parameters are required 828 if (param.getRequired()) { 829 if (RestParamType.query == param.getType()) { 830 binding.addRequiredQueryParameter(param.getName()); 831 } else if (RestParamType.header == param.getType()) { 832 binding.addRequiredHeader(param.getName()); 833 } else if (RestParamType.body == param.getType()) { 834 binding.setRequiredBody(true); 835 } 836 } 837 } 838 839 route.setRestBindingDefinition(binding); 840 841 // create the from endpoint uri which is using the rest component 842 String from = buildFromUri(verb); 843 844 // append options 845 Map<String, Object> options = new HashMap<>(); 846 // verb takes precedence over configuration on rest 847 if (verb.getConsumes() != null) { 848 options.put("consumes", verb.getConsumes()); 849 } else if (getConsumes() != null) { 850 options.put("consumes", getConsumes()); 851 } 852 if (verb.getProduces() != null) { 853 options.put("produces", verb.getProduces()); 854 } else if (getProduces() != null) { 855 options.put("produces", getProduces()); 856 } 857 858 // append optional type binding information 859 String inType = binding.getType(); 860 if (inType != null) { 861 options.put("inType", inType); 862 } 863 String outType = binding.getOutType(); 864 if (outType != null) { 865 options.put("outType", outType); 866 } 867 868 if (component != null && !component.isEmpty()) { 869 options.put("consumerComponentName", component); 870 } 871 if (producerComponent != null && !producerComponent.isEmpty()) { 872 options.put("producerComponentName", producerComponent); 873 } 874 875 // include optional description, which we favor from 1) to/route 876 // description 2) verb description 3) rest description 877 // this allows end users to define general descriptions and override 878 // then per to/route or verb 879 String description = verb.getTo() != null ? verb.getTo().getDescriptionText() : route.getDescriptionText(); 880 if (description == null) { 881 description = verb.getDescriptionText(); 882 } 883 if (description == null) { 884 description = getDescriptionText(); 885 } 886 if (description != null) { 887 options.put("description", description); 888 } 889 890 if (!options.isEmpty()) { 891 String query; 892 try { 893 query = URISupport.createQueryString(options); 894 } catch (URISyntaxException e) { 895 throw RuntimeCamelException.wrapRuntimeCamelException(e); 896 } 897 from = from + "?" + query; 898 } 899 900 String path = getPath(); 901 String s1 = FileUtil.stripTrailingSeparator(path); 902 String s2 = FileUtil.stripLeadingSeparator(verb.getUri()); 903 String allPath; 904 if (s1 != null && s2 != null) { 905 allPath = s1 + "/" + s2; 906 } else if (path != null) { 907 allPath = path; 908 } else { 909 allPath = verb.getUri(); 910 } 911 912 // each {} is a parameter (url templating) 913 if (allPath != null) { 914 String[] arr = allPath.split("\\/"); 915 for (String a : arr) { 916 // need to resolve property placeholders first 917 try { 918 a = camelContext.resolvePropertyPlaceholders(a); 919 } catch (Exception e) { 920 throw RuntimeCamelException.wrapRuntimeCamelException(e); 921 } 922 923 Matcher m = Pattern.compile("\\{(.*?)\\}").matcher(a); 924 while (m.find()) { 925 String key = m.group(1); 926 // merge if exists 927 boolean found = false; 928 for (RestOperationParamDefinition param : verb.getParams()) { 929 // name is mandatory 930 String name = param.getName(); 931 StringHelper.notEmpty(name, "parameter name"); 932 // need to resolve property placeholders first 933 try { 934 name = camelContext.resolvePropertyPlaceholders(name); 935 } catch (Exception e) { 936 throw RuntimeCamelException.wrapRuntimeCamelException(e); 937 } 938 if (name.equalsIgnoreCase(key)) { 939 param.type(RestParamType.path); 940 found = true; 941 break; 942 } 943 } 944 if (!found) { 945 param(verb).name(key).type(RestParamType.path).endParam(); 946 } 947 } 948 } 949 } 950 951 if (verb.getType() != null) { 952 String bodyType = verb.getType(); 953 if (bodyType.endsWith("[]")) { 954 bodyType = "List[" + bodyType.substring(0, bodyType.length() - 2) + "]"; 955 } 956 RestOperationParamDefinition param = findParam(verb, RestParamType.body.name()); 957 if (param == null) { 958 // must be body type and set the model class as data type 959 param(verb).name(RestParamType.body.name()).type(RestParamType.body).dataType(bodyType).endParam(); 960 } else { 961 // must be body type and set the model class as data type 962 param.type(RestParamType.body).dataType(bodyType); 963 } 964 } 965 966 // the route should be from this rest endpoint 967 route.fromRest(from); 968 route.setRestDefinition(this); 969 answer.add(route); 970 } 971 } 972 973 private String buildUri(VerbDefinition verb) { 974 if (path != null && verb.getUri() != null) { 975 return path + ":" + verb.getUri(); 976 } else if (path != null) { 977 return path; 978 } else if (verb.getUri() != null) { 979 return verb.getUri(); 980 } else { 981 return ""; 982 } 983 } 984 985 private RestOperationParamDefinition findParam(VerbDefinition verb, String name) { 986 for (RestOperationParamDefinition param : verb.getParams()) { 987 if (name.equals(param.getName())) { 988 return param; 989 } 990 } 991 return null; 992 } 993 994}