1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.system;
23
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.List;
27 import java.util.ListIterator;
28 import java.util.Map;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.CopyOnWriteArrayList;
31
32 import javax.management.MBeanRegistration;
33 import javax.management.MBeanServer;
34 import javax.management.Notification;
35 import javax.management.ObjectName;
36
37 import org.jboss.dependency.spi.Controller;
38 import org.jboss.dependency.spi.ControllerContext;
39 import org.jboss.dependency.spi.ControllerMode;
40 import org.jboss.dependency.spi.ControllerState;
41 import org.jboss.dependency.spi.DependencyInfo;
42 import org.jboss.dependency.spi.DependencyItem;
43 import org.jboss.deployment.DeploymentException;
44 import org.jboss.deployment.DeploymentInfo;
45 import org.jboss.deployment.DeploymentState;
46 import org.jboss.kernel.Kernel;
47 import org.jboss.kernel.plugins.bootstrap.basic.BasicBootstrap;
48 import org.jboss.kernel.spi.dependency.KernelController;
49 import org.jboss.logging.Logger;
50 import org.jboss.mx.server.ServerConstants;
51 import org.jboss.mx.util.JBossNotificationBroadcasterSupport;
52 import org.jboss.mx.util.ObjectNameFactory;
53 import org.jboss.system.metadata.ServiceMetaData;
54 import org.jboss.system.metadata.ServiceMetaDataParser;
55 import org.jboss.system.microcontainer.LifecycleDependencyItem;
56 import org.jboss.system.microcontainer.ServiceControllerContext;
57 import org.w3c.dom.Element;
58
59 /**
60 * This is the main Service Controller. A controller can deploy a service to a
61 * jboss.system It installs by delegating, it configures by delegating<p>
62 *
63 * This class has been rewritten to delegate to the microcontainer's
64 * generic controller. Like the original ServiceController, all state
65 * transitions must be handled manually, e.g. driven by the deployer
66 * invoking create, start, stop, etc.
67 * That is with one exception; we register ourselves an automatic context.
68 *
69 * @see org.jboss.system.Service
70 *
71 * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
72 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
73 * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
74 * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
75 * @author <a href="adrian@jboss.com">Adrian Brock</a>
76 * @version $Revision: 73445 $
77 */
78 public class ServiceController extends JBossNotificationBroadcasterSupport
79 implements ServiceControllerMBean, MBeanRegistration
80 {
81 /** The ObjectName of the default loader repository */
82 public static final ObjectName DEFAULT_LOADER_REPOSITORY = ObjectNameFactory.create(ServerConstants.DEFAULT_LOADER_NAME);
83
84 /** The operation name for lifecycle */
85 public static final String JBOSS_INTERNAL_LIFECYCLE = "jbossInternalLifecycle";
86
87 /** The signature for lifecycle operations */
88 public static final String[] JBOSS_INTERNAL_LIFECYCLE_SIG = new String[] { String.class.getName() };
89
90 /** Class logger. */
91 private static final Logger log = Logger.getLogger(ServiceController.class);
92
93 /** The kernel */
94 protected Kernel kernel;
95
96 /** A callback to the JMX MBeanServer */
97 protected MBeanServer server;
98
99 /** The service binding */
100 protected ServiceBinding serviceBinding;
101
102 /** The contexts */
103 protected Map<ObjectName, ServiceControllerContext> installed = new ConcurrentHashMap<ObjectName, ServiceControllerContext>();
104
105 /** The contexts in installation order */
106 protected CopyOnWriteArrayList<ServiceControllerContext> installedOrder = new CopyOnWriteArrayList<ServiceControllerContext>();
107
108 /**
109 * Get the MBeanServer
110 *
111 * @return the server
112 */
113 public MBeanServer getMBeanServer()
114 {
115 return server;
116 }
117
118 /**
119 * Set the server.
120 *
121 * @param server the server.
122 */
123 public void setMBeanServer(MBeanServer server)
124 {
125 this.server = server;
126 }
127
128 /**
129 * Get the kernel.
130 *
131 * @return the kernel.
132 */
133 public Kernel getKernel()
134 {
135 return kernel;
136 }
137
138 /**
139 * Set the kernel.
140 *
141 * @param kernel the kernel.
142 */
143 public void setKernel(Kernel kernel)
144 {
145 this.kernel = kernel;
146 }
147
148 /**
149 * Get the serviceBinding.
150 *
151 * @return the serviceBinding.
152 */
153 public ServiceBinding getServiceBinding()
154 {
155 return serviceBinding;
156 }
157
158 /**
159 * Plugin a ServiceBinding policy
160 *
161 * @param serviceBinding policy
162 */
163 public void setServiceBinding(ServiceBinding serviceBinding)
164 {
165 this.serviceBinding = serviceBinding;
166 }
167
168 public List<ServiceContext> listDeployed()
169 {
170 // Retrieve the service context from all our installed contexts
171 ArrayList<ServiceContext> result = new ArrayList<ServiceContext>(installedOrder.size());
172 for (ServiceControllerContext context : installedOrder)
173 result.add(context.getServiceContext());
174 return result;
175 }
176
177 public List<ServiceContext> listIncompletelyDeployed()
178 {
179 // Retrieve the service contexts that are not deployed properly
180 ArrayList<ServiceContext> result = new ArrayList<ServiceContext>();
181 for (ServiceControllerContext context : installedOrder)
182 {
183 ServiceContext sc = context.getServiceContext();
184 if (sc.state != ServiceContext.CREATED &&
185 sc.state != ServiceContext.RUNNING &&
186 sc.state != ServiceContext.STOPPED &&
187 sc.state != ServiceContext.DESTROYED)
188 {
189 result.add(sc);
190 }
191 }
192 return result;
193 }
194
195 public List<ObjectName> listDeployedNames()
196 {
197 // Get all the object names from our installed contexts
198 ArrayList<ObjectName> result = new ArrayList<ObjectName>(installed.size());
199 for (ObjectName name : installed.keySet())
200 result.add(name);
201 return result;
202 }
203
204 public String listConfiguration(ObjectName[] objectNames) throws Exception
205 {
206 return ServiceConfigurator.getConfiguration(server, this, objectNames);
207 }
208
209 public void validateDeploymentState(DeploymentInfo di, DeploymentState state)
210 {
211 ArrayList<ObjectName> mbeans = new ArrayList<ObjectName>(di.mbeans);
212 if (di.deployedObject != null)
213 mbeans.add(di.deployedObject);
214 boolean mbeansStateIsValid = true;
215 for (int m = 0; m < mbeans.size(); ++m)
216 {
217 ObjectName serviceName = mbeans.get(m);
218 ServiceContext ctx = getServiceContext(serviceName);
219 if (ctx != null && state == DeploymentState.STARTED)
220 mbeansStateIsValid &= ctx.state == ServiceContext.RUNNING;
221 }
222 if (mbeansStateIsValid == true)
223 di.state = state;
224 }
225
226 public List<ObjectName> install(List<ServiceMetaData> metaDatas, ObjectName loaderName) throws DeploymentException
227 {
228 KernelController controller = kernel.getController();
229
230 // Track the registered mbeans both for returning the result
231 // and uninstalling in the event of an error
232 List<ObjectName> result = new ArrayList<ObjectName>(metaDatas.size());
233 List<ServiceControllerContext> contexts = new ArrayList<ServiceControllerContext>(metaDatas.size());
234
235 // Go through each mbean in the passed xml
236 for (ServiceMetaData metaData : metaDatas)
237 {
238 metaData.setClassLoaderName(loaderName);
239
240 // Install the context to the configured level
241 ServiceControllerContext context = new ServiceControllerContext(this, metaData);
242 try
243 {
244 doInstall(controller, context);
245 contexts.add(context);
246 doChange(controller, context, ControllerState.CONFIGURED, "configure");
247 result.add(context.getObjectName());
248 }
249 catch (Throwable t)
250 {
251 // Something went wrong
252 for (ServiceControllerContext ctx : contexts)
253 safelyRemoveAnyRegisteredContext(ctx);
254
255 DeploymentException.rethrowAsDeploymentException("Error during install", t);
256 }
257 }
258 return result;
259 }
260
261 public ObjectName install(ServiceMetaData metaData, ObjectName loaderName) throws DeploymentException
262 {
263 KernelController controller = kernel.getController();
264 metaData.setClassLoaderName(loaderName);
265 ObjectName name = metaData.getObjectName();
266
267 // Install the context to the configured level
268 ServiceControllerContext context = new ServiceControllerContext(this, metaData);
269 try
270 {
271 doInstall(controller, context);
272 doChange(controller, context, ControllerState.CONFIGURED, "configure");
273 return context.getObjectName();
274 }
275 catch (Throwable t)
276 {
277 throw DeploymentException.rethrowAsDeploymentException("Error during install " + name, t);
278 }
279 }
280
281 public List<ObjectName> install(Element config, ObjectName loaderName) throws DeploymentException
282 {
283 // Parse the xml
284 ServiceMetaDataParser parser = new ServiceMetaDataParser(config);
285 List<ServiceMetaData> metaDatas = parser.parse();
286 return install(metaDatas, loaderName);
287 }
288
289 /**
290 * Install an MBean without any meta data
291 *
292 * @param name the object name
293 * @param object the mbean object
294 * @throws DeploymentException for any error
295 */
296 public void install(ObjectName name, Object object) throws DeploymentException
297 {
298 if (name == null)
299 throw new IllegalArgumentException("Null name");
300 if (object == null)
301 throw new IllegalArgumentException("Null object");
302
303 KernelController controller = kernel.getController();
304
305 ServiceControllerContext context = new ServiceControllerContext(this, name, object);
306 try
307 {
308 doInstall(controller, context);
309 doChange(controller, context, ControllerState.CONFIGURED, "configure");
310 }
311 catch (Throwable t)
312 {
313 // Something went wrong
314 safelyRemoveAnyRegisteredContext(context);
315
316 DeploymentException.rethrowAsDeploymentException("Error during install", t);
317 }
318 }
319
320 public void register(ObjectName serviceName) throws Exception
321 {
322 register(serviceName, null);
323 }
324
325 public void register(ObjectName serviceName, Collection<ObjectName> depends) throws Exception
326 {
327 register(serviceName, depends, true);
328 }
329
330 public void register(ObjectName serviceName, Collection<ObjectName> depends, boolean includeLifecycle) throws Exception
331 {
332 if (serviceName == null)
333 {
334 log.warn("Ignoring request to register null service: ", new Exception("STACKTRACE"));
335 return;
336 }
337
338 log.debug("Registering service " + serviceName);
339
340 // This is an already registered mbean
341 KernelController controller = kernel.getController();
342 ServiceControllerContext context = new ServiceControllerContext(this, serviceName, includeLifecycle);
343 if (depends != null)
344 addDependencies(context, depends);
345
346 // Install the context to the configured level
347 try
348 {
349 doInstall(controller, context);
350 doChange(controller, context, ControllerState.CONFIGURED, "configure");
351 }
352 catch (Throwable t)
353 {
354 // Something went wrong
355 safelyRemoveAnyRegisteredContext(context);
356
357 DeploymentException.rethrowAsDeploymentException("Error during register: " + serviceName, t);
358 }
359 }
360
361 public void create(ObjectName serviceName) throws Exception
362 {
363 create(serviceName, null);
364 }
365
366 public void create(ObjectName serviceName, Collection<ObjectName> depends) throws Exception
367 {
368 if (serviceName == null)
369 {
370 log.warn("Ignoring request to create null service: ", new Exception("STACKTRACE"));
371 return;
372 }
373
374 log.debug("Creating service " + serviceName);
375
376 // Register if not already done so
377 ServiceControllerContext context = installed.get(serviceName);
378 if (context == null)
379 {
380 register(serviceName, depends);
381 context = installed.get(serviceName);
382 }
383 ServiceContext ctx = context.getServiceContext();
384
385 // If we are already created (can happen in dependencies) or failed just return
386 if (ctx.state == ServiceContext.CREATED
387 || ctx.state == ServiceContext.RUNNING
388 || ctx.state == ServiceContext.FAILED)
389 {
390 log.debug("Ignoring create request for service: " + ctx.objectName + " at state " + ctx.getStateString());
391 return;
392 }
393
394 // Request the mbean go to the created state
395 KernelController controller = kernel.getController();
396 try
397 {
398 doChange(controller, context, ControllerState.CREATE, "create");
399 }
400 catch (Throwable t)
401 {
402 log.warn("Problem creating service " + serviceName, t);
403 }
404 }
405
406 public void start(ObjectName serviceName) throws Exception
407 {
408 if (serviceName == null)
409 {
410 log.warn("Ignoring request to start null service: ", new Exception("STACKTRACE"));
411 return;
412 }
413
414 log.debug("starting service " + serviceName);
415
416 // Register if not already done so
417 ServiceControllerContext context = installed.get(serviceName);
418 if (context == null)
419 {
420 register(serviceName, null);
421 context = installed.get(serviceName);
422 }
423 ServiceContext ctx = context.getServiceContext();
424
425 // If we are already started (can happen in dependencies) just return
426 if (ctx.state == ServiceContext.RUNNING || ctx.state == ServiceContext.FAILED)
427 {
428 log.debug("Ignoring start request for service: " + ctx.objectName + " at state " + ctx.getStateString());
429 return;
430 }
431
432 // Request the mbean go to the fully installed state
433 KernelController controller = kernel.getController();
434 try
435 {
436 doChange(controller, context, ControllerState.INSTALLED, "start");
437 }
438 catch (Throwable t)
439 {
440 log.warn("Problem starting service " + serviceName, t);
441 }
442 }
443
444 public void restart(ObjectName serviceName) throws Exception
445 {
446 if (serviceName == null)
447 {
448 log.warn("Ignoring request to restart null service: ", new Exception("STACKTRACE"));
449 return;
450 }
451
452 log.debug("restarting service " + serviceName);
453 stop(serviceName);
454 start(serviceName);
455 }
456
457 public void stop(ObjectName serviceName) throws Exception
458 {
459 if (serviceName == null)
460 {
461 log.warn("Ignoring request to stop null service: ", new Exception("STACKTRACE"));
462 return;
463 }
464
465 log.debug("stopping service: " + serviceName);
466
467 ServiceControllerContext context = installed.get(serviceName);
468 if (context == null)
469 {
470 log.warn("Ignoring request to stop nonexistent service: " + serviceName);
471 return;
472 }
473
474 // If we are already stopped (can happen in dependencies) just return
475 ServiceContext ctx = context.getServiceContext();
476 if (ctx.state != ServiceContext.RUNNING)
477 {
478 log.debug("Ignoring stop request for service: " + ctx.objectName + " at state " + ctx.getStateString());
479 return;
480 }
481
482 // Request the mbean go back to the created state
483 KernelController controller = kernel.getController();
484 try
485 {
486 doChange(controller, context, ControllerState.CREATE, null);
487 }
488 catch (Throwable t)
489 {
490 log.warn("Problem stopping service " + serviceName, t);
491 }
492 }
493
494 public void destroy(ObjectName serviceName) throws Exception
495 {
496 if (serviceName == null)
497 {
498 log.warn("Ignoring request to destroy null service: ", new Exception("STACKTRACE"));
499 return;
500 }
501
502 log.debug("destroying service: " + serviceName);
503
504 ServiceControllerContext context = installed.get(serviceName);
505 if (context == null)
506 {
507 log.warn("Ignoring request to destroy nonexistent service: " + serviceName);
508 return;
509 }
510
511 // If we are already destroyed (can happen in dependencies) just return
512 ServiceContext ctx = context.getServiceContext();
513 if (ctx.state == ServiceContext.DESTROYED || ctx.state == ServiceContext.NOTYETINSTALLED || ctx.state == ServiceContext.FAILED)
514 {
515 log.debug("Ignoring destroy request for service: " + ctx.objectName + " at state " + ctx.getStateString());
516 return;
517 }
518
519 // Request the mbean go the configured state
520 KernelController controller = kernel.getController();
521 try
522 {
523 doChange(controller, context, ControllerState.CONFIGURED, null);
524 }
525 catch (Throwable t)
526 {
527 log.warn("Problem stopping service " + serviceName, t);
528 }
529 }
530
531 public void remove(ObjectName objectName) throws Exception
532 {
533 if (objectName == null)
534 {
535 log.warn("Ignoring request to remove null service: ", new Exception("STACKTRACE"));
536 return;
537 }
538
539 // Removal can be attempted twice, this is because ServiceMBeanSupport does a "double check"
540 // to make sure the ServiceController is tidied up
541 // However, if the tidyup is done correctly, it invokes this method recursively:
542 // ServiceController::remove -> MBeanServer::unregisterMBean
543 // ServiceMBeanSupport::postDeregister -> ServiceController::remove
544 ServiceControllerContext context = installed.remove(objectName);
545 if (context == null)
546 {
547 log.trace("Ignoring request to remove nonexistent service: " + objectName);
548 return;
549 }
550 installedOrder.remove(context);
551 log.debug("removing service: " + objectName);
552
553 // Uninstall the context
554 safelyRemoveAnyRegisteredContext(context);
555 }
556
557 public ServiceContext getServiceContext(ObjectName serviceName)
558 {
559 ServiceControllerContext context = installed.get(serviceName);
560 if (context != null)
561 return context.getServiceContext();
562 return null;
563 }
564
565 public void shutdown()
566 {
567 log.debug("Stopping " + installedOrder.size() + " services");
568
569 KernelController controller = kernel.getController();
570
571 int serviceCounter = 0;
572
573 // Uninstall all the contexts we know about
574 ListIterator<ServiceControllerContext> iterator = installedOrder.listIterator(installedOrder.size());
575 while (iterator.hasPrevious())
576 {
577 ServiceControllerContext context = iterator.previous();
578 controller.uninstall(context.getName());
579 ++serviceCounter;
580 }
581 log.debug("Stopped " + serviceCounter + " services");
582
583 // Shutdown ourselves
584 controller.uninstall(ServiceControllerMBean.OBJECT_NAME.getCanonicalName());
585 }
586
587 public ObjectName preRegister(MBeanServer server, ObjectName name)
588 throws Exception
589 {
590 this.server = server;
591
592 if( kernel == null )
593 {
594 // Bootstrap the microcontainer.
595 BasicBootstrap bootstrap = new BasicBootstrap();
596 bootstrap.run();
597 kernel = bootstrap.getKernel();
598 }
599
600 log.debug("Controller MBean online");
601 return name == null ? OBJECT_NAME : name;
602 }
603
604 public void postRegister(Boolean registrationDone)
605 {
606 if (registrationDone.booleanValue() == false)
607 log.fatal("Registration of ServiceController failed");
608 else
609 {
610 // Register the ServiceController as a running service
611 KernelController controller = kernel.getController();
612 ServiceControllerContext context = new ServiceControllerContext(this, ServiceControllerMBean.OBJECT_NAME);
613 context.setMode(ControllerMode.AUTOMATIC);
614 try
615 {
616 controller.install(context);
617 }
618 catch (Throwable t)
619 {
620 log.fatal("Error registering service controller", t);
621 }
622 }
623 }
624
625 public void preDeregister()
626 throws Exception
627 {
628 }
629
630 public void postDeregister()
631 {
632 installed.clear();
633 installedOrder.clear();
634 server = null;
635 }
636
637 /**
638 * Install a context
639 *
640 * @param controller the controller
641 * @param context the context
642 * @throws Throwable for any error
643 */
644 private void doInstall(KernelController controller, ServiceControllerContext context) throws Throwable
645 {
646 controller.install(context);
647 installed.put(context.getObjectName(), context);
648 installedOrder.add(context);
649 }
650
651 /**
652 * Change a context
653 *
654 * @param controller the controller
655 * @param context the context
656 * @param requiredState the require state
657 * @param logWait log the waiting dependencies
658 * @throws Throwable for any error
659 */
660 private void doChange(KernelController controller, ServiceControllerContext context, ControllerState requiredState, String logWait) throws Throwable
661 {
662 if (ControllerMode.ON_DEMAND.equals(context.getMode()) == false)
663 {
664 controller.change(context, requiredState);
665 ControllerState state = context.getState();
666 if (logWait != null && requiredState.equals(state) == false && state != ControllerState.ERROR)
667 log.debug("Waiting in " + logWait + " of " + context.getObjectName() + " on " + getUnresolvedDependencies(context, requiredState));
668 }
669 }
670
671 /**
672 * Sends outs controller notifications about service lifecycle events
673 *
674 * @param type the notification type
675 * @param serviceName the service name
676 */
677 public void sendControllerNotification(String type, ObjectName serviceName)
678 {
679 Notification notification = new Notification(type, this, super.nextNotificationSequenceNumber());
680 notification.setUserData(serviceName);
681 sendNotification(notification);
682 }
683
684 /**
685 * Add the passed lifecycle dependencies to the context
686 *
687 * @param context the context
688 * @param depends the dependencies
689 */
690 private void addDependencies(ServiceControllerContext context, Collection<ObjectName> depends)
691 {
692 DependencyInfo info = context.getDependencyInfo();
693 for (ObjectName other : depends)
694 {
695 info.addIDependOn(new LifecycleDependencyItem(context.getName(), other.getCanonicalName(), ControllerState.CREATE));
696 info.addIDependOn(new LifecycleDependencyItem(context.getName(), other.getCanonicalName(), ControllerState.START));
697 }
698 }
699
700 /**
701 * Get the unresolved dependencies
702 *
703 * @param context the context
704 * @param state the state we want to move to
705 * @return the unresolved dependencies
706 */
707 private String getUnresolvedDependencies(ServiceControllerContext context, ControllerState state)
708 {
709 boolean first = true;
710
711 StringBuilder builder = new StringBuilder();
712 for (DependencyItem item : context.getDependencyInfo().getUnresolvedDependencies(null))
713 {
714 if (item.isResolved() == false && item.getWhenRequired() == state)
715 {
716 if (first)
717 first = false;
718 else
719 builder.append(' ');
720 builder.append(item.getIDependOn());
721 }
722 }
723 return builder.toString();
724 }
725
726 /**
727 * Safely remove any potentially registered context (usually after an error)
728 *
729 * @param ctx the context
730 */
731 private void safelyRemoveAnyRegisteredContext(ServiceControllerContext ctx)
732 {
733 // First the context must have a controller
734 Controller controller = ctx.getController();
735 if (controller != null)
736 {
737 // The name must be registered and it must be our context
738 Object name = ctx.getName();
739 ControllerContext registered = controller.getContext(name, null);
740 if (registered == ctx)
741 controller.uninstall(name);
742 }
743 }
744 }