/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package com.bc.ceres.core.runtime.internal;
import com.bc.ceres.core.CoreException;
import com.bc.ceres.core.ExtensionFactory;
import com.bc.ceres.core.ExtensionManager;
import com.bc.ceres.core.ServiceRegistry;
import com.bc.ceres.core.ServiceRegistryManager;
import com.bc.ceres.core.SingleTypeExtensionFactory;
import com.bc.ceres.core.runtime.Activator;
import com.bc.ceres.core.runtime.ConfigurationElement;
import com.bc.ceres.core.runtime.Extension;
import com.bc.ceres.core.runtime.ExtensionPoint;
import com.bc.ceres.core.runtime.Module;
import com.bc.ceres.core.runtime.ModuleContext;
import com.bc.ceres.core.runtime.ModuleState;
import com.bc.ceres.core.runtime.RuntimeRunnable;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.logging.Level;
public class RuntimeActivator implements Activator {
private static RuntimeActivator instance;
private Map<String, RuntimeRunnable> applications;
private List<ServiceRegistration> serviceRegistrations;
private ModuleContext moduleContext;
private ClassLoader resourcesClassLoader;
public static RuntimeActivator getInstance() {
return instance;
}
public RuntimeActivator() {
instance = this;
}
public RuntimeRunnable getApplication(String id) {
return applications.get(id);
}
public ModuleContext getModuleContext() {
return moduleContext;
}
@Override
public void start(ModuleContext moduleContext) throws CoreException {
this.moduleContext = moduleContext;
initApplications();
initServiceProviders();
initAdapters();
}
@Override
public void stop(ModuleContext moduleContext) throws CoreException {
try {
disposeServiceProviders();
disposeApplications();
} finally {
this.moduleContext = null;
}
}
public ClassLoader getResourcesClassLoader() {
if (resourcesClassLoader == null) {
synchronized (this) {
if (resourcesClassLoader == null) {
resourcesClassLoader = createResourcesClassLoader();
}
}
}
return resourcesClassLoader;
}
private void initServiceProviders() {
ClassLoader providerLoader = getResourcesClassLoader();
serviceRegistrations = new ArrayList<>(32);
ExtensionPoint extensionPoint = moduleContext.getModule().getExtensionPoint("serviceProviders");
Extension[] extensions = extensionPoint.getExtensions();
for (Extension extension : extensions) {
ConfigurationElement[] children = extension.getConfigurationElement().getChildren("serviceProvider");
for (ConfigurationElement child : children) {
String providerClassName = child.getValue();
Module declaringModule = extension.getDeclaringModule();
if (declaringModule.getState().is(ModuleState.RESOLVED)) {
Class<?> providerClass = getProviderClass(declaringModule, providerClassName);
if (providerClass != null) {
try {
Set<ServiceRegistration> serviceRegistrationsForClass = getServiceRegistrations(providerClass, providerLoader);
for (ServiceRegistration serviceRegistration : serviceRegistrationsForClass) {
String[] providerImplClassNames = getProviderImplClassNames(serviceRegistration);
if (providerImplClassNames != null) {
for (String providerImplClassName : providerImplClassNames) {
Class<?> providerImplClass = getProviderImplClass(serviceRegistration, providerImplClassName);
if (providerImplClass != null) {
registerProviderImpl(serviceRegistration, providerImplClass);
}
}
}
}
} catch (IOException e) {
moduleContext.getLogger().log(Level.SEVERE,
String.format("Failed to load service provider [%s]",
providerClassName), e);
}
}
}
}
}
}
private ClassLoader createResourcesClassLoader() {
ArrayList<URL> urlArrayList = new ArrayList<>();
for (Module module : moduleContext.getModules()) {
if (module.getState().is(ModuleState.RESOLVED)) {
URL location = module.getLocation();
urlArrayList.add(location);
}
}
return new URLClassLoader(urlArrayList.toArray(new URL[urlArrayList.size()]), new NullClassLoader());
}
private void registerProviderImpl(ServiceRegistration serviceRegistration, Class<?> providerImplClass) {
Class<?> providerClass = serviceRegistration.serviceRegistry.getServiceType();
if (providerClass.isAssignableFrom(providerImplClass)) {
Object providerImpl = getProviderImpl(providerImplClass);
if (providerImpl != null) {
serviceRegistration.serviceRegistry.addService(providerImpl);
serviceRegistration.providerImpl = providerImpl;
moduleContext.getLogger().info(String.format("Module [%s]: Service [%s] registered",
serviceRegistration.module.getSymbolicName(),
serviceRegistration.providerImpl.getClass()));
serviceRegistrations.add(serviceRegistration);
}
} else {
moduleContext.getLogger().severe(String.format("Service [%s] is not of type [%s]",
providerImplClass.toString(),
providerClass.toString()));
}
}
private Object getProviderImpl(Class<?> providerImplClass) {
Object providerImpl = null;
try {
providerImpl = providerImplClass.newInstance();
} catch (Throwable t) {
moduleContext.getLogger().log(Level.SEVERE,
String.format("Failed to instantiate service of type [%s]",
providerImplClass.toString()), t);
}
return providerImpl;
}
private Class<?> getProviderImplClass(ServiceRegistration serviceRegistration, String providerImplClassName) {
Class<?> providerImplClass = null;
try {
providerImplClass = serviceRegistration.module.loadClass(providerImplClassName);
} catch (Throwable t) {
moduleContext.getLogger().log(Level.SEVERE,
String.format("Failed to load service type [%s]",
providerImplClassName), t);
}
return providerImplClass;
}
private String[] getProviderImplClassNames(ServiceRegistration serviceRegistration) {
String[] providerImplClassNames = null;
try {
providerImplClassNames = parseSpiConfiguration(serviceRegistration.url);
} catch (IOException e) {
moduleContext.getLogger().log(Level.SEVERE,
String.format(
"Failed to load configuration [%s] from module [%s].",
serviceRegistration.url,
serviceRegistration.module.getName()), e);
}
return providerImplClassNames;
}
private Class<?> getProviderClass(Module declaringModule, String providerClassName) {
Class<?> providerClass = null;
try {
providerClass = declaringModule.loadClass(providerClassName);
} catch (Throwable t) {
moduleContext.getLogger().log(Level.SEVERE,
String.format("Failed to load service provider [%s].",
providerClassName), t);
}
return providerClass;
}
private Set<ServiceRegistration> getServiceRegistrations(Class<?> providerClass, ClassLoader providerLoader) throws IOException {
ServiceRegistry serviceRegistry = ServiceRegistryManager.getInstance().getServiceRegistry(providerClass);
HashSet<ServiceRegistration> serviceRegistrationsForClass = new HashSet<ServiceRegistration>(10);
String resourcePath = "META-INF/services/" + providerClass.getName();
Enumeration<URL> resources = providerLoader.getResources(resourcePath);
if (resources != null) {
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
Module module = getModule(url);
if (module != null) {
ServiceRegistration serviceRegistration = new ServiceRegistration(url, module, serviceRegistry);
if (!serviceRegistrationsForClass.contains(serviceRegistration)) {
serviceRegistrationsForClass.add(serviceRegistration);
} else {
moduleContext.getLogger().warning(String.format("Service already registered: [%s].", serviceRegistration));
}
} else {
moduleContext.getLogger().warning("Module not found for service provider URL " + url);
}
}
}
return serviceRegistrationsForClass;
}
private Module getModule(URL url) {
String urlString = url.toExternalForm();
Module module = getModule(urlString);
if (module == null && urlString.startsWith("jar:")) {
urlString = urlString.substring(4);
module = getModule(urlString);
}
return module;
}
private Module getModule(String urlAsString) {
for (Module module : moduleContext.getModules()) {
if (urlAsString.startsWith(module.getLocation().toExternalForm())) {
return module;
}
}
return null;
}
private static class ServiceRegistration {
final URL url;
final Module module;
final ServiceRegistry serviceRegistry;
Object providerImpl;
public ServiceRegistration(URL url, Module module, ServiceRegistry serviceRegistry) {
this.url = url;
this.module = module;
this.serviceRegistry = serviceRegistry;
}
@Override
public int hashCode() {
return url.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return url.equals(((ServiceRegistration) obj).url);
}
@Override
public String toString() {
return url.toString();
}
}
private void disposeServiceProviders() {
for (ServiceRegistration serviceRegistration : serviceRegistrations) {
ServiceRegistry serviceRegistry = serviceRegistration.serviceRegistry;
Object providerImpl = serviceRegistration.providerImpl;
serviceRegistry.removeService(providerImpl);
moduleContext.getLogger().info(String.format("Module [%s]: Service [%s] unregistered",
serviceRegistration.module.getSymbolicName(),
serviceRegistration.providerImpl.getClass()));
}
serviceRegistrations.clear();
resourcesClassLoader = null;
}
private void initApplications() {
applications = new HashMap<>(3);
ExtensionPoint extensionPoint = moduleContext.getModule().getExtensionPoint("applications");
Extension[] extensions = extensionPoint.getExtensions();
for (int i = 0; i < extensions.length; i++) {
Extension extension = extensions[i];
ConfigurationElement[] children = extension.getConfigurationElement().getChildren("application");
for (ConfigurationElement child : children) {
String appId = child.getAttribute("id");
if (isNullOrEmpty(appId)) {
moduleContext.getLogger().severe(
"Missing identifier for extension " + i + " of extension point [applications].");
continue;
} else if (applications.containsKey(appId)) {
moduleContext.getLogger().warning(
"Identifier [" + appId + "] is already in use within extension point [applications].");
}
RuntimeRunnable application = null;
try {
// Run client code
application = child.createExecutableExtension(RuntimeRunnable.class);
} catch (Throwable e) {
Module declaringModule = extension.getDeclaringModule();
String msg = String.format("Failed to register application [%s] (declared by module [%s]).",
appId, declaringModule.getSymbolicName());
moduleContext.getLogger().log(Level.SEVERE, msg, e);
}
if (application != null) {
applications.put(appId, application);
Module declaringModule = extension.getDeclaringModule();
String msg = String.format("Module [%s]: Application [%s] registered using instance of [%s].",
declaringModule.getSymbolicName(), appId, application.getClass());
moduleContext.getLogger().info(msg);
}
}
}
}
private void disposeApplications() {
applications.clear();
applications = null;
}
private void initAdapters() {
ExtensionPoint extensionPoint = moduleContext.getModule().getExtensionPoint("adapters");
Extension[] extensions = extensionPoint.getExtensions();
for (Extension extension : extensions) {
ConfigurationElement[] children = extension.getConfigurationElement().getChildren("adapter");
for (ConfigurationElement child : children) {
try {
registerAdapter(extension, child);
} catch (Exception e) {
moduleContext.getLogger().log(Level.SEVERE,
String.format("Module [%s]: Failed to register an [adapter] extension of point [adapters].",
extension.getDeclaringModule().getSymbolicName()), e);
}
}
}
}
private void registerAdapter(Extension extension, ConfigurationElement child) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Module declaringModule = extension.getDeclaringModule();
String extensibleTypeName = getChildValue(child, "extensibleType");
String extensionFactoryName = getChildValue(child, "extensionFactory");
String extensionTypeName = getChildValue(child, "extensionType");
String extensionSubTypeName = getChildValue(child, "extensionSubType");
if (!isNullOrEmpty(extensibleTypeName)) {
if (!isNullOrEmpty(extensionFactoryName)) {
Class<?> extensibleType = declaringModule.loadClass(extensibleTypeName);
ExtensionFactory extensionFactory = (ExtensionFactory) declaringModule.loadClass(extensionFactoryName).newInstance();
registerAdapter(declaringModule, extensibleType, extensionFactory);
} else if (!isNullOrEmpty(extensionTypeName)) {
if (!isNullOrEmpty(extensionSubTypeName)) {
Class<?> extensibleType = declaringModule.loadClass(extensibleTypeName);
Class<?> extensionType = declaringModule.loadClass(extensionTypeName);
Class<?> extensionSubType = declaringModule.loadClass(extensionSubTypeName);
registerAdapter(declaringModule, extensibleType, new SingleTypeExtensionFactory(extensionType, extensionSubType));
} else {
Class<?> extensibleType = declaringModule.loadClass(extensibleTypeName);
Class<?> extensionType = declaringModule.loadClass(extensionTypeName);
registerAdapter(declaringModule, extensibleType, new SingleTypeExtensionFactory(extensionType));
}
} else {
moduleContext.getLogger().severe(
String.format("Module [%s]: Missing either element 'extensionFactory' or 'extensionType' in an extension of point [adapters].", declaringModule.getSymbolicName()));
}
} else {
moduleContext.getLogger().severe(
String.format("Module [%s]: Missing element 'extensibleType' in an extension of point [adapters].", declaringModule.getSymbolicName()));
}
}
private void registerAdapter(Module declaringModule, Class<?> extensibleType, ExtensionFactory extensionFactory) {
ExtensionManager.getInstance().register(extensibleType, extensionFactory);
moduleContext.getLogger().info(String.format("Module [%s]: Adapter registered: A [%s] can now be extended to %s",
declaringModule.getSymbolicName(),
extensibleType,
Arrays.toString(extensionFactory.getExtensionTypes())));
}
private String getChildValue(ConfigurationElement child, String elementName) {
ConfigurationElement element = child.getChild(elementName);
return element != null ? element.getValue() : null;
}
private boolean isNullOrEmpty(String extendibleTypeName) {
return extendibleTypeName == null || extendibleTypeName.trim().length() == 0;
}
public static String[] parseSpiConfiguration(URL resource) throws IOException {
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.openStream()))) {
ArrayList<String> classNames = new ArrayList<String>(3);
while (true) {
String s = bufferedReader.readLine();
if (s == null) {
break;
}
int i = s.indexOf('#');
if (i >= 0) {
s = s.substring(0, i);
}
s = s.trim();
if (s.length() > 0) {
classNames.add(s);
}
}
return classNames.toArray(new String[classNames.size()]);
}
}
private static class NullClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
@Override
protected URL findResource(String name) {
return null;
}
@Override
protected String findLibrary(String libname) {
return null;
}
@Override
protected Enumeration<URL> findResources(String name) throws IOException {
return new Vector<URL>().elements();
}
}
}