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