/*
* Copyright 2011 Harald Wellmann.
*
* 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.ops4j.pax.exam.inject.internal;
import static org.ops4j.pax.exam.Constants.EXAM_SERVICE_TIMEOUT_DEFAULT;
import static org.ops4j.pax.exam.Constants.EXAM_SERVICE_TIMEOUT_KEY;
import java.lang.reflect.Field;
import javax.inject.Inject;
import org.ops4j.pax.exam.TestContainerException;
import org.ops4j.pax.exam.util.Filter;
import org.ops4j.pax.exam.util.Injector;
import org.ops4j.pax.swissbox.tracker.ServiceLookup;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleReference;
/**
* Injects services into all fields of the given class annotated with {@link Inject}. This includes
* the fields of all superclasses. By default, the Injector will wait for a given timeout if no
* matching service is available.
* <p>
* If the field has no {@link Filter} annotation, the service will be looked up by type with a
* filter {@code (objectClass=<type>)}. If the field has {@link Filter} annotation with a non empty
* value, this partial filter string will be taken to build a composite filter of the form
* {@code (&(objectClass=<type>)<filter>)}.
* <p>
* If there is more than one matching service, the first one obtained from the service registry will
* be injected.
* <p>
* Fields of type {@link BundleContext} will be injected with the BundleContext passed to this
* Injector.
* <p>
* Constructor, setter or parameter injection is not supported.
*
* @author Harald Wellmann
* @since 2.3.0, August 2011
*/
public class ServiceInjector implements Injector {
public void injectFields(Object target) {
Class<?> targetClass = target.getClass();
while (targetClass != Object.class) {
injectDeclaredFields(target, targetClass);
targetClass = targetClass.getSuperclass();
}
}
private void injectDeclaredFields(Object target, Class<?> targetClass) {
for (Field field : targetClass.getDeclaredFields()) {
if (field.getAnnotation(Inject.class) != null) {
injectField(target, targetClass, field);
}
}
}
private void injectField(Object target, Class<?> targetClass, Field field) {
Class<?> type = field.getType();
String filterString = "";
String timeoutProp = System.getProperty(EXAM_SERVICE_TIMEOUT_KEY,
EXAM_SERVICE_TIMEOUT_DEFAULT);
long timeout = Integer.parseInt(timeoutProp);
Filter filter = field.getAnnotation(Filter.class);
if (filter != null) {
filterString = filter.value();
timeout = filter.timeout();
}
// Retrieve bundle Context just before calling getService to avoid that the bundle restarts
// in between
BundleContext bc = getBundleContext(targetClass, timeout);
Object service = (BundleContext.class == type) ? bc : ServiceLookup.getService(bc, type,
timeout, filterString);
setField(target, field, service);
}
private void setField(Object target, Field field, Object service) {
try {
if (field.isAccessible()) {
field.set(target, service);
}
else {
field.setAccessible(true);
try {
field.set(target, service);
}
finally {
field.setAccessible(false);
}
}
}
catch (IllegalAccessException exc) {
throw new RuntimeException(exc);
}
}
private BundleContext getBundleContext(Class<?> klass, long timeout) {
try {
BundleReference bundleRef = BundleReference.class.cast(klass.getClassLoader());
Bundle bundle = bundleRef.getBundle();
return getBundleContext(bundle, timeout);
}
catch (ClassCastException exc) {
throw new TestContainerException("class " + klass.getName()
+ " is not loaded from an OSGi bundle");
}
}
/**
* Retrieve bundle context from given bundle. If the bundle is being restarted the bundle
* context can be null for some time
*
* @param bundle
* @param timeout TODO
* @return bundleContext or exception if bundleContext is null after timeout
*/
private BundleContext getBundleContext(Bundle bundle, long timeout) {
long endTime = System.currentTimeMillis() + timeout;
BundleContext bc = null;
while (bc == null) {
bc = bundle.getBundleContext();
if (bc == null) {
if (System.currentTimeMillis() >= endTime) {
throw new TestContainerException(
"Unable to retrieve bundle context from bundle " + bundle);
}
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
// Ignore
}
}
}
return bc;
}
}