Package org.springframework.osgi.service.importer.support.internal.collection

Source Code of org.springframework.osgi.service.importer.support.internal.collection.OsgiServiceCollection

/*
* Copyright 2006-2008 the original author or authors.
*
* 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.
*/

package org.springframework.osgi.service.importer.support.internal.collection;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.osgi.service.ServiceUnavailableException;
import org.springframework.osgi.service.importer.DefaultOsgiServiceDependency;
import org.springframework.osgi.service.importer.ImportedOsgiServiceProxy;
import org.springframework.osgi.service.importer.OsgiServiceDependency;
import org.springframework.osgi.service.importer.OsgiServiceLifecycleListener;
import org.springframework.osgi.service.importer.support.internal.aop.ProxyPlusCallback;
import org.springframework.osgi.service.importer.support.internal.aop.ServiceProxyCreator;
import org.springframework.osgi.service.importer.support.internal.dependency.ImporterStateListener;
import org.springframework.osgi.service.importer.support.internal.util.OsgiServiceBindingUtils;
import org.springframework.osgi.util.OsgiListenerUtils;
import org.springframework.util.Assert;

/**
* OSGi service dynamic collection - allows iterating while the underlying
* storage is being shrunk/expanded. This collection is read-only - its content
* is being retrieved dynamically from the OSGi platform.
*
* <p/> This collection and its iterators are thread-safe. That is, multiple
* threads can access the collection. However, since the collection is
* read-only, it cannot be modified by the client.
*
* @see Collection
* @author Costin Leau
*/
public class OsgiServiceCollection implements Collection, InitializingBean, CollectionProxy, DisposableBean {

  /**
   * Listener tracking the OSGi services which form the dynamic collection.
   *
   * @author Costin Leau
   */
  private class Listener implements ServiceListener {

    public void serviceChanged(ServiceEvent event) {
      ClassLoader tccl = Thread.currentThread().getContextClassLoader();

      try {
        Thread.currentThread().setContextClassLoader(classLoader);
        ServiceReference ref = event.getServiceReference();
        Long serviceId = (Long) ref.getProperty(Constants.SERVICE_ID);
        boolean collectionModified = false;

        ProxyPlusCallback ppc = null;
        Object proxy = null;

        // flag used for sending state events
        boolean shouldInformStateListeners = false;

        switch (event.getType()) {

          case (ServiceEvent.REGISTERED):
          case (ServiceEvent.MODIFIED):
            // same as ServiceEvent.REGISTERED
            synchronized (serviceProxies) {
              if (!servicesIdMap.containsKey(serviceId)) {
                ppc = proxyCreator.createServiceProxy(ref);
                proxy = ppc.proxy;
                // let the dynamic collection decide if the service
                // is added or not (think set, sorted set)
                if (serviceProxies.add(proxy)) {
                  collectionModified = true;
                  // check if the list was empty before adding something to it
                  shouldInformStateListeners = (serviceProxies.size() == 1);
                  servicesIdMap.put(serviceId, ppc);
                }
              }
            }
            // inform listeners
            // TODO: should this be part of the lock also?
            if (collectionModified) {
              OsgiServiceBindingUtils.callListenersBind(context, proxy, ref, listeners);

              if (serviceRequiredAtStartup && shouldInformStateListeners)
                notifySatisfiedStateListeners();
            }

            break;
          case (ServiceEvent.UNREGISTERING):
            synchronized (serviceProxies) {
              // remove service id / proxy association
              ppc = (ProxyPlusCallback) servicesIdMap.remove(serviceId);
              if (ppc != null) {
                proxy = ppc.proxy;
                // before removal, allow analysis
                checkDeadProxies(proxy);
                // remove service proxy
                collectionModified = serviceProxies.remove(proxy);
                // invalidate it
                invalidateProxy(ppc);

                // check if the list is empty
                shouldInformStateListeners = (serviceProxies.isEmpty());
              }
            }
            // TODO: should this be part of the lock also?
            if (collectionModified) {
              OsgiServiceBindingUtils.callListenersUnbind(context, proxy, ref, listeners);

              if (serviceRequiredAtStartup && shouldInformStateListeners)
                notifyUnsatisfiedStateListeners();
            }

            break;

          default:
            throw new IllegalArgumentException("unsupported event type:" + event);
        }
      }
      // OSGi swallows these exceptions so make sure we get a chance to
      // see them.
      catch (Throwable re) {
        if (log.isWarnEnabled()) {
          log.warn("serviceChanged() processing failed", re);
        }
      }
      finally {
        Thread.currentThread().setContextClassLoader(tccl);
      }
    }

    private void notifySatisfiedStateListeners() {
      synchronized (stateListeners) {
        for (Iterator iterator = stateListeners.iterator(); iterator.hasNext();) {
          ImporterStateListener stateListener = (ImporterStateListener) iterator.next();
          stateListener.importerSatisfied(eventSource, dependency);
        }
      }
    }

    private void notifyUnsatisfiedStateListeners() {
      synchronized (stateListeners) {
        for (Iterator iterator = stateListeners.iterator(); iterator.hasNext();) {
          ImporterStateListener stateListener = (ImporterStateListener) iterator.next();
          stateListener.importerUnsatisfied(eventSource, dependency);
        }
      }
    }
  }

  /**
   * Read-only iterator wrapper around the dynamic collection iterator.
   *
   * @author Costin Leau
   *
   */
  protected class OsgiServiceIterator implements Iterator {

    // dynamic thread-safe iterator
    private final Iterator iter = serviceProxies.iterator();


    public boolean hasNext() {
      mandatoryServiceCheck();
      return iter.hasNext();
    }

    public Object next() {
      synchronized (serviceProxies) {
        mandatoryServiceCheck();
        Object proxy = iter.next();
        return (proxy == null ? tailDeadProxy : proxy);
      }
    }

    public void remove() {
      // write operations disabled
      throw new UnsupportedOperationException();
    }
  }


  private static final Log log = LogFactory.getLog(OsgiServiceCollection.class);

  // map of services
  // the service id is used as key while the service proxy is used for
  // values
  // Map<ServiceId, ImporterProxy>
  //
  // NOTE: this collection is protected by the 'serviceProxies' lock.
  protected final Map servicesIdMap = new LinkedHashMap(8);

  /**
   * The dynamic collection.
   */
  protected DynamicCollection serviceProxies;

  /**
   * Recall the last proxy for the rare case, where a service goes down
   * between the #hasNext() and #next() call of an iterator.
   *
   * Subclasses should implement their own strategy when it comes to assign a
   * value to it through
   */
  protected volatile Object tailDeadProxy;

  private boolean serviceRequiredAtStartup = true;

  private final Filter filter;

  private final BundleContext context;

  /** TCCL to set between calling listeners */
  private final ClassLoader classLoader;

  /** Service proxy creator */
  private final ServiceProxyCreator proxyCreator;

  private OsgiServiceLifecycleListener[] listeners = new OsgiServiceLifecycleListener[0];

  private final ServiceListener listener;

  /** state listener */
  private List stateListeners = Collections.EMPTY_LIST;

  private final Object lock = new Object();

  /** dependency object */
  private OsgiServiceDependency dependency;

  /** dependable service importer */
  private Object eventSource;

  /** event source (importer) name */
  private String sourceName;


  public OsgiServiceCollection(Filter filter, BundleContext context, ClassLoader classLoader,
      ServiceProxyCreator proxyCreator) {
    Assert.notNull(classLoader, "ClassLoader is required");
    Assert.notNull(context, "context is required");

    this.filter = filter;
    this.context = context;
    this.classLoader = classLoader;

    this.proxyCreator = proxyCreator;
    listener = new Listener();
  }

  public void afterPropertiesSet() {
    // create service proxies collection
    this.serviceProxies = createInternalDynamicStorage();

    boolean trace = log.isTraceEnabled();

    dependency = new DefaultOsgiServiceDependency(sourceName, filter, serviceRequiredAtStartup);

    if (trace)
      log.trace("Adding osgi listener for services matching [" + filter + "]");
    OsgiListenerUtils.addServiceListener(context, listener, filter);

    if (serviceRequiredAtStartup) {
      if (trace)
        log.trace("1..x cardinality - looking for service [" + filter + "] at startup...");
      mandatoryServiceCheck();
    }
  }

  public void destroy() {
    OsgiListenerUtils.removeServiceListener(context, listener);

    synchronized (serviceProxies) {
      for (Iterator iterator = serviceProxies.iterator(); iterator.hasNext();) {
        ImportedOsgiServiceProxy serviceProxy = (ImportedOsgiServiceProxy) iterator.next();
        ServiceReference ref = serviceProxy.getServiceReference();

        // get first the destruction callback
        ProxyPlusCallback ppc = (ProxyPlusCallback) servicesIdMap.get((Long) ref.getProperty(Constants.SERVICE_ID));
        listener.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, ref));

        try {
          ppc.destructionCallback.destroy();
        }
        catch (Exception ex) {
          log.error("Exception occurred while destroying proxy " + ppc.proxy, ex);
        }
      }

      serviceProxies.clear();
      servicesIdMap.clear();
    }
  }

  /**
   * Check to see whether at least one service is available.
   */
  protected void mandatoryServiceCheck() {
    if (serviceRequiredAtStartup && serviceProxies.isEmpty())
      throw new ServiceUnavailableException(filter);
  }

  public boolean isSatisfied() {
    if (serviceRequiredAtStartup)
      return (!serviceProxies.isEmpty());
    else
      return true;
  }

  /**
   * Create the dynamic storage used internally. The storage <strong>has</strong>
   * to be thread-safe.
   */
  protected DynamicCollection createInternalDynamicStorage() {
    return new DynamicCollection();
  }

  private void invalidateProxy(ProxyPlusCallback ppc) {
    // don't do anything (the proxy will simply thrown an exception if still in use)
  }

  /**
   * Hook for tracking the last disappearing service to cope with the rare
   * case, where the last service in the collection disappears between calls
   * to hasNext() and next() on an iterator at the end of the collection.
   *
   * @param proxy
   */
  protected void checkDeadProxies(Object proxy, int proxyCollectionPos) {
    if (proxyCollectionPos == serviceProxies.size() - 1)
      tailDeadProxy = proxy;
  }

  /**
   * Private method, computing the index and share it with subclasses.
   *
   * @param proxy
   */
  private void checkDeadProxies(Object proxy) {
    // no need for a collection lock (already have it)
    int index = serviceProxies.indexOf(proxy);
    checkDeadProxies(proxy, index);
  }

  public void setServiceImporter(Object importer) {
    this.eventSource = importer;
  }

  public void setServiceImporterName(String name) {
    this.sourceName = name;
  }

  public Iterator iterator() {
    return new OsgiServiceIterator();
  }

  public int size() {
    mandatoryServiceCheck();
    return serviceProxies.size();
  }

  public String toString() {
    mandatoryServiceCheck();
    synchronized (serviceProxies) {
      return serviceProxies.toString();
    }
  }

  //
  // write operations forbidden
  //
  public boolean remove(Object o) {
    throw new UnsupportedOperationException();
  }

  public boolean removeAll(Collection c) {
    throw new UnsupportedOperationException();
  }

  public boolean add(Object o) {
    throw new UnsupportedOperationException();
  }

  public boolean addAll(Collection c) {
    throw new UnsupportedOperationException();
  }

  public void clear() {
    throw new UnsupportedOperationException();
  }

  public boolean retainAll(Collection c) {
    throw new UnsupportedOperationException();
  }

  public boolean contains(Object o) {
    mandatoryServiceCheck();
    return serviceProxies.contains(o);
  }

  public boolean containsAll(Collection c) {
    mandatoryServiceCheck();
    return serviceProxies.containsAll(c);
  }

  public boolean isEmpty() {
    mandatoryServiceCheck();
    return size() == 0;
  }

  public Object[] toArray() {
    mandatoryServiceCheck();
    return serviceProxies.toArray();
  }

  public Object[] toArray(Object[] array) {
    mandatoryServiceCheck();
    return serviceProxies.toArray(array);
  }

  /**
   * @param listeners The listeners to set.
   */
  public void setListeners(OsgiServiceLifecycleListener[] listeners) {
    Assert.notNull(listeners);
    this.listeners = listeners;
  }

  public void setRequiredAtStartup(boolean serviceRequiredAtStartup) {
    this.serviceRequiredAtStartup = serviceRequiredAtStartup;
  }

  public void setStateListeners(List stateListeners) {
    synchronized (lock) {
      this.stateListeners = stateListeners;
    }
  }
}
TOP

Related Classes of org.springframework.osgi.service.importer.support.internal.collection.OsgiServiceCollection

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.