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