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.RestApiConsumerFactory;
033import org.apache.camel.spi.RestApiProcessorFactory;
034import org.apache.camel.spi.RestConfiguration;
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;
040
041/**
042 * The rest-api component is used for providing Swagger API of the REST services which has been defined using the rest-dsl in Camel.
043 */
044@UriEndpoint(scheme = "rest-api", title = "REST API", syntax = "rest-api:path/contextIdPattern", consumerOnly = true, label = "core,rest", lenientProperties = true)
045public class RestApiEndpoint extends DefaultEndpoint {
046
047    public static final String DEFAULT_API_COMPONENT_NAME = "swagger";
048    public static final String RESOURCE_PATH = "META-INF/services/org/apache/camel/rest/";
049
050    @UriPath
051    @Metadata(required = "true")
052    private String path;
053    @UriPath
054    private String contextIdPattern;
055    @UriParam
056    private String componentName;
057    @UriParam
058    private String apiComponentName;
059
060    private Map<String, Object> parameters;
061
062    public RestApiEndpoint(String endpointUri, RestApiComponent component) {
063        super(endpointUri, component);
064        setExchangePattern(ExchangePattern.InOut);
065    }
066
067    @Override
068    public RestApiComponent getComponent() {
069        return (RestApiComponent) super.getComponent();
070    }
071
072    public String getPath() {
073        return path;
074    }
075
076    /**
077     * The base path
078     */
079    public void setPath(String path) {
080        this.path = path;
081    }
082
083    public String getContextIdPattern() {
084        return contextIdPattern;
085    }
086
087    /**
088     * Optional CamelContext id pattern to only allow Rest APIs from rest services within CamelContext's which name matches the pattern.
089     */
090    public void setContextIdPattern(String contextIdPattern) {
091        this.contextIdPattern = contextIdPattern;
092    }
093
094    public String getComponentName() {
095        return componentName;
096    }
097
098    /**
099     * The Camel Rest component to use for the REST transport, such as restlet, spark-rest.
100     * If no component has been explicit configured, then Camel will lookup if there is a Camel component
101     * that integrates with the Rest DSL, or if a org.apache.camel.spi.RestConsumerFactory is registered in the registry.
102     * If either one is found, then that is being used.
103     */
104    public void setComponentName(String componentName) {
105        this.componentName = componentName;
106    }
107
108    public String getApiComponentName() {
109        return apiComponentName;
110    }
111
112    /**
113     * The Camel Rest API component to use for generating the API of the REST services, such as swagger.
114     */
115    public void setApiComponentName(String apiComponentName) {
116        this.apiComponentName = apiComponentName;
117    }
118
119    public Map<String, Object> getParameters() {
120        return parameters;
121    }
122
123    /**
124     * Additional parameters to configure the consumer of the REST transport for this REST service
125     */
126    public void setParameters(Map<String, Object> parameters) {
127        this.parameters = parameters;
128    }
129
130    @Override
131    public Producer createProducer() throws Exception {
132        RestApiProcessorFactory factory = null;
133
134        RestConfiguration config = getCamelContext().getRestConfiguration(componentName, true);
135
136        // lookup in registry
137        Set<RestApiProcessorFactory> factories = getCamelContext().getRegistry().findByType(RestApiProcessorFactory.class);
138        if (factories != null && factories.size() == 1) {
139            factory = factories.iterator().next();
140        }
141
142        // lookup on classpath using factory finder to automatic find it (just add camel-swagger-java to classpath etc)
143        if (factory == null) {
144            String name = apiComponentName != null ? apiComponentName : config.getApiComponent();
145            if (name == null) {
146                name = DEFAULT_API_COMPONENT_NAME;
147            }
148            try {
149                FactoryFinder finder = getCamelContext().getFactoryFinder(RESOURCE_PATH);
150                Object instance = finder.newInstance(name);
151                if (instance instanceof RestApiProcessorFactory) {
152                    factory = (RestApiProcessorFactory) instance;
153                }
154            } catch (NoFactoryAvailableException e) {
155                // ignore
156            }
157        }
158
159        if (factory != null) {
160
161            // if no explicit port/host configured, then use port from rest configuration
162            String host = "";
163            int port = 80;
164
165            if (config.getHost() != null) {
166                host = config.getHost();
167            }
168            int num = config.getPort();
169            if (num > 0) {
170                port = num;
171            }
172
173            // if no explicit hostname set then resolve the hostname
174            if (ObjectHelper.isEmpty(host)) {
175                if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.allLocalIp) {
176                    host = "0.0.0.0";
177                } else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localHostName) {
178                    host = HostUtils.getLocalHostName();
179                } else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localIp) {
180                    host = HostUtils.getLocalIp();
181                }
182
183                // no host was configured so calculate a host to use
184                // there should be no schema in the host (but only port)
185                String targetHost = host + (port != 80 ? ":" + port : "");
186                getParameters().put("host", targetHost);
187            }
188
189            // the base path should start with a leading slash
190            String path = getPath();
191            if (path != null && !path.startsWith("/")) {
192                path = "/" + path;
193            }
194
195            // whether listing of the context id's is enabled or not
196            boolean contextIdListing = config.isApiContextListing();
197
198            Processor processor = factory.createApiProcessor(getCamelContext(), path, getContextIdPattern(), contextIdListing, config, getParameters());
199            return new RestApiProducer(this, processor);
200        } else {
201            throw new IllegalStateException("Cannot find RestApiProcessorFactory in Registry or classpath (such as the camel-swagger-java component)");
202        }
203    }
204
205    @Override
206    public Consumer createConsumer(Processor processor) throws Exception {
207        RestApiConsumerFactory factory = null;
208        String cname = null;
209
210        // we use the rest component as the HTTP consumer to service the API
211        // the API then uses the api component (eg usually camel-swagger-java) to build the API
212        if (getComponentName() != null) {
213            Object comp = getCamelContext().getRegistry().lookupByName(getComponentName());
214            if (comp != null && comp instanceof RestApiConsumerFactory) {
215                factory = (RestApiConsumerFactory) comp;
216            } else {
217                comp = getCamelContext().getComponent(getComponentName());
218                if (comp != null && comp instanceof RestApiConsumerFactory) {
219                    factory = (RestApiConsumerFactory) comp;
220                }
221            }
222
223            if (factory == null) {
224                if (comp != null) {
225                    throw new IllegalArgumentException("Component " + getComponentName() + " is not a RestApiConsumerFactory");
226                } else {
227                    throw new NoSuchBeanException(getComponentName(), RestApiConsumerFactory.class.getName());
228                }
229            }
230            cname = getComponentName();
231        }
232
233        // try all components
234        if (factory == null) {
235            for (String name : getCamelContext().getComponentNames()) {
236                Component comp = getCamelContext().getComponent(name);
237                if (comp != null && comp instanceof RestApiConsumerFactory) {
238                    factory = (RestApiConsumerFactory) comp;
239                    cname = name;
240                    break;
241                }
242            }
243        }
244
245        // lookup in registry
246        if (factory == null) {
247            Set<RestApiConsumerFactory> factories = getCamelContext().getRegistry().findByType(RestApiConsumerFactory.class);
248            if (factories != null && factories.size() == 1) {
249                factory = factories.iterator().next();
250            }
251        }
252
253        if (factory != null) {
254            // calculate the url to the rest API service
255            RestConfiguration config = getCamelContext().getRestConfiguration(cname, true);
256
257            // calculate the url to the rest API service
258            String path = getPath();
259            if (path != null && !path.startsWith("/")) {
260                path = "/" + path;
261            }
262
263            Consumer consumer = factory.createApiConsumer(getCamelContext(), processor, path, config, getParameters());
264            configureConsumer(consumer);
265
266            return consumer;
267        } else {
268            throw new IllegalStateException("Cannot find RestApiConsumerFactory in Registry or as a Component to use");
269        }
270    }
271
272    @Override
273    public boolean isSingleton() {
274        return true;
275    }
276
277    @Override
278    public boolean isLenientProperties() {
279        return true;
280    }
281}