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