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