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     */
017    package org.apache.camel.impl;
018    
019    import java.util.ArrayList;
020    import java.util.HashMap;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Map;
024    import java.util.concurrent.ExecutorService;
025    import java.util.concurrent.ScheduledExecutorService;
026    import java.util.concurrent.ThreadFactory;
027    import java.util.concurrent.ThreadPoolExecutor;
028    import java.util.concurrent.TimeUnit;
029    
030    import org.apache.camel.CamelContext;
031    import org.apache.camel.NamedNode;
032    import org.apache.camel.ThreadPoolRejectedPolicy;
033    import org.apache.camel.model.OptionalIdentifiedDefinition;
034    import org.apache.camel.model.ProcessorDefinition;
035    import org.apache.camel.model.ProcessorDefinitionHelper;
036    import org.apache.camel.model.RouteDefinition;
037    import org.apache.camel.spi.ExecutorServiceManager;
038    import org.apache.camel.spi.LifecycleStrategy;
039    import org.apache.camel.spi.ThreadPoolFactory;
040    import org.apache.camel.spi.ThreadPoolProfile;
041    import org.apache.camel.support.ServiceSupport;
042    import org.apache.camel.util.ObjectHelper;
043    import org.apache.camel.util.URISupport;
044    import org.apache.camel.util.concurrent.CamelThreadFactory;
045    import org.apache.camel.util.concurrent.SizedScheduledExecutorService;
046    import org.apache.camel.util.concurrent.ThreadHelper;
047    import org.slf4j.Logger;
048    import org.slf4j.LoggerFactory;
049    
050    /**
051     * @version 
052     */
053    public class DefaultExecutorServiceManager extends ServiceSupport implements ExecutorServiceManager {
054        private static final Logger LOG = LoggerFactory.getLogger(DefaultExecutorServiceManager.class);
055    
056        private final CamelContext camelContext;
057        private ThreadPoolFactory threadPoolFactory = new DefaultThreadPoolFactory();
058        private final List<ExecutorService> executorServices = new ArrayList<ExecutorService>();
059        private String threadNamePattern;
060        private String defaultThreadPoolProfileId = "defaultThreadPoolProfile";
061        private final Map<String, ThreadPoolProfile> threadPoolProfiles = new HashMap<String, ThreadPoolProfile>();
062        private ThreadPoolProfile builtIndefaultProfile;
063    
064        public DefaultExecutorServiceManager(CamelContext camelContext) {
065            this.camelContext = camelContext;
066    
067            builtIndefaultProfile = new ThreadPoolProfile(defaultThreadPoolProfileId);
068            builtIndefaultProfile.setDefaultProfile(true);
069            builtIndefaultProfile.setPoolSize(10);
070            builtIndefaultProfile.setMaxPoolSize(20);
071            builtIndefaultProfile.setKeepAliveTime(60L);
072            builtIndefaultProfile.setTimeUnit(TimeUnit.SECONDS);
073            builtIndefaultProfile.setMaxQueueSize(1000);
074            builtIndefaultProfile.setRejectedPolicy(ThreadPoolRejectedPolicy.CallerRuns);
075    
076            registerThreadPoolProfile(builtIndefaultProfile);
077        }
078    
079        @Override
080        public ThreadPoolFactory getThreadPoolFactory() {
081            return threadPoolFactory;
082        }
083    
084        @Override
085        public void setThreadPoolFactory(ThreadPoolFactory threadPoolFactory) {
086            this.threadPoolFactory = threadPoolFactory;
087        }
088    
089        @Override
090        public void registerThreadPoolProfile(ThreadPoolProfile profile) {
091            ObjectHelper.notNull(profile, "profile");
092            ObjectHelper.notEmpty(profile.getId(), "id", profile);
093            threadPoolProfiles.put(profile.getId(), profile);
094        }
095    
096        @Override
097        public ThreadPoolProfile getThreadPoolProfile(String id) {
098            return threadPoolProfiles.get(id);
099        }
100    
101        @Override
102        public ThreadPoolProfile getDefaultThreadPoolProfile() {
103            return getThreadPoolProfile(defaultThreadPoolProfileId);
104        }
105    
106        @Override
107        public void setDefaultThreadPoolProfile(ThreadPoolProfile defaultThreadPoolProfile) {
108            threadPoolProfiles.remove(defaultThreadPoolProfileId);
109            defaultThreadPoolProfile.addDefaults(builtIndefaultProfile);
110    
111            LOG.info("Using custom DefaultThreadPoolProfile: " + defaultThreadPoolProfile);
112    
113            this.defaultThreadPoolProfileId = defaultThreadPoolProfile.getId();
114            defaultThreadPoolProfile.setDefaultProfile(true);
115            registerThreadPoolProfile(defaultThreadPoolProfile);
116        }
117    
118        @Override
119        public String getThreadNamePattern() {
120            return threadNamePattern;
121        }
122    
123        @Override
124        public void setThreadNamePattern(String threadNamePattern) {
125            // must set camel id here in the pattern and let the other placeholders be resolved on demand
126            String name = threadNamePattern.replaceFirst("#camelId#", this.camelContext.getName());
127            this.threadNamePattern = name;
128        }
129        
130        @Override
131        public String resolveThreadName(String name) {
132            return ThreadHelper.resolveThreadName(threadNamePattern, name);
133        }
134    
135        @Override
136        public ExecutorService newDefaultThreadPool(Object source, String name) {
137            return newThreadPool(source, name, getDefaultThreadPoolProfile());
138        }
139    
140        @Override
141        public ScheduledExecutorService newDefaultScheduledThreadPool(Object source, String name) {
142            return newScheduledThreadPool(source, name, getDefaultThreadPoolProfile());
143        }
144    
145        @Override
146        public ExecutorService newThreadPool(Object source, String name, String profileId) {
147            ThreadPoolProfile profile = getThreadPoolProfile(profileId);
148            if (profile != null) {
149                return newThreadPool(source, name, profile);
150            } else {
151                // no profile with that id
152                return null;
153            }
154        }
155    
156        @Override
157        public ExecutorService newThreadPool(Object source, String name, ThreadPoolProfile profile) {
158            String sanitizedName = URISupport.sanitizeUri(name);
159            ObjectHelper.notNull(profile, "ThreadPoolProfile");
160    
161            ThreadPoolProfile defaultProfile = getDefaultThreadPoolProfile();
162            profile.addDefaults(defaultProfile);
163    
164            ThreadFactory threadFactory = createThreadFactory(sanitizedName, true);
165            ExecutorService executorService = threadPoolFactory.newThreadPool(profile, threadFactory);
166            onThreadPoolCreated(executorService, source, profile.getId());
167            if (LOG.isDebugEnabled()) {
168                LOG.debug("Created new ThreadPool for source: {} with name: {}. -> {}", new Object[]{source, sanitizedName, executorService});
169            }
170    
171            return executorService;
172        }
173    
174        @Override
175        public ExecutorService newThreadPool(Object source, String name, int poolSize, int maxPoolSize) {
176            ThreadPoolProfile profile = new ThreadPoolProfile(name);
177            profile.setPoolSize(poolSize);
178            profile.setMaxPoolSize(maxPoolSize);
179            return  newThreadPool(source, name, profile);
180        }
181    
182        @Override
183        public ExecutorService newSingleThreadExecutor(Object source, String name) {
184            return newFixedThreadPool(source, name, 1);
185        }
186    
187        @Override
188        public ExecutorService newCachedThreadPool(Object source, String name) {
189            String sanitizedName = URISupport.sanitizeUri(name);
190            ExecutorService answer = threadPoolFactory.newCachedThreadPool(createThreadFactory(sanitizedName, true));
191            onThreadPoolCreated(answer, source, null);
192    
193            if (LOG.isDebugEnabled()) {
194                LOG.debug("Created new CachedThreadPool for source: {} with name: {}. -> {}", new Object[]{source, sanitizedName, answer});
195            }
196            return answer;
197        }
198    
199        @Override
200        public ExecutorService newFixedThreadPool(Object source, String name, int poolSize) {
201            ThreadPoolProfile profile = new ThreadPoolProfile(name);
202            profile.setPoolSize(poolSize);
203            profile.setMaxPoolSize(poolSize);
204            profile.setKeepAliveTime(0L);
205            return newThreadPool(source, name, profile);
206        }
207    
208        @Override
209        public ScheduledExecutorService newSingleThreadScheduledExecutor(Object source, String name) {
210            return newScheduledThreadPool(source, name, 1);
211        }
212        
213        @Override
214        public ScheduledExecutorService newScheduledThreadPool(Object source, String name, ThreadPoolProfile profile) {
215            String sanitizedName = URISupport.sanitizeUri(name);
216            profile.addDefaults(getDefaultThreadPoolProfile());
217            ScheduledExecutorService answer = threadPoolFactory.newScheduledThreadPool(profile, createThreadFactory(sanitizedName, true));
218            onThreadPoolCreated(answer, source, null);
219    
220            if (LOG.isDebugEnabled()) {
221                LOG.debug("Created new ScheduledThreadPool for source: {} with name: {}. -> {}", new Object[]{source, sanitizedName, answer});
222            }
223            return answer;
224        }
225    
226        @Override
227        public ScheduledExecutorService newScheduledThreadPool(Object source, String name, String profileId) {
228            ThreadPoolProfile profile = getThreadPoolProfile(profileId);
229            if (profile != null) {
230                return newScheduledThreadPool(source, name, profile);
231            } else {
232                // no profile with that id
233                return null;
234            }
235        }
236    
237        @Override
238        public ScheduledExecutorService newScheduledThreadPool(Object source, String name, int poolSize) {
239            ThreadPoolProfile profile = new ThreadPoolProfile(name);
240            profile.setPoolSize(poolSize);
241            return newScheduledThreadPool(source, name, profile);
242        }
243    
244        @Override
245        public void shutdown(ExecutorService executorService) {
246            ObjectHelper.notNull(executorService, "executorService");
247    
248            if (!executorService.isShutdown()) {
249                LOG.debug("Shutdown ExecutorService: {}", executorService);
250                executorService.shutdown();
251                LOG.trace("Shutdown ExecutorService: {} complete.", executorService);
252            }
253    
254            if (executorService instanceof ThreadPoolExecutor) {
255                ThreadPoolExecutor threadPool = (ThreadPoolExecutor) executorService;
256                for (LifecycleStrategy lifecycle : camelContext.getLifecycleStrategies()) {
257                    lifecycle.onThreadPoolRemove(camelContext, threadPool);
258                }
259            }
260    
261            // remove reference as its shutdown
262            executorServices.remove(executorService);
263        }
264    
265        @Override
266        public List<Runnable> shutdownNow(ExecutorService executorService) {
267            return doShutdownNow(executorService, true);
268        }
269    
270        private List<Runnable> doShutdownNow(ExecutorService executorService, boolean remove) {
271            ObjectHelper.notNull(executorService, "executorService");
272    
273            List<Runnable> answer = null;
274            if (!executorService.isShutdown()) {
275                LOG.debug("ShutdownNow ExecutorService: {}", executorService);
276                answer = executorService.shutdownNow();
277                LOG.trace("ShutdownNow ExecutorService: {} complete.", executorService);
278            }
279    
280            if (executorService instanceof ThreadPoolExecutor) {
281                ThreadPoolExecutor threadPool = (ThreadPoolExecutor) executorService;
282                for (LifecycleStrategy lifecycle : camelContext.getLifecycleStrategies()) {
283                    lifecycle.onThreadPoolRemove(camelContext, threadPool);
284                }
285            }
286    
287            // remove reference as its shutdown
288            if (remove) {
289                executorServices.remove(executorService);
290            }
291    
292            return answer;
293        }
294    
295        /**
296         * Strategy callback when a new {@link java.util.concurrent.ExecutorService} have been created.
297         *
298         * @param executorService the created {@link java.util.concurrent.ExecutorService}
299         */
300        protected void onNewExecutorService(ExecutorService executorService) {
301            // noop
302        }
303    
304        @Override
305        protected void doStart() throws Exception {
306            if (threadNamePattern == null) {
307                // set default name pattern which includes the camel context name
308                threadNamePattern = "Camel (" + camelContext.getName() + ") thread ##counter# - #name#";
309            }
310        }
311    
312        @Override
313        protected void doStop() throws Exception {
314            // noop
315        }
316    
317        @Override
318        protected void doShutdown() throws Exception {
319            // shutdown all executor services by looping
320            for (ExecutorService executorService : executorServices) {
321                // only log if something goes wrong as we want to shutdown them all
322                try {
323                    // must not remove during looping, as we clear the list afterwards
324                    doShutdownNow(executorService, false);
325                } catch (Throwable e) {
326                    LOG.warn("Error occurred during shutdown of ExecutorService: "
327                            + executorService + ". This exception will be ignored.", e);
328                }
329            }
330            executorServices.clear();
331    
332            // do not clear the default profile as we could potential be restarted
333            Iterator<ThreadPoolProfile> it = threadPoolProfiles.values().iterator();
334            while (it.hasNext()) {
335                ThreadPoolProfile profile = it.next();
336                if (!profile.isDefaultProfile()) {
337                    it.remove();
338                }
339            }
340        }
341    
342        /**
343         * Invoked when a new thread pool is created.
344         * This implementation will invoke the {@link LifecycleStrategy#onThreadPoolAdd(org.apache.camel.CamelContext,
345         * java.util.concurrent.ThreadPoolExecutor, String, String, String, String) LifecycleStrategy.onThreadPoolAdd} method,
346         * which for example will enlist the thread pool in JMX management.
347         *
348         * @param executorService the thread pool
349         * @param source          the source to use the thread pool
350         * @param threadPoolProfileId profile id, if the thread pool was created from a thread pool profile
351         */
352        private void onThreadPoolCreated(ExecutorService executorService, Object source, String threadPoolProfileId) {
353            // add to internal list of thread pools
354            executorServices.add(executorService);
355    
356            String id;
357            String sourceId = null;
358            String routeId = null;
359    
360            // extract id from source
361            if (source instanceof NamedNode) {
362                id = ((OptionalIdentifiedDefinition<?>) source).idOrCreate(this.camelContext.getNodeIdFactory());
363                // and let source be the short name of the pattern
364                sourceId = ((NamedNode) source).getShortName();
365            } else if (source instanceof String) {
366                id = (String) source;
367            } else if (source != null) {
368                // fallback and use the simple class name with hashcode for the id so its unique for this given source
369                id = source.getClass().getSimpleName() + "(" + ObjectHelper.getIdentityHashCode(source) + ")";
370            } else {
371                // no source, so fallback and use the simple class name from thread pool and its hashcode identity so its unique
372                id = executorService.getClass().getSimpleName() + "(" + ObjectHelper.getIdentityHashCode(executorService) + ")";
373            }
374    
375            // id is mandatory
376            ObjectHelper.notEmpty(id, "id for thread pool " + executorService);
377    
378            // extract route id if possible
379            if (source instanceof ProcessorDefinition) {
380                RouteDefinition route = ProcessorDefinitionHelper.getRoute((ProcessorDefinition<?>) source);
381                if (route != null) {
382                    routeId = route.idOrCreate(this.camelContext.getNodeIdFactory());
383                }
384            }
385    
386            // let lifecycle strategy be notified as well which can let it be managed in JMX as well
387            ThreadPoolExecutor threadPool = null;
388            if (executorService instanceof ThreadPoolExecutor) {
389                threadPool = (ThreadPoolExecutor) executorService;
390            } else if (executorService instanceof SizedScheduledExecutorService) {
391                threadPool = ((SizedScheduledExecutorService) executorService).getScheduledThreadPoolExecutor();
392            }
393            if (threadPool != null) {
394                for (LifecycleStrategy lifecycle : camelContext.getLifecycleStrategies()) {
395                    lifecycle.onThreadPoolAdd(camelContext, threadPool, id, sourceId, routeId, threadPoolProfileId);
396                }
397            }
398    
399            // now call strategy to allow custom logic
400            onNewExecutorService(executorService);
401        }
402    
403        private ThreadFactory createThreadFactory(String name, boolean isDaemon) {
404            ThreadFactory threadFactory = new CamelThreadFactory(threadNamePattern, name, isDaemon);
405            return threadFactory;
406        }
407    
408    }