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