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 }