io.neba.core.selftests.SelftestRegistrar Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of io.neba.neba-core Show documentation
Show all versions of io.neba.neba-core Show documentation
Contains the entire NEBA core implementation, i.e. the framework that interprets the
NEBA API annotations and provides implementations for the service and lifecycle callback
interfaces provided in the NEBA API. This package must not export anything as
its implementation details are entirely private.
/**
* Copyright 2013 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 io.neba.core.selftests;
import io.neba.api.annotations.SelfTest;
import io.neba.core.blueprint.EventhandlingBarrier;
import org.eclipse.gemini.blueprint.service.importer.ImportedOsgiServiceProxy;
import org.osgi.framework.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static io.neba.core.util.BundleUtil.displayNameOf;
import static org.springframework.beans.factory.BeanFactoryUtils.isFactoryDereference;
/**
* Detects beans that have methods annotated with {@link SelfTest}.
* Considers all beans defined in an application context unless they are OSGi
* references, i.e. from a foreign bundle.
*
* @author Olaf Otto
*/
@Service
public class SelftestRegistrar {
private static final long EVERY_30_SECONDS = 30 * 1000;
private final Collection selftestReferences = new LinkedHashSet();
private final String selftestAnnotationName = SelfTest.class.getName();
private final Logger logger = LoggerFactory.getLogger(getClass());
public void registerSelftests(ConfigurableListableBeanFactory factory, Bundle bundle) {
String[] beanNames = BeanFactoryUtils.beanNamesIncludingAncestors(factory);
for (String beanName : beanNames) {
if (factory.containsBeanDefinition(beanName) && !isInternal(beanName)) {
findSelftests(factory, beanName, bundle);
}
}
}
public List getSelftestReferences() {
return new ArrayList(this.selftestReferences);
}
@Scheduled(fixedRate = EVERY_30_SECONDS)
public void removeInvalidReferences() {
if (EventhandlingBarrier.tryBegin()) {
try {
this.logger.debug("Checking for references to beans from inactive bundles...");
for (Iterator it = this.selftestReferences.iterator(); it.hasNext(); ) {
final SelftestReference reference = it.next();
if (!reference.isValid()) {
this.logger.info("Reference to " + reference + " is invalid, removing.");
it.remove();
}
}
this.logger.debug("Completed checking for references to beans from inactive bundles.");
} finally {
EventhandlingBarrier.end();
}
}
}
private void findSelftests(final ConfigurableListableBeanFactory factory, String beanName, Bundle bundle) {
BeanDefinition definition = factory.getBeanDefinition(beanName);
if (isOsgiServiceReference(factory, beanName)) {
this.logger.info("Skipping bean " + beanName + " from bundle " + displayNameOf(bundle) + ", it is an osgi service reference.");
} else if (definition instanceof AnnotatedBeanDefinition) {
findSelftestUsingBeanDefinition(factory, beanName, bundle, definition);
} else {
findSelftestUsingReflection(factory, beanName, bundle);
}
}
/**
* A bean may be the representation of an OSGi service provided by a
* different bundle - in this case we must not check it for selftests, as
* this is done in the service's source bundle.
*/
private boolean isOsgiServiceReference(BeanFactory factory, String beanName) {
Class> beanType = factory.getType(beanName);
return beanType != null && ImportedOsgiServiceProxy.class.isAssignableFrom(beanType);
}
/**
* If a bean was detected by classpath scanning, i.e. is annotated (e.g.
* with {@link org.springframework.stereotype.Component}), the bean
* definition already contains metadata for all bean annotations. It is thus
* more efficient to use this metadata than using reflection.
*/
private void findSelftestUsingBeanDefinition(ConfigurableListableBeanFactory factory,
String beanName, Bundle bundle, BeanDefinition definition) {
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) definition;
AnnotationMetadata metadata = annotatedBeanDefinition.getMetadata();
if (isSelftestingBean(metadata)) {
for (MethodMetadata selftestMethodMetadata : getSelfTestMethods(metadata)) {
this.selftestReferences.add(new SelftestReference(factory, beanName, selftestMethodMetadata, bundle));
}
}
}
/**
* In case no annotation metadata exists, find selftests by checking all
* methods for the {@link SelfTest} annotation.
*/
private void findSelftestUsingReflection(final ConfigurableListableBeanFactory factory, final String beanName, final Bundle bundle) {
Class> beanType = factory.getType(beanName);
if (beanType != null) {
beanType = unproxy(beanType);
ReflectionUtils.doWithMethods(beanType, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
SelfTest selfTest = AnnotationUtils.findAnnotation(method, SelfTest.class);
if (selfTest != null) {
String methodName = method.getName();
selftestReferences.add(new SelftestReference(factory, beanName, selfTest, methodName, bundle));
}
}
});
}
}
/**
* Certain prefixes mark bean definitions as "internal", i.e. definitions of
* factory-internal service beans or automatically generated infrastructure
* bean definitions.
*/
private boolean isInternal(String beanName) {
return isFactoryDereference(beanName) || beanName.startsWith("scopedTarget.");
}
/**
* Since proxies may implement a type's signature but not include a type's
* annotations, we need to unproxy types before scanning for annotations.
*/
private Class> unproxy(Class> beanType) {
Class> unproxiedType = beanType;
if (ClassUtils.isCglibProxyClass(beanType)) {
// It is a dynamic subclass re-implementing the same methods.
unproxiedType = beanType.getSuperclass();
}
return unproxiedType;
}
private Set getSelfTestMethods(AnnotationMetadata metadata) {
return metadata.getAnnotatedMethods(this.selftestAnnotationName);
}
private boolean isSelftestingBean(AnnotationMetadata metadata) {
return metadata.hasAnnotatedMethods(this.selftestAnnotationName);
}
public void unregister(Bundle bundle) {
removeSelftests(bundle);
}
private synchronized void removeSelftests(Bundle bundle) {
this.logger.info("Removing bundle " + displayNameOf(bundle) + " from the selftest registry...");
Iterator i = this.selftestReferences.iterator();
while (i.hasNext()) {
if (i.next().getBundleId() == bundle.getBundleId()) {
i.remove();
}
}
this.logger.info("Bundle " + displayNameOf(bundle) + " was removed from the selftest registry.");
}
}