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