/** * 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.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import org.apache.camel.CamelContext; import org.apache.camel.Component; import org.apache.camel.impl.DefaultEndpoint; import org.apache.camel.spi.ExecutorServiceManager; import org.apache.camel.spi.ThreadPoolProfile; import org.apache.camel.spi.UriParam; import org.apache.camel.util.EndpointHelper; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstract base class for API Component Endpoints. */ public abstract class AbstractApiEndpoint<E extends ApiName, T> extends DefaultEndpoint implements PropertyNamesInterceptor, PropertiesInterceptor { // thread pool executor with Endpoint Class name as keys private static Map<String, ExecutorService> executorServiceMap = new ConcurrentHashMap<String, ExecutorService>(); // logger protected final Logger log = LoggerFactory.getLogger(getClass()); // API name protected final E apiName; // API method name protected final String methodName; // API method helper protected final ApiMethodHelper<? extends ApiMethod> methodHelper; // endpoint configuration protected final T configuration; // property name for Exchange 'In' message body @UriParam(description = "Sets the name of a parameter to be passed in the exchange In Body") protected String inBody; // candidate methods based on method name and endpoint configuration private List<ApiMethod> candidates; // cached Executor service private ExecutorService executorService; // cached property names and values private Set<String> endpointPropertyNames; private Map<String, Object> endpointProperties; public AbstractApiEndpoint(String endpointUri, Component component, E apiName, String methodName, ApiMethodHelper<? extends ApiMethod> methodHelper, T endpointConfiguration) { super(endpointUri, component); this.apiName = apiName; this.methodName = methodName; this.methodHelper = methodHelper; this.configuration = endpointConfiguration; } public boolean isSingleton() { return true; } /** * Returns generated helper that extends {@link ApiMethodPropertiesHelper} to work with API properties. * @return properties helper. */ protected abstract ApiMethodPropertiesHelper<T> getPropertiesHelper(); @Override public void configureProperties(Map<String, Object> options) { super.configureProperties(options); // set configuration properties first try { T configuration = getConfiguration(); EndpointHelper.setReferenceProperties(getCamelContext(), configuration, options); EndpointHelper.setProperties(getCamelContext(), configuration, options); } catch (Exception e) { throw new IllegalArgumentException(e); } // validate and initialize state initState(); afterConfigureProperties(); } /** * Initialize proxies, create server connections, etc. after endpoint properties have been configured. */ protected abstract void afterConfigureProperties(); /** * Initialize endpoint state, including endpoint arguments, find candidate methods, etc. */ private void initState() { // compute endpoint property names and values this.endpointPropertyNames = Collections.unmodifiableSet( getPropertiesHelper().getEndpointPropertyNames(configuration)); final HashMap<String, Object> properties = new HashMap<String, Object>(); getPropertiesHelper().getEndpointProperties(configuration, properties); this.endpointProperties = Collections.unmodifiableMap(properties); // get endpoint property names final Set<String> arguments = new HashSet<>(endpointPropertyNames); // add inBody argument for producers if (inBody != null) { arguments.add(inBody); } interceptPropertyNames(arguments); // create a list of candidate methods candidates = new ArrayList<>(); candidates.addAll(methodHelper.getCandidateMethods(methodName, arguments)); candidates = Collections.unmodifiableList(candidates); // error if there are no candidates if (candidates.isEmpty()) { throw new IllegalArgumentException( String.format("No matching method for %s/%s, with arguments %s", apiName.getName(), methodName, arguments)); } // log missing/extra properties for debugging if (log.isDebugEnabled()) { final Set<String> missing = methodHelper.getMissingProperties(methodName, arguments); if (!missing.isEmpty()) { log.debug("Method {} could use one or more properties from {}", methodName, missing); } } } @Override public void interceptPropertyNames(Set<String> propertyNames) { // do nothing by default } @Override public void interceptProperties(Map<String, Object> properties) { // do nothing by default } /** * Returns endpoint configuration object. * One of the generated *EndpointConfiguration classes that extends component configuration class. * * @return endpoint configuration object */ public final T getConfiguration() { return configuration; } /** * Returns API name. * @return apiName property. */ public final E getApiName() { return apiName; } /** * Returns method name. * @return methodName property. */ public final String getMethodName() { return methodName; } /** * Returns method helper. * @return methodHelper property. */ public final ApiMethodHelper<? extends ApiMethod> getMethodHelper() { return methodHelper; } /** * Returns candidate methods for this endpoint. * @return list of candidate methods. */ public final List<ApiMethod> getCandidates() { return candidates; } /** * Returns name of parameter passed in the exchange In Body. * @return inBody property. */ public final String getInBody() { return inBody; } /** * Sets the name of a parameter to be passed in the exchange In Body. * @param inBody parameter name * @throws IllegalArgumentException for invalid parameter name. */ public final void setInBody(String inBody) throws IllegalArgumentException { // validate property name ObjectHelper.notNull(inBody, "inBody"); if (!getPropertiesHelper().getValidEndpointProperties(getConfiguration()).contains(inBody)) { throw new IllegalArgumentException("Unknown property " + inBody); } this.inBody = inBody; } public final Set<String> getEndpointPropertyNames() { return endpointPropertyNames; } public final Map<String, Object> getEndpointProperties() { return endpointProperties; } /** * Returns an instance of an API Proxy based on apiName, method and args. * Called by {@link AbstractApiConsumer} or {@link AbstractApiProducer}. * * @param method method about to be invoked * @param args method arguments * @return a Java object that implements the method to be invoked. * @see AbstractApiProducer * @see AbstractApiConsumer */ public abstract Object getApiProxy(ApiMethod method, Map<String, Object> args); private static ExecutorService getExecutorService( Class<? extends AbstractApiEndpoint> endpointClass, CamelContext context, String threadProfileName) { // lookup executorService for extending class name final String endpointClassName = endpointClass.getName(); ExecutorService executorService = executorServiceMap.get(endpointClassName); // CamelContext will shutdown thread pool when it shutdown so we can // lazy create it on demand // but in case of hot-deploy or the likes we need to be able to // re-create it (its a shared static instance) if (executorService == null || executorService.isTerminated() || executorService.isShutdown()) { final ExecutorServiceManager manager = context.getExecutorServiceManager(); // try to lookup a pool first based on profile ThreadPoolProfile poolProfile = manager.getThreadPoolProfile( threadProfileName); if (poolProfile == null) { poolProfile = manager.getDefaultThreadPoolProfile(); } // create a new pool using the custom or default profile executorService = manager.newScheduledThreadPool(endpointClass, threadProfileName, poolProfile); executorServiceMap.put(endpointClassName, executorService); } return executorService; } public final ExecutorService getExecutorService() { if (this.executorService == null) { // synchronize on class to avoid creating duplicate class level executors synchronized (getClass()) { this.executorService = getExecutorService(getClass(), getCamelContext(), getThreadProfileName()); } } return this.executorService; } /** * Returns Thread profile name. Generated as a constant THREAD_PROFILE_NAME in *Constants. * @return thread profile name to use. */ protected abstract String getThreadProfileName(); }