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.util.ArrayList; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026import java.util.concurrent.ConcurrentHashMap; 027import java.util.concurrent.ExecutorService; 028 029import org.apache.camel.CamelContext; 030import org.apache.camel.Component; 031import org.apache.camel.impl.DefaultEndpoint; 032import org.apache.camel.spi.ExecutorServiceManager; 033import org.apache.camel.spi.ThreadPoolProfile; 034import org.apache.camel.spi.UriParam; 035import org.apache.camel.util.EndpointHelper; 036import org.apache.camel.util.ObjectHelper; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040/** 041 * Abstract base class for API Component Endpoints. 042 */ 043public abstract class AbstractApiEndpoint<E extends ApiName, T> 044 extends DefaultEndpoint implements PropertyNamesInterceptor, PropertiesInterceptor { 045 046 // thread pool executor with Endpoint Class name as keys 047 private static Map<String, ExecutorService> executorServiceMap = new ConcurrentHashMap<String, ExecutorService>(); 048 049 // logger 050 protected final Logger log = LoggerFactory.getLogger(getClass()); 051 052 // API name 053 protected final E apiName; 054 055 // API method name 056 protected final String methodName; 057 058 // API method helper 059 protected final ApiMethodHelper<? extends ApiMethod> methodHelper; 060 061 // endpoint configuration 062 protected final T configuration; 063 064 // property name for Exchange 'In' message body 065 @UriParam(description = "Sets the name of a parameter to be passed in the exchange In Body") 066 protected String inBody; 067 068 // candidate methods based on method name and endpoint configuration 069 private List<ApiMethod> candidates; 070 071 // cached Executor service 072 private ExecutorService executorService; 073 074 // cached property names and values 075 private Set<String> endpointPropertyNames; 076 private Map<String, Object> endpointProperties; 077 078 public AbstractApiEndpoint(String endpointUri, Component component, 079 E apiName, String methodName, ApiMethodHelper<? extends ApiMethod> methodHelper, T endpointConfiguration) { 080 super(endpointUri, component); 081 082 this.apiName = apiName; 083 this.methodName = methodName; 084 this.methodHelper = methodHelper; 085 this.configuration = endpointConfiguration; 086 } 087 088 public boolean isSingleton() { 089 return true; 090 } 091 092 /** 093 * Returns generated helper that extends {@link ApiMethodPropertiesHelper} to work with API properties. 094 * @return properties helper. 095 */ 096 protected abstract ApiMethodPropertiesHelper<T> getPropertiesHelper(); 097 098 @Override 099 public void configureProperties(Map<String, Object> options) { 100 super.configureProperties(options); 101 102 // set configuration properties first 103 try { 104 T configuration = getConfiguration(); 105 EndpointHelper.setReferenceProperties(getCamelContext(), configuration, options); 106 EndpointHelper.setProperties(getCamelContext(), configuration, options); 107 } catch (Exception e) { 108 throw new IllegalArgumentException(e); 109 } 110 111 // validate and initialize state 112 initState(); 113 114 afterConfigureProperties(); 115 } 116 117 /** 118 * Initialize proxies, create server connections, etc. after endpoint properties have been configured. 119 */ 120 protected abstract void afterConfigureProperties(); 121 122 /** 123 * Initialize endpoint state, including endpoint arguments, find candidate methods, etc. 124 */ 125 private void initState() { 126 127 // compute endpoint property names and values 128 this.endpointPropertyNames = Collections.unmodifiableSet( 129 getPropertiesHelper().getEndpointPropertyNames(configuration)); 130 final HashMap<String, Object> properties = new HashMap<String, Object>(); 131 getPropertiesHelper().getEndpointProperties(configuration, properties); 132 this.endpointProperties = Collections.unmodifiableMap(properties); 133 134 // get endpoint property names 135 final Set<String> arguments = new HashSet<>(endpointPropertyNames); 136 // add inBody argument for producers 137 if (inBody != null) { 138 arguments.add(inBody); 139 } 140 141 interceptPropertyNames(arguments); 142 143 // create a list of candidate methods 144 candidates = new ArrayList<>(); 145 candidates.addAll(methodHelper.getCandidateMethods(methodName, arguments)); 146 candidates = Collections.unmodifiableList(candidates); 147 148 // error if there are no candidates 149 if (candidates.isEmpty()) { 150 throw new IllegalArgumentException( 151 String.format("No matching method for %s/%s, with arguments %s", 152 apiName.getName(), methodName, arguments)); 153 } 154 155 // log missing/extra properties for debugging 156 if (log.isDebugEnabled()) { 157 final Set<String> missing = methodHelper.getMissingProperties(methodName, arguments); 158 if (!missing.isEmpty()) { 159 log.debug("Method {} could use one or more properties from {}", methodName, missing); 160 } 161 } 162 } 163 164 @Override 165 public void interceptPropertyNames(Set<String> propertyNames) { 166 // do nothing by default 167 } 168 169 @Override 170 public void interceptProperties(Map<String, Object> properties) { 171 // do nothing by default 172 } 173 174 /** 175 * Returns endpoint configuration object. 176 * One of the generated *EndpointConfiguration classes that extends component configuration class. 177 * 178 * @return endpoint configuration object 179 */ 180 public final T getConfiguration() { 181 return configuration; 182 } 183 184 /** 185 * Returns API name. 186 * @return apiName property. 187 */ 188 public final E getApiName() { 189 return apiName; 190 } 191 192 /** 193 * Returns method name. 194 * @return methodName property. 195 */ 196 public final String getMethodName() { 197 return methodName; 198 } 199 200 /** 201 * Returns method helper. 202 * @return methodHelper property. 203 */ 204 public final ApiMethodHelper<? extends ApiMethod> getMethodHelper() { 205 return methodHelper; 206 } 207 208 /** 209 * Returns candidate methods for this endpoint. 210 * @return list of candidate methods. 211 */ 212 public final List<ApiMethod> getCandidates() { 213 return candidates; 214 } 215 216 /** 217 * Returns name of parameter passed in the exchange In Body. 218 * @return inBody property. 219 */ 220 public final String getInBody() { 221 return inBody; 222 } 223 224 /** 225 * Sets the name of a parameter to be passed in the exchange In Body. 226 * @param inBody parameter name 227 * @throws IllegalArgumentException for invalid parameter name. 228 */ 229 public final void setInBody(String inBody) throws IllegalArgumentException { 230 // validate property name 231 ObjectHelper.notNull(inBody, "inBody"); 232 if (!getPropertiesHelper().getValidEndpointProperties(getConfiguration()).contains(inBody)) { 233 throw new IllegalArgumentException("Unknown property " + inBody); 234 } 235 this.inBody = inBody; 236 } 237 238 public final Set<String> getEndpointPropertyNames() { 239 return endpointPropertyNames; 240 } 241 242 public final Map<String, Object> getEndpointProperties() { 243 return endpointProperties; 244 } 245 246 /** 247 * Returns an instance of an API Proxy based on apiName, method and args. 248 * Called by {@link AbstractApiConsumer} or {@link AbstractApiProducer}. 249 * 250 * @param method method about to be invoked 251 * @param args method arguments 252 * @return a Java object that implements the method to be invoked. 253 * @see AbstractApiProducer 254 * @see AbstractApiConsumer 255 */ 256 public abstract Object getApiProxy(ApiMethod method, Map<String, Object> args); 257 258 private static ExecutorService getExecutorService( 259 Class<? extends AbstractApiEndpoint> endpointClass, CamelContext context, String threadProfileName) { 260 261 // lookup executorService for extending class name 262 final String endpointClassName = endpointClass.getName(); 263 ExecutorService executorService = executorServiceMap.get(endpointClassName); 264 265 // CamelContext will shutdown thread pool when it shutdown so we can 266 // lazy create it on demand 267 // but in case of hot-deploy or the likes we need to be able to 268 // re-create it (its a shared static instance) 269 if (executorService == null || executorService.isTerminated() || executorService.isShutdown()) { 270 final ExecutorServiceManager manager = context.getExecutorServiceManager(); 271 272 // try to lookup a pool first based on profile 273 ThreadPoolProfile poolProfile = manager.getThreadPoolProfile( 274 threadProfileName); 275 if (poolProfile == null) { 276 poolProfile = manager.getDefaultThreadPoolProfile(); 277 } 278 279 // create a new pool using the custom or default profile 280 executorService = manager.newScheduledThreadPool(endpointClass, threadProfileName, poolProfile); 281 282 executorServiceMap.put(endpointClassName, executorService); 283 } 284 285 return executorService; 286 } 287 288 public final ExecutorService getExecutorService() { 289 if (this.executorService == null) { 290 // synchronize on class to avoid creating duplicate class level executors 291 synchronized (getClass()) { 292 this.executorService = getExecutorService(getClass(), getCamelContext(), getThreadProfileName()); 293 } 294 } 295 return this.executorService; 296 } 297 298 /** 299 * Returns Thread profile name. Generated as a constant THREAD_PROFILE_NAME in *Constants. 300 * @return thread profile name to use. 301 */ 302 protected abstract String getThreadProfileName(); 303}