io.neba.core.selftests.SelftestRegistrar Maven / Gradle / Ivy
/**
* 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.");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy