org.fabric3.fabric.container.component.scope.SingletonScopeContainer Maven / Gradle / Ivy
/*
* Fabric3
* Copyright (c) 2009-2015 Metaform Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Portions originally based on Apache Tuscany 2007
* licensed under the Apache 2.0 license.
*/
package org.fabric3.fabric.container.component.scope;
import javax.xml.namespace.QName;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.fabric3.api.annotation.monitor.Monitor;
import org.fabric3.api.model.type.component.Scope;
import org.fabric3.spi.container.component.GroupInitializationException;
import org.fabric3.spi.container.component.InstanceDestructionException;
import org.fabric3.spi.container.component.InstanceInitException;
import org.fabric3.spi.container.component.InstanceLifecycleException;
import org.fabric3.spi.container.component.ScopedComponent;
import org.fabric3.spi.container.objectfactory.ObjectCreationException;
import org.oasisopen.sca.annotation.Destroy;
/**
* Abstract container for components that have only one implementation instance.
*
* Components deployed via a deployable composite are associated with the same context. When a context starts and stops, components will receive initialization
* and destruction callbacks. Eager initialization is also supported.
*/
public abstract class SingletonScopeContainer extends AbstractScopeContainer {
private static final Object EMPTY = new Object();
private final Map instances;
// The map of instance/component pairs to destroy keyed by the deployable composite the component was deployed with.
// The queues of instance/component pairs are ordered by the sequence in which the deployables were deployed.
// The instance/component pairs themselves are ordered by the sequence in which they were instantiated.
private final Map> destroyQueues;
// the queue of components to eagerly initialize in each group
private final Map> initQueues = new HashMap<>();
// components that are in the process of being created
private final Map pending;
protected SingletonScopeContainer(Scope scope, @Monitor ScopeContainerMonitor monitor) {
super(scope, monitor);
instances = new ConcurrentHashMap<>();
pending = new ConcurrentHashMap<>();
destroyQueues = new LinkedHashMap<>();
}
public void register(ScopedComponent component) {
super.register(component);
if (component.isEagerInit()) {
QName deployable = component.getDeployable();
synchronized (initQueues) {
List initQueue = initQueues.get(deployable);
if (initQueue == null) {
initQueue = new ArrayList<>();
initQueues.put(deployable, initQueue);
}
initQueue.add(component);
}
}
instances.put(component, EMPTY);
}
public void unregister(ScopedComponent component) {
super.unregister(component);
// FIXME should this component be destroyed already or do we need to stop it?
instances.remove(component);
if (component.isEagerInit()) {
QName deployable = component.getDeployable();
synchronized (initQueues) {
List initQueue = initQueues.get(deployable);
initQueue.remove(component);
if (initQueue.isEmpty()) {
initQueues.remove(deployable);
}
}
}
}
public void startContext(QName deployable) throws GroupInitializationException {
eagerInitialize(deployable);
// Destroy queues must be updated *after* components have been eagerly initialized since the latter may have dependencies from other
// contexts. These other contexts need to be put into the destroy queue ahead of the current initializing context so the dependencies
// are destroyed after the eagerly initialized components (the destroy queues are iterated in reverse order).
synchronized (destroyQueues) {
if (!destroyQueues.containsKey(deployable)) {
destroyQueues.put(deployable, new ArrayList());
}
}
}
public void stopContext(QName deployable) {
synchronized (destroyQueues) {
List list = destroyQueues.get(deployable);
if (list == null) {
// this can happen with domain scope where a non-leader runtime does not activate a context
return;
}
destroyInstances(list);
}
}
@Destroy
public synchronized void stop() {
super.stop();
synchronized (destroyQueues) {
destroyQueues.clear();
}
synchronized (initQueues) {
initQueues.clear();
}
instances.clear();
}
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter"})
public Object getInstance(ScopedComponent component) throws InstanceLifecycleException {
Object instance = instances.get(component);
if (instance != EMPTY && instance != null) {
return instance;
}
CountDownLatch latch;
// Avoid race condition where two or more threads attempt to initialize the component instance concurrently.
// Pending component instantiations are tracked and threads block on a latch until they are complete.
synchronized (component) {
latch = pending.get(component);
if (latch != null) {
try {
// wait on the instantiation
latch.await(5, TimeUnit.MINUTES);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new InstanceInitException("Error creating instance for: " + component.getUri(), e);
}
// an instance wrapper is now available as the instantiation has completed
return instances.get(component);
} else {
latch = new CountDownLatch(1);
pending.put(component, latch);
}
}
try {
instance = component.createInstance();
// some component instances such as system singletons may already be started
// if (!component.isInstanceStarted()) {
component.startInstance(instance);
List queue;
QName deployable = component.getDeployable();
synchronized (destroyQueues) {
queue = destroyQueues.get(deployable);
if (queue == null) {
// The context has not been initialized. This can happen if two deployable composites are deployed simultaneously and a
// component in the first composite to be deployed references a component in the second composite. In this case,
// create the destroy queue prior to the context being started.
queue = new ArrayList<>();
destroyQueues.put(deployable, queue);
}
}
queue.add(new Pair(component, instance));
//}
instances.put(component, instance);
latch.countDown();
return instance;
} catch (ObjectCreationException e) {
throw new InstanceInitException("Error creating instance for: " + component.getUri(), e);
} finally {
pending.remove(component);
}
}
public void releaseInstance(ScopedComponent component, Object instance) {
// no-op
}
public List