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.component.rest; 018 019import java.util.Map; 020import java.util.Set; 021 022import org.apache.camel.Component; 023import org.apache.camel.Consumer; 024import org.apache.camel.ExchangePattern; 025import org.apache.camel.NoFactoryAvailableException; 026import org.apache.camel.NoSuchBeanException; 027import org.apache.camel.Processor; 028import org.apache.camel.Producer; 029import org.apache.camel.impl.DefaultEndpoint; 030import org.apache.camel.model.rest.RestBindingMode; 031import org.apache.camel.processor.UnmarshalProcessor; 032import org.apache.camel.spi.FactoryFinder; 033import org.apache.camel.spi.Metadata; 034import org.apache.camel.spi.RestConfiguration; 035import org.apache.camel.spi.RestConsumerFactory; 036import org.apache.camel.spi.RestProducerFactory; 037import org.apache.camel.spi.UriEndpoint; 038import org.apache.camel.spi.UriParam; 039import org.apache.camel.spi.UriPath; 040import org.apache.camel.util.HostUtils; 041import org.apache.camel.util.ObjectHelper; 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044 045/** 046 * The rest component is used for either hosting REST services (consumer) or calling external REST services (producer). 047 */ 048@UriEndpoint(firstVersion = "2.14.0", scheme = "rest", title = "REST", syntax = "rest:method:path:uriTemplate", label = "core,rest", lenientProperties = true) 049public class RestEndpoint extends DefaultEndpoint { 050 051 public static final String[] DEFAULT_REST_CONSUMER_COMPONENTS = new String[]{"coap", "netty-http", "netty4-http", "jetty", "restlet", "servlet", "spark-java", "undertow"}; 052 public static final String[] DEFAULT_REST_PRODUCER_COMPONENTS = new String[]{"http", "http4", "netty4-http", "jetty", "restlet", "undertow"}; 053 public static final String DEFAULT_API_COMPONENT_NAME = "swagger"; 054 public static final String RESOURCE_PATH = "META-INF/services/org/apache/camel/rest/"; 055 056 private static final Logger LOG = LoggerFactory.getLogger(RestEndpoint.class); 057 058 @UriPath(label = "common", enums = "get,post,put,delete,patch,head,trace,connect,options") @Metadata(required = "true") 059 private String method; 060 @UriPath(label = "common") @Metadata(required = "true") 061 private String path; 062 @UriPath(label = "common") 063 private String uriTemplate; 064 @UriParam(label = "common") 065 private String consumes; 066 @UriParam(label = "common") 067 private String produces; 068 @UriParam(label = "common") 069 private String componentName; 070 @UriParam(label = "common") 071 private String inType; 072 @UriParam(label = "common") 073 private String outType; 074 @UriParam(label = "common") 075 private String routeId; 076 @UriParam(label = "consumer") 077 private String description; 078 @UriParam(label = "producer") 079 private String apiDoc; 080 @UriParam(label = "producer") 081 private String host; 082 @UriParam(label = "producer", multiValue = true) 083 private String queryParameters; 084 @UriParam(label = "producer") 085 private RestBindingMode bindingMode; 086 087 private Map<String, Object> parameters; 088 089 public RestEndpoint(String endpointUri, RestComponent component) { 090 super(endpointUri, component); 091 setExchangePattern(ExchangePattern.InOut); 092 } 093 094 @Override 095 public RestComponent getComponent() { 096 return (RestComponent) super.getComponent(); 097 } 098 099 public String getMethod() { 100 return method; 101 } 102 103 /** 104 * HTTP method to use. 105 */ 106 public void setMethod(String method) { 107 this.method = method; 108 } 109 110 public String getPath() { 111 return path; 112 } 113 114 /** 115 * The base path 116 */ 117 public void setPath(String path) { 118 this.path = path; 119 } 120 121 public String getUriTemplate() { 122 return uriTemplate; 123 } 124 125 /** 126 * The uri template 127 */ 128 public void setUriTemplate(String uriTemplate) { 129 this.uriTemplate = uriTemplate; 130 } 131 132 public String getConsumes() { 133 return consumes; 134 } 135 136 /** 137 * Media type such as: 'text/xml', or 'application/json' this REST service accepts. 138 * By default we accept all kinds of types. 139 */ 140 public void setConsumes(String consumes) { 141 this.consumes = consumes; 142 } 143 144 public String getProduces() { 145 return produces; 146 } 147 148 /** 149 * Media type such as: 'text/xml', or 'application/json' this REST service returns. 150 */ 151 public void setProduces(String produces) { 152 this.produces = produces; 153 } 154 155 public String getComponentName() { 156 return componentName; 157 } 158 159 /** 160 * The Camel Rest component to use for the REST transport, such as restlet, spark-rest. 161 * If no component has been explicit configured, then Camel will lookup if there is a Camel component 162 * that integrates with the Rest DSL, or if a org.apache.camel.spi.RestConsumerFactory is registered in the registry. 163 * If either one is found, then that is being used. 164 */ 165 public void setComponentName(String componentName) { 166 this.componentName = componentName; 167 } 168 169 public String getInType() { 170 return inType; 171 } 172 173 /** 174 * To declare the incoming POJO binding type as a FQN class name 175 */ 176 public void setInType(String inType) { 177 this.inType = inType; 178 } 179 180 public String getOutType() { 181 return outType; 182 } 183 184 /** 185 * To declare the outgoing POJO binding type as a FQN class name 186 */ 187 public void setOutType(String outType) { 188 this.outType = outType; 189 } 190 191 public String getRouteId() { 192 return routeId; 193 } 194 195 /** 196 * Name of the route this REST services creates 197 */ 198 public void setRouteId(String routeId) { 199 this.routeId = routeId; 200 } 201 202 public String getDescription() { 203 return description; 204 } 205 206 /** 207 * Human description to document this REST service 208 */ 209 public void setDescription(String description) { 210 this.description = description; 211 } 212 213 public Map<String, Object> getParameters() { 214 return parameters; 215 } 216 217 /** 218 * Additional parameters to configure the consumer of the REST transport for this REST service 219 */ 220 public void setParameters(Map<String, Object> parameters) { 221 this.parameters = parameters; 222 } 223 224 public String getApiDoc() { 225 return apiDoc; 226 } 227 228 /** 229 * The swagger api doc resource to use. 230 * The resource is loaded from classpath by default and must be in JSon format. 231 */ 232 public void setApiDoc(String apiDoc) { 233 this.apiDoc = apiDoc; 234 } 235 236 public String getHost() { 237 return host; 238 } 239 240 /** 241 * Host and port of HTTP service to use (override host in swagger schema) 242 */ 243 public void setHost(String host) { 244 this.host = host; 245 } 246 247 public String getQueryParameters() { 248 return queryParameters; 249 } 250 251 /** 252 * Query parameters for the HTTP service to call 253 */ 254 public void setQueryParameters(String queryParameters) { 255 this.queryParameters = queryParameters; 256 } 257 258 public RestBindingMode getBindingMode() { 259 return bindingMode; 260 } 261 262 /** 263 * Configures the binding mode for the producer. If set to anything 264 * other than 'off' the producer will try to convert the body of 265 * the incoming message from inType to the json or xml, and the 266 * response from json or xml to outType. 267 */ 268 public void setBindingMode(final RestBindingMode bindingMode) { 269 this.bindingMode = bindingMode; 270 } 271 272 @Override 273 public Producer createProducer() throws Exception { 274 if (ObjectHelper.isEmpty(host)) { 275 // hostname must be provided 276 throw new IllegalArgumentException("Hostname must be configured on either restConfiguration" 277 + " or in the rest endpoint uri as a query parameter with name host, eg rest:" + method + ":" + path + "?host=someserver"); 278 } 279 280 RestProducerFactory apiDocFactory = null; 281 RestProducerFactory factory = null; 282 283 if (apiDoc != null) { 284 LOG.debug("Discovering camel-swagger-java on classpath for using api-doc: {}", apiDoc); 285 // lookup on classpath using factory finder to automatic find it (just add camel-swagger-java to classpath etc) 286 try { 287 FactoryFinder finder = getCamelContext().getFactoryFinder(RESOURCE_PATH); 288 Object instance = finder.newInstance(DEFAULT_API_COMPONENT_NAME); 289 if (instance instanceof RestProducerFactory) { 290 // this factory from camel-swagger-java will facade the http component in use 291 apiDocFactory = (RestProducerFactory) instance; 292 } 293 parameters.put("apiDoc", apiDoc); 294 } catch (NoFactoryAvailableException e) { 295 throw new IllegalStateException("Cannot find camel-swagger-java on classpath to use with api-doc: " + apiDoc); 296 } 297 } 298 299 String cname = getComponentName(); 300 if (cname != null) { 301 Object comp = getCamelContext().getRegistry().lookupByName(getComponentName()); 302 if (comp != null && comp instanceof RestProducerFactory) { 303 factory = (RestProducerFactory) comp; 304 } else { 305 comp = getCamelContext().getComponent(getComponentName()); 306 if (comp != null && comp instanceof RestProducerFactory) { 307 factory = (RestProducerFactory) comp; 308 } 309 } 310 311 if (factory == null) { 312 if (comp != null) { 313 throw new IllegalArgumentException("Component " + getComponentName() + " is not a RestProducerFactory"); 314 } else { 315 throw new NoSuchBeanException(getComponentName(), RestProducerFactory.class.getName()); 316 } 317 } 318 cname = getComponentName(); 319 } 320 321 // try all components 322 if (factory == null) { 323 for (String name : getCamelContext().getComponentNames()) { 324 Component comp = getCamelContext().getComponent(name); 325 if (comp != null && comp instanceof RestProducerFactory) { 326 factory = (RestProducerFactory) comp; 327 cname = name; 328 break; 329 } 330 } 331 } 332 333 parameters.put("componentName", cname); 334 335 // lookup in registry 336 if (factory == null) { 337 Set<RestProducerFactory> factories = getCamelContext().getRegistry().findByType(RestProducerFactory.class); 338 if (factories != null && factories.size() == 1) { 339 factory = factories.iterator().next(); 340 } 341 } 342 343 // no explicit factory found then try to see if we can find any of the default rest consumer components 344 // and there must only be exactly one so we safely can pick this one 345 if (factory == null) { 346 RestProducerFactory found = null; 347 String foundName = null; 348 for (String name : DEFAULT_REST_PRODUCER_COMPONENTS) { 349 Object comp = getCamelContext().getComponent(name, true); 350 if (comp != null && comp instanceof RestProducerFactory) { 351 if (found == null) { 352 found = (RestProducerFactory) comp; 353 foundName = name; 354 } else { 355 throw new IllegalArgumentException("Multiple RestProducerFactory found on classpath. Configure explicit which component to use"); 356 } 357 } 358 } 359 if (found != null) { 360 LOG.debug("Auto discovered {} as RestProducerFactory", foundName); 361 factory = found; 362 } 363 } 364 365 if (factory != null) { 366 LOG.debug("Using RestProducerFactory: {}", factory); 367 368 Producer producer; 369 if (apiDocFactory != null) { 370 // wrap the factory using the api doc factory which will use the factory 371 parameters.put("restProducerFactory", factory); 372 producer = apiDocFactory.createProducer(getCamelContext(), host, method, path, uriTemplate, queryParameters, consumes, produces, parameters); 373 } else { 374 producer = factory.createProducer(getCamelContext(), host, method, path, uriTemplate, queryParameters, consumes, produces, parameters); 375 } 376 RestConfiguration config = getCamelContext().getRestConfiguration(cname, true); 377 RestProducer answer = new RestProducer(this, producer, config); 378 answer.setOutType(outType); 379 answer.setType(inType); 380 answer.setBindingMode(bindingMode); 381 382 return answer; 383 } else { 384 throw new IllegalStateException("Cannot find RestProducerFactory in Registry or as a Component to use"); 385 } 386 } 387 388 @Override 389 public Consumer createConsumer(Processor processor) throws Exception { 390 RestConsumerFactory factory = null; 391 String cname = null; 392 if (getComponentName() != null) { 393 Object comp = getCamelContext().getRegistry().lookupByName(getComponentName()); 394 if (comp != null && comp instanceof RestConsumerFactory) { 395 factory = (RestConsumerFactory) comp; 396 } else { 397 comp = getCamelContext().getComponent(getComponentName()); 398 if (comp != null && comp instanceof RestConsumerFactory) { 399 factory = (RestConsumerFactory) comp; 400 } 401 } 402 403 if (factory == null) { 404 if (comp != null) { 405 throw new IllegalArgumentException("Component " + getComponentName() + " is not a RestConsumerFactory"); 406 } else { 407 throw new NoSuchBeanException(getComponentName(), RestConsumerFactory.class.getName()); 408 } 409 } 410 cname = getComponentName(); 411 } 412 413 // try all components 414 if (factory == null) { 415 for (String name : getCamelContext().getComponentNames()) { 416 Component comp = getCamelContext().getComponent(name); 417 if (comp != null && comp instanceof RestConsumerFactory) { 418 factory = (RestConsumerFactory) comp; 419 cname = name; 420 break; 421 } 422 } 423 } 424 425 // lookup in registry 426 if (factory == null) { 427 Set<RestConsumerFactory> factories = getCamelContext().getRegistry().findByType(RestConsumerFactory.class); 428 if (factories != null && factories.size() == 1) { 429 factory = factories.iterator().next(); 430 } 431 } 432 433 // no explicit factory found then try to see if we can find any of the default rest consumer components 434 // and there must only be exactly one so we safely can pick this one 435 if (factory == null) { 436 RestConsumerFactory found = null; 437 String foundName = null; 438 for (String name : DEFAULT_REST_CONSUMER_COMPONENTS) { 439 Object comp = getCamelContext().getComponent(name, true); 440 if (comp != null && comp instanceof RestConsumerFactory) { 441 if (found == null) { 442 found = (RestConsumerFactory) comp; 443 foundName = name; 444 } else { 445 throw new IllegalArgumentException("Multiple RestConsumerFactory found on classpath. Configure explicit which component to use"); 446 } 447 } 448 } 449 if (found != null) { 450 LOG.debug("Auto discovered {} as RestConsumerFactory", foundName); 451 factory = found; 452 } 453 } 454 455 if (factory != null) { 456 // if no explicit port/host configured, then use port from rest configuration 457 String scheme = "http"; 458 String host = ""; 459 int port = 80; 460 461 RestConfiguration config = getCamelContext().getRestConfiguration(cname, true); 462 if (config.getScheme() != null) { 463 scheme = config.getScheme(); 464 } 465 if (config.getHost() != null) { 466 host = config.getHost(); 467 } 468 int num = config.getPort(); 469 if (num > 0) { 470 port = num; 471 } 472 473 // if no explicit hostname set then resolve the hostname 474 if (ObjectHelper.isEmpty(host)) { 475 if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.allLocalIp) { 476 host = "0.0.0.0"; 477 } else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localHostName) { 478 host = HostUtils.getLocalHostName(); 479 } else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localIp) { 480 host = HostUtils.getLocalIp(); 481 } 482 } 483 484 // calculate the url to the rest service 485 String path = getPath(); 486 if (!path.startsWith("/")) { 487 path = "/" + path; 488 } 489 490 // there may be an optional context path configured to help Camel calculate the correct urls for the REST services 491 // this may be needed when using camel-servlet where we cannot get the actual context-path or port number of the servlet engine 492 // during init of the servlet 493 String contextPath = config.getContextPath(); 494 if (contextPath != null) { 495 if (!contextPath.startsWith("/")) { 496 path = "/" + contextPath + path; 497 } else { 498 path = contextPath + path; 499 } 500 } 501 502 String baseUrl = scheme + "://" + host + (port != 80 ? ":" + port : "") + path; 503 504 String url = baseUrl; 505 if (uriTemplate != null) { 506 // make sure to avoid double slashes 507 if (uriTemplate.startsWith("/")) { 508 url = url + uriTemplate; 509 } else { 510 url = url + "/" + uriTemplate; 511 } 512 } 513 514 Consumer consumer = factory.createConsumer(getCamelContext(), processor, getMethod(), getPath(), 515 getUriTemplate(), getConsumes(), getProduces(), config, getParameters()); 516 configureConsumer(consumer); 517 518 // add to rest registry so we can keep track of them, we will remove from the registry when the consumer is removed 519 // the rest registry will automatic keep track when the consumer is removed, 520 // and un-register the REST service from the registry 521 getCamelContext().getRestRegistry().addRestService(consumer, url, baseUrl, getPath(), getUriTemplate(), getMethod(), 522 getConsumes(), getProduces(), getInType(), getOutType(), getRouteId(), getDescription()); 523 return consumer; 524 } else { 525 throw new IllegalStateException("Cannot find RestConsumerFactory in Registry or as a Component to use"); 526 } 527 } 528 529 @Override 530 public boolean isSingleton() { 531 return true; 532 } 533 534 @Override 535 public boolean isLenientProperties() { 536 return true; 537 } 538}