001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors. 
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.repository.service;
025    
026    import java.util.Locale;
027    import net.jcip.annotations.GuardedBy;
028    import net.jcip.annotations.ThreadSafe;
029    import org.jboss.dna.common.i18n.I18n;
030    import org.jboss.dna.common.util.Logger;
031    import org.jboss.dna.repository.RepositoryI18n;
032    
033    /**
034     * Simple abstract implementation of the service administrator interface that can be easily subclassed by services that require an
035     * administrative interface.
036     * 
037     * @author Randall Hauch
038     */
039    @ThreadSafe
040    public abstract class AbstractServiceAdministrator implements ServiceAdministrator {
041    
042        private volatile State state;
043        private final I18n serviceName;
044        private final Logger logger;
045    
046        protected AbstractServiceAdministrator( I18n serviceName,
047                                                State initialState ) {
048            assert initialState != null;
049            assert serviceName != null;
050            this.state = initialState;
051            this.serviceName = serviceName;
052            this.logger = Logger.getLogger(getClass());
053        }
054    
055        /**
056         * Return the current state of this service.
057         * 
058         * @return the current state
059         */
060        public State getState() {
061            return this.state;
062        }
063    
064        /**
065         * Set the state of the service. This method does nothing if the desired state matches the current state.
066         * 
067         * @param state the desired state
068         * @return this object for method chaining purposes
069         * @see #setState(String)
070         * @see #start()
071         * @see #pause()
072         * @see #shutdown()
073         */
074        @GuardedBy( "this" )
075        public synchronized ServiceAdministrator setState( State state ) {
076            switch (state) {
077                case STARTED:
078                    start();
079                    break;
080                case PAUSED:
081                    pause();
082                    break;
083                case SHUTDOWN:
084                case TERMINATED:
085                    shutdown();
086                    break;
087            }
088            return this;
089        }
090    
091        /**
092         * Set the state of the service. This method does nothing if the desired state matches the current state.
093         * 
094         * @param state the desired state in string form
095         * @return this object for method chaining purposes
096         * @throws IllegalArgumentException if the specified state string is null or does not match one of the predefined
097         *         {@link ServiceAdministrator.State predefined enumerated values}
098         * @see #setState(State)
099         * @see #start()
100         * @see #pause()
101         * @see #shutdown()
102         */
103        public ServiceAdministrator setState( String state ) {
104            State newState = state == null ? null : State.valueOf(state.toUpperCase());
105            if (newState == null) {
106                throw new IllegalArgumentException(RepositoryI18n.invalidStateString.text(state));
107            }
108            return setState(newState);
109        }
110    
111        /**
112         * Start monitoring and sequence the events. This method can be called multiple times, including after the service is
113         * {@link #pause() paused}. However, once the service is {@link #shutdown() shutdown}, it cannot be started or paused.
114         * 
115         * @return this object for method chaining purposes
116         * @throws IllegalStateException if called when the service has been {@link #shutdown() shutdown}.
117         * @see #pause()
118         * @see #shutdown()
119         * @see #isStarted()
120         */
121        public synchronized ServiceAdministrator start() {
122            switch (this.state) {
123                case STARTED:
124                    break;
125                case PAUSED:
126                    logger.trace("Starting \"{0}\"", getServiceName());
127                    doStart(this.state);
128                    this.state = State.STARTED;
129                    logger.trace("Started \"{0}\"", getServiceName());
130                    break;
131                case SHUTDOWN:
132                case TERMINATED:
133                    throw new IllegalStateException(RepositoryI18n.serviceShutdowAndMayNotBeStarted.text(getServiceName()));
134            }
135            return this;
136        }
137    
138        /**
139         * Implementation of the functionality to switch to the started state. This method is only called if the state from which the
140         * service is transitioning is appropriate ({@link ServiceAdministrator.State#PAUSED}). This method does nothing by default,
141         * and should be overridden if needed.
142         * 
143         * @param fromState the state from which this service is transitioning; never null
144         * @throws IllegalStateException if the service is such that it cannot be transitioned from the supplied state
145         */
146        @GuardedBy( "this" )
147        protected void doStart( State fromState ) {
148        }
149    
150        /**
151         * Temporarily stop monitoring and sequencing events. This method can be called multiple times, including after the service is
152         * {@link #start() started}. However, once the service is {@link #shutdown() shutdown}, it cannot be started or paused.
153         * 
154         * @return this object for method chaining purposes
155         * @throws IllegalStateException if called when the service has been {@link #shutdown() shutdown}.
156         * @see #start()
157         * @see #shutdown()
158         * @see #isPaused()
159         */
160        public synchronized ServiceAdministrator pause() {
161            switch (this.state) {
162                case STARTED:
163                    logger.trace("Pausing \"{0}\"", getServiceName());
164                    doPause(this.state);
165                    this.state = State.PAUSED;
166                    logger.trace("Paused \"{0}\"", getServiceName());
167                    break;
168                case PAUSED:
169                    break;
170                case SHUTDOWN:
171                case TERMINATED:
172                    throw new IllegalStateException(RepositoryI18n.serviceShutdowAndMayNotBePaused.text(getServiceName()));
173            }
174            return this;
175        }
176    
177        /**
178         * Implementation of the functionality to switch to the paused state. This method is only called if the state from which the
179         * service is transitioning is appropriate ({@link ServiceAdministrator.State#STARTED}). This method does nothing by default,
180         * and should be overridden if needed.
181         * 
182         * @param fromState the state from which this service is transitioning; never null
183         * @throws IllegalStateException if the service is such that it cannot be transitioned from the supplied state
184         */
185        @GuardedBy( "this" )
186        protected void doPause( State fromState ) {
187        }
188    
189        /**
190         * Permanently stop monitoring and sequencing events. This method can be called multiple times, but only the first call has an
191         * effect. Once the service has been shutdown, it may not be {@link #start() restarted} or {@link #pause() paused}.
192         * 
193         * @return this object for method chaining purposes
194         * @see #start()
195         * @see #pause()
196         * @see #isShutdown()
197         */
198        public synchronized ServiceAdministrator shutdown() {
199            switch (this.state) {
200                case STARTED:
201                case PAUSED:
202                    logger.trace("Initiating shutdown of \"{0}\"", getServiceName());
203                    this.state = State.SHUTDOWN;
204                    doShutdown(this.state);
205                    logger.trace("Initiated shutdown of \"{0}\"", getServiceName());
206                    isTerminated();
207                    break;
208                case SHUTDOWN:
209                case TERMINATED:
210                    isTerminated();
211                    break;
212            }
213            return this;
214        }
215    
216        /**
217         * Implementation of the functionality to switch to the shutdown state. This method is only called if the state from which the
218         * service is transitioning is appropriate ({@link ServiceAdministrator.State#STARTED} or
219         * {@link ServiceAdministrator.State#PAUSED}). This method does nothing by default, and should be overridden if needed.
220         * 
221         * @param fromState the state from which this service is transitioning; never null
222         * @throws IllegalStateException if the service is such that it cannot be transitioned from the supplied state
223         */
224        @GuardedBy( "this" )
225        protected void doShutdown( State fromState ) {
226        }
227    
228        /**
229         * Return whether this service has been started and is currently running.
230         * 
231         * @return true if started and currently running, or false otherwise
232         * @see #start()
233         * @see #pause()
234         * @see #isPaused()
235         * @see #isShutdown()
236         */
237        public boolean isStarted() {
238            return this.state == State.STARTED;
239        }
240    
241        /**
242         * Return whether this service is currently paused.
243         * 
244         * @return true if currently paused, or false otherwise
245         * @see #pause()
246         * @see #start()
247         * @see #isStarted()
248         * @see #isShutdown()
249         */
250        public boolean isPaused() {
251            return this.state == State.PAUSED;
252        }
253    
254        /**
255         * Return whether this service is stopped and unable to be restarted.
256         * 
257         * @return true if currently shutdown, or false otherwise
258         * @see #shutdown()
259         * @see #isPaused()
260         * @see #isStarted()
261         */
262        public boolean isShutdown() {
263            return this.state == State.SHUTDOWN || this.state == State.TERMINATED;
264        }
265    
266        /**
267         * {@inheritDoc}
268         */
269        public boolean isTerminated() {
270            switch (this.state) {
271                case PAUSED:
272                case STARTED:
273                case SHUTDOWN:
274                    if (doCheckIsTerminated()) {
275                        this.state = State.TERMINATED;
276                        logger.trace("Service \"{0}\" has terminated", getServiceName());
277                        return true;
278                    }
279                    return false;
280                case TERMINATED:
281                    return true;
282            }
283            return false;
284        }
285    
286        /**
287         * Subclasses should implement this method to determine whether the service has completed shutdown.
288         * 
289         * @return true if terminated, or false otherwise
290         */
291        protected abstract boolean doCheckIsTerminated();
292    
293        /**
294         * Get the name of this service in the current locale.
295         * 
296         * @return the service name
297         */
298        public String getServiceName() {
299            return this.serviceName.text();
300        }
301    
302        /**
303         * Get the name of this service in the specified locale.
304         * 
305         * @param locale the locale in which the service name is to be returned; may be null if the default locale is to be used
306         * @return the service name
307         */
308        public String getServiceName( Locale locale ) {
309            return this.serviceName.text(locale);
310        }
311    
312    }