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;
018
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.LinkedHashSet;
022import java.util.List;
023import java.util.Set;
024
025import org.apache.camel.Channel;
026import org.apache.camel.Navigate;
027import org.apache.camel.Processor;
028import org.apache.camel.Service;
029import org.apache.camel.ShutdownableService;
030import org.apache.camel.StatefulService;
031import org.apache.camel.SuspendableService;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * A collection of helper methods for working with {@link Service} objects.
037 * 
038 * @version
039 */
040public final class ServiceHelper {
041    private static final Logger LOG = LoggerFactory.getLogger(ServiceHelper.class);
042
043    /**
044     * Utility classes should not have a public constructor.
045     */
046    private ServiceHelper() {
047    }
048
049    /**
050     * Starts the given {@code value} if it's a {@link Service} or a collection of it.
051     * <p/>
052     * Calling this method has no effect if {@code value} is {@code null}.
053     * 
054     * @see #startService(Service)
055     * @see #startServices(Collection)
056     */
057    public static void startService(Object value) throws Exception {
058        if (value instanceof Service) {
059            startService((Service)value);
060        } else if (value instanceof Collection) {
061            startServices((Collection<?>)value);
062        }
063    }
064    
065    /**
066     * Starts the given {@code service}.
067     * <p/>
068     * Calling this method has no effect if {@code service} is {@code null}.
069     * 
070     * @see Service#start()
071     */
072    public static void startService(Service service) throws Exception {
073        if (service != null) {
074            service.start();
075        }
076    }
077
078    /**
079     * Starts each element of the given {@code services} if {@code services} itself is
080     * not {@code null}, otherwise this method would return immediately.
081     * 
082     * @see #startServices(Collection)
083     */
084    public static void startServices(Object... services) throws Exception {
085        if (services == null) {
086            return;
087        }
088        List<Object> list = Arrays.asList(services);
089        startServices(list);
090    }
091
092    /**
093     * Starts each element of the given {@code services} if {@code services} itself is
094     * not {@code null}, otherwise this method would return immediately.
095     * 
096     * @see #startService(Object)
097     */
098    public static void startServices(Collection<?> services) throws Exception {
099        if (services == null) {
100            return;
101        }
102        for (Object value : services) {
103            startService(value);
104        }
105    }
106
107    /**
108     * Stops each element of the given {@code services} if {@code services} itself is
109     * not {@code null}, otherwise this method would return immediately.
110     * <p/>
111     * If there's any exception being thrown while stopping the elements one after the
112     * other this method would rethrow the <b>first</b> such exception being thrown.
113     * 
114     * @see #stopServices(Collection)
115     */
116    public static void stopServices(Object... services) throws Exception {
117        if (services == null) {
118            return;
119        }
120        List<Object> list = Arrays.asList(services);
121        stopServices(list);
122    }
123
124    /**
125     * Stops the given {@code value}, rethrowing the first exception caught.
126     * <p/>
127     * Calling this method has no effect if {@code value} is {@code null}.
128     * 
129     * @see Service#stop()
130     * @see #stopServices(Collection)
131     */
132    public static void stopService(Object value) throws Exception {
133        if (isStopped(value)) {
134            // only stop service if not already stopped
135            LOG.trace("Service already stopped: {}", value);
136            return;
137        }
138        if (value instanceof Service) {
139            Service service = (Service)value;
140            LOG.trace("Stopping service {}", value);
141            service.stop();
142        } else if (value instanceof Collection) {
143            stopServices((Collection<?>)value);
144        }
145    }
146
147    /**
148     * Stops each element of the given {@code services} if {@code services} itself is
149     * not {@code null}, otherwise this method would return immediately.
150     * <p/>
151     * If there's any exception being thrown while stopping the elements one after the
152     * other this method would rethrow the <b>first</b> such exception being thrown.
153     * 
154     * @see #stopService(Object)
155     */
156    public static void stopServices(Collection<?> services) throws Exception {
157        if (services == null) {
158            return;
159        }
160        Exception firstException = null;
161        for (Object value : services) {
162            try {
163                stopService(value);
164            } catch (Exception e) {
165                if (LOG.isDebugEnabled()) {
166                    LOG.debug("Caught exception stopping service: " + value, e);
167                }
168                if (firstException == null) {
169                    firstException = e;
170                }
171            }
172        }
173        if (firstException != null) {
174            throw firstException;
175        }
176    }
177
178    /**
179     * Stops and shutdowns each element of the given {@code services} if {@code services} itself is
180     * not {@code null}, otherwise this method would return immediately.
181     * <p/>
182     * If there's any exception being thrown while stopping/shutting down the elements one after
183     * the other this method would rethrow the <b>first</b> such exception being thrown.
184     * 
185     * @see #stopAndShutdownServices(Collection)
186     */
187    public static void stopAndShutdownServices(Object... services) throws Exception {
188        if (services == null) {
189            return;
190        }
191        List<Object> list = Arrays.asList(services);
192        stopAndShutdownServices(list);
193    }
194
195    /**
196     * Stops and shutdowns the given {@code service}, rethrowing the first exception caught.
197     * <p/>
198     * Calling this method has no effect if {@code value} is {@code null}.
199     * 
200     * @see #stopService(Object)
201     * @see ShutdownableService#shutdown()
202     */
203    public static void stopAndShutdownService(Object value) throws Exception {
204        stopService(value);
205
206        // then try to shutdown
207        if (value instanceof ShutdownableService) {
208            ShutdownableService service = (ShutdownableService)value;
209            LOG.trace("Shutting down service {}", value);
210            service.shutdown();
211        }
212    }
213
214    /**
215     * Stops and shutdowns each element of the given {@code services} if {@code services}
216     * itself is not {@code null}, otherwise this method would return immediately.
217     * <p/>
218     * If there's any exception being thrown while stopping/shutting down the elements one after
219     * the other this method would rethrow the <b>first</b> such exception being thrown.
220     * 
221     * @see #stopService(Object)
222     * @see ShutdownableService#shutdown()
223     */
224    public static void stopAndShutdownServices(Collection<?> services) throws Exception {
225        if (services == null) {
226            return;
227        }
228        Exception firstException = null;
229
230        for (Object value : services) {
231
232            try {
233                // must stop it first
234                stopService(value);
235
236                // then try to shutdown
237                if (value instanceof ShutdownableService) {
238                    ShutdownableService service = (ShutdownableService)value;
239                    LOG.trace("Shutting down service: {}", service);
240                    service.shutdown();
241                }
242            } catch (Exception e) {
243                if (LOG.isDebugEnabled()) {
244                    LOG.debug("Caught exception shutting down service: " + value, e);
245                }
246                if (firstException == null) {
247                    firstException = e;
248                }
249            }
250        }
251        if (firstException != null) {
252            throw firstException;
253        }
254    }
255
256    /**
257     * Resumes each element of the given {@code services} if {@code services} itself is
258     * not {@code null}, otherwise this method would return immediately.
259     * <p/>
260     * If there's any exception being thrown while resuming the elements one after the
261     * other this method would rethrow the <b>first</b> such exception being thrown.
262     * 
263     * @see #resumeService(Service)
264     */
265    public static void resumeServices(Collection<?> services) throws Exception {
266        if (services == null) {
267            return;
268        }
269        Exception firstException = null;
270        for (Object value : services) {
271            if (value instanceof Service) {
272                Service service = (Service)value;
273                try {
274                    resumeService(service);
275                } catch (Exception e) {
276                    if (LOG.isDebugEnabled()) {
277                        LOG.debug("Caught exception resuming service: " + service, e);
278                    }
279                    if (firstException == null) {
280                        firstException = e;
281                    }
282                }
283            }
284        }
285        if (firstException != null) {
286            throw firstException;
287        }
288    }
289
290    /**
291     * Resumes the given {@code service}.
292     * <p/>
293     * If {@code service} is a {@link org.apache.camel.SuspendableService} then
294     * it's {@link org.apache.camel.SuspendableService#resume()} is called but
295     * <b>only</b> if {@code service} is already {@link #isSuspended(Object)
296     * suspended}.
297     * <p/>
298     * If {@code service} is <b>not</b> a
299     * {@link org.apache.camel.SuspendableService} then it's
300     * {@link org.apache.camel.Service#start()} is called.
301     * <p/>
302     * Calling this method has no effect if {@code service} is {@code null}.
303     * 
304     * @param service the service
305     * @return <tt>true</tt> if either <tt>resume</tt> method or
306     *         {@link #startService(Service)} was called, <tt>false</tt>
307     *         otherwise.
308     * @throws Exception is thrown if error occurred
309     * @see #startService(Service)
310     */
311    public static boolean resumeService(Service service) throws Exception {
312        if (service instanceof SuspendableService) {
313            SuspendableService ss = (SuspendableService) service;
314            if (ss.isSuspended()) {
315                LOG.debug("Resuming service {}", service);
316                ss.resume();
317                return true;
318            } else {
319                return false;
320            }
321        } else {
322            startService(service);
323            return true;
324        }
325    }
326
327    /**
328     * Suspends each element of the given {@code services} if {@code services} itself is
329     * not {@code null}, otherwise this method would return immediately.
330     * <p/>
331     * If there's any exception being thrown while suspending the elements one after the
332     * other this method would rethrow the <b>first</b> such exception being thrown.
333     * 
334     * @see #suspendService(Service)
335     */
336    public static void suspendServices(Collection<?> services) throws Exception {
337        if (services == null) {
338            return;
339        }
340        Exception firstException = null;
341        for (Object value : services) {
342            if (value instanceof Service) {
343                Service service = (Service)value;
344                try {
345                    suspendService(service);
346                } catch (Exception e) {
347                    if (LOG.isDebugEnabled()) {
348                        LOG.debug("Caught exception suspending service: " + service, e);
349                    }
350                    if (firstException == null) {
351                        firstException = e;
352                    }
353                }
354            }
355        }
356        if (firstException != null) {
357            throw firstException;
358        }
359    }
360
361    /**
362     * Suspends the given {@code service}.
363     * <p/>
364     * If {@code service} is a {@link org.apache.camel.SuspendableService} then
365     * it's {@link org.apache.camel.SuspendableService#suspend()} is called but
366     * <b>only</b> if {@code service} is <b>not</b> already
367     * {@link #isSuspended(Object) suspended}.
368     * <p/>
369     * If {@code service} is <b>not</b> a
370     * {@link org.apache.camel.SuspendableService} then it's
371     * {@link org.apache.camel.Service#stop()} is called.
372     * <p/>
373     * Calling this method has no effect if {@code service} is {@code null}.
374     * 
375     * @param service the service
376     * @return <tt>true</tt> if either the <tt>suspend</tt> method or
377     *         {@link #stopService(Object)} was called, <tt>false</tt>
378     *         otherwise.
379     * @throws Exception is thrown if error occurred
380     * @see #stopService(Object)
381     */
382    public static boolean suspendService(Service service) throws Exception {
383        if (service instanceof SuspendableService) {
384            SuspendableService ss = (SuspendableService) service;
385            if (!ss.isSuspended()) {
386                LOG.trace("Suspending service {}", service);
387                ss.suspend();
388                return true;
389            } else {
390                return false;
391            }
392        } else {
393            stopService(service);
394            return true;
395        }
396    }
397
398    /**
399     * Is the given service stopping or already stopped?
400     *
401     * @return <tt>true</tt> if stopping or already stopped, <tt>false</tt> otherwise
402     * @see StatefulService#isStopping()
403     * @see StatefulService#isStopped()
404     */
405    public static boolean isStopped(Object value) {
406        if (value instanceof StatefulService) {
407            StatefulService service = (StatefulService) value;
408            if (service.isStopping() || service.isStopped()) {
409                return true;
410            }
411        }
412        return false;
413    }
414
415    /**
416     * Is the given service starting or already started?
417     *
418     * @return <tt>true</tt> if starting or already started, <tt>false</tt> otherwise
419     * @see StatefulService#isStarting()
420     * @see StatefulService#isStarted()
421     */
422    public static boolean isStarted(Object value) {
423        if (value instanceof StatefulService) {
424            StatefulService service = (StatefulService) value;
425            if (service.isStarting() || service.isStarted()) {
426                return true;
427            }
428        }
429        return false;
430    }
431    
432    /**
433     * Is the given service suspending or already suspended?
434     *
435     * @return <tt>true</tt> if suspending or already suspended, <tt>false</tt> otherwise
436     * @see StatefulService#isSuspending()
437     * @see StatefulService#isSuspended()
438     */
439    public static boolean isSuspended(Object value) {
440        if (value instanceof StatefulService) {
441            StatefulService service = (StatefulService) value;
442            if (service.isSuspending() || service.isSuspended()) {
443                return true;
444            }
445        }
446        return false;
447    }
448
449    /**
450     * Gathers all child services by navigating the service to recursively gather all child services.
451     * <p/>
452     * The returned set does <b>not</b> include the children being error handler.
453     *
454     * @param service the service
455     * @return the services, including the parent service, and all its children
456     */
457    public static Set<Service> getChildServices(Service service) {
458        return getChildServices(service, false);
459    }
460
461    /**
462     * Gathers all child services by navigating the service to recursively gather all child services.
463     *
464     * @param service the service
465     * @param includeErrorHandler whether to include error handlers
466     * @return the services, including the parent service, and all its children
467     */
468    public static Set<Service> getChildServices(Service service, boolean includeErrorHandler) {
469        Set<Service> answer = new LinkedHashSet<Service>();
470        doGetChildServices(answer, service, includeErrorHandler);
471        return answer;
472    }
473
474    private static void doGetChildServices(Set<Service> services, Service service, boolean includeErrorHandler) {
475        services.add(service);
476        if (service instanceof Navigate) {
477            Navigate<?> nav = (Navigate<?>) service;
478            if (nav.hasNext()) {
479                List<?> children = nav.next();
480                for (Object child : children) {
481                    if (child instanceof Channel) {
482                        if (includeErrorHandler) {
483                            // special for error handler as they are tied to the Channel
484                            Processor errorHandler = ((Channel) child).getErrorHandler();
485                            if (errorHandler != null && errorHandler instanceof Service) {
486                                services.add((Service) errorHandler);
487                            }
488                        }
489                        Processor next = ((Channel) child).getNextProcessor();
490                        if (next != null && next instanceof Service) {
491                            services.add((Service) next);
492                        }
493                    }
494                    if (child instanceof Service) {
495                        doGetChildServices(services, (Service) child, includeErrorHandler);
496                    }
497                }
498            }
499        }
500    }
501    
502}