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.util.component; 018 019import java.lang.reflect.InvocationTargetException; 020import java.util.ArrayList; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.camel.CamelContext; 028import org.apache.camel.CamelException; 029import org.apache.camel.ComponentConfiguration; 030import org.apache.camel.Endpoint; 031import org.apache.camel.impl.UriEndpointComponent; 032import org.apache.camel.spi.EndpointCompleter; 033import org.apache.camel.spi.Metadata; 034import org.apache.camel.util.IntrospectionSupport; 035import org.apache.camel.util.ObjectHelper; 036 037/** 038 * Abstract base class for API Component Camel {@link org.apache.camel.Component} classes. 039 */ 040public abstract class AbstractApiComponent<E extends Enum<E> & ApiName, T, S extends ApiCollection<E, T>> 041 extends UriEndpointComponent implements EndpointCompleter { 042 043 @Metadata(label = "advanced") 044 protected T configuration; 045 046 // API collection 047 protected final S collection; 048 049 // API name class 050 protected final Class<E> apiNameClass; 051 052 public AbstractApiComponent(Class<? extends Endpoint> endpointClass, 053 Class<E> apiNameClass, S collection) { 054 super(endpointClass); 055 this.collection = collection; 056 this.apiNameClass = apiNameClass; 057 } 058 059 public AbstractApiComponent(CamelContext context, Class<? extends Endpoint> endpointClass, 060 Class<E> apiNameClass, S collection) { 061 super(context, endpointClass); 062 this.collection = collection; 063 this.apiNameClass = apiNameClass; 064 } 065 066 protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { 067 // split remaining path to get API name and method 068 final String[] pathElements = remaining.split("/"); 069 String apiNameStr; 070 String methodName; 071 switch (pathElements.length) { 072 case 1: 073 apiNameStr = ""; 074 methodName = pathElements[0]; 075 break; 076 case 2: 077 apiNameStr = pathElements[0]; 078 methodName = pathElements[1]; 079 break; 080 default: 081 throw new CamelException("Invalid URI path [" + remaining 082 + "], must be of the format " + collection.getApiNames() + "/<operation-name>"); 083 } 084 085 try { 086 // get API enum from apiName string 087 final E apiName = getApiName(apiNameStr); 088 089 final T endpointConfiguration = createEndpointConfiguration(apiName); 090 final Endpoint endpoint = createEndpoint(uri, methodName, apiName, endpointConfiguration); 091 092 // set endpoint property inBody 093 setProperties(endpoint, parameters); 094 095 // configure endpoint properties and initialize state 096 endpoint.configureProperties(parameters); 097 098 return endpoint; 099 } catch (InvocationTargetException e) { 100 if (e.getCause() instanceof IllegalArgumentException) { 101 throw new CamelException("Invalid URI path prefix [" + remaining 102 + "], must be one of " + collection.getApiNames()); 103 } 104 throw e; 105 } 106 } 107 108 protected abstract E getApiName(String apiNameStr) throws IllegalArgumentException; 109 110 protected abstract Endpoint createEndpoint(String uri, String methodName, E apiName, T endpointConfiguration); 111 112 protected T createEndpointConfiguration(E name) throws Exception { 113 final Map<String, Object> componentProperties = new HashMap<>(); 114 // copy component configuration, if set 115 if (configuration != null) { 116 IntrospectionSupport.getProperties(configuration, componentProperties, null, false); 117 } 118 119 // create endpoint configuration with component properties 120 final T endpointConfiguration = collection.getEndpointConfiguration(name); 121 IntrospectionSupport.setProperties(endpointConfiguration, componentProperties); 122 return endpointConfiguration; 123 } 124 125 public T getConfiguration() { 126 return configuration; 127 } 128 129 public void setConfiguration(T configuration) { 130 this.configuration = configuration; 131 } 132 133 @Override 134 public List<String> completeEndpointPath(ComponentConfiguration configuration, String completionText) { 135 final List<String> result = new ArrayList<>(); 136 137 final Set<String> apiNames = collection.getApiNames(); 138 boolean useDefaultName = apiNames.size() == 1 && apiNames.contains(""); 139 140 // check if there is an API name present 141 completionText = ObjectHelper.isEmpty(completionText) ? "" : completionText; 142 final int prefixEnd = completionText.indexOf('/'); 143 final int pathEnd = completionText.lastIndexOf('?'); 144 145 // empty or incomplete API prefix, and no options, add API names or method names if useDefaultName 146 final Map<E, ? extends ApiMethodHelper<? extends ApiMethod>> apiHelpers = collection.getApiHelpers(); 147 if (prefixEnd == -1 && pathEnd == -1) { 148 149 if (useDefaultName) { 150 151 // complete method names for default API 152 final Set<Class<? extends ApiMethod>> apiMethods = collection.getApiMethods().keySet(); 153 final Class<? extends ApiMethod> apiMethod = apiMethods.iterator().next(); 154 final ApiMethodHelper<? extends ApiMethod> helper = apiHelpers.values().iterator().next(); 155 getCompletedMethods(result, completionText, apiMethod, helper); 156 } else { 157 158 // complete API names 159 for (String name : apiNames) { 160 if (!name.isEmpty() || name.startsWith(completionText)) { 161 result.add(name); 162 } 163 } 164 } 165 166 // path with complete API name prefix, but no options 167 } else if (prefixEnd != -1 && pathEnd == -1) { 168 169 // complete method names for specified API 170 final E apiName = getApiNameOrNull(completionText.substring(0, prefixEnd)); 171 if (apiName != null) { 172 final ApiMethodHelper<? extends ApiMethod> helper = apiHelpers.get(apiName); 173 completionText = completionText.substring(prefixEnd + 1); 174 for (Map.Entry<Class<? extends ApiMethod>, E> entry : collection.getApiMethods().entrySet()) { 175 if (entry.getValue().equals(apiName)) { 176 getCompletedMethods(result, completionText, entry.getKey(), helper); 177 break; 178 } 179 } 180 } 181 182 // complete options 183 } else { 184 185 // get last option text 186 final int lastParam = completionText.lastIndexOf('&'); 187 String optionText; 188 if (lastParam != -1) { 189 optionText = completionText.substring(lastParam + 1); 190 } else { 191 optionText = completionText.substring(pathEnd); 192 } 193 194 String methodName = null; 195 ApiMethodHelper<? extends ApiMethod> helper = null; 196 if (useDefaultName) { 197 198 // get default endpoint configuration and helper 199 methodName = completionText.substring(0, pathEnd); 200 helper = apiHelpers.values().iterator().next(); 201 } else { 202 203 // get API name and method name, if they exist 204 final String[] pathElements = completionText.substring(0, pathEnd).split("/"); 205 if (pathElements.length == 2) { 206 final E apiName = getApiNameOrNull(pathElements[0]); 207 methodName = pathElements[1]; 208 helper = collection.getHelper(apiName); 209 } 210 } 211 if (helper != null && !ObjectHelper.isEmpty(methodName)) { 212 // get other options from configuration 213 Set<String> existingOptions = configuration.getParameters().keySet(); 214 // get all method options 215 try { 216 final List<Object> arguments = helper.getArguments(methodName); 217 final int nArgs = arguments.size(); 218 final Set<String> options = new HashSet<>(); 219 for (int i = 1; i < nArgs; i += 2) { 220 options.add((String) arguments.get(i)); 221 } 222 options.removeAll(existingOptions); 223 224 // return matching options 225 for (String option : options) { 226 if (option.startsWith(optionText)) { 227 result.add(option); 228 } 229 } 230 } catch (IllegalArgumentException ignore) { 231 // thrown from getArguments() when no matching methods, 232 // return an empty result 233 } 234 } 235 } 236 237 return result; 238 } 239 240 // returns null instead of throwing IllegalArgumentException for invalid name 241 protected E getApiNameOrNull(String nameStr) { 242 try { 243 return getApiName(nameStr); 244 } catch (IllegalArgumentException ignore) { 245 return null; 246 } 247 } 248 249 protected void getCompletedMethods(List<String> result, String completionText, 250 Class<? extends ApiMethod> apiMethod, ApiMethodHelper<? extends ApiMethod> helper) { 251 // add potential method names 252 final ApiMethod[] methods = apiMethod.getEnumConstants(); 253 for (ApiMethod method : methods) { 254 final String name = method.getName(); 255 if (name.startsWith(completionText)) { 256 result.add(name); 257 } 258 } 259 // add potential aliases 260 final Map<String, Set<String>> aliases = helper.getAliases(); 261 for (String alias : aliases.keySet()) { 262 if (alias.startsWith(completionText)) { 263 result.add(alias); 264 } 265 } 266 } 267}