/*
* Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors.
*
* 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.switchyard.deploy.osgi.internal;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.BundleTracker;
import org.osgi.util.tracker.BundleTrackerCustomizer;
import org.switchyard.common.util.ProviderRegistry;
/**
* ProviderRegistryImpl.
*/
public class ProviderRegistryImpl implements ProviderRegistry.Registry, BundleTrackerCustomizer<Bundle> {
public static final long DEFAULT_TIMEOUT = 0l;
public static final String TIMEOUT = "org.switchyard.providers.timeout";
private Map<String, List<Callable<Class>>> _factories;
private ReadWriteLock _lock = new ReentrantReadWriteLock();
private final BundleContext _bundleContext;
private final BundleTracker<Bundle> _tracker;
private ConcurrentMap<Long, Map<String, Callable<Class>>> allFactories = new ConcurrentHashMap<Long, Map<String, Callable<Class>>>();
/**
* Create a new instance of ProviderRegistryImpl.
* @param bundleContext bundleContext
*/
public ProviderRegistryImpl(BundleContext bundleContext) {
_bundleContext = bundleContext;
_tracker = new BundleTracker<Bundle>(bundleContext, Bundle.ACTIVE, this);
_tracker.open();
}
@Override
public <T> T getProvider(Class<T> clazz) {
try {
T provider = null;
// Attempt to resolve via META-INF/services first
Class<? extends T> pvdClass = locate(clazz);
if (pvdClass != null) {
provider = pvdClass.newInstance();
} else {
// Not found in META-INF/services, try OSGi Service Registry
ServiceReference<T> ref = _bundleContext.getServiceReference(clazz);
if (ref != null) {
provider = _bundleContext.getService(ref);
}
}
return provider;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public <T> List<T> getProviders(Class<T> clazz) {
try {
List<T> providers = new ArrayList<T>();
List<Class<? extends T>> pvds = locateAll(clazz);
for (Class<? extends T> pvd : pvds) {
providers.add(pvd.newInstance());
}
return providers;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void destroy() {
_tracker.close();
}
@Override
public Bundle addingBundle(Bundle bundle, BundleEvent event) {
register(bundle);
return bundle;
}
@Override
public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) {
}
@Override
public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) {
unregister(bundle.getBundleId());
}
public void register(String id, Callable<Class> factory) {
_lock.writeLock().lock();
try {
if (_factories == null) {
_factories = new HashMap<String, List<Callable<Class>>>();
}
List<Callable<Class>> l = _factories.get(id);
if (l == null) {
l = new ArrayList<Callable<Class>>();
_factories.put(id, l);
}
l.add(0, factory);
synchronized (_lock) {
_lock.notifyAll();
}
} finally {
_lock.writeLock().unlock();
}
}
public void unregister(String id, Callable<Class> factory) {
_lock.writeLock().lock();
try {
if (_factories != null) {
List<Callable<Class>> l = _factories.get(id);
if (l != null) {
l.remove(factory);
}
}
} finally {
_lock.writeLock().unlock();
}
}
public <T> Class<? extends T> locate(Class<T> factoryId) {
return locate(factoryId, factoryId.getName());
}
private static long getTimeout() {
long timeout = DEFAULT_TIMEOUT;
try {
String prop = System.getProperty(TIMEOUT);
if (prop != null) {
timeout = Long.parseLong(prop);
}
} catch (Throwable t) { }
return timeout;
}
public <T> Class<? extends T> locate(Class<T> factoryClass, String factoryId) {
long timeout = getTimeout();
if (timeout <= 0) {
return doLocate(factoryClass, factoryId);
}
long t0 = System.currentTimeMillis();
long t1 = t0;
while (t1 - t0 < timeout) {
Class<? extends T> impl = doLocate(factoryClass, factoryId);
if (impl != null) {
return impl;
}
synchronized (_lock) {
try {
_lock.wait(timeout - (t1 - t0));
} catch (InterruptedException e) {
return null;
}
}
t1 = System.currentTimeMillis();
}
return null;
}
private <T> Class<? extends T> doLocate(Class<T> factoryClass, String factoryId) {
_lock.readLock().lock();
try {
if (_factories != null) {
List<Callable<Class>> l = _factories.get(factoryId);
if (l != null && !l.isEmpty()) {
// look up the System property first
String factoryClassName = System.getProperty(factoryId);
try {
for (Callable<Class> i : l) {
Class c = null;
try {
c = i.call();
} catch (Exception ex) {
// do nothing here
}
if (c != null && factoryClass == c.getClassLoader().loadClass(factoryClass.getName())
&& (factoryClassName == null || c.getName().equals(factoryClassName)))
{
return c;
}
}
} catch (Exception ex) {
// do nothing here
}
}
}
return null;
} finally {
_lock.readLock().unlock();
}
}
public <T> List<Class<? extends T>> locateAll(Class<T> factoryId) {
return locateAll(factoryId, factoryId.getName());
}
public <T> List<Class<? extends T>> locateAll(Class<T> factoryClass, String factoryId) {
_lock.readLock().lock();
try {
List<Class<? extends T>> classes = new ArrayList<Class<? extends T>>();
if (_factories != null) {
List<Callable<Class>> l = _factories.get(factoryId);
if (l != null) {
for (Callable<Class> i : l) {
try {
Class c = i.call();
if (c != null && factoryClass.isAssignableFrom(c)) {
classes.add(c);
}
} catch (Exception e) {
}
}
}
}
return classes;
} finally {
_lock.readLock().unlock();
}
}
protected void register(final Bundle bundle) {
Map<String, Callable<Class>> map = allFactories.get(bundle.getBundleId());
Enumeration<URL> e = bundle.findEntries("META-INF/services/", "*", false);
if (e != null) {
while (e.hasMoreElements()) {
final URL u = e.nextElement();
final String url = u.toString();
if (url.endsWith("/")) {
continue;
}
final String factoryId = url.substring(url.lastIndexOf("/") + 1);
if (map == null) {
map = new HashMap<String, Callable<Class>>();
allFactories.put(bundle.getBundleId(), map);
}
map.put(factoryId, new BundleFactoryLoader(factoryId, u, bundle));
}
}
if (map != null) {
for (Map.Entry<String, Callable<Class>> entry : map.entrySet()) {
register(entry.getKey(), entry.getValue());
}
}
}
protected void unregister(long bundleId) {
Map<String, Callable<Class>> map = allFactories.remove(bundleId);
if (map != null) {
for (Map.Entry<String, Callable<Class>> entry : map.entrySet()) {
unregister(entry.getKey(), entry.getValue());
}
}
}
private class BundleFactoryLoader implements Callable<Class> {
private final String factoryId;
private final URL u;
private final Bundle bundle;
private volatile Class<?> clazz;
public BundleFactoryLoader(String factoryId, URL u, Bundle bundle) {
this.factoryId = factoryId;
this.u = u;
this.bundle = bundle;
}
public Class call() throws Exception {
try {
if (clazz == null){
synchronized (this) {
if (clazz == null){
BufferedReader br = new BufferedReader(new InputStreamReader(u.openStream(), "UTF-8"));
try {
String factoryClassName = br.readLine();
while (factoryClassName != null) {
factoryClassName = factoryClassName.trim();
if (factoryClassName.charAt(0) != '#') {
clazz = bundle.loadClass(factoryClassName);
return clazz;
}
factoryClassName = br.readLine();
}
} finally {
br.close();
}
}
}
}
return clazz;
} catch (Exception e) {
throw e;
} catch (Error e) {
throw e;
}
}
@Override
public String toString() {
return u.toString();
}
@Override
public int hashCode() {
return u.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BundleFactoryLoader) {
return u.equals(((BundleFactoryLoader) obj).u);
} else {
return false;
}
}
}
}