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