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