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 }