/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.util.component; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.camel.CamelContext; import org.apache.camel.CamelException; import org.apache.camel.ComponentConfiguration; import org.apache.camel.Endpoint; import org.apache.camel.impl.UriEndpointComponent; import org.apache.camel.spi.EndpointCompleter; import org.apache.camel.spi.Metadata; import org.apache.camel.util.IntrospectionSupport; import org.apache.camel.util.ObjectHelper; /** * Abstract base class for API Component Camel {@link org.apache.camel.Component} classes. */ public abstract class AbstractApiComponent<E extends Enum<E> & ApiName, T, S extends ApiCollection<E, T>> extends UriEndpointComponent implements EndpointCompleter { @Metadata(label = "advanced") protected T configuration; // API collection protected final S collection; // API name class protected final Class<E> apiNameClass; public AbstractApiComponent(Class<? extends Endpoint> endpointClass, Class<E> apiNameClass, S collection) { super(endpointClass); this.collection = collection; this.apiNameClass = apiNameClass; } public AbstractApiComponent(CamelContext context, Class<? extends Endpoint> endpointClass, Class<E> apiNameClass, S collection) { super(context, endpointClass); this.collection = collection; this.apiNameClass = apiNameClass; } protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { // split remaining path to get API name and method final String[] pathElements = remaining.split("/"); String apiNameStr; String methodName; switch (pathElements.length) { case 1: apiNameStr = ""; methodName = pathElements[0]; break; case 2: apiNameStr = pathElements[0]; methodName = pathElements[1]; break; default: throw new CamelException("Invalid URI path [" + remaining + "], must be of the format " + collection.getApiNames() + "/<operation-name>"); } try { // get API enum from apiName string final E apiName = getApiName(apiNameStr); final T endpointConfiguration = createEndpointConfiguration(apiName); final Endpoint endpoint = createEndpoint(uri, methodName, apiName, endpointConfiguration); // set endpoint property inBody setProperties(endpoint, parameters); // configure endpoint properties and initialize state endpoint.configureProperties(parameters); return endpoint; } catch (InvocationTargetException e) { if (e.getCause() instanceof IllegalArgumentException) { throw new CamelException("Invalid URI path prefix [" + remaining + "], must be one of " + collection.getApiNames()); } throw e; } } protected abstract E getApiName(String apiNameStr) throws IllegalArgumentException; protected abstract Endpoint createEndpoint(String uri, String methodName, E apiName, T endpointConfiguration); protected T createEndpointConfiguration(E name) throws Exception { final Map<String, Object> componentProperties = new HashMap<String, Object>(); // copy component configuration, if set if (configuration != null) { IntrospectionSupport.getProperties(configuration, componentProperties, null, false); } // create endpoint configuration with component properties final T endpointConfiguration = collection.getEndpointConfiguration(name); IntrospectionSupport.setProperties(endpointConfiguration, componentProperties); return endpointConfiguration; } public T getConfiguration() { return configuration; } public void setConfiguration(T configuration) { this.configuration = configuration; } @Override public List<String> completeEndpointPath(ComponentConfiguration configuration, String completionText) { final List<String> result = new ArrayList<String>(); final Set<String> apiNames = collection.getApiNames(); boolean useDefaultName = apiNames.size() == 1 && apiNames.contains(""); // check if there is an API name present completionText = ObjectHelper.isEmpty(completionText) ? "" : completionText; final int prefixEnd = completionText.indexOf('/'); final int pathEnd = completionText.lastIndexOf('?'); // empty or incomplete API prefix, and no options, add API names or method names if useDefaultName final Map<E, ? extends ApiMethodHelper<? extends ApiMethod>> apiHelpers = collection.getApiHelpers(); if (prefixEnd == -1 && pathEnd == -1) { if (useDefaultName) { // complete method names for default API final Set<Class<? extends ApiMethod>> apiMethods = collection.getApiMethods().keySet(); final Class<? extends ApiMethod> apiMethod = apiMethods.iterator().next(); final ApiMethodHelper<? extends ApiMethod> helper = apiHelpers.values().iterator().next(); getCompletedMethods(result, completionText, apiMethod, helper); } else { // complete API names for (String name : apiNames) { if (!name.isEmpty() || name.startsWith(completionText)) { result.add(name); } } } // path with complete API name prefix, but no options } else if (prefixEnd != -1 && pathEnd == -1) { // complete method names for specified API final E apiName = getApiNameOrNull(completionText.substring(0, prefixEnd)); if (apiName != null) { final ApiMethodHelper<? extends ApiMethod> helper = apiHelpers.get(apiName); completionText = completionText.substring(prefixEnd + 1); for (Map.Entry<Class<? extends ApiMethod>, E> entry : collection.getApiMethods().entrySet()) { if (entry.getValue().equals(apiName)) { getCompletedMethods(result, completionText, entry.getKey(), helper); break; } } } // complete options } else { // get last option text final int lastParam = completionText.lastIndexOf('&'); String optionText; if (lastParam != -1) { optionText = completionText.substring(lastParam + 1); } else { optionText = completionText.substring(pathEnd); } String methodName = null; ApiMethodHelper<? extends ApiMethod> helper = null; if (useDefaultName) { // get default endpoint configuration and helper methodName = completionText.substring(0, pathEnd); helper = apiHelpers.values().iterator().next(); } else { // get API name and method name, if they exist final String[] pathElements = completionText.substring(0, pathEnd).split("/"); if (pathElements.length == 2) { final E apiName = getApiNameOrNull(pathElements[0]); methodName = pathElements[1]; helper = collection.getHelper(apiName); } } if (helper != null && !ObjectHelper.isEmpty(methodName)) { // get other options from configuration Set<String> existingOptions = configuration.getParameters().keySet(); // get all method options try { final List<Object> arguments = helper.getArguments(methodName); final int nArgs = arguments.size(); final Set<String> options = new HashSet<String>(); for (int i = 1; i < nArgs; i += 2) { options.add((String) arguments.get(i)); } options.removeAll(existingOptions); // return matching options for (String option : options) { if (option.startsWith(optionText)) { result.add(option); } } } catch (IllegalArgumentException ignore) { // thrown from getArguments() when no matching methods, // return an empty result } } } return result; } // returns null instead of throwing IllegalArgumentException for invalid name protected E getApiNameOrNull(String nameStr) { try { return getApiName(nameStr); } catch (IllegalArgumentException ignore) { return null; } } protected void getCompletedMethods(List<String> result, String completionText, Class<? extends ApiMethod> apiMethod, ApiMethodHelper<? extends ApiMethod> helper) { // add potential method names final ApiMethod[] methods = apiMethod.getEnumConstants(); for (ApiMethod method : methods) { final String name = method.getName(); if (name.startsWith(completionText)) { result.add(name); } } // add potential aliases final Map<String, Set<String>> aliases = helper.getAliases(); for (String alias : aliases.keySet()) { if (alias.startsWith(completionText)) { result.add(alias); } } } }