/*
* #%L
* Gravia :: Runtime :: API
* %%
* Copyright (C) 2013 - 2014 JBoss by Red Hat
* %%
* 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.
* #L%
*/
package org.jboss.gravia.runtime.spi;
import static org.jboss.gravia.runtime.spi.RuntimeLogger.LOGGER;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jboss.gravia.resource.Attachable;
import org.jboss.gravia.resource.Resource;
import org.jboss.gravia.resource.ResourceIdentity;
import org.jboss.gravia.resource.Version;
import org.jboss.gravia.resource.VersionRange;
import org.jboss.gravia.resource.spi.AttachableSupport;
import org.jboss.gravia.runtime.Module;
import org.jboss.gravia.runtime.Module.State;
import org.jboss.gravia.runtime.ModuleContext;
import org.jboss.gravia.runtime.ModuleEvent;
import org.jboss.gravia.runtime.ModuleException;
import org.jboss.gravia.runtime.Runtime;
import org.jboss.gravia.utils.IllegalArgumentAssertion;
import org.jboss.gravia.utils.IllegalStateAssertion;
/**
* The abstract base implementation for a {@link Runtime}
*
* @author thomas.diesler@jboss.com
* @since 27-Sep-2013
*/
public abstract class AbstractRuntime implements Runtime {
private final ResourceIdentity systemIdentity = ResourceIdentity.create("gravia-system", Version.emptyVersion);
private final Map<Long, Module> modules = new ConcurrentHashMap<Long, Module>();
private final CountDownLatch shutdownComplete = new CountDownLatch(1);
private final AtomicBoolean shutdown = new AtomicBoolean();
private final RuntimeEventsManager runtimeEvents;
private final PropertiesProvider properties;
protected AbstractRuntime(PropertiesProvider propertiesProvider) {
IllegalArgumentAssertion.assertNotNull(propertiesProvider, "propertiesProvider");
runtimeEvents = new RuntimeEventsManager();
properties = propertiesProvider;
}
protected abstract AbstractModule createModule(ClassLoader classLoader, Resource resource, Dictionary<String, String> headers, Attachable context);
protected abstract ModuleEntriesProvider getDefaultEntriesProvider(Module module, Attachable context);
@Override
public final Object getProperty(String key) {
return properties.getProperty(key);
}
@Override
public Object getRequiredProperty(String propName) {
Object result = properties.getProperty(propName);
IllegalStateAssertion.assertNotNull(result, "Cannot obtain required property: " + propName);
return result;
}
@Override
public final Object getProperty(String key, Object defaultValue) {
return properties.getProperty(key, defaultValue);
}
@SuppressWarnings("unchecked")
public <A> A adapt(Class<A> type) {
A result = null;
if (type.isAssignableFrom(RuntimeEventsManager.class)) {
result = (A) runtimeEvents;
} else if (type.isAssignableFrom(ModuleContext.class)) {
result = (A) getModuleContext();
}
return result;
}
protected final ResourceIdentity getSystemIdentity() {
return systemIdentity;
}
@Override
public ModuleContext getModuleContext() {
Module sysmodule = getModule(0);
return sysmodule != null ? sysmodule.getModuleContext() : null;
}
@Override
public final Module getModule(long id) {
return modules.get(id);
}
@Override
public final Module getModule(ResourceIdentity identity) {
for (Module module : modules.values()) {
if (module.getIdentity().equals(identity))
return module;
}
return null;
}
@Override
public final Module getModule(ClassLoader classLoader) {
Set<Module> modules = getModules(classLoader);
return modules.size() > 0 ? modules.iterator().next() : null;
}
@Override
public final Set<Module> getModules() {
return new HashSet<Module>(modules.values());
}
@Override
public final Set<Module> getModules(ClassLoader classLoader) {
Set<Module> result = getModules();
Iterator<Module> iterator = result.iterator();
while(iterator.hasNext()) {
Module module = iterator.next();
if (!module.adapt(ClassLoader.class).equals(classLoader)) {
iterator.remove();
}
}
return result;
}
@Override
public Set<Module> getModules(String symbolicName, VersionRange range) {
Set<Module> result = getModules();
Iterator<Module> iterator = result.iterator();
while(iterator.hasNext()) {
ResourceIdentity modid = iterator.next().getIdentity();
if (symbolicName != null && !symbolicName.equals(modid.getSymbolicName())) {
iterator.remove();
}
if (range != null && !range.includes(modid.getVersion())) {
iterator.remove();
}
}
return result;
}
@Override
public final Module installModule(ClassLoader classLoader, Dictionary<String, String> headers) throws ModuleException {
assertNoShutdown();
return installModule(classLoader, null, headers, null);
}
@Override
public final Module installModule(ClassLoader classLoader, Resource resource, Dictionary<String, String> headers) throws ModuleException {
assertNoShutdown();
return installModule(classLoader, resource, headers, null);
}
@Override
public final Module installModule(ClassLoader classLoader, Resource resource, Dictionary<String, String> headers, Attachable context) throws ModuleException {
assertNoShutdown();
context = context != null ? context : new AttachableSupport();
AbstractModule module = createModule(classLoader, resource, headers, context);
// Attach the {@link ModuleEntriesProvider}
ModuleEntriesProvider entriesProvider = context.getAttachment(AbstractModule.MODULE_ENTRIES_PROVIDER_KEY);
entriesProvider = entriesProvider != null ? entriesProvider : getDefaultEntriesProvider(module, context);
if (entriesProvider != null) {
module.putAttachment(AbstractModule.MODULE_ENTRIES_PROVIDER_KEY, entriesProvider);
}
if (getModule(module.getIdentity()) != null)
throw new ModuleException("Module already installed: " + module);
modules.put(module.getModuleId(), module);
// #1 The module's state is set to {@code INSTALLED}.
module.setState(State.INSTALLED);
// #2 A module event of type {@link ModuleEvent#INSTALLED} is fired.
runtimeEvents.fireModuleEvent(module, ModuleEvent.INSTALLED);
LOGGER.info("Installed: {}", module);
return module;
}
protected void uninstallModule(Module module) {
modules.remove(module.getModuleId());
LOGGER.info("Uninstalled: {}", module);
}
@Override
public Runtime shutdown() {
if (shutdown.compareAndSet(false, true)) {
new ShutdownThread().start();
}
return this;
}
@Override
public boolean shutdownInProgress() {
return shutdown.get();
}
@Override
public boolean awaitShutdown(long timeout, TimeUnit unit) throws InterruptedException {
return shutdownComplete.await(timeout, unit);
}
@Override
public boolean shutdownComplete() {
return shutdownComplete.getCount() == 0;
}
protected void assertNoShutdown() {
if (shutdown.get()) {
throw new IllegalStateException(shutdownComplete() ? "Runtime has been shut down" : "Runtime is shuting down");
}
}
protected void doShutdown() {
for (Module module : getModules()) {
if (!module.getIdentity().equals(systemIdentity)) {
module.uninstall();
}
}
}
class ShutdownThread extends Thread {
@Override
public void run() {
try {
doShutdown();
} finally {
shutdownComplete.countDown();
}
}
}
}