/* * #%L * Gravia :: Runtime :: Embedded * %% * 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.embedded.internal; import static org.jboss.gravia.runtime.spi.RuntimeLogger.LOGGER; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.jboss.gravia.resource.ResourceIdentity; import org.jboss.gravia.runtime.Module; import org.jboss.gravia.runtime.ServiceEvent; import org.jboss.gravia.runtime.ServiceException; import org.jboss.gravia.runtime.ServiceFactory; import org.jboss.gravia.runtime.ServiceReference; import org.jboss.gravia.runtime.ServiceRegistration; import org.jboss.gravia.runtime.spi.AbstractModule; import org.jboss.gravia.utils.CaseInsensitiveDictionary; import org.jboss.gravia.utils.IllegalArgumentAssertion; import org.jboss.gravia.utils.UnmodifiableDictionary; /** * The internal representation of a service * * @author thomas.diesler@jboss.com * @since 27-Sep-2013 * * @ThreadSafe */ @SuppressWarnings("rawtypes") final class ServiceState<S> implements ServiceRegistration<S>, ServiceReference<S> { private final RuntimeServicesManager serviceManager; private final Module ownerModule; private final String[] classNames; private final ValueProvider<S> valueProvider; private final ServiceReference<S> reference; private final Set<AbstractModule> usingModules = new HashSet<AbstractModule>(); private final Map<ResourceIdentity, ServiceFactoryHolder<S>> factoryValues; private final ServiceRegistration<S> registration; // The properties private CaseInsensitiveDictionary<Object> prevProperties; private CaseInsensitiveDictionary<Object> currProperties; private Object propsLock = new Object(); private String cachedToString; interface ValueProvider<S> { boolean isFactoryValue(); S getValue(); } @SuppressWarnings("unchecked") ServiceState(RuntimeServicesManager serviceManager, Module owner, long serviceId, String[] classNames, ValueProvider<S> valueProvider, Dictionary properties) { assert serviceManager != null : "Null serviceManager"; assert owner != null : "Null owner"; assert classNames != null && classNames.length > 0 : "Null clazzes"; assert valueProvider != null : "Null valueProvider"; this.serviceManager = serviceManager; this.ownerModule = owner; this.valueProvider = valueProvider; this.classNames = classNames; if (!valueProvider.isFactoryValue() && !checkValidClassNames(owner, classNames, valueProvider.getValue())) throw new IllegalArgumentException("Invalid objectClass: " + Arrays.toString(classNames)); if (properties == null) properties = new Hashtable(); properties.put(org.jboss.gravia.Constants.SERVICE_ID, serviceId); properties.put(org.jboss.gravia.Constants.OBJECTCLASS, classNames); this.currProperties = new CaseInsensitiveDictionary(properties); this.cachedToString = updateCachedToString(); // Create the {@link ServiceRegistration} and {@link ServiceReference} this.registration = new ServiceRegistrationWrapper(this); this.reference = new ServiceReferenceWrapper(this); this.factoryValues = valueProvider.isFactoryValue() ? new ConcurrentHashMap<ResourceIdentity, ServiceFactoryHolder<S>>() : null; } static ServiceState assertServiceState(ServiceReference sref) { assert sref != null : "Null sref"; if (sref instanceof ServiceReferenceWrapper) { sref = ((ServiceReferenceWrapper) sref).getServiceState(); } return (ServiceState) sref; } S getScopedValue(Module module) { // For non-factory services, return the value if (valueProvider.isFactoryValue() == false) return valueProvider.getValue(); S result = null; // [TODO] #46 Remove workaround for Endless loop on class defined failed // https://issues.jboss.org/browse/LOGMGR-88 ClassLoader tccl = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(null); try { ServiceFactoryHolder<S> factoryHolder = getFactoryHolder(module); if (factoryHolder == null) { ServiceFactory factory = (ServiceFactory) valueProvider.getValue(); factoryHolder = new ServiceFactoryHolder<S>(module, factory); factoryValues.put(module.getIdentity(), factoryHolder); } result = factoryHolder.getService(); } catch (Throwable th) { String message = "Cannot get factory value from: " + this; ServiceException ex = new ServiceException(message, ServiceException.FACTORY_EXCEPTION, th); LOGGER.error(message, ex); } } finally { Thread.currentThread().setContextClassLoader(tccl); } return result; } void ungetScopedValue(Module module) { if (valueProvider.isFactoryValue()) { ServiceFactoryHolder factoryHolder = getFactoryHolder(module); if (factoryHolder != null) { try { factoryHolder.ungetService(); } catch (RuntimeException rte) { ServiceException ex = new ServiceException("Cannot unget factory value", ServiceException.FACTORY_EXCEPTION, rte); LOGGER.error("Cannot unget factory value", ex); } } } } private ServiceFactoryHolder<S> getFactoryHolder(Module module) { return factoryValues != null ? factoryValues.get(module.getIdentity()) : null; } ServiceRegistration<S> getRegistration() { return registration; } List<String> getClassNames() { return Arrays.asList(classNames); } @Override public ServiceReference<S> getReference() { assertNotUnregistered(); return reference; } @Override public void unregister() { assertNotUnregistered(); unregisterInternal(); } private void unregisterInternal() { serviceManager.unregisterService(this); } @Override public Object getProperty(String key) { synchronized (propsLock) { return key != null ? currProperties.get(key) : null; } } @Override public String[] getPropertyKeys() { synchronized (propsLock) { List<String> result = new ArrayList<String>(); Enumeration<String> keys = currProperties.keys(); while (keys.hasMoreElements()) result.add(keys.nextElement()); return result.toArray(new String[result.size()]); } } @Override @SuppressWarnings({ "unchecked" }) public void setProperties(Dictionary properties) { assertNotUnregistered(); // Remember the previous properties for a potential // delivery of the MODIFIED_ENDMATCH event synchronized (propsLock) { prevProperties = currProperties; if (properties == null) properties = new Hashtable(); properties.put(org.jboss.gravia.Constants.SERVICE_ID, currProperties.get(org.jboss.gravia.Constants.SERVICE_ID)); properties.put(org.jboss.gravia.Constants.OBJECTCLASS, currProperties.get(org.jboss.gravia.Constants.OBJECTCLASS)); currProperties = new CaseInsensitiveDictionary(properties); } // This event is synchronously delivered after the service properties have been modified. serviceManager.fireServiceEvent(ownerModule, ServiceEvent.MODIFIED, this); } @SuppressWarnings("unchecked") Dictionary<String, ?> getPreviousProperties() { synchronized (propsLock) { return new UnmodifiableDictionary(prevProperties); } } Module getServiceOwner() { return ownerModule; } @Override public Module getModule() { return (isUnregistered() ? null : ownerModule); } void addUsingModule(AbstractModule module) { synchronized (usingModules) { usingModules.add(module); } } void removeUsingModule(AbstractModule module) { synchronized (usingModules) { usingModules.remove(module); } } Set<AbstractModule> getUsingModulesInternal() { // Return an unmodifieable snapshot of the set synchronized (usingModules) { return Collections.unmodifiableSet(new HashSet<AbstractModule>(usingModules)); } } @Override public boolean isAssignableTo(Module module, String className) { IllegalArgumentAssertion.assertNotNull(module, "module"); IllegalArgumentAssertion.assertNotNull(className, "className"); if (module == ownerModule || className.startsWith("java.")) return true; if (module.getState() == Module.State.UNINSTALLED) return false; ClassLoader moduleClassLoader = module.adapt(ClassLoader.class); if (moduleClassLoader == null) { LOGGER.info("No ClassLoader for: {}", module); return false; } Class<?> targetClass; try { targetClass = moduleClassLoader.loadClass(className); } catch (Throwable th) { // If the requesting module does not have a wire to the // service package it cannot be constraint on that package. LOGGER.trace("Requesting module [{}] cannot load class: {}", module, className); return true; } ClassLoader ownerClassLoader = ownerModule.adapt(ClassLoader.class); if (ownerClassLoader == null) { LOGGER.trace("Registrant module [{}] has no class loader for: {}", ownerModule, className); return true; } // For the module that registered the service referenced by this ServiceReference (registrant module); // find the source for the package. If no source is found then return true if the registrant module // is equal to the specified module; otherwise return false Class<?> serviceClass; try { serviceClass = ownerClassLoader.loadClass(className); } catch (ClassNotFoundException e) { LOGGER.trace("Registrant module [{}] cannot load class: {}", ownerModule, className); return true; } // If the package source of the registrant module is equal to the package source of the specified module // then return true; otherwise return false. if (targetClass != serviceClass) { LOGGER.trace("Not assignable: {}", className); return false; } return true; } @Override public int compareTo(Object sref) { if (!(sref instanceof ServiceReference)) throw new IllegalArgumentException("Invalid ServiceReference: " + sref); Comparator<ServiceReference<?>> comparator = ServiceReferenceComparator.getInstance(); return comparator.compare(this, (ServiceReference) sref); } boolean isUnregistered() { return registration == null; } private void assertNotUnregistered() { if (isUnregistered()) throw new IllegalStateException("Service unregistered: " + this); } private boolean checkValidClassNames(Module module, String[] classNames, Object value) { assert module != null : "Null module"; assert classNames != null && classNames.length > 0 : "Null service classes"; assert value != null : "Null value"; if (value instanceof ServiceFactory) return true; boolean result = true; for (String className : classNames) { if (className == null) { result = false; break; } try { Class<?> valueClass = value.getClass(); // Use Class.forName with classloader argument as the classloader // might be null (for JRE provided types). Class<?> clazz = Class.forName(className, false, valueClass.getClassLoader()); if (clazz.isAssignableFrom(valueClass) == false) { LOGGER.error("Service interface [{}] loaded from [{}] is not assignable from [{}] loaded from [{}]", new Object[] { className, clazz.getClassLoader(), valueClass.getName(), valueClass.getClassLoader() }); result = false; break; } } catch (ClassNotFoundException ex) { LOGGER.error("Cannot load [{}] from: {}", className, module); result = false; break; } } return result; } private String updateCachedToString() { synchronized (propsLock) { Map<String, Object> clone = new LinkedHashMap<String, Object>(currProperties); Map<String, Object> props = new LinkedHashMap<String, Object>(); // service.id props.put(org.jboss.gravia.Constants.SERVICE_ID, clone.remove(org.jboss.gravia.Constants.SERVICE_ID)); // service.pid String pid = (String) clone.remove(org.jboss.gravia.Constants.SERVICE_PID); if (pid != null) { props.put(org.jboss.gravia.Constants.SERVICE_PID, pid); } // objectClass String[] classes = (String[]) clone.remove(org.jboss.gravia.Constants.OBJECTCLASS); props.put(org.jboss.gravia.Constants.OBJECTCLASS, Arrays.asList(classes)); // all other props props.putAll(clone); return "ServiceState" + props; } } @Override public String toString() { return cachedToString.toString(); } class ServiceFactoryHolder<T> { ThreadLocal<Module> factoryRecursion = new ThreadLocal<Module>(); ServiceFactory factory; AtomicInteger useCount; Module module; T value; ServiceFactoryHolder(Module module, ServiceFactory factory) { this.module = module; this.factory = factory; this.useCount = new AtomicInteger(); } @SuppressWarnings("unchecked") synchronized T getService() { // If this method is called recursively for the same module // then it must return null to break the recursion. if (factoryRecursion.get() == module) { LOGGER.error("ServiceFactory recusion for " + module + " in: " + this, new RuntimeException()); return null; } else { factoryRecursion.set(module); } try { // Multiple calls to getService() return the same value if (useCount.getAndIncrement() > 0) { return value; } // The Framework will check if the returned service object is an instance of all the // classes named when the service was registered. If not, then null is returned to the module. T retValue = (T) factory.getService(module, getRegistration()); if (retValue != null && checkValidClassNames(ownerModule, (String[]) getProperty(org.jboss.gravia.Constants.OBJECTCLASS), retValue)) { value = retValue; } // If the service object returned by the ServiceFactory object is not an instanceof all the classes named // when the service was registered or the ServiceFactory object throws an exception, // null is returned and a Framework event of type {@link FrameworkEvent#ERROR} // containing a {@link ServiceException} describing the error is fired. if (value == null) { String message = "Cannot get factory value from: " + factory; ServiceException ex = new ServiceException(message, ServiceException.FACTORY_ERROR); LOGGER.error(message, ex); } } finally { factoryRecursion.set(null); } return value; } @SuppressWarnings("unchecked") synchronized void ungetService() { if (useCount.get() == 0) return; // Call unget on the factory when done if (useCount.decrementAndGet() == 0) { factory.ungetService(module, getRegistration(), value); value = null; } } } }