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