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