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}