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}