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}