/**
* Copyright OPS4J
*
* 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.
*
* @author nmw
* @version $Id: $Id
*/
package org.ops4j.pax.wicket.internal.injection;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import net.sf.cglib.proxy.Factory;
import org.ops4j.pax.wicket.api.PaxWicketBeanAllowNull;
import org.ops4j.pax.wicket.api.PaxWicketBeanInjectionSource;
import org.ops4j.pax.wicket.spi.OverwriteProxy;
import org.ops4j.pax.wicket.spi.ProxyTargetLocator;
import org.ops4j.pax.wicket.spi.ProxyTargetLocatorFactory;
import org.ops4j.pax.wicket.util.proxy.LazyInitProxyFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleReference;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BundleAnalysingComponentInstantiationListener extends AbstractPaxWicketInjector {
/**
*
*/
private static final ProxyTargetLocatorFactory[] EMPTY_ARRAY = new ProxyTargetLocatorFactory[0];
private static final Logger LOGGER = LoggerFactory.getLogger(BundleAnalysingComponentInstantiationListener.class);
private final BundleContext bundleContext;
private String bundleResources = "";
private final String defaultInjectionSource;
private final ServiceTracker<ProxyTargetLocatorFactory, ProxyTargetLocatorFactory> tracker;
/**
* <p>Constructor for BundleAnalysingComponentInstantiationListener.</p>
*
* @param bundleContext a {@link org.osgi.framework.BundleContext} object.
* @param defaultInjectionSource a {@link java.lang.String} object.
* @param tracker a {@link org.osgi.util.tracker.ServiceTracker} object.
*/
public BundleAnalysingComponentInstantiationListener(BundleContext bundleContext, String defaultInjectionSource,
ServiceTracker<ProxyTargetLocatorFactory, ProxyTargetLocatorFactory> tracker) {
this.bundleContext = bundleContext;
this.defaultInjectionSource = defaultInjectionSource;
this.tracker = tracker;
// TODO use ExtendedBundle instead
Enumeration<URL> entries = bundleContext.getBundle().findEntries("/", "*.class", true);
if (entries == null) {
// bundle with no .class files (see PAXWICKET-305)
return;
}
while (entries.hasMoreElements()) {
String urlRepresentation =
entries.nextElement().toExternalForm().replace("bundle://.+?/", "").replace('/', '.');
LOGGER.trace("Found entry {} in bundle {}", urlRepresentation, bundleContext.getBundle().getSymbolicName());
bundleResources += urlRepresentation;
}
}
/**
* <p>injectionPossible.</p>
*
* @param component a {@link java.lang.Class} object.
* @return a boolean.
*/
public boolean injectionPossible(Class<?> component) {
String name = component.getName();
LOGGER.debug("Try to find class {} in bundle {}", name, bundleContext.getBundle().getSymbolicName());
String searchString = name.replaceAll("\\$\\$.*", "");
searchString = searchString.replaceAll("\\$", "\\\\\\$"); // for nested and anonymous classes
if (bundleResources.matches(".*" + searchString + ".*")) {
LOGGER.trace("Found class {} in bundle {}", name, bundleContext.getBundle().getSymbolicName());
return true;
}
LOGGER.trace("Class {} not available in bundle {}", name, bundleContext.getBundle().getSymbolicName());
return false;
}
/** {@inheritDoc} */
public void inject(Object component, Class<?> toHandle) {
ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
try {
Class<?> realClass = toHandle;
Map<String, String> overwrites = null;
String injectionSource = PaxWicketBeanInjectionSource.INJECTION_SOURCE_SCAN;
if (Factory.class.isInstance(component)) {
overwrites = ((OverwriteProxy) ((Factory) component).getCallback(0)).getOverwrites();
injectionSource = ((OverwriteProxy) ((Factory) component).getCallback(0)).getInjectionSource();
realClass = realClass.getSuperclass();
} else {
injectionSource = PaxWicketBeanInjectionSource.INJECTION_SOURCE_SCAN;
}
if (injectionSource == null || injectionSource.length() > 0) {
injectionSource = defaultInjectionSource;
}
Thread.currentThread().setContextClassLoader(realClass.getClassLoader());
List<Field> fields = getSingleLevelOfFields(realClass);
for (Field field : fields) {
if (!field.isAnnotationPresent(Inject.class)) {
continue;
}
PaxWicketBeanInjectionSource annotation = field.getAnnotation(PaxWicketBeanInjectionSource.class);
if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) {
injectionSource = annotation.value();
}
Object value;
if (field.getType().equals(BundleContext.class)) {
// Is this the special BundleContext type?
value = injectBundleContext(realClass, field);
} else if (field.getType().equals(Future.class)) {
value = injectFuture(field, realClass, overwrites, injectionSource);
} else {
ProxyTargetLocator locator =
createProxyTargetLocator(field, getBeanType(field), realClass, overwrites, injectionSource,
false);
if (locator != null) {
Object proxy = LazyInitProxyFactory.createProxy(getBeanType(field),
locator);
value = proxy;
} else {
value = null;
}
}
if (value == null) {
if (field.getType().isPrimitive()) {
throw new IllegalStateException("The primitive field " + field.getName()
+ " is not allowed to be set to null");
}
if (field.getAnnotation(PaxWicketBeanAllowNull.class) == null) {
throw new IllegalStateException("The field " + field.getName()
+ " is not allowed to be set to null, but value for injection was finally a null value");
}
}
setField(component, field, value);
}
} finally {
Thread.currentThread().setContextClassLoader(currentClassLoader);
}
}
private Future<?> injectFuture(Field field, final Class<?> page,
Map<String, String> overwrites,
String injectionSource) {
Class<?> realClass = getGenericTypeArgument(field);
ProxyTargetLocator locator =
createProxyTargetLocator(field, realClass, page, overwrites, injectionSource, true);
return InjectionFuture.create(realClass, locator);
}
/**
* Takes a field and returns the type argument for this
*
* @param field a {@link java.lang.reflect.Field} object.
* @return the type of the generic parameter of that field
*/
public static Class<?> getGenericTypeArgument(Field field) {
// TODO Might this better be located under SPI in an "InjectionUtil" class or something?
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (actualTypeArguments.length == 1) {
Type type = actualTypeArguments[0];
if (type instanceof Class<?>) {
Class<?> realClass = (Class<?>) type;
return realClass;
} else {
throw new IllegalArgumentException(
"only direct class types are allowed for generic parameter on field " + field.getName()
+ " of type "
+ genericType.getClass().getName() + "<T>, but " + type
+ " was given!");
}
} else {
throw new IllegalArgumentException("only one type parameter expected for field " + field.getName()
+ " but " + actualTypeArguments.length
+ " where found!");
}
} else {
throw new IllegalArgumentException(
"The field '" + field.getName()
+ "' is not a ParameterizedType but this is required for type inferrence!");
}
}
private BundleContext injectBundleContext(Class<?> requestingClass, Field field) {
ClassLoader classLoader = requestingClass.getClassLoader();
if (classLoader instanceof BundleReference) {
BundleReference bundleReference = (BundleReference) classLoader;
Bundle bundle = bundleReference.getBundle();
return bundle.getBundleContext();
} else {
throw new IllegalStateException("the classloader does not provide a BundleReference");
}
}
private ProxyTargetLocator createProxyTargetLocator(Field field, Class<?> realFieldType, final Class<?> page,
Map<String, String> overwrites,
String injectionSource, boolean returnFutureLocators) {
ProxyTargetLocatorFactory[] factories = tracker.getServices(EMPTY_ARRAY);
if (factories.length == 0) {
// If no factories are present we will wait for 5 seconds for at least one
// TODO: Should this be configurable?
try {
factories = new ProxyTargetLocatorFactory[]{ tracker.waitForService(TimeUnit.SECONDS.toMillis(5)) };
} catch (InterruptedException e) {
// We ignore this...
}
}
List<ProxyTargetLocator> locators = new ArrayList<ProxyTargetLocator>(1);
for (ProxyTargetLocatorFactory factory : factories) {
if (factory == null) {
continue;
}
if (injectionSource == null || injectionSource.length() == 0 || injectionSource.equals(factory.getName())
|| PaxWicketBeanInjectionSource.INJECTION_SOURCE_SCAN.equals(injectionSource)) {
try {
// We consider this factory...
ProxyTargetLocator locator;
if (returnFutureLocators
&& factory instanceof ProxyTargetLocatorFactory.DelayableProxyTargetLocatorFactory) {
locator = ((ProxyTargetLocatorFactory.DelayableProxyTargetLocatorFactory) factory)
.createFutureProxyTargetLocator(bundleContext, field, realFieldType, page, overwrites);
} else {
locator =
factory.createProxyTargetLocator(bundleContext, field, page, overwrites);
}
if (locator != null) {
locators.add(locator);
}
} catch (RuntimeException e) {
LOGGER.warn("Ignored ProxyTargetLocatorFactory factory {} because of RuntimeException",
factory.getName(),
e);
}
}
}
if (locators.isEmpty()) {
if (field.getAnnotation(PaxWicketBeanAllowNull.class) != null) {
return null;
} else {
throw new IllegalStateException(
String
.format(
"No injection source found for field [%s] in class [%s] and field is not marked as null allowed, the following injectors where queried: %s",
field.getName(), page.getName(), toInjectNameString(factories)));
}
} else {
if (locators.size() > 1) {
LOGGER
.warn(
"More than one injection source could be considered for field [{}] in class [{}] to archive consistent behaviour use an explicit injection source",
field.getName(), page.getName());
}
return locators.get(0);
}
}
/**
* @param factories
* @return
*/
private Object toInjectNameString(ProxyTargetLocatorFactory[] factories) {
StringBuilder sb = new StringBuilder();
sb.append("[");
boolean first = true;
for (int i = 0; i < factories.length; i++) {
ProxyTargetLocatorFactory factory = factories[i];
if (factory != null) {
if (!first) {
sb.append(", ");
}
first = false;
sb.append(factory);
}
}
sb.append("]");
return sb;
}
}