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 }