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.List; 023import java.util.Map; 024import javax.xml.bind.annotation.XmlAccessType; 025import javax.xml.bind.annotation.XmlAccessorType; 026import javax.xml.bind.annotation.XmlAttribute; 027import javax.xml.bind.annotation.XmlElementRef; 028import javax.xml.bind.annotation.XmlRootElement; 029 030import org.apache.camel.CamelContext; 031import org.apache.camel.model.OptionalIdentifiedDefinition; 032import org.apache.camel.model.RouteDefinition; 033import org.apache.camel.model.ToDefinition; 034import org.apache.camel.spi.Metadata; 035import org.apache.camel.util.ObjectHelper; 036import org.apache.camel.util.URISupport; 037 038/** 039 * Defines a rest service using the rest-dsl 040 */ 041@Metadata(label = "rest") 042@XmlRootElement(name = "rest") 043@XmlAccessorType(XmlAccessType.FIELD) 044public class RestDefinition extends OptionalIdentifiedDefinition<RestDefinition> { 045 046 @XmlAttribute 047 private String path; 048 049 @XmlAttribute 050 private String consumes; 051 052 @XmlAttribute 053 private String produces; 054 055 @XmlAttribute @Metadata(defaultValue = "auto") 056 private RestBindingMode bindingMode; 057 058 @XmlAttribute 059 private Boolean skipBindingOnErrorCode; 060 061 @XmlAttribute 062 private Boolean enableCORS; 063 064 @XmlElementRef 065 private List<VerbDefinition> verbs = new ArrayList<VerbDefinition>(); 066 067 @Override 068 public String getLabel() { 069 return "rest"; 070 } 071 072 public String getPath() { 073 return path; 074 } 075 076 /** 077 * Path of the rest service, such as "/foo" 078 */ 079 public void setPath(String path) { 080 this.path = path; 081 } 082 083 public String getConsumes() { 084 return consumes; 085 } 086 087 /** 088 * To define the content type what the REST service consumes (accept as input), such as application/xml or application/json. 089 * This option will override what may be configured on a parent level 090 */ 091 public void setConsumes(String consumes) { 092 this.consumes = consumes; 093 } 094 095 public String getProduces() { 096 return produces; 097 } 098 099 /** 100 * To define the content type what the REST service produces (uses for output), such as application/xml or application/json 101 * This option will override what may be configured on a parent level 102 */ 103 public void setProduces(String produces) { 104 this.produces = produces; 105 } 106 107 public RestBindingMode getBindingMode() { 108 return bindingMode; 109 } 110 111 /** 112 * Sets the binding mode to use. 113 * This option will override what may be configured on a parent level 114 * <p/> 115 * The default value is auto 116 */ 117 public void setBindingMode(RestBindingMode bindingMode) { 118 this.bindingMode = bindingMode; 119 } 120 121 public List<VerbDefinition> getVerbs() { 122 return verbs; 123 } 124 125 /** 126 * The HTTP verbs this REST service accepts and uses 127 */ 128 public void setVerbs(List<VerbDefinition> verbs) { 129 this.verbs = verbs; 130 } 131 132 public Boolean getSkipBindingOnErrorCode() { 133 return skipBindingOnErrorCode; 134 } 135 136 /** 137 * Whether to skip binding on output if there is a custom HTTP error code header. 138 * This allows to build custom error messages that do not bind to json / xml etc, as success messages otherwise will do. 139 * This option will override what may be configured on a parent level 140 */ 141 public void setSkipBindingOnErrorCode(Boolean skipBindingOnErrorCode) { 142 this.skipBindingOnErrorCode = skipBindingOnErrorCode; 143 } 144 145 public Boolean getEnableCORS() { 146 return enableCORS; 147 } 148 149 /** 150 * Whether to enable CORS headers in the HTTP response. 151 * This option will override what may be configured on a parent level 152 * <p/> 153 * The default value is false. 154 */ 155 public void setEnableCORS(Boolean enableCORS) { 156 this.enableCORS = enableCORS; 157 } 158 159 // Fluent API 160 //------------------------------------------------------------------------- 161 162 163 /** 164 * To set the base path of this REST service 165 */ 166 public RestDefinition path(String path) { 167 setPath(path); 168 return this; 169 } 170 171 public RestDefinition get() { 172 return addVerb("get", null); 173 } 174 175 public RestDefinition get(String uri) { 176 return addVerb("get", uri); 177 } 178 179 public RestDefinition post() { 180 return addVerb("post", null); 181 } 182 183 public RestDefinition post(String uri) { 184 return addVerb("post", uri); 185 } 186 187 public RestDefinition put() { 188 return addVerb("put", null); 189 } 190 191 public RestDefinition put(String uri) { 192 return addVerb("put", uri); 193 } 194 195 public RestDefinition delete() { 196 return addVerb("delete", null); 197 } 198 199 public RestDefinition delete(String uri) { 200 return addVerb("delete", uri); 201 } 202 203 public RestDefinition head() { 204 return addVerb("head", null); 205 } 206 207 public RestDefinition head(String uri) { 208 return addVerb("head", uri); 209 } 210 211 public RestDefinition verb(String verb) { 212 return addVerb(verb, null); 213 } 214 215 public RestDefinition verb(String verb, String uri) { 216 return addVerb(verb, uri); 217 } 218 219 @Override 220 public RestDefinition id(String id) { 221 if (getVerbs().isEmpty()) { 222 super.id(id); 223 } else { 224 // add on last verb as that is how the Java DSL works 225 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 226 verb.id(id); 227 } 228 229 return this; 230 } 231 232 @Override 233 public RestDefinition description(String text) { 234 if (getVerbs().isEmpty()) { 235 super.description(text); 236 } else { 237 // add on last verb as that is how the Java DSL works 238 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 239 verb.description(text); 240 } 241 242 return this; 243 } 244 245 @Override 246 public RestDefinition description(String id, String text, String lang) { 247 if (getVerbs().isEmpty()) { 248 super.description(id, text, lang); 249 } else { 250 // add on last verb as that is how the Java DSL works 251 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 252 verb.description(id, text, lang); 253 } 254 255 return this; 256 } 257 258 public RestDefinition consumes(String mediaType) { 259 if (getVerbs().isEmpty()) { 260 this.consumes = mediaType; 261 } else { 262 // add on last verb as that is how the Java DSL works 263 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 264 verb.setConsumes(mediaType); 265 } 266 267 return this; 268 } 269 270 public RestDefinition produces(String mediaType) { 271 if (getVerbs().isEmpty()) { 272 this.produces = mediaType; 273 } else { 274 // add on last verb as that is how the Java DSL works 275 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 276 verb.setProduces(mediaType); 277 } 278 279 return this; 280 } 281 282 public RestDefinition type(Class<?> classType) { 283 // add to last verb 284 if (getVerbs().isEmpty()) { 285 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 286 } 287 288 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 289 verb.setType(classType.getCanonicalName()); 290 return this; 291 } 292 293 public RestDefinition typeList(Class<?> classType) { 294 // add to last verb 295 if (getVerbs().isEmpty()) { 296 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 297 } 298 299 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 300 // list should end with [] to indicate array 301 verb.setType(classType.getCanonicalName() + "[]"); 302 return this; 303 } 304 305 public RestDefinition outType(Class<?> classType) { 306 // add to last verb 307 if (getVerbs().isEmpty()) { 308 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 309 } 310 311 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 312 verb.setOutType(classType.getCanonicalName()); 313 return this; 314 } 315 316 public RestDefinition outTypeList(Class<?> classType) { 317 // add to last verb 318 if (getVerbs().isEmpty()) { 319 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 320 } 321 322 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 323 // list should end with [] to indicate array 324 verb.setOutType(classType.getCanonicalName() + "[]"); 325 return this; 326 } 327 328 public RestDefinition bindingMode(RestBindingMode mode) { 329 if (getVerbs().isEmpty()) { 330 this.bindingMode = mode; 331 } else { 332 // add on last verb as that is how the Java DSL works 333 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 334 verb.setBindingMode(mode); 335 } 336 337 return this; 338 } 339 340 public RestDefinition skipBindingOnErrorCode(boolean skipBindingOnErrorCode) { 341 if (getVerbs().isEmpty()) { 342 this.skipBindingOnErrorCode = skipBindingOnErrorCode; 343 } else { 344 // add on last verb as that is how the Java DSL works 345 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 346 verb.setSkipBindingOnErrorCode(skipBindingOnErrorCode); 347 } 348 349 return this; 350 } 351 352 public RestDefinition enableCORS(boolean enableCORS) { 353 if (getVerbs().isEmpty()) { 354 this.enableCORS = enableCORS; 355 } else { 356 // add on last verb as that is how the Java DSL works 357 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 358 verb.setEnableCORS(enableCORS); 359 } 360 361 return this; 362 } 363 364 /** 365 * Routes directly to the given endpoint. 366 * <p/> 367 * If you need additional routing capabilities, then use {@link #route()} instead. 368 * 369 * @param uri the uri of the endpoint 370 * @return this builder 371 */ 372 public RestDefinition to(String uri) { 373 // add to last verb 374 if (getVerbs().isEmpty()) { 375 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 376 } 377 378 ToDefinition to = new ToDefinition(uri); 379 380 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 381 verb.setTo(to); 382 return this; 383 } 384 385 public RouteDefinition route() { 386 // add to last verb 387 if (getVerbs().isEmpty()) { 388 throw new IllegalArgumentException("Must add verb first, such as get/post/delete"); 389 } 390 391 // link them together so we can navigate using Java DSL 392 RouteDefinition route = new RouteDefinition(); 393 route.setRestDefinition(this); 394 VerbDefinition verb = getVerbs().get(getVerbs().size() - 1); 395 verb.setRoute(route); 396 return route; 397 } 398 399 // Implementation 400 //------------------------------------------------------------------------- 401 402 private RestDefinition addVerb(String verb, String uri) { 403 VerbDefinition answer; 404 405 if ("get".equals(verb)) { 406 answer = new GetVerbDefinition(); 407 } else if ("post".equals(verb)) { 408 answer = new PostVerbDefinition(); 409 } else if ("delete".equals(verb)) { 410 answer = new DeleteVerbDefinition(); 411 } else if ("head".equals(verb)) { 412 answer = new HeadVerbDefinition(); 413 } else if ("put".equals(verb)) { 414 answer = new PutVerbDefinition(); 415 } else { 416 answer = new VerbDefinition(); 417 answer.setMethod(verb); 418 } 419 420 answer.setRest(this); 421 answer.setUri(uri); 422 getVerbs().add(answer); 423 return this; 424 } 425 426 /** 427 * Transforms this REST definition into a list of {@link org.apache.camel.model.RouteDefinition} which 428 * Camel routing engine can add and run. This allows us to define REST services using this 429 * REST DSL and turn those into regular Camel routes. 430 */ 431 public List<RouteDefinition> asRouteDefinition(CamelContext camelContext) { 432 List<RouteDefinition> answer = new ArrayList<RouteDefinition>(); 433 434 for (VerbDefinition verb : getVerbs()) { 435 // either the verb has a singular to or a embedded route 436 RouteDefinition route = verb.getRoute(); 437 if (route == null) { 438 // it was a singular to, so add a new route and add the singular 439 // to as output to this route 440 route = new RouteDefinition(); 441 route.getOutputs().add(verb.getTo()); 442 } 443 444 // add the binding 445 RestBindingDefinition binding = new RestBindingDefinition(); 446 binding.setType(verb.getType()); 447 binding.setOutType(verb.getOutType()); 448 // verb takes precedence over configuration on rest 449 if (verb.getConsumes() != null) { 450 binding.setConsumes(verb.getConsumes()); 451 } else { 452 binding.setConsumes(getConsumes()); 453 } 454 if (verb.getProduces() != null) { 455 binding.setProduces(verb.getProduces()); 456 } else { 457 binding.setProduces(getProduces()); 458 } 459 if (verb.getBindingMode() != null) { 460 binding.setBindingMode(verb.getBindingMode()); 461 } else { 462 binding.setBindingMode(getBindingMode()); 463 } 464 if (verb.getSkipBindingOnErrorCode() != null) { 465 binding.setSkipBindingOnErrorCode(verb.getSkipBindingOnErrorCode()); 466 } else { 467 binding.setSkipBindingOnErrorCode(getSkipBindingOnErrorCode()); 468 } 469 if (verb.getEnableCORS() != null) { 470 binding.setEnableCORS(verb.getEnableCORS()); 471 } else { 472 binding.setEnableCORS(getEnableCORS()); 473 } 474 route.getOutputs().add(0, binding); 475 476 // create the from endpoint uri which is using the rest component 477 String from = "rest:" + verb.asVerb() + ":" + buildUri(verb); 478 479 // append options 480 Map<String, Object> options = new HashMap<String, Object>(); 481 // verb takes precedence over configuration on rest 482 if (verb.getConsumes() != null) { 483 options.put("consumes", verb.getConsumes()); 484 } else if (getConsumes() != null) { 485 options.put("consumes", getConsumes()); 486 } 487 if (verb.getProduces() != null) { 488 options.put("produces", verb.getProduces()); 489 } else if (getProduces() != null) { 490 options.put("produces", getProduces()); 491 } 492 493 // append optional type binding information 494 String inType = binding.getType(); 495 if (inType != null) { 496 options.put("inType", inType); 497 } 498 String outType = binding.getOutType(); 499 if (outType != null) { 500 options.put("outType", outType); 501 } 502 // if no route id has been set, then use the verb id as route id 503 if (!route.hasCustomIdAssigned()) { 504 // use id of verb as route id 505 String id = verb.getId(); 506 if (id != null) { 507 route.setId(id); 508 } 509 } 510 String routeId = route.idOrCreate(camelContext.getNodeIdFactory()); 511 options.put("routeId", routeId); 512 513 // include optional description, which we favor from 1) to/route description 2) verb description 3) rest description 514 // this allows end users to define general descriptions and override then per to/route or verb 515 String description = verb.getTo() != null ? verb.getTo().getDescriptionText() : route.getDescriptionText(); 516 if (description == null) { 517 description = verb.getDescriptionText(); 518 } 519 if (description == null) { 520 description = getDescriptionText(); 521 } 522 if (description != null) { 523 options.put("description", description); 524 } 525 526 if (!options.isEmpty()) { 527 String query; 528 try { 529 query = URISupport.createQueryString(options); 530 } catch (URISyntaxException e) { 531 throw ObjectHelper.wrapRuntimeCamelException(e); 532 } 533 from = from + "?" + query; 534 } 535 536 // the route should be from this rest endpoint 537 route.fromRest(from); 538 route.setRestDefinition(this); 539 answer.add(route); 540 } 541 542 return answer; 543 } 544 545 private String buildUri(VerbDefinition verb) { 546 if (path != null && verb.getUri() != null) { 547 return path + ":" + verb.getUri(); 548 } else if (path != null) { 549 return path; 550 } else if (verb.getUri() != null) { 551 return verb.getUri(); 552 } else { 553 return ""; 554 } 555 } 556 557}