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;
018
019import java.io.InputStream;
020import java.util.Properties;
021import java.util.concurrent.atomic.AtomicBoolean;
022
023import org.apache.camel.ServiceStatus;
024import org.apache.camel.StatefulService;
025import org.apache.camel.util.IOHelper;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029/**
030 * A useful base class which ensures that a service is only initialized once and
031 * provides some helper methods for enquiring of its status.
032 * <p/>
033 * Implementations can extend this base class and implement {@link org.apache.camel.SuspendableService}
034 * in case they support suspend/resume.
035 *
036 * @version 
037 */
038public abstract class ServiceSupport implements StatefulService {
039    private static final Logger LOG = LoggerFactory.getLogger(ServiceSupport.class);
040
041    protected final AtomicBoolean started = new AtomicBoolean(false);
042    protected final AtomicBoolean starting = new AtomicBoolean(false);
043    protected final AtomicBoolean stopping = new AtomicBoolean(false);
044    protected final AtomicBoolean stopped = new AtomicBoolean(false);
045    protected final AtomicBoolean suspending = new AtomicBoolean(false);
046    protected final AtomicBoolean suspended = new AtomicBoolean(false);
047    protected final AtomicBoolean shuttingdown = new AtomicBoolean(false);
048    protected final AtomicBoolean shutdown = new AtomicBoolean(false);
049
050    private String version;
051
052    public void start() throws Exception {
053        if (isStarting() || isStarted()) {
054            // only start service if not already started
055            LOG.trace("Service already started");
056            return;
057        }
058        if (starting.compareAndSet(false, true)) {
059            LOG.trace("Starting service");
060            try {
061                doStart();
062                started.set(true);
063                starting.set(false);
064                stopping.set(false);
065                stopped.set(false);
066                suspending.set(false);
067                suspended.set(false);
068                shutdown.set(false);
069                shuttingdown.set(false);
070            } catch (Exception e) {
071                try {
072                    stop();
073                } catch (Exception e2) {
074                    // Ignore exceptions as we want to show the original exception
075                } finally {
076                    // ensure flags get reset to stopped as we failed during starting
077                    stopping.set(false);
078                    stopped.set(true);
079                    starting.set(false);
080                    started.set(false);
081                    suspending.set(false);
082                    suspended.set(false);
083                    shutdown.set(false);
084                    shuttingdown.set(false);
085                }
086                throw e;
087            } 
088        }
089    }
090    
091    public void stop() throws Exception {
092        if (isStopped()) {
093            LOG.trace("Service already stopped");
094            return;
095        }
096        if (isStopping()) {
097            LOG.trace("Service already stopping");
098            return;
099        }
100        stopping.set(true);
101        try {
102            doStop();
103        } finally {
104            stopping.set(false);
105            stopped.set(true);
106            starting.set(false);
107            started.set(false);
108            suspending.set(false);
109            suspended.set(false);
110            shutdown.set(false);
111            shuttingdown.set(false);            
112        }
113    }
114
115    @Override
116    public void suspend() throws Exception {
117        if (!suspended.get()) {
118            if (suspending.compareAndSet(false, true)) {
119                try {
120                    starting.set(false);
121                    stopping.set(false);
122                    doSuspend();
123                } finally {
124                    stopped.set(false);
125                    stopping.set(false);
126                    starting.set(false);
127                    started.set(false);
128                    suspending.set(false);
129                    suspended.set(true);
130                    shutdown.set(false);
131                    shuttingdown.set(false);
132                }
133            }
134        }
135    }
136
137    @Override
138    public void resume() throws Exception {
139        if (suspended.get()) {
140            if (starting.compareAndSet(false, true)) {
141                try {
142                    doResume();
143                } finally {
144                    started.set(true);
145                    starting.set(false);
146                    stopping.set(false);
147                    stopped.set(false);
148                    suspending.set(false);
149                    suspended.set(false);
150                    shutdown.set(false);
151                    shuttingdown.set(false);
152                }
153            }
154        }
155    }
156
157    @Override
158    public void shutdown() throws Exception {
159        if (shutdown.get()) {
160            LOG.trace("Service already shut down");
161            return;
162        }
163        // ensure we are stopped first
164        stop();
165
166        if (shuttingdown.compareAndSet(false, true)) {
167            try {
168                doShutdown();
169            } finally {
170                // shutdown is also stopped so only set shutdown flags
171                shutdown.set(true);
172                shuttingdown.set(false);
173            }
174        }
175    }
176
177    @Override
178    public ServiceStatus getStatus() {
179        // we should check the ---ing states first, as this indicate the state is in the middle of doing that
180        if (isStarting()) {
181            return ServiceStatus.Starting;
182        }
183        if (isStopping()) {
184            return ServiceStatus.Stopping;
185        }
186        if (isSuspending()) {
187            return ServiceStatus.Suspending;
188        }
189
190        // then check for the regular states
191        if (isStarted()) {
192            return ServiceStatus.Started;
193        }
194        if (isStopped()) {
195            return ServiceStatus.Stopped;
196        }
197        if (isSuspended()) {
198            return ServiceStatus.Suspended;
199        }
200
201        // use stopped as fallback
202        return ServiceStatus.Stopped;
203    }
204    
205    @Override
206    public boolean isStarted() {
207        return started.get();
208    }
209
210    @Override
211    public boolean isStarting() {
212        return starting.get();
213    }
214
215    @Override
216    public boolean isStopping() {
217        return stopping.get();
218    }
219
220    @Override
221    public boolean isStopped() {
222        return stopped.get();
223    }
224
225    @Override
226    public boolean isSuspending() {
227        return suspending.get();
228    }
229
230    @Override
231    public boolean isSuspended() {
232        return suspended.get();
233    }
234
235    @Override
236    public boolean isRunAllowed() {
237        // if we have not yet initialized, then all options is false
238        boolean unused1 = !started.get() && !starting.get() && !stopping.get() && !stopped.get();
239        boolean unused2 = !suspending.get() && !suspended.get() && !shutdown.get() && !shuttingdown.get();
240        if (unused1 && unused2) {
241            return false;
242        }
243        return !isStoppingOrStopped();
244    }
245
246    /**
247     * Is the service in progress of being stopped or already stopped
248     */
249    public boolean isStoppingOrStopped() {
250        return stopping.get() || stopped.get();
251    }
252
253    /**
254     * Is the service in progress of being suspended or already suspended
255     */
256    public boolean isSuspendingOrSuspended() {
257        return suspending.get() || suspended.get();
258    }
259
260    /**
261     * Implementations override this method to support customized start/stop.
262     * <p/>
263     * <b>Important: </b> See {@link #doStop()} for more details.
264     * 
265     * @see #doStop()
266     */
267    protected abstract void doStart() throws Exception;
268
269    /**
270     * Implementations override this method to support customized start/stop.
271     * <p/>
272     * <b>Important:</b> Camel will invoke this {@link #doStop()} method when
273     * the service is being stopped. This method will <b>also</b> be invoked
274     * if the service is still in <i>uninitialized</i> state (eg has not
275     * been started). The method is <b>always</b> called to allow the service
276     * to do custom logic when the service is being stopped, such as when
277     * {@link org.apache.camel.CamelContext} is shutting down.
278     * 
279     * @see #doStart() 
280     */
281    protected abstract void doStop() throws Exception;
282
283    /**
284     * Implementations override this method to support customized suspend/resume.
285     */
286    protected void doSuspend() throws Exception {
287        // noop
288    }
289
290    /**
291     * Implementations override this method to support customized suspend/resume.
292     */
293    protected void doResume() throws Exception {
294        // noop
295    }
296
297    /**
298     * Implementations override this method to perform customized shutdown.
299     */
300    protected void doShutdown() throws Exception {
301        // noop
302    }
303
304    @Override
305    public synchronized String getVersion() {
306        if (version != null) {
307            return version;
308        }
309        InputStream is = null;
310        // try to load from maven properties first
311        try {
312            Properties p = new Properties();
313            is = getClass().getResourceAsStream("/META-INF/maven/org.apache.camel/camel-core/pom.properties");
314            if (is != null) {
315                p.load(is);
316                version = p.getProperty("version", "");
317            }
318        } catch (Exception e) {
319            // ignore
320        } finally {
321            if (is != null) {
322                IOHelper.close(is);
323            }
324        }
325
326        // fallback to using Java API
327        if (version == null) {
328            Package aPackage = getClass().getPackage();
329            if (aPackage != null) {
330                version = aPackage.getImplementationVersion();
331                if (version == null) {
332                    version = aPackage.getSpecificationVersion();
333                }
334            }
335        }
336
337        if (version == null) {
338            // we could not compute the version so use a blank
339            version = "";
340        }
341
342        return version;
343    }
344}