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