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 }