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.NoSuchBeanException;
026import org.apache.camel.Processor;
027import org.apache.camel.Producer;
028import org.apache.camel.impl.DefaultEndpoint;
029import org.apache.camel.spi.Metadata;
030import org.apache.camel.spi.RestConfiguration;
031import org.apache.camel.spi.RestConsumerFactory;
032import org.apache.camel.spi.UriEndpoint;
033import org.apache.camel.spi.UriParam;
034import org.apache.camel.spi.UriPath;
035import org.apache.camel.util.HostUtils;
036import org.apache.camel.util.ObjectHelper;
037
038/**
039 * The rest component is used for hosting REST services which has been defined using the rest-dsl in Camel.
040 */
041@UriEndpoint(scheme = "rest", title = "REST", syntax = "rest:method:path:uriTemplate", consumerOnly = true, label = "core,rest", lenientProperties = true)
042public class RestEndpoint extends DefaultEndpoint {
043
044    @UriPath(enums = "get,post,put,delete,patch,head,trace,connect,options") @Metadata(required = "true")
045    private String method;
046    @UriPath @Metadata(required = "true")
047    private String path;
048    @UriPath
049    private String uriTemplate;
050    @UriParam
051    private String consumes;
052    @UriParam
053    private String produces;
054    @UriParam
055    private String componentName;
056    @UriParam
057    private String inType;
058    @UriParam
059    private String outType;
060    @UriParam
061    private String routeId;
062    @UriParam
063    private String description;
064
065    private Map<String, Object> parameters;
066
067    public RestEndpoint(String endpointUri, RestComponent component) {
068        super(endpointUri, component);
069        setExchangePattern(ExchangePattern.InOut);
070    }
071
072    @Override
073    public RestComponent getComponent() {
074        return (RestComponent) super.getComponent();
075    }
076
077    public String getMethod() {
078        return method;
079    }
080
081    /**
082     * HTTP method to use.
083     */
084    public void setMethod(String method) {
085        this.method = method;
086    }
087
088    public String getPath() {
089        return path;
090    }
091
092    /**
093     * The base path
094     */
095    public void setPath(String path) {
096        this.path = path;
097    }
098
099    public String getUriTemplate() {
100        return uriTemplate;
101    }
102
103    /**
104     * The uri template
105     */
106    public void setUriTemplate(String uriTemplate) {
107        this.uriTemplate = uriTemplate;
108    }
109
110    public String getConsumes() {
111        return consumes;
112    }
113
114    /**
115     * Media type such as: 'text/xml', or 'application/json' this REST service accepts.
116     * By default we accept all kinds of types.
117     */
118    public void setConsumes(String consumes) {
119        this.consumes = consumes;
120    }
121
122    public String getProduces() {
123        return produces;
124    }
125
126    /**
127     * Media type such as: 'text/xml', or 'application/json' this REST service returns.
128     */
129    public void setProduces(String produces) {
130        this.produces = produces;
131    }
132
133    public String getComponentName() {
134        return componentName;
135    }
136
137    /**
138     * The Camel Rest component to use for the REST transport, such as restlet, spark-rest.
139     * If no component has been explicit configured, then Camel will lookup if there is a Camel component
140     * that integrates with the Rest DSL, or if a org.apache.camel.spi.RestConsumerFactory is registered in the registry.
141     * If either one is found, then that is being used.
142     */
143    public void setComponentName(String componentName) {
144        this.componentName = componentName;
145    }
146
147    public String getInType() {
148        return inType;
149    }
150
151    /**
152     * To declare the incoming POJO binding type as a FQN class name
153     */
154    public void setInType(String inType) {
155        this.inType = inType;
156    }
157
158    public String getOutType() {
159        return outType;
160    }
161
162    /**
163     * To declare the outgoing POJO binding type as a FQN class name
164     */
165    public void setOutType(String outType) {
166        this.outType = outType;
167    }
168
169    public String getRouteId() {
170        return routeId;
171    }
172
173    /**
174     * Name of the route this REST services creates
175     */
176    public void setRouteId(String routeId) {
177        this.routeId = routeId;
178    }
179
180    public String getDescription() {
181        return description;
182    }
183
184    /**
185     * Human description to document this REST service
186     */
187    public void setDescription(String description) {
188        this.description = description;
189    }
190
191    public Map<String, Object> getParameters() {
192        return parameters;
193    }
194
195    /**
196     * Additional parameters to configure the consumer of the REST transport for this REST service
197     */
198    public void setParameters(Map<String, Object> parameters) {
199        this.parameters = parameters;
200    }
201
202    @Override
203    public Producer createProducer() throws Exception {
204        throw new UnsupportedOperationException("Producer not supported");
205    }
206
207    @Override
208    public Consumer createConsumer(Processor processor) throws Exception {
209        RestConsumerFactory factory = null;
210        String cname = null;
211        if (getComponentName() != null) {
212            Object comp = getCamelContext().getRegistry().lookupByName(getComponentName());
213            if (comp != null && comp instanceof RestConsumerFactory) {
214                factory = (RestConsumerFactory) comp;
215            } else {
216                comp = getCamelContext().getComponent(getComponentName());
217                if (comp != null && comp instanceof RestConsumerFactory) {
218                    factory = (RestConsumerFactory) comp;
219                }
220            }
221
222            if (factory == null) {
223                if (comp != null) {
224                    throw new IllegalArgumentException("Component " + getComponentName() + " is not a RestConsumerFactory");
225                } else {
226                    throw new NoSuchBeanException(getComponentName(), RestConsumerFactory.class.getName());
227                }
228            }
229            cname = getComponentName();
230        }
231
232        // try all components
233        if (factory == null) {
234            for (String name : getCamelContext().getComponentNames()) {
235                Component comp = getCamelContext().getComponent(name);
236                if (comp != null && comp instanceof RestConsumerFactory) {
237                    factory = (RestConsumerFactory) comp;
238                    cname = name;
239                    break;
240                }
241            }
242        }
243
244        // lookup in registry
245        if (factory == null) {
246            Set<RestConsumerFactory> factories = getCamelContext().getRegistry().findByType(RestConsumerFactory.class);
247            if (factories != null && factories.size() == 1) {
248                factory = factories.iterator().next();
249            }
250        }
251
252        if (factory != null) {
253            // if no explicit port/host configured, then use port from rest configuration
254            String scheme = "http";
255            String host = "";
256            int port = 80;
257
258            RestConfiguration config = getCamelContext().getRestConfiguration(cname, true);
259            if (config.getScheme() != null) {
260                scheme = config.getScheme();
261            }
262            if (config.getHost() != null) {
263                host = config.getHost();
264            }
265            int num = config.getPort();
266            if (num > 0) {
267                port = num;
268            }
269
270            // if no explicit hostname set then resolve the hostname
271            if (ObjectHelper.isEmpty(host)) {
272                if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.allLocalIp) {
273                    host = "0.0.0.0";
274                } else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localHostName) {
275                    host = HostUtils.getLocalHostName();
276                } else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localIp) {
277                    host = HostUtils.getLocalIp();
278                }
279            }
280
281            // calculate the url to the rest service
282            String path = getPath();
283            if (!path.startsWith("/")) {
284                path = "/" + path;
285            }
286
287            // there may be an optional context path configured to help Camel calculate the correct urls for the REST services
288            // this may be needed when using camel-servlet where we cannot get the actual context-path or port number of the servlet engine
289            // during init of the servlet
290            String contextPath = config.getContextPath();
291            if (contextPath != null) {
292                if (!contextPath.startsWith("/")) {
293                    path = "/" + contextPath + path;
294                } else {
295                    path = contextPath + path;
296                }
297            }
298
299            String baseUrl = scheme + "://" + host + (port != 80 ? ":" + port : "") + path;
300
301            String url = baseUrl;
302            if (uriTemplate != null) {
303                // make sure to avoid double slashes
304                if (uriTemplate.startsWith("/")) {
305                    url = url + uriTemplate;
306                } else {
307                    url = url + "/" + uriTemplate;
308                }
309            }
310
311            Consumer consumer = factory.createConsumer(getCamelContext(), processor, getMethod(), getPath(),
312                    getUriTemplate(), getConsumes(), getProduces(), config, getParameters());
313            configureConsumer(consumer);
314
315            // add to rest registry so we can keep track of them, we will remove from the registry when the consumer is removed
316            // the rest registry will automatic keep track when the consumer is removed,
317            // and un-register the REST service from the registry
318            getCamelContext().getRestRegistry().addRestService(consumer, url, baseUrl, getPath(), getUriTemplate(), getMethod(),
319                    getConsumes(), getProduces(), getInType(), getOutType(), getRouteId(), getDescription());
320            return consumer;
321        } else {
322            throw new IllegalStateException("Cannot find RestConsumerFactory in Registry or as a Component to use");
323        }
324    }
325
326    @Override
327    public boolean isSingleton() {
328        return true;
329    }
330
331    @Override
332    public boolean isLenientProperties() {
333        return true;
334    }
335}