/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 WARRANTIESOR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.aries.jndi.url;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.OperationNotSupportedException;
import javax.naming.ServiceUnavailableException;
import org.apache.aries.util.nls.MessageUtil;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.blueprint.container.BlueprintContainer;
import org.osgi.service.blueprint.container.NoSuchComponentException;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
public class BlueprintURLContext implements Context
{
private static final String BLUEPRINT_NAMESPACE = "blueprint:comp/";
private Bundle _callersBundle;
private Map<String, Object> _env;
private NameParser _parser = new BlueprintNameParser();
private BlueprintName _parentName;
// listBindings wants a NamingEnumeration<Binding>
// list wants a NamingEnumeration<NameClassPair>
// Both are very similar. As per ServiceRegistryListContext we delegate to a closure to do the final processing
private interface ComponentProcessor<T> {
T get (Binding b);
}
private static class BlueprintComponentNamingEnumeration<T> implements NamingEnumeration<T>
{
private Binding[] blueprintIdToComponentBindings;
private int position = 0;
private ComponentProcessor<T> processor;
public BlueprintComponentNamingEnumeration (Bundle callersBundle, ComponentProcessor<T> p) throws ServiceUnavailableException
{
ServiceReference blueprintContainerRef = getBlueprintContainerRef(callersBundle);
try {
BlueprintContainer blueprintContainer = (BlueprintContainer) callersBundle.getBundleContext().getService(blueprintContainerRef);
@SuppressWarnings("unchecked")
Set<String> componentIds = blueprintContainer.getComponentIds();
blueprintIdToComponentBindings = new Binding[componentIds.size()];
Iterator<String> idIterator= componentIds.iterator();
for (int i=0; i < blueprintIdToComponentBindings.length; i++) {
String id = idIterator.next();
Object o = blueprintContainer.getComponentInstance(id);
blueprintIdToComponentBindings[i] = new Binding (id, o);
}
processor = p;
} finally {
callersBundle.getBundleContext().ungetService(blueprintContainerRef);
}
}
@Override
public boolean hasMoreElements()
{
return position < blueprintIdToComponentBindings.length;
}
@Override
public T nextElement()
{
if (!hasMoreElements()) throw new NoSuchElementException();
Binding bindingToProcess = blueprintIdToComponentBindings[position];
position++;
T result = processor.get(bindingToProcess);
return result;
}
@Override
public T next() throws NamingException
{
return nextElement();
}
@Override
public boolean hasMore() throws NamingException
{
return hasMoreElements();
}
@Override
public void close() throws NamingException
{
// Nothing to do
}
}
@SuppressWarnings("unchecked")
public BlueprintURLContext(Bundle callersBundle, Hashtable<?, ?> env)
{
_callersBundle = callersBundle;
_env = new HashMap<String, Object>();
_env.putAll((Map<? extends String, ? extends Object>) env);
_parentName = null;
}
private BlueprintURLContext (Bundle callersBundle, BlueprintName parentName, Map<String, Object> env) {
_callersBundle = callersBundle;
_parentName = parentName;
_env = env;
}
@Override
protected void finalize() throws NamingException {
close();
}
@Override
public Object addToEnvironment(String propName, Object propVal)
throws NamingException
{
return _env.put(propName, propVal);
}
@Override
public void bind(Name n, Object o) throws NamingException
{
throw new OperationNotSupportedException();
}
@Override
public void bind(String s, Object o) throws NamingException
{
throw new OperationNotSupportedException();
}
@Override
public void close() throws NamingException
{
_env = null;
}
@Override
public Name composeName(Name name, Name prefix) throws NamingException
{
String result = prefix + "/" + name;
String ns = BLUEPRINT_NAMESPACE;
if (result.startsWith(ns)) {
ns = "";
}
return _parser.parse(ns + result);
}
@Override
public String composeName(String name, String prefix) throws NamingException
{
String result = prefix + "/" + name;
String ns = BLUEPRINT_NAMESPACE;
if (result.startsWith(ns)) {
ns = "";
}
_parser.parse(ns + result);
return result;
}
@Override
public Context createSubcontext(Name n) throws NamingException
{
throw new OperationNotSupportedException();
}
@Override
public Context createSubcontext(String s) throws NamingException
{
throw new OperationNotSupportedException();
}
@Override
public void destroySubcontext(Name n) throws NamingException
{
// No-op we don't support sub-contexts in our context
}
@Override
public void destroySubcontext(String s) throws NamingException
{
// No-op we don't support sub-contexts in our context
}
@Override
public Hashtable<?, ?> getEnvironment() throws NamingException
{
Hashtable<Object, Object> environment = new Hashtable<Object, Object>();
environment.putAll(_env);
return environment;
}
@Override
public String getNameInNamespace() throws NamingException
{
throw new OperationNotSupportedException();
}
@Override
public NameParser getNameParser(Name n) throws NamingException
{
return _parser;
}
@Override
public NameParser getNameParser(String s) throws NamingException
{
return _parser;
}
@Override
public NamingEnumeration<NameClassPair> list(Name name) throws NamingException
{
return list(name.toString());
}
@Override
public NamingEnumeration<NameClassPair> list(String s) throws NamingException
{
NamingEnumeration<NameClassPair> result = new BlueprintComponentNamingEnumeration<NameClassPair>(_callersBundle, new ComponentProcessor<NameClassPair>() {
@Override
public NameClassPair get(Binding b)
{
NameClassPair result = new NameClassPair (b.getName(), b.getClassName());
return result;
}
});
return result;
}
@Override
public NamingEnumeration<Binding> listBindings(Name name) throws NamingException
{
return listBindings(name.toString());
}
@Override
public NamingEnumeration<Binding> listBindings(String name)
throws NamingException
{
NamingEnumeration<Binding> result = new BlueprintComponentNamingEnumeration<Binding>(_callersBundle, new ComponentProcessor<Binding>() {
@Override
public Binding get(Binding b)
{
return b;
}
});
return result;
}
@Override
public Object lookup(Name name) throws NamingException, ServiceUnavailableException
{
ServiceReference blueprintContainerRef = getBlueprintContainerRef(_callersBundle);
Object result;
try {
BlueprintContainer blueprintContainer = (BlueprintContainer)
_callersBundle.getBundleContext().getService(blueprintContainerRef);
BlueprintName bpName;
if (name instanceof BlueprintName) {
bpName = (BlueprintName) name;
} else if (_parentName != null) {
bpName = new BlueprintName(_parentName.toString() + "/" + name.toString());
} else {
bpName = (BlueprintName) _parser.parse(name.toString());
}
if (bpName.hasComponent()) {
String componentId = bpName.getComponentId();
try {
result = blueprintContainer.getComponentInstance(componentId);
} catch (NoSuchComponentException nsce) {
throw new NameNotFoundException(nsce.getMessage());
}
} else {
result = new BlueprintURLContext(_callersBundle, bpName, _env);
}
} finally {
_callersBundle.getBundleContext().ungetService(blueprintContainerRef);
}
return result;
}
@Override
public Object lookup(String name) throws NamingException
{
if (_parentName != null) {
name = _parentName.toString() + "/" + name;
}
Object result = lookup (_parser.parse(name));
return result;
}
@Override
public Object lookupLink(Name n) throws NamingException
{
throw new OperationNotSupportedException();
}
@Override
public Object lookupLink(String s) throws NamingException
{
throw new OperationNotSupportedException();
}
@Override
public void rebind(Name n, Object o) throws NamingException
{
throw new OperationNotSupportedException();
}
@Override
public void rebind(String s, Object o) throws NamingException
{
throw new OperationNotSupportedException();
}
@Override
public Object removeFromEnvironment(String propName) throws NamingException
{
return _env.remove(propName);
}
@Override
public void rename(Name nOld, Name nNew) throws NamingException
{
throw new OperationNotSupportedException();
}
@Override
public void rename(String sOld, String sNew) throws NamingException
{
throw new OperationNotSupportedException();
}
@Override
public void unbind(Name n) throws NamingException
{
throw new OperationNotSupportedException();
}
@Override
public void unbind(String s) throws NamingException
{
throw new OperationNotSupportedException();
}
/**
* Look for a BluepintContainer service in a given bundle
* @param b Bundle to look in
* @return BlueprintContainer service, or null if none available
*/
private static ServiceReference findBPCRef (Bundle b)
{
ServiceReference[] refs = b.getRegisteredServices();
ServiceReference result = null;
if (refs != null) {
outer: for (ServiceReference r : refs) {
String[] objectClasses = (String[]) r.getProperty(Constants.OBJECTCLASS);
for (String objectClass : objectClasses) {
if (objectClass.equals(BlueprintContainer.class.getName())) {
// Arguably we could put an r.isAssignableTo(jndi-url-bundle, BlueprintContainer.class.getName())
// check here. But if you've got multiple, class-space inconsistent instances of blueprint in
// your environment, you've almost certainly got other problems.
result = r;
break outer;
}
}
}
}
return result;
}
private static final MessageUtil MESSAGES = MessageUtil.createMessageUtil(BlueprintURLContext.class, "org.apache.aries.jndi.nls.jndiUrlMessages");
/**
* Obtain a BlueprintContainerService for the given bundle. If the service isn't there, wait for up
* to the blueprint.graceperiod defined for that bundle for one to be published.
* @param b The Bundle to look in
* @return BlueprintContainerService instance for that bundle
* @throws ServiceUnavailableException If no BlueprinContainerService found
*/
private static ServiceReference getBlueprintContainerRef(Bundle b) throws ServiceUnavailableException
{
ServiceReference result = findBPCRef(b);
if (result == null) {
Semaphore s = new Semaphore(0);
AtomicReference<ServiceReference> bpcRef = new AtomicReference<ServiceReference>();
ServiceTracker st = new ServiceTracker (b.getBundleContext(), BlueprintContainer.class.getName(),
new BlueprintContainerServiceTrackerCustomizer (b, s, bpcRef));
st.open();
// Make another check for the BlueprintContainer service in case it came up just before our tracker engaged
int graceperiod = getGracePeriod(b);
result = findBPCRef(b);
if (result == null && graceperiod >= 0) {
if (graceperiod == 0) { // Wait for an unlimited period
try {
s.acquire();
} catch (InterruptedException ix) {}
} else {
try {
s.tryAcquire(graceperiod, TimeUnit.MILLISECONDS);
} catch (InterruptedException ix) {}
}
}
result = bpcRef.get();
st.close();
}
if (result == null) {
throw new ServiceUnavailableException (MESSAGES.getMessage("no.blueprint.container", b.getSymbolicName() + '/' + b.getVersion()));
}
return result;
}
static final Pattern graceP = Pattern.compile(".*;\\s*blueprint.graceperiod\\s*:=\\s*\"?([A-Za-z]+).*");
static final Pattern timeoutP = Pattern.compile(".*;\\s*blueprint.timeout\\s*:=\\s*\"?([0-9]+).*");
/**
* Determine the blueprint.timeout set for a given bundle
* @param b The bundle to inspect
* @return -1 if blueprint.graceperiod is false, otherwise the value of blueprint.timeout,
* or 300000 if blueprint.graceperiod is true and no value is given for
* blueprint.timeout.
*/
public static int getGracePeriod (Bundle b) {
int result = 300000; // Blueprint default
boolean gracePeriodSet = true; // Blueprint default
String bundleSymbolicName = (String)b.getHeaders().get(Constants.BUNDLE_SYMBOLICNAME);
// I'd like to use ManifestHeaderProcessor here but as of December 15th 2010 it lives
// application-utils, and I don't want to make that a dependency of jndi-url
Matcher m = graceP.matcher(bundleSymbolicName);
if (m.matches()) {
String gracePeriod = m.group(1);
gracePeriodSet = !gracePeriod.equalsIgnoreCase("false"); // See OSGi Enterprise spec 4.2 section 121.3.2.1 step 6
}
if (!gracePeriodSet) {
result = -1;
} else {
m = timeoutP.matcher(bundleSymbolicName);
if (m.matches()) {
String timeout = m.group(1);
try {
result = Integer.valueOf(timeout);
} catch (NumberFormatException nfx) {
// Noop: result stays at its default value
}
}
}
return result;
}
private static class BlueprintContainerServiceTrackerCustomizer implements ServiceTrackerCustomizer
{
Bundle bundleToFindBPCServiceIn = null;
Semaphore semaphore;
AtomicReference<ServiceReference> atomicRef = null;
public BlueprintContainerServiceTrackerCustomizer (Bundle b, Semaphore s, AtomicReference<ServiceReference> aref) {
bundleToFindBPCServiceIn = b;
semaphore = s;
atomicRef = aref;
}
@Override
public Object addingService(ServiceReference reference)
{
Object result = null;
if (bundleToFindBPCServiceIn.equals(reference.getBundle())) {
atomicRef.set(reference);
semaphore.release();
result = reference.getBundle().getBundleContext().getService(reference);
}
return result;
}
@Override
public void modifiedService(ServiceReference reference, Object service) {}
@Override
public void removedService(ServiceReference reference, Object service) {}
}
}