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