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