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