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<String>(); 136 arguments.addAll(endpointPropertyNames); 137 138 // add inBody argument for producers 139 if (inBody != null) { 140 arguments.add(inBody); 141 } 142 143 interceptPropertyNames(arguments); 144 145 final String[] argNames = arguments.toArray(new String[arguments.size()]); 146 147 // create a list of candidate methods 148 candidates = new ArrayList<ApiMethod>(); 149 candidates.addAll(methodHelper.getCandidateMethods(methodName, argNames)); 150 candidates = Collections.unmodifiableList(candidates); 151 152 // error if there are no candidates 153 if (candidates.isEmpty()) { 154 throw new IllegalArgumentException( 155 String.format("No matching method for %s/%s, with arguments %s", 156 apiName.getName(), methodName, arguments)); 157 } 158 159 // log missing/extra properties for debugging 160 if (log.isDebugEnabled()) { 161 final Set<String> missing = methodHelper.getMissingProperties(methodName, arguments); 162 if (!missing.isEmpty()) { 163 log.debug("Method {} could use one or more properties from {}", methodName, missing); 164 } 165 } 166 } 167 168 @Override 169 public void interceptPropertyNames(Set<String> propertyNames) { 170 // do nothing by default 171 } 172 173 @Override 174 public void interceptProperties(Map<String, Object> properties) { 175 // do nothing by default 176 } 177 178 /** 179 * Returns endpoint configuration object. 180 * One of the generated *EndpointConfiguration classes that extends component configuration class. 181 * 182 * @return endpoint configuration object 183 */ 184 public final T getConfiguration() { 185 return configuration; 186 } 187 188 /** 189 * Returns API name. 190 * @return apiName property. 191 */ 192 public final E getApiName() { 193 return apiName; 194 } 195 196 /** 197 * Returns method name. 198 * @return methodName property. 199 */ 200 public final String getMethodName() { 201 return methodName; 202 } 203 204 /** 205 * Returns method helper. 206 * @return methodHelper property. 207 */ 208 public final ApiMethodHelper<? extends ApiMethod> getMethodHelper() { 209 return methodHelper; 210 } 211 212 /** 213 * Returns candidate methods for this endpoint. 214 * @return list of candidate methods. 215 */ 216 public final List<ApiMethod> getCandidates() { 217 return candidates; 218 } 219 220 /** 221 * Returns name of parameter passed in the exchange In Body. 222 * @return inBody property. 223 */ 224 public final String getInBody() { 225 return inBody; 226 } 227 228 /** 229 * Sets the name of a parameter to be passed in the exchange In Body. 230 * @param inBody parameter name 231 * @throws IllegalArgumentException for invalid parameter name. 232 */ 233 public final void setInBody(String inBody) throws IllegalArgumentException { 234 // validate property name 235 ObjectHelper.notNull(inBody, "inBody"); 236 if (!getPropertiesHelper().getValidEndpointProperties(getConfiguration()).contains(inBody)) { 237 throw new IllegalArgumentException("Unknown property " + inBody); 238 } 239 this.inBody = inBody; 240 } 241 242 public final Set<String> getEndpointPropertyNames() { 243 return endpointPropertyNames; 244 } 245 246 public final Map<String, Object> getEndpointProperties() { 247 return endpointProperties; 248 } 249 250 /** 251 * Returns an instance of an API Proxy based on apiName, method and args. 252 * Called by {@link AbstractApiConsumer} or {@link AbstractApiProducer}. 253 * 254 * @param method method about to be invoked 255 * @param args method arguments 256 * @return a Java object that implements the method to be invoked. 257 * @see AbstractApiProducer 258 * @see AbstractApiConsumer 259 */ 260 public abstract Object getApiProxy(ApiMethod method, Map<String, Object> args); 261 262 private static ExecutorService getExecutorService( 263 Class<? extends AbstractApiEndpoint> endpointClass, CamelContext context, String threadProfileName) { 264 265 // lookup executorService for extending class name 266 final String endpointClassName = endpointClass.getName(); 267 ExecutorService executorService = executorServiceMap.get(endpointClassName); 268 269 // CamelContext will shutdown thread pool when it shutdown so we can 270 // lazy create it on demand 271 // but in case of hot-deploy or the likes we need to be able to 272 // re-create it (its a shared static instance) 273 if (executorService == null || executorService.isTerminated() || executorService.isShutdown()) { 274 final ExecutorServiceManager manager = context.getExecutorServiceManager(); 275 276 // try to lookup a pool first based on profile 277 ThreadPoolProfile poolProfile = manager.getThreadPoolProfile( 278 threadProfileName); 279 if (poolProfile == null) { 280 poolProfile = manager.getDefaultThreadPoolProfile(); 281 } 282 283 // create a new pool using the custom or default profile 284 executorService = manager.newScheduledThreadPool(endpointClass, threadProfileName, poolProfile); 285 286 executorServiceMap.put(endpointClassName, executorService); 287 } 288 289 return executorService; 290 } 291 292 public final ExecutorService getExecutorService() { 293 if (this.executorService == null) { 294 // synchronize on class to avoid creating duplicate class level executors 295 synchronized (getClass()) { 296 this.executorService = getExecutorService(getClass(), getCamelContext(), getThreadProfileName()); 297 } 298 } 299 return this.executorService; 300 } 301 302 /** 303 * Returns Thread profile name. Generated as a constant THREAD_PROFILE_NAME in *Constants. 304 * @return thread profile name to use. 305 */ 306 protected abstract String getThreadProfileName(); 307}