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.HashMap;
020import java.util.Iterator;
021import java.util.LinkedHashMap;
022import java.util.Map;
023import java.util.function.Consumer;
024import java.util.function.Supplier;
025
026import org.apache.camel.CamelContext;
027import org.apache.camel.ComponentVerifier;
028import org.apache.camel.Endpoint;
029import org.apache.camel.VerifiableComponent;
030import org.apache.camel.impl.DefaultComponent;
031import org.apache.camel.model.rest.RestConstants;
032import org.apache.camel.spi.Metadata;
033import org.apache.camel.spi.RestConfiguration;
034import org.apache.camel.util.CamelContextHelper;
035import org.apache.camel.util.FileUtil;
036import org.apache.camel.util.IntrospectionSupport;
037import org.apache.camel.util.ObjectHelper;
038import org.apache.camel.util.URISupport;
039
040/**
041 * Rest component.
042 */
043@Metadata(label = "verifiers", enums = "parameters,connectivity")
044public class RestComponent extends DefaultComponent implements VerifiableComponent {
045
046    @Metadata(label = "common")
047    private String componentName;
048    @Metadata(label = "producer")
049    private String apiDoc;
050    @Metadata(label = "producer")
051    private String host;
052
053    @Override
054    protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
055        String restConfigurationName = getAndRemoveParameter(parameters, "componentName", String.class, componentName);
056
057        RestEndpoint answer = new RestEndpoint(uri, this);
058        answer.setComponentName(restConfigurationName);
059        answer.setApiDoc(apiDoc);
060
061        RestConfiguration config = new RestConfiguration();
062        mergeConfigurations(config, findGlobalRestConfiguration());
063        mergeConfigurations(config, getCamelContext().getRestConfiguration(restConfigurationName, true));
064
065        // if no explicit host was given, then fallback and use default configured host
066        String h = getAndRemoveOrResolveReferenceParameter(parameters, "host", String.class, host);
067        if (h == null) {
068            h = config.getHost();
069            int port = config.getPort();
070            // is there a custom port number
071            if (port > 0 && port != 80 && port != 443) {
072                h += ":" + port;
073            }
074        }
075        // host must start with http:// or https://
076        if (h != null && !(h.startsWith("http://") || h.startsWith("https://"))) {
077            h = "http://" + h;
078        }
079        answer.setHost(h);
080
081        setProperties(answer, parameters);
082        if (!parameters.isEmpty()) {
083            // use only what remains and at this point parameters that have been used have been removed
084            // without overwriting any query parameters set via queryParameters endpoint option
085            final Map<String, Object> queryParameters = new LinkedHashMap<>(parameters);
086            final Map<String, Object> existingQueryParameters = URISupport.parseQuery(answer.getQueryParameters());
087            queryParameters.putAll(existingQueryParameters);
088
089            final String remainingParameters = URISupport.createQueryString(queryParameters);
090            answer.setQueryParameters(remainingParameters);
091        }
092
093        answer.setParameters(parameters);
094
095        if (!remaining.contains(":")) {
096            throw new IllegalArgumentException("Invalid syntax. Must be rest:method:path[:uriTemplate] where uriTemplate is optional");
097        }
098
099        String method = ObjectHelper.before(remaining, ":");
100        String s = ObjectHelper.after(remaining, ":");
101
102        String path;
103        String uriTemplate;
104        if (s != null && s.contains(":")) {
105            path = ObjectHelper.before(s, ":");
106            uriTemplate = ObjectHelper.after(s, ":");
107        } else {
108            path = s;
109            uriTemplate = null;
110        }
111
112        // remove trailing slashes
113        path = FileUtil.stripTrailingSeparator(path);
114        uriTemplate = FileUtil.stripTrailingSeparator(uriTemplate);
115
116        answer.setMethod(method);
117        answer.setPath(path);
118        answer.setUriTemplate(uriTemplate);
119
120        // if no explicit component name was given, then fallback and use default configured component name
121        if (answer.getComponentName() == null) {
122            String name = config.getProducerComponent();
123            if (name == null) {
124                // fallback and use the consumer name
125                name = config.getComponent();
126            }
127            answer.setComponentName(name);
128        }
129        // if no explicit producer api was given, then fallback and use default configured
130        if (answer.getApiDoc() == null) {
131            answer.setApiDoc(config.getProducerApiDoc());
132        }
133
134        return answer;
135    }
136
137    public String getComponentName() {
138        return componentName;
139    }
140
141    /**
142     * The Camel Rest component to use for the REST transport, such as restlet, spark-rest.
143     * If no component has been explicit configured, then Camel will lookup if there is a Camel component
144     * that integrates with the Rest DSL, or if a org.apache.camel.spi.RestConsumerFactory (consumer)
145     * or org.apache.camel.spi.RestProducerFactory (producer) is registered in the registry.
146     * If either one is found, then that is being used.
147     */
148    public void setComponentName(String componentName) {
149        this.componentName = componentName;
150    }
151
152    public String getApiDoc() {
153        return apiDoc;
154    }
155
156    /**
157     * The swagger api doc resource to use.
158     * The resource is loaded from classpath by default and must be in JSon format.
159     */
160    public void setApiDoc(String apiDoc) {
161        this.apiDoc = apiDoc;
162    }
163
164    public String getHost() {
165        return host;
166    }
167
168    /**
169     * Host and port of HTTP service to use (override host in swagger schema)
170     */
171    public void setHost(String host) {
172        this.host = host;
173    }
174
175    // ****************************************
176    // Helpers
177    // ****************************************
178
179    private RestConfiguration findGlobalRestConfiguration() {
180        CamelContext context = getCamelContext();
181
182        RestConfiguration conf = CamelContextHelper.lookup(context, RestConstants.DEFAULT_REST_CONFIGURATION_ID, RestConfiguration.class);
183        if (conf == null) {
184            conf = CamelContextHelper.findByType(getCamelContext(), RestConfiguration.class);
185        }
186
187        return conf;
188    }
189
190    private RestConfiguration mergeConfigurations(RestConfiguration conf, RestConfiguration from) throws Exception {
191        if (conf == from) {
192            return conf;
193        }
194        if (from != null) {
195            Map<String, Object> map = IntrospectionSupport.getNonNullProperties(from);
196
197            // Remove properties as they need to be manually managed
198            Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
199            while (it.hasNext()) {
200                Map.Entry<String, Object> entry = it.next();
201                if (entry.getValue() instanceof Map) {
202                    it.remove();
203                }
204            }
205
206            // Copy common options, will override those in conf
207            IntrospectionSupport.setProperties(getCamelContext(), getCamelContext().getTypeConverter(), conf, map);
208
209            // Merge properties
210            mergeProperties(conf::getComponentProperties, from::getComponentProperties, conf::setComponentProperties);
211            mergeProperties(conf::getEndpointProperties, from::getEndpointProperties, conf::setEndpointProperties);
212            mergeProperties(conf::getConsumerProperties, from::getConsumerProperties, conf::setConsumerProperties);
213            mergeProperties(conf::getDataFormatProperties, from::getDataFormatProperties, conf::setDataFormatProperties);
214            mergeProperties(conf::getApiProperties, from::getApiProperties, conf::setApiProperties);
215            mergeProperties(conf::getCorsHeaders, from::getCorsHeaders, conf::setCorsHeaders);
216        }
217
218        return conf;
219    }
220
221    private <T> void mergeProperties(Supplier<Map<String, T>> base, Supplier<Map<String, T>> addons, Consumer<Map<String, T>> consumer) {
222        Map<String, T> baseMap = base.get();
223        Map<String, T> addonsMap = addons.get();
224
225        if (baseMap != null || addonsMap != null) {
226            HashMap<String, T> result = new HashMap<>();
227            if (baseMap != null) {
228                result.putAll(baseMap);
229            }
230            if (addonsMap != null) {
231                result.putAll(addonsMap);
232            }
233
234            consumer.accept(result);
235        }
236    }
237
238    /**
239     * Get the {@link ComponentVerifier}
240     *
241     * @return the Component Verifier
242     */
243    @Override
244    public ComponentVerifier getVerifier() {
245        return new RestComponentVerifier(this);
246    }
247}