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.UriParam;
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    @UriParam
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<String, Object>();
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<String>();
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<String>();
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}