/**
* Licensed to the Austrian Association for Software Tool Integration (AASTI)
* under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. The AASTI 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 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.openengsb.core.ekb.modelregistry.tracker.internal;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.openengsb.core.api.Constants;
import org.openengsb.core.api.model.ModelDescription;
import org.openengsb.core.api.model.annotation.Model;
import org.openengsb.core.ekb.api.ModelGraph;
import org.openengsb.core.ekb.api.ModelRegistry;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.util.tracker.BundleTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of the model registry. It also implements a bundle tracker which checks bundles for models and
* register/unregister them.
*/
public final class ModelRegistryService extends BundleTracker implements ModelRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger(ModelRegistryService.class);
private EKBClassLoader ekbClassLoader;
private ModelGraph graphDb;
public ModelRegistryService(BundleContext context) {
super(context, Bundle.ACTIVE, null);
}
@Override
public Object addingBundle(Bundle bundle, BundleEvent event) {
Set<ModelDescription> models = scanBundleForModels(bundle);
for (ModelDescription model : models) {
registerModel(model);
LOGGER.info("Registered model: {}", model);
}
return models;
}
@Override
public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
@SuppressWarnings("unchecked")
Set<ModelDescription> models = (Set<ModelDescription>) object;
for (ModelDescription model : models) {
unregisterModel(model);
LOGGER.info("Unregistered model: {}", model);
}
}
/**
* Check all found classes of the bundle if they are models and return a set of all found model descriptions.
*/
private Set<ModelDescription> scanBundleForModels(Bundle bundle) {
Set<ModelDescription> models = new HashSet<ModelDescription>();
if (!shouldSkipBundle(bundle)) {
models = loadModelsOfBundle(bundle);
}
return models;
}
/**
* Returns true if the given bundle should be skipped in the model search process. Returns false otherwise.
*/
private boolean shouldSkipBundle(Bundle bundle) {
return bundle.getHeaders().get(Constants.PROVIDE_MODELS_HEADER) == null;
}
/**
* Searches the bundle for model classes and return a set of them.
*/
private Set<ModelDescription> loadModelsOfBundle(Bundle bundle) {
Enumeration<URL> classEntries = bundle.findEntries("/", "*.class", true);
Set<ModelDescription> models = new HashSet<ModelDescription>();
if (classEntries == null) {
LOGGER.debug("Found no classes in the bundle {}", bundle);
return models;
}
while (classEntries.hasMoreElements()) {
URL classURL = classEntries.nextElement();
String classname = extractClassName(classURL);
if (isModelClass(classname, bundle)) {
models.add(new ModelDescription(classname, bundle.getVersion().toString()));
}
}
return models;
}
/**
* Returns true if the class with the given class name contained in the given bundle is a model and false if not or
* the class couldn't be loaded.
*/
private boolean isModelClass(String classname, Bundle bundle) {
LOGGER.debug("Check if class '{}' is a model class", classname);
Class<?> clazz;
try {
clazz = bundle.loadClass(classname);
} catch (ClassNotFoundException e) {
LOGGER.warn("Bundle could not load its own class: '{}' bundle: '{}'", classname, bundle.getSymbolicName());
LOGGER.debug("Exact error: ", e);
return false;
} catch (NoClassDefFoundError e) {
// ignore since this happens if bundle have optional imports
return false;
} catch (Error e) {
// if this catch clause is reached, then the bundle which caused this error need to be checked. There
// is something wrong with the setup of the bundle (e.g. double import of a library of different versions)
LOGGER.warn("Error while loading class: '{}' in bundle: '{}'", classname, bundle.getSymbolicName());
LOGGER.debug("Exact error: ", e);
return false;
}
return clazz.isAnnotationPresent(Model.class);
}
/**
* Converts an URL to a class into a class name
*/
private String extractClassName(URL classURL) {
String path = classURL.getPath();
return path
.replaceAll("^/", "")
.replaceAll(".class$", "")
.replaceAll("\\/", ".");
}
@Override
public void registerModel(ModelDescription model) {
LOGGER.debug("Added model {} to model registry", model);
graphDb.addModel(model);
}
@Override
public void unregisterModel(ModelDescription model) {
LOGGER.debug("Removed model {} from model registry", model);
graphDb.removeModel(model);
}
@Override
public Class<?> loadModel(ModelDescription model) throws ClassNotFoundException {
return ekbClassLoader.loadModel(model);
}
@Override
public List<String> getAnnotatedFields(ModelDescription model, Class<? extends Annotation> annotationClass)
throws ClassNotFoundException {
Class<?> clazz = loadModel(model);
List<String> result = new ArrayList<String>();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(annotationClass)) {
result.add(field.getName());
}
}
return result;
}
public void setEkbClassLoader(EKBClassLoader ekbClassLoader) {
this.ekbClassLoader = ekbClassLoader;
}
public void setGraphDb(ModelGraph graphDb) {
this.graphDb = graphDb;
}
}