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