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