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