Package org.jboss.gravia.runtime.embedded.internal

Source Code of org.jboss.gravia.runtime.embedded.internal.ServiceState$ServiceFactoryHolder

/*
* #%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] 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;
            }
        }
    }
}
TOP

Related Classes of org.jboss.gravia.runtime.embedded.internal.ServiceState$ServiceFactoryHolder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.