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