/* * #%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.net.URL; import java.util.Collections; import java.util.Dictionary; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.jboss.gravia.Constants; import org.jboss.gravia.resource.Attachable; import org.jboss.gravia.resource.AttachmentKey; import org.jboss.gravia.resource.DictionaryResourceBuilder; import org.jboss.gravia.resource.Resource; import org.jboss.gravia.resource.ResourceBuilder; import org.jboss.gravia.resource.ResourceIdentity; import org.jboss.gravia.resource.Version; import org.jboss.gravia.resource.spi.AttachableSupport; import org.jboss.gravia.runtime.Module; import org.jboss.gravia.runtime.ModuleContext; import org.jboss.gravia.runtime.Runtime; import org.jboss.gravia.runtime.ServiceReference; import org.jboss.gravia.utils.CaseInsensitiveDictionary; import org.jboss.gravia.utils.IllegalArgumentAssertion; import org.jboss.gravia.utils.UnmodifiableDictionary; import org.osgi.framework.Bundle; /** * The abstract base implementaiton for all {@link Module}s. * * @author thomas.diesler@jboss.com * @since 27-Sep-2013 */ public abstract class AbstractModule implements Module, Attachable { public static AttachmentKey<ModuleEntriesProvider> MODULE_ENTRIES_PROVIDER_KEY = AttachmentKey.create(ModuleEntriesProvider.class); private final AbstractRuntime runtime; private final ClassLoader classLoader; private final Resource resource; private final Dictionary<String, String> headers; private final Attachable attachments = new AttachableSupport(); private final ConcurrentHashMap<ServiceReference<?>, AtomicInteger> usedServices = new ConcurrentHashMap<ServiceReference<?>, AtomicInteger>(); protected AbstractModule(AbstractRuntime runtime, ClassLoader classLoader, Resource resource, Dictionary<String, String> headers) { IllegalArgumentAssertion.assertNotNull(runtime, "runtime"); IllegalArgumentAssertion.assertNotNull(classLoader, "classLoader"); this.runtime = runtime; this.classLoader = classLoader; // One of the two must be given to determine the identity if (resource == null && headers == null) throw new IllegalArgumentException("Cannot create module identity"); // Build the resource if (resource == null) { ResourceBuilder builder = new DictionaryResourceBuilder().load(headers); resource = builder.getResource(); } this.resource = resource; // Build the headers ResourceIdentity resourceIdentity = resource.getIdentity(); Hashtable<String, String> clonedHeaders = new Hashtable<String, String>(); if (headers != null) { Enumeration<String> keys = headers.keys(); while(keys.hasMoreElements()) { String key = keys.nextElement(); String value = headers.get(key); clonedHeaders.put(key, value); } } if (clonedHeaders.get(Constants.GRAVIA_IDENTITY_CAPABILITY) == null) { String identityHeader = getIdentityHeader(resourceIdentity); clonedHeaders.put(Constants.GRAVIA_IDENTITY_CAPABILITY, identityHeader); } this.headers = new UnmodifiableDictionary<String, String>(new CaseInsensitiveDictionary<String>(clonedHeaders)); // Verify the resource & headers identity ResourceIdentity headersIdentity = new DictionaryResourceBuilder().load(clonedHeaders).getResource().getIdentity(); if (!resourceIdentity.equals(headersIdentity)) throw new IllegalArgumentException("Resource and header identity does not match: " + resourceIdentity); } public static AbstractModule assertAbstractModule(Module module) { if (!(module instanceof AbstractModule)) throw new IllegalArgumentException("Not an AbstractModule: " + module); return (AbstractModule) module; } private String getIdentityHeader(ResourceIdentity identity) { String symbolicName = identity.getSymbolicName(); Version version = identity.getVersion(); return symbolicName + ";version=" + version; } protected abstract void setState(State newState); protected AbstractRuntime getRuntime() { return runtime; } protected ClassLoader getClassLoader() { return classLoader; } @Override public ResourceIdentity getIdentity() { return resource.getIdentity(); } @Override @SuppressWarnings("unchecked") public <A> A adapt(Class<A> type) { A result = null; if (type.isAssignableFrom(Bundle.class)) { result = (A) getBundleAdaptor(this); } else if (type.isAssignableFrom(Runtime.class)) { result = (A) runtime; } else if (type.isAssignableFrom(AbstractRuntime.class)) { result = (A) runtime; } else if (type.isAssignableFrom(ClassLoader.class)) { result = (A) classLoader; } else if (type.isAssignableFrom(Resource.class)) { result = (A) resource; } else if (type.isAssignableFrom(Module.class)) { result = (A) this; } else if (type.isAssignableFrom(ModuleContext.class)) { result = (A) getModuleContext(); } else if (type.isAssignableFrom(ModuleEntriesProvider.class)) { result = (A) getAttachment(MODULE_ENTRIES_PROVIDER_KEY); } return result; } protected abstract Bundle getBundleAdaptor(Module module); @Override public <T> T putAttachment(AttachmentKey<T> key, T value) { return attachments.putAttachment(key, value); } @Override public <T> T getAttachment(AttachmentKey<T> key) { return attachments.getAttachment(key); } @Override public <T> boolean hasAttachment(AttachmentKey<T> key) { return attachments.hasAttachment(key); } @Override public <T> T removeAttachment(AttachmentKey<T> key) { return attachments.removeAttachment(key); } @Override public Class<?> loadClass(String className) throws ClassNotFoundException { assertNotUninstalled(); return classLoader.loadClass(className); } @Override public Dictionary<String, String> getHeaders() { return headers; } @Override public URL getEntry(String path) { ModuleEntriesProvider entriesProvider = adapt(ModuleEntriesProvider.class); return entriesProvider != null ? entriesProvider.getEntry(path) : null; } @Override public List<String> getEntryPaths(String path) { ModuleEntriesProvider entriesProvider = adapt(ModuleEntriesProvider.class); return entriesProvider != null ? entriesProvider.getEntryPaths(path) : Collections.<String>emptyList(); } @Override public List<URL> findEntries(String path, String filePattern, boolean recurse) { ModuleEntriesProvider entriesProvider = adapt(ModuleEntriesProvider.class); return entriesProvider != null ? entriesProvider.findEntries(path, filePattern, recurse) : Collections.<URL>emptyList(); } public Set<ServiceReference<?>> getServicesInUseInternal() { return Collections.unmodifiableSet(usedServices.keySet()); } public void addServiceInUse(ServiceReference<?> serviceState) { LOGGER.trace("Add service in use {} to: {}", serviceState, this); usedServices.putIfAbsent(serviceState, new AtomicInteger()); AtomicInteger count = usedServices.get(serviceState); count.incrementAndGet(); } public int removeServiceInUse(ServiceReference<?> serviceState) { LOGGER.trace("Remove service in use {} from: {}", serviceState, this); AtomicInteger count = usedServices.get(serviceState); if (count == null) return -1; int countVal = count.decrementAndGet(); if (countVal == 0) usedServices.remove(serviceState); return countVal; } protected void assertNotUninstalled() { if (getState() == State.UNINSTALLED) throw new IllegalStateException("Module already uninstalled: " + this); } @Override public String toString() { return "Module[" + getIdentity() + "]"; } }