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