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 LOG.debug("Discovering camel-swagger-java on classpath as fallback for using api-doc: {}", apiDoc); 299 Object instance = finder.newInstance("swagger"); 300 if (instance instanceof RestProducerFactory) { 301 // this factory from camel-swagger-java will facade the http component in use 302 apiDocFactory = (RestProducerFactory) instance; 303 } 304 parameters.put("apiDoc", apiDoc); 305 } catch (Exception ex) { 306 307 throw new IllegalStateException("Cannot find camel-openapi-java neither camel-swagger-java on classpath to use with api-doc: " + apiDoc); 308 } 309 310 } 311 } 312 313 String cname = getComponentName(); 314 if (cname != null) { 315 Object comp = getCamelContext().getRegistry().lookupByName(getComponentName()); 316 if (comp instanceof RestProducerFactory) { 317 factory = (RestProducerFactory) comp; 318 } else { 319 comp = setupComponent(getComponentName(), getCamelContext(), (Map<String, Object>) parameters.get("component")); 320 if (comp instanceof RestProducerFactory) { 321 factory = (RestProducerFactory) comp; 322 } 323 } 324 325 if (factory == null) { 326 if (comp != null) { 327 throw new IllegalArgumentException("Component " + getComponentName() + " is not a RestProducerFactory"); 328 } else { 329 throw new NoSuchBeanException(getComponentName(), RestProducerFactory.class.getName()); 330 } 331 } 332 cname = getComponentName(); 333 } 334 335 // try all components 336 if (factory == null) { 337 for (String name : getCamelContext().getComponentNames()) { 338 Component comp = setupComponent(name, getCamelContext(), (Map<String, Object>) parameters.get("component")); 339 if (comp instanceof RestProducerFactory) { 340 factory = (RestProducerFactory) comp; 341 cname = name; 342 break; 343 } 344 } 345 } 346 347 parameters.put("componentName", cname); 348 349 // lookup in registry 350 if (factory == null) { 351 Set<RestProducerFactory> factories = getCamelContext().getRegistry().findByType(RestProducerFactory.class); 352 if (factories != null && factories.size() == 1) { 353 factory = factories.iterator().next(); 354 } 355 } 356 357 // no explicit factory found then try to see if we can find any of the default rest consumer components 358 // and there must only be exactly one so we safely can pick this one 359 if (factory == null) { 360 RestProducerFactory found = null; 361 String foundName = null; 362 for (String name : DEFAULT_REST_PRODUCER_COMPONENTS) { 363 Object comp = setupComponent(getComponentName(), getCamelContext(), (Map<String, Object>) parameters.get("component")); 364 if (comp instanceof RestProducerFactory) { 365 if (found == null) { 366 found = (RestProducerFactory) comp; 367 foundName = name; 368 } else { 369 throw new IllegalArgumentException("Multiple RestProducerFactory found on classpath. Configure explicit which component to use"); 370 } 371 } 372 } 373 if (found != null) { 374 LOG.debug("Auto discovered {} as RestProducerFactory", foundName); 375 factory = found; 376 } 377 } 378 379 if (factory != null) { 380 LOG.debug("Using RestProducerFactory: {}", factory); 381 382 RestConfiguration config = getCamelContext().getRestConfiguration(cname, true); 383 384 Producer producer; 385 if (apiDocFactory != null) { 386 // wrap the factory using the api doc factory which will use the factory 387 parameters.put("restProducerFactory", factory); 388 producer = apiDocFactory.createProducer(getCamelContext(), host, method, path, uriTemplate, queryParameters, consumes, produces, config, parameters); 389 } else { 390 producer = factory.createProducer(getCamelContext(), host, method, path, uriTemplate, queryParameters, consumes, produces, config, parameters); 391 } 392 393 RestProducer answer = new RestProducer(this, producer, config); 394 answer.setOutType(outType); 395 answer.setType(inType); 396 answer.setBindingMode(bindingMode); 397 398 return answer; 399 } else { 400 throw new IllegalStateException("Cannot find RestProducerFactory in Registry or as a Component to use"); 401 } 402 } 403 404 @Override 405 public Consumer createConsumer(Processor processor) throws Exception { 406 RestConsumerFactory factory = null; 407 String cname = null; 408 if (getComponentName() != null) { 409 Object comp = getCamelContext().getRegistry().lookupByName(getComponentName()); 410 if (comp instanceof RestConsumerFactory) { 411 factory = (RestConsumerFactory) comp; 412 } else { 413 comp = getCamelContext().getComponent(getComponentName()); 414 if (comp instanceof RestConsumerFactory) { 415 factory = (RestConsumerFactory) comp; 416 } 417 } 418 419 if (factory == null) { 420 if (comp != null) { 421 throw new IllegalArgumentException("Component " + getComponentName() + " is not a RestConsumerFactory"); 422 } else { 423 throw new NoSuchBeanException(getComponentName(), RestConsumerFactory.class.getName()); 424 } 425 } 426 cname = getComponentName(); 427 } 428 429 // try all components 430 if (factory == null) { 431 for (String name : getCamelContext().getComponentNames()) { 432 Component comp = getCamelContext().getComponent(name); 433 if (comp instanceof RestConsumerFactory) { 434 factory = (RestConsumerFactory) comp; 435 cname = name; 436 break; 437 } 438 } 439 } 440 441 // lookup in registry 442 if (factory == null) { 443 Set<RestConsumerFactory> factories = getCamelContext().getRegistry().findByType(RestConsumerFactory.class); 444 if (factories != null && factories.size() == 1) { 445 factory = factories.iterator().next(); 446 } 447 } 448 449 // no explicit factory found then try to see if we can find any of the default rest consumer components 450 // and there must only be exactly one so we safely can pick this one 451 if (factory == null) { 452 RestConsumerFactory found = null; 453 String foundName = null; 454 for (String name : DEFAULT_REST_CONSUMER_COMPONENTS) { 455 Object comp = getCamelContext().getComponent(name, true); 456 if (comp instanceof RestConsumerFactory) { 457 if (found == null) { 458 found = (RestConsumerFactory) comp; 459 foundName = name; 460 } else { 461 throw new IllegalArgumentException("Multiple RestConsumerFactory found on classpath. Configure explicit which component to use"); 462 } 463 } 464 } 465 if (found != null) { 466 LOG.debug("Auto discovered {} as RestConsumerFactory", foundName); 467 factory = found; 468 } 469 } 470 471 if (factory != null) { 472 // if no explicit port/host configured, then use port from rest configuration 473 String scheme = "http"; 474 String host = ""; 475 int port = 80; 476 477 RestConfiguration config = getCamelContext().getRestConfiguration(cname, true); 478 if (config.getScheme() != null) { 479 scheme = config.getScheme(); 480 } 481 if (config.getHost() != null) { 482 host = config.getHost(); 483 } 484 int num = config.getPort(); 485 if (num > 0) { 486 port = num; 487 } 488 489 // if no explicit hostname set then resolve the hostname 490 if (ObjectHelper.isEmpty(host)) { 491 if (config.getHostNameResolver() == RestConfiguration.RestHostNameResolver.allLocalIp) { 492 host = "0.0.0.0"; 493 } else if (config.getHostNameResolver() == RestConfiguration.RestHostNameResolver.localHostName) { 494 host = HostUtils.getLocalHostName(); 495 } else if (config.getHostNameResolver() == RestConfiguration.RestHostNameResolver.localIp) { 496 host = HostUtils.getLocalIp(); 497 } 498 } 499 500 // calculate the url to the rest service 501 String path = getPath(); 502 if (!path.startsWith("/")) { 503 path = "/" + path; 504 } 505 506 // there may be an optional context path configured to help Camel calculate the correct urls for the REST services 507 // this may be needed when using camel-servlet where we cannot get the actual context-path or port number of the servlet engine 508 // during init of the servlet 509 String contextPath = config.getContextPath(); 510 if (contextPath != null) { 511 if (!contextPath.startsWith("/")) { 512 path = "/" + contextPath + path; 513 } else { 514 path = contextPath + path; 515 } 516 } 517 518 String baseUrl = scheme + "://" + host + (port != 80 ? ":" + port : "") + path; 519 520 String url = baseUrl; 521 if (uriTemplate != null) { 522 // make sure to avoid double slashes 523 if (uriTemplate.startsWith("/")) { 524 url = url + uriTemplate; 525 } else { 526 url = url + "/" + uriTemplate; 527 } 528 } 529 530 Consumer consumer = factory.createConsumer(getCamelContext(), processor, getMethod(), getPath(), 531 getUriTemplate(), getConsumes(), getProduces(), config, getParameters()); 532 configureConsumer(consumer); 533 534 // add to rest registry so we can keep track of them, we will remove from the registry when the consumer is removed 535 // the rest registry will automatic keep track when the consumer is removed, 536 // and un-register the REST service from the registry 537 getCamelContext().getRestRegistry().addRestService(consumer, url, baseUrl, getPath(), getUriTemplate(), getMethod(), 538 getConsumes(), getProduces(), getInType(), getOutType(), getRouteId(), getDescription()); 539 return consumer; 540 } else { 541 throw new IllegalStateException("Cannot find RestConsumerFactory in Registry or as a Component to use"); 542 } 543 } 544 545 @Override 546 public boolean isSingleton() { 547 return true; 548 } 549 550 @Override 551 public boolean isLenientProperties() { 552 return true; 553 } 554}