001/* 002 * (C) Copyright 2006-2020 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * Anahide Tchertchian 019 */ 020package org.nuxeo.runtime.model.impl; 021 022import java.net.URL; 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import org.apache.logging.log4j.LogManager; 032import org.apache.logging.log4j.Logger; 033import org.nuxeo.common.xmap.annotation.XContent; 034import org.nuxeo.common.xmap.annotation.XNode; 035import org.nuxeo.common.xmap.annotation.XNodeList; 036import org.nuxeo.common.xmap.annotation.XNodeMap; 037import org.nuxeo.common.xmap.annotation.XObject; 038import org.nuxeo.runtime.ComponentEvent; 039import org.nuxeo.runtime.RuntimeMessage; 040import org.nuxeo.runtime.RuntimeMessage.Level; 041import org.nuxeo.runtime.RuntimeMessage.Source; 042import org.nuxeo.runtime.Version; 043import org.nuxeo.runtime.api.Framework; 044import org.nuxeo.runtime.model.Component; 045import org.nuxeo.runtime.model.ComponentInstance; 046import org.nuxeo.runtime.model.ComponentManager; 047import org.nuxeo.runtime.model.ComponentName; 048import org.nuxeo.runtime.model.ConfigurationDescriptor; 049import org.nuxeo.runtime.model.Extension; 050import org.nuxeo.runtime.model.ExtensionPoint; 051import org.nuxeo.runtime.model.Property; 052import org.nuxeo.runtime.model.RegistrationInfo; 053import org.nuxeo.runtime.model.RuntimeContext; 054 055/** 056 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 057 */ 058@XObject("component") 059public class RegistrationInfoImpl implements RegistrationInfo { 060 061 private static final Logger log = LogManager.getLogger(ComponentManager.class); 062 063 // Note: some of these instance variables are accessed directly from other 064 // classes in this package. 065 066 ComponentManagerImpl manager; 067 068 @XNode("@service") 069 ServiceDescriptor serviceDescriptor; 070 071 // the managed object name 072 @XNode("@name") 073 ComponentName name; 074 075 @XNode("@disabled") 076 boolean disabled; 077 078 @XNode("configuration") 079 ConfigurationDescriptor config; 080 081 // the registration state 082 int state = UNREGISTERED; 083 084 // my aliases 085 @XNodeList(value = "alias", type = HashSet.class, componentType = ComponentName.class) 086 Set<ComponentName> aliases = new HashSet<>(); 087 088 // the object names I depend of 089 @XNodeList(value = "require", type = HashSet.class, componentType = ComponentName.class) 090 Set<ComponentName> requires = new HashSet<>(); 091 092 @XNode("implementation@class") 093 String implementation; 094 095 @XNodeList(value = "extension-point", type = ExtensionPointImpl[].class, componentType = ExtensionPointImpl.class) 096 ExtensionPointImpl[] extensionPoints = new ExtensionPointImpl[0]; 097 098 @XNodeList(value = "extension", type = ExtensionImpl[].class, componentType = ExtensionImpl.class) 099 ExtensionImpl[] extensions = new ExtensionImpl[0]; 100 101 @XNodeMap(value = "property", key = "@name", type = HashMap.class, componentType = Property.class) 102 Map<String, Property> properties = new HashMap<>(); 103 104 @XNode("@version") 105 Version version = Version.ZERO; 106 107 /** 108 * To be set when deploying configuration components that are not in a bundle (e.g. from config. dir). Represent the 109 * bundle that will be assumed to be the owner of the component. 110 */ 111 @XNode("@bundle") 112 String bundle; 113 114 @XContent("documentation") 115 String documentation; 116 117 URL xmlFileUrl; 118 119 /** 120 * @since 9.2 121 */ 122 String sourceId; 123 124 /** 125 * This is used by the component persistence service to identify registration that was dynamically created and 126 * persisted by users. 127 */ 128 boolean isPersistent; 129 130 RuntimeContext context; 131 132 // the managed component 133 ComponentInstance component; 134 135 public RegistrationInfoImpl() { 136 } 137 138 /** 139 * Useful when dynamically registering components 140 * 141 * @param name the component name 142 */ 143 public RegistrationInfoImpl(ComponentName name) { 144 this.name = name; 145 } 146 147 /** 148 * Attach to a manager - this method must be called after all registration fields are initialized. 149 */ 150 public void attach(ComponentManagerImpl manager) { 151 if (this.manager != null) { 152 throw new IllegalStateException("Registration '" + name + "' was already attached to a manager"); 153 } 154 this.manager = manager; 155 } 156 157 public void setContext(RuntimeContext context) { 158 this.context = context; 159 } 160 161 @Override 162 public boolean isDisabled() { 163 return disabled; 164 } 165 166 @Override 167 public final boolean isPersistent() { 168 return isPersistent; 169 } 170 171 @Override 172 public void setPersistent(boolean isPersistent) { 173 this.isPersistent = isPersistent; 174 } 175 176 public void destroy() { 177 requires.clear(); 178 aliases.clear(); 179 properties.clear(); 180 extensionPoints = new ExtensionPointImpl[0]; 181 extensions = new ExtensionImpl[0]; 182 version = null; 183 component = null; 184 name = null; 185 manager = null; 186 } 187 188 public final boolean isDisposed() { 189 return name == null; 190 } 191 192 @Override 193 public ExtensionPoint[] getExtensionPoints() { 194 return extensionPoints; 195 } 196 197 @Override 198 public ComponentInstance getComponent() { 199 return component; 200 } 201 202 /** 203 * Reload the underlying component if reload is supported 204 * 205 * @deprecated since 9.3, but in fact since 5.6, see only usage in LiveInstallTask#reloadComponent 206 */ 207 @Deprecated 208 public synchronized void reload() { 209 if (component != null) { 210 component.reload(); 211 } 212 } 213 214 @Override 215 public ComponentName getName() { 216 return name; 217 } 218 219 @Override 220 public Map<String, Property> getProperties() { 221 return properties; 222 } 223 224 @Override 225 public int getState() { 226 return state; 227 } 228 229 @Override 230 public Extension[] getExtensions() { 231 if (!useFormerLifecycleManagement()) { 232 // we shouldn't check extension points here because it is done each time we get the extensions 233 // do it like that for new system for now (which will be used only when we will switch xml contributions to 234 // new component lifecycle system) 235 return checkExtensions(); 236 } 237 return extensions; 238 } 239 240 @Override 241 public Set<ComponentName> getAliases() { 242 return aliases == null ? Collections.emptySet() : aliases; 243 } 244 245 @Override 246 public Set<ComponentName> getRequiredComponents() { 247 return requires; 248 } 249 250 @Override 251 public RuntimeContext getContext() { 252 return context; 253 } 254 255 @Override 256 public String getBundle() { 257 return bundle; 258 } 259 260 @Override 261 public Version getVersion() { 262 return version; 263 } 264 265 @Override 266 public String getDocumentation() { 267 return documentation; 268 } 269 270 @Override 271 public String toString() { 272 return "RegistrationInfo: " + name; 273 } 274 275 @Override 276 public ComponentManager getManager() { 277 return manager; 278 } 279 280 synchronized void register() { 281 if (state != UNREGISTERED) { 282 return; 283 } 284 state = REGISTERED; 285 manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_REGISTERED, this)); 286 } 287 288 synchronized void unregister() { 289 if (state == UNREGISTERED) { 290 return; 291 } 292 if (state == ACTIVATED || state == RESOLVED || state == START_FAILURE) { 293 unresolve(); 294 } 295 state = UNREGISTERED; 296 manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_UNREGISTERED, this)); 297 destroy(); 298 } 299 300 @Override 301 public void setState(int state) { 302 this.state = state; 303 int componentEvent = -1; 304 switch (state) { 305 case UNREGISTERED: 306 componentEvent = ComponentEvent.COMPONENT_UNREGISTERED; 307 break; 308 case REGISTERED: 309 componentEvent = ComponentEvent.COMPONENT_REGISTERED; 310 break; 311 case RESOLVED: 312 componentEvent = ComponentEvent.COMPONENT_RESOLVED; 313 break; 314 case ACTIVATING: 315 componentEvent = ComponentEvent.ACTIVATING_COMPONENT; 316 break; 317 case ACTIVATED: 318 componentEvent = ComponentEvent.ACTIVATING_COMPONENT; 319 break; 320 case STARTING: 321 componentEvent = ComponentEvent.STARTING_COMPONENT; 322 break; 323 case STARTED: 324 componentEvent = ComponentEvent.COMPONENT_STARTED; 325 break; 326 case START_FAILURE: 327 break; 328 case STOPPING: 329 componentEvent = ComponentEvent.STOPPING_COMPONENT; 330 break; 331 case DEACTIVATING: 332 componentEvent = ComponentEvent.DEACTIVATING_COMPONENT; 333 break; 334 } 335 if (componentEvent > -1) { 336 manager.sendEvent(new ComponentEvent(componentEvent, this)); 337 } 338 } 339 340 /** 341 * @deprecated since 9.2 seems unused 342 */ 343 @Deprecated 344 public synchronized void restart() { 345 deactivate(); 346 instantiate(); 347 activate(); 348 } 349 350 @Override 351 public int getApplicationStartedOrder() { 352 if (component == null) { 353 return 0; 354 } 355 Object ci = component.getInstance(); 356 if (!(ci instanceof Component)) { 357 return 0; 358 } 359 return ((Component) ci).getApplicationStartedOrder(); 360 } 361 362 public synchronized void start() { 363 if (state != ACTIVATED) { 364 return; 365 } 366 state = STARTING; 367 manager.sendEvent(new ComponentEvent(ComponentEvent.STARTING_COMPONENT, this)); 368 try { 369 if (component != null) { 370 Object ci = component.getInstance(); 371 if (ci instanceof Component) { 372 ((Component) ci).start(component); 373 } 374 } 375 log.debug("Component started: {}", name); 376 state = STARTED; 377 manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_STARTED, this)); 378 } catch (RuntimeException e) { 379 String message = String.format("Component %s notification of application started failed: %s", 380 component == null ? null : component.getName(), e.getMessage()); 381 log.error(message, e); 382 Framework.getRuntime() 383 .getMessageHandler() 384 .addMessage( 385 new RuntimeMessage(Level.ERROR, message, Source.COMPONENT, component.getName().getName())); 386 state = START_FAILURE; 387 } 388 } 389 390 @Override 391 public boolean isStarted() { 392 return state == STARTED; 393 } 394 395 public synchronized void stop() throws InterruptedException { 396 // TODO use timeout 397 if (state != STARTED) { 398 return; 399 } 400 state = STOPPING; 401 manager.sendEvent(new ComponentEvent(ComponentEvent.STOPPING_COMPONENT, this)); 402 if (component != null) { 403 Object ci = component.getInstance(); 404 if (ci instanceof Component) { 405 ((Component) ci).stop(component); 406 } 407 } 408 log.debug("Component stopped: {}", name); 409 state = ACTIVATED; 410 manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_STOPPED, this)); 411 } 412 413 /** 414 * Instantiates corresponding component. 415 * <p> 416 * Allows registering listeners on ComponentManager at component instantiation, before all components activation. 417 * <p> 418 * Should be called before {@link #activate()}. 419 * 420 * @return false in case of error during instantiation, true otherwise. 421 * @since 11.3 422 */ 423 public synchronized boolean instantiate() { 424 if (state != RESOLVED) { 425 return false; 426 } 427 try { 428 component = new ComponentInstanceImpl(this); 429 return true; 430 } catch (RuntimeException e) { 431 String msg = "Failed to instantiate component: " + implementation; 432 log.error(msg, e); 433 msg += " (" + e.toString() + ')'; 434 Framework.getRuntime() 435 .getMessageHandler() 436 .addMessage(new RuntimeMessage(Level.ERROR, msg, Source.COMPONENT, getName().getName())); 437 return false; 438 } 439 } 440 441 public synchronized void activate() { 442 if (state != RESOLVED) { 443 return; 444 } 445 446 state = ACTIVATING; 447 manager.sendEvent(new ComponentEvent(ComponentEvent.ACTIVATING_COMPONENT, this)); 448 449 // activate component 450 try { 451 component.activate(); 452 } catch (RuntimeException e) { 453 String msg = "Failed to activate component: " + implementation; 454 log.error(msg, e); 455 msg += " (" + e.toString() + ')'; 456 Framework.getRuntime() 457 .getMessageHandler() 458 .addMessage(new RuntimeMessage(Level.ERROR, msg, Source.COMPONENT, getName().getName())); 459 return; 460 } 461 462 log.debug("Component activated: {}", name); 463 state = ACTIVATED; 464 manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_ACTIVATED, this)); 465 466 // register contributed extensions if any 467 if (extensions != null) { 468 for (Extension xt : checkExtensions()) { 469 xt.setComponent(component); 470 try { 471 manager.registerExtension(xt); 472 } catch (RuntimeException e) { 473 ComponentName compName = xt.getComponent().getName(); 474 String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: " 475 + xt.getExtensionPoint() + " in component: " + compName; 476 log.error(msg, e); 477 msg += " (" + e.toString() + ')'; 478 Framework.getRuntime() 479 .getMessageHandler() 480 .addMessage(new RuntimeMessage(Level.ERROR, msg, Source.EXTENSION, compName.getName())); 481 } 482 } 483 } 484 485 // register pending extensions if any 486 List<ComponentName> names = new ArrayList<>(1 + aliases.size()); 487 names.add(name); 488 names.addAll(aliases); 489 for (ComponentName n : names) { 490 Set<Extension> pendingExt = manager.pendingExtensions.remove(n); 491 if (pendingExt == null) { 492 continue; 493 } 494 for (Extension xt : pendingExt) { 495 ComponentManagerImpl.loadContributions(this, xt); 496 try { 497 component.registerExtension(xt); 498 manager.sendEvent(new ComponentEvent(ComponentEvent.EXTENSION_REGISTERED, 499 ((ComponentInstanceImpl) xt.getComponent()).ri, xt)); 500 } catch (RuntimeException e) { 501 ComponentName compName = xt.getComponent().getName(); 502 String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: " 503 + xt.getExtensionPoint() + " in component: " + compName; 504 log.error(msg, e); 505 msg += " (" + e.toString() + ')'; 506 Framework.getRuntime() 507 .getMessageHandler() 508 .addMessage(new RuntimeMessage(Level.ERROR, msg, Source.EXTENSION, compName.getName())); 509 } 510 } 511 } 512 } 513 514 public synchronized void deactivate() { 515 deactivate(true); 516 } 517 518 /** 519 * Deactivate the component. If mustUnregisterExtensions is false then the call was made by the manager because all 520 * components are stopped (and deactivated) so the extension unregister should not be done (this will speedup the 521 * stop and also fix broken component which are not correctly defining dependencies.) 522 * 523 * @since 9.2 524 */ 525 public synchronized void deactivate(boolean mustUnregisterExtensions) { 526 if (state != ACTIVATED && state != START_FAILURE) { 527 return; 528 } 529 530 state = DEACTIVATING; 531 manager.sendEvent(new ComponentEvent(ComponentEvent.DEACTIVATING_COMPONENT, this)); 532 533 if (mustUnregisterExtensions) { 534 // unregister contributed extensions if any 535 if (extensions != null) { 536 for (Extension xt : extensions) { 537 try { 538 manager.unregisterExtension(xt); 539 } catch (RuntimeException e) { 540 String msg = "Failed to unregister extension. Contributor: " + xt.getComponent() + " to " 541 + xt.getTargetComponent() + "; xpoint: " + xt.getExtensionPoint(); 542 log.error(msg, e); 543 Framework.getRuntime() 544 .getMessageHandler() 545 .addMessage(new RuntimeMessage(Level.ERROR, msg, Source.EXTENSION, 546 xt.getComponent().getName().getName())); 547 } 548 } 549 } 550 } 551 552 component.deactivate(); 553 log.debug("Component deactivated: {}", name); 554 555 component = null; 556 557 state = RESOLVED; 558 manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_DEACTIVATED, this)); 559 } 560 561 public synchronized void resolve() { 562 if (state != REGISTERED) { 563 return; 564 } 565 566 // register services 567 manager.registerServices(this); 568 569 // components are no more automatically activated when resolved. see ComponentManager#start() 570 state = RESOLVED; 571 manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_RESOLVED, this)); 572 } 573 574 public synchronized void unresolve() { 575 if (state == REGISTERED || state == UNREGISTERED) { 576 return; 577 } 578 579 // un-register services 580 manager.unregisterServices(this); 581 582 // components are no more automatically deactivated when unresolved. see ComponentManager#stop() 583 state = REGISTERED; 584 manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_UNRESOLVED, this)); 585 } 586 587 @Override 588 // not synchronized, intermediate states from other synchronized methods 589 // are not a problem 590 public boolean isActivated() { 591 return state == ACTIVATED; 592 } 593 594 @Override 595 // not synchronized, intermediate states from other synchronized methods 596 // are not a problem 597 public boolean isResolved() { 598 return state == RESOLVED; 599 } 600 601 @Override 602 public String[] getProvidedServiceNames() { 603 if (serviceDescriptor != null) { 604 return serviceDescriptor.services; 605 } 606 return null; 607 } 608 609 public ServiceDescriptor getServiceDescriptor() { 610 return serviceDescriptor; 611 } 612 613 @Override 614 public String getImplementation() { 615 return implementation; 616 } 617 618 /** 619 * Checks extensions and returns only valid ones. 620 * 621 * @since 11.3 622 */ 623 protected Extension[] checkExtensions() { 624 var validExtensions = new ArrayList<Extension>(); 625 for (ExtensionImpl xt : extensions) { 626 if (xt.target == null) { 627 String msg = String.format( 628 "Bad extension declaration (no target attribute specified) on component '%s'", getName()); 629 Framework.getRuntime() 630 .getMessageHandler() 631 .addMessage(new RuntimeMessage(Level.ERROR, msg, Source.EXTENSION, getName().getName())); 632 continue; 633 } else { 634 validExtensions.add(xt); 635 } 636 } 637 return validExtensions.toArray(Extension[]::new); 638 } 639 640 @Override 641 public URL getXmlFileUrl() { 642 return xmlFileUrl; 643 } 644 645 @Override 646 public String getSourceId() { 647 return sourceId; 648 } 649 650 @Override 651 public boolean equals(Object obj) { 652 if (obj == this) { 653 return true; 654 } 655 if (obj instanceof RegistrationInfo) { 656 return name.equals(((RegistrationInfo) obj).getName()); 657 } 658 return false; 659 } 660 661 @Override 662 public int hashCode() { 663 return name.hashCode(); 664 } 665 666 /** 667 * Use former way for {@link RegistrationInfoImpl}. 668 * 669 * @since 9.3 670 */ 671 @Override 672 public boolean useFormerLifecycleManagement() { 673 return true; 674 } 675 676}