1 /*
2 * ModeShape (http://www.modeshape.org)
3 * See the COPYRIGHT.txt file distributed with this work for information
4 * regarding copyright ownership. Some portions may be licensed
5 * to Red Hat, Inc. under one or more contributor license agreements.
6 * See the AUTHORS.txt file in the distribution for a full listing of
7 * individual contributors.
8 *
9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10 * is licensed to you under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation; either version 2.1 of
12 * the License, or (at your option) any later version.
13 *
14 * ModeShape is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this software; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23 */
24 package org.modeshape.repository.service;
25
26 import java.util.Locale;
27 import net.jcip.annotations.GuardedBy;
28 import net.jcip.annotations.ThreadSafe;
29 import org.modeshape.common.i18n.I18n;
30 import org.modeshape.common.util.Logger;
31 import org.modeshape.repository.RepositoryI18n;
32
33 /**
34 * Simple abstract implementation of the service administrator interface that can be easily subclassed by services that require an
35 * administrative interface.
36 */
37 @ThreadSafe
38 public abstract class AbstractServiceAdministrator implements ServiceAdministrator {
39
40 private volatile State state;
41 private final I18n serviceName;
42 private final Logger logger;
43
44 protected AbstractServiceAdministrator( I18n serviceName,
45 State initialState ) {
46 assert initialState != null;
47 assert serviceName != null;
48 this.state = initialState;
49 this.serviceName = serviceName;
50 this.logger = Logger.getLogger(getClass());
51 }
52
53 /**
54 * Return the current state of this service.
55 *
56 * @return the current state
57 */
58 public State getState() {
59 return this.state;
60 }
61
62 /**
63 * Set the state of the service. This method does nothing if the desired state matches the current state.
64 *
65 * @param state the desired state
66 * @return this object for method chaining purposes
67 * @see #setState(String)
68 * @see #start()
69 * @see #pause()
70 * @see #shutdown()
71 */
72 @GuardedBy( "this" )
73 public synchronized ServiceAdministrator setState( State state ) {
74 switch (state) {
75 case STARTED:
76 start();
77 break;
78 case PAUSED:
79 pause();
80 break;
81 case SHUTDOWN:
82 case TERMINATED:
83 shutdown();
84 break;
85 }
86 return this;
87 }
88
89 /**
90 * Set the state of the service. This method does nothing if the desired state matches the current state.
91 *
92 * @param state the desired state in string form
93 * @return this object for method chaining purposes
94 * @throws IllegalArgumentException if the specified state string is null or does not match one of the predefined
95 * {@link ServiceAdministrator.State predefined enumerated values}
96 * @see #setState(State)
97 * @see #start()
98 * @see #pause()
99 * @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 }