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 != null && comp instanceof RestProducerFactory) { 302 factory = (RestProducerFactory) comp; 303 } else { 304 comp = getCamelContext().getComponent(getComponentName()); 305 if (comp != null && 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 != null && 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 != null && 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 Producer producer; 368 if (apiDocFactory != null) { 369 // wrap the factory using the api doc factory which will use the factory 370 parameters.put("restProducerFactory", factory); 371 producer = apiDocFactory.createProducer(getCamelContext(), host, method, path, uriTemplate, queryParameters, consumes, produces, parameters); 372 } else { 373 producer = factory.createProducer(getCamelContext(), host, method, path, uriTemplate, queryParameters, consumes, produces, parameters); 374 } 375 RestConfiguration config = getCamelContext().getRestConfiguration(cname, true); 376 RestProducer answer = new RestProducer(this, producer, config); 377 answer.setOutType(outType); 378 answer.setType(inType); 379 answer.setBindingMode(bindingMode); 380 381 return answer; 382 } else { 383 throw new IllegalStateException("Cannot find RestProducerFactory in Registry or as a Component to use"); 384 } 385 } 386 387 @Override 388 public Consumer createConsumer(Processor processor) throws Exception { 389 RestConsumerFactory factory = null; 390 String cname = null; 391 if (getComponentName() != null) { 392 Object comp = getCamelContext().getRegistry().lookupByName(getComponentName()); 393 if (comp != null && comp instanceof RestConsumerFactory) { 394 factory = (RestConsumerFactory) comp; 395 } else { 396 comp = getCamelContext().getComponent(getComponentName()); 397 if (comp != null && comp instanceof RestConsumerFactory) { 398 factory = (RestConsumerFactory) comp; 399 } 400 } 401 402 if (factory == null) { 403 if (comp != null) { 404 throw new IllegalArgumentException("Component " + getComponentName() + " is not a RestConsumerFactory"); 405 } else { 406 throw new NoSuchBeanException(getComponentName(), RestConsumerFactory.class.getName()); 407 } 408 } 409 cname = getComponentName(); 410 } 411 412 // try all components 413 if (factory == null) { 414 for (String name : getCamelContext().getComponentNames()) { 415 Component comp = getCamelContext().getComponent(name); 416 if (comp != null && comp instanceof RestConsumerFactory) { 417 factory = (RestConsumerFactory) comp; 418 cname = name; 419 break; 420 } 421 } 422 } 423 424 // lookup in registry 425 if (factory == null) { 426 Set<RestConsumerFactory> factories = getCamelContext().getRegistry().findByType(RestConsumerFactory.class); 427 if (factories != null && factories.size() == 1) { 428 factory = factories.iterator().next(); 429 } 430 } 431 432 // no explicit factory found then try to see if we can find any of the default rest consumer components 433 // and there must only be exactly one so we safely can pick this one 434 if (factory == null) { 435 RestConsumerFactory found = null; 436 String foundName = null; 437 for (String name : DEFAULT_REST_CONSUMER_COMPONENTS) { 438 Object comp = getCamelContext().getComponent(name, true); 439 if (comp != null && comp instanceof RestConsumerFactory) { 440 if (found == null) { 441 found = (RestConsumerFactory) comp; 442 foundName = name; 443 } else { 444 throw new IllegalArgumentException("Multiple RestConsumerFactory found on classpath. Configure explicit which component to use"); 445 } 446 } 447 } 448 if (found != null) { 449 LOG.debug("Auto discovered {} as RestConsumerFactory", foundName); 450 factory = found; 451 } 452 } 453 454 if (factory != null) { 455 // if no explicit port/host configured, then use port from rest configuration 456 String scheme = "http"; 457 String host = ""; 458 int port = 80; 459 460 RestConfiguration config = getCamelContext().getRestConfiguration(cname, true); 461 if (config.getScheme() != null) { 462 scheme = config.getScheme(); 463 } 464 if (config.getHost() != null) { 465 host = config.getHost(); 466 } 467 int num = config.getPort(); 468 if (num > 0) { 469 port = num; 470 } 471 472 // if no explicit hostname set then resolve the hostname 473 if (ObjectHelper.isEmpty(host)) { 474 if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.allLocalIp) { 475 host = "0.0.0.0"; 476 } else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localHostName) { 477 host = HostUtils.getLocalHostName(); 478 } else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localIp) { 479 host = HostUtils.getLocalIp(); 480 } 481 } 482 483 // calculate the url to the rest service 484 String path = getPath(); 485 if (!path.startsWith("/")) { 486 path = "/" + path; 487 } 488 489 // there may be an optional context path configured to help Camel calculate the correct urls for the REST services 490 // this may be needed when using camel-servlet where we cannot get the actual context-path or port number of the servlet engine 491 // during init of the servlet 492 String contextPath = config.getContextPath(); 493 if (contextPath != null) { 494 if (!contextPath.startsWith("/")) { 495 path = "/" + contextPath + path; 496 } else { 497 path = contextPath + path; 498 } 499 } 500 501 String baseUrl = scheme + "://" + host + (port != 80 ? ":" + port : "") + path; 502 503 String url = baseUrl; 504 if (uriTemplate != null) { 505 // make sure to avoid double slashes 506 if (uriTemplate.startsWith("/")) { 507 url = url + uriTemplate; 508 } else { 509 url = url + "/" + uriTemplate; 510 } 511 } 512 513 Consumer consumer = factory.createConsumer(getCamelContext(), processor, getMethod(), getPath(), 514 getUriTemplate(), getConsumes(), getProduces(), config, getParameters()); 515 configureConsumer(consumer); 516 517 // add to rest registry so we can keep track of them, we will remove from the registry when the consumer is removed 518 // the rest registry will automatic keep track when the consumer is removed, 519 // and un-register the REST service from the registry 520 getCamelContext().getRestRegistry().addRestService(consumer, url, baseUrl, getPath(), getUriTemplate(), getMethod(), 521 getConsumes(), getProduces(), getInType(), getOutType(), getRouteId(), getDescription()); 522 return consumer; 523 } else { 524 throw new IllegalStateException("Cannot find RestConsumerFactory in Registry or as a Component to use"); 525 } 526 } 527 528 @Override 529 public boolean isSingleton() { 530 return true; 531 } 532 533 @Override 534 public boolean isLenientProperties() { 535 return true; 536 } 537}