/*******************************************************************************
* Copyright (c) 2011, 2014 Wind River Systems, Inc. and others. All rights reserved.
* This program and the accompanying materials are made available under the terms
* of the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.runtime.services;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.expressions.EvaluationContext;
import org.eclipse.core.expressions.EvaluationResult;
import org.eclipse.core.expressions.Expression;
import org.eclipse.core.expressions.ExpressionConverter;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tcf.te.runtime.nls.Messages;
import org.eclipse.tcf.te.runtime.services.activator.CoreBundleActivator;
import org.eclipse.tcf.te.runtime.services.interfaces.IService;
/**
* Abstract service manager implementation.
*/
public abstract class AbstractServiceManager {
// Map for all contributed services stored by their respective unique id
private Map<String, ServiceProxy> services = new HashMap<String, ServiceProxy>();
/**
* Proxy to provide lazy loading of contributing plug-ins.
*/
protected class ServiceProxy implements IExecutableExtension {
// Reference to the configuration element
private IConfigurationElement configElement = null;
// The id of the service contribution
public String id;
// The class implementing the service
public String clazz;
// The service instance
private IService service = null;
// The list of service types the service is implementing
private List<Class<? extends IService>> serviceTypes = new ArrayList<Class<? extends IService>>();
// The converted expression
private Expression expression;
/**
* Constructor.
*/
protected ServiceProxy() {
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, java.lang.String, java.lang.Object)
*/
@Override
public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException {
Assert.isNotNull(config);
this.configElement = config;
// Initialize the id field by reading the <id> extension attribute.
// Throws an exception if the id is empty or null.
id = config.getAttribute("id"); //$NON-NLS-1$
if (id == null || "".equals(id.trim())) { //$NON-NLS-1$
throw new CoreException(new Status(IStatus.ERROR,
CoreBundleActivator.getUniqueIdentifier(),
NLS.bind(Messages.Extension_error_missingRequiredAttribute, "id", config.getContributor().getName()))); //$NON-NLS-1$
}
// Read the class attribute. If null, check for the class sub element
clazz = config.getAttribute("class"); //$NON-NLS-1$
if (clazz == null) {
IConfigurationElement[] children = config.getChildren("class"); //$NON-NLS-1$
// Single element definition assumed (see extension point schema)
if (children.length > 0) {
clazz = children[0].getAttribute("class"); //$NON-NLS-1$
}
}
if (clazz == null || "".equals(clazz.trim())) { //$NON-NLS-1$
throw new CoreException(new Status(IStatus.ERROR,
CoreBundleActivator.getUniqueIdentifier(),
NLS.bind(Messages.Extension_error_missingRequiredAttribute, "class", config.getContributor().getName()))); //$NON-NLS-1$
}
// Read the "enablement" sub element of the extension
IConfigurationElement[] children = configElement.getChildren("enablement"); //$NON-NLS-1$
// Only one "enablement" element is expected
if (children != null && children.length > 0) {
expression = ExpressionConverter.getDefault().perform(children[0]);
}
}
/**
* Add a type to the proxy. Types are used unless the proxy is instantiated to provide lazy
* loading of services. After instantiated, a service will be identified only by its type
* and implementing or extending interfaces or super-types.
*
* @param serviceType The type to add.
*/
public void addType(Class<? extends IService> serviceType) {
Assert.isNotNull(serviceType);
if (service == null && serviceTypes != null && !serviceTypes.contains(serviceType)) {
serviceTypes.add(serviceType);
}
}
/**
* Return the real service instance for this proxy.
*/
protected IService getService(boolean unique) {
if ((service == null || unique) && configElement != null) {
try {
// Create the service class instance via the configuration element
Object service = configElement.createExecutableExtension("class"); //$NON-NLS-1$
if (service instanceof IService) {
if (unique) {
return (IService)service;
}
this.service = (IService)service;
}
else {
IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), "Service '" + service.getClass().getName() + "' not of type IService."); //$NON-NLS-1$ //$NON-NLS-2$
Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
}
}
catch (CoreException e) {
IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), "Cannot create service '" + clazz + "'.", e); //$NON-NLS-1$ //$NON-NLS-2$
Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
}
if (serviceTypes != null) {
serviceTypes.clear();
}
serviceTypes = null;
}
return service;
}
/**
* Check whether this proxy holds a service that is suitable for the given type.
*
* @param serviceType The service type. Must not be <code>null</code>.
* @return <code>True</code> if the proxy holds a suitable service, <code>false</code> otherwise.
*/
protected boolean isMatching(Class<? extends IService> serviceType) {
Assert.isNotNull(serviceType);
if (service != null) {
return serviceType.isInstance(service);
}
else if (configElement != null) {
if (serviceType.getClass().getName().equals(clazz)) {
return true;
}
if (serviceTypes != null) {
for (Class<? extends IService> type : serviceTypes) {
if (type.equals(serviceType)) {
return true;
}
}
}
}
return false;
}
/**
* Check whether this proxy holds a service that is suitable for the given type.
*
* @param serviceTypeName The service type name. Must not be <code>null</code>.
* @return <code>True</code> if the proxy holds a suitable service, <code>false</code> otherwise.
*/
protected boolean isMatching(String serviceTypeName) {
Assert.isNotNull(serviceTypeName);
if (service != null) {
Class<?> clazz = service.getClass();
while (clazz != null) {
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> interfaze : interfaces) {
if (serviceTypeName.equals(interfaze.getName())) {
return true;
}
}
clazz = clazz.getSuperclass();
}
}
else if (configElement != null) {
if (serviceTypeName.equals(clazz)) {
return true;
}
for (Class<? extends IService> type : serviceTypes) {
if (serviceTypeName.equals(type.getName())) {
return true;
}
}
}
return false;
}
/**
* Returns if or if not the service contribution is enabled for the given service context.
* <p>
* If the given service context is <code>null</code>, only globally unbound services are
* enabled.
*
* @param context The service context or <code>null</code>.
* @return <code>True</code> if the service contribution is enabled for the given service
* context, <code>false</code> otherwise.
*/
protected boolean isEnabled(Object context) {
if (context == null) {
return getEnablement() == null;
}
Expression enablement = getEnablement();
// The service contribution is enabled by default if no expression is specified.
boolean enabled = enablement == null;
if (enablement != null) {
// Set the default variable to the service context.
EvaluationContext evalContext = new EvaluationContext(null, context);
// Allow plugin activation
evalContext.setAllowPluginActivation(true);
// Evaluate the expression
try {
enabled = enablement.evaluate(evalContext).equals(EvaluationResult.TRUE);
} catch (CoreException e) {
IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), e.getLocalizedMessage(), e);
Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
}
}
return enabled;
}
/**
* Returns the id of the service contribution.
*
* @return The service contribution id.
*/
protected String getId() {
return id;
}
/**
* Returns the enablement expression.
*
* @return The enablement expression or <code>null</code>.
*/
protected Expression getEnablement() {
return expression;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof ServiceProxy) {
return id != null ? id.equals(((ServiceProxy)obj).id) : ((ServiceProxy)obj).id == null;
}
return super.equals(obj);
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return id != null ? id.hashCode() : super.hashCode();
}
public boolean equals(IService service) {
Assert.isNotNull(service);
return clazz != null ? clazz.equals(service.getClass().getCanonicalName()) : false;
}
public boolean equals(ServiceProxy proxy) {
Assert.isNotNull(proxy);
return clazz != null ? clazz.equals(proxy.clazz) : false;
}
}
/**
* Constructor.
*/
protected AbstractServiceManager() {
loadServices();
}
/**
* Creates a new service proxy instance and initialize it.
*
* @param config The configuration element. Must not be <code>null</code>.
* @return The new service proxy instance.
*/
protected ServiceProxy getServiceProxy(IConfigurationElement config) {
Assert.isNotNull(config);
ServiceProxy proxy = new ServiceProxy();
try {
proxy.setInitializationData(config, null, null);
} catch (CoreException e) {
if (Platform.inDebugMode()) {
Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(e.getStatus());
}
}
return proxy;
}
/**
* Get a service for the given service context that implements at least the needed service type.
* <p>
* If an interface type is given, the service with the highest implementation is returned. This
* may result in a random selection depending on the extension registration order, especially
* when a service interface is implemented two times in different hierarchy paths. If a class
* type is given, if available, the service of exactly that class is returned. Otherwise the
* highest implementation is returned.
*
* @param context The service context or <code>null</code>.
* @param serviceType The service type the service should at least implement or extend.
*
* @return The service or <code>null</code>.
*/
public <V extends IService> V getService(Object context, Class<? extends V> serviceType) {
return getService(context, serviceType, false);
}
/**
* Get a service for the given service context that implements at least the needed service type.
* <p>
* If an interface type is given, the service with the highest implementation is returned. This
* may result in a random selection depending on the extension registration order, especially
* when a service interface is implemented two times in different hierarchy paths. If a class
* type is given, if available, the service of exactly that class is returned. Otherwise the
* highest implementation is returned.
*
* @param context The service context or <code>null</code>.
* @param serviceType The service type the service should at least implement or extend.
* @param unique <code>true</code> if a new instance of the service is needed.
*
* @return The service or <code>null</code>.
*/
@SuppressWarnings("unchecked")
public <V extends IService> V getService(Object context, Class<? extends V> serviceType, boolean unique) {
Assert.isNotNull(serviceType);
Collection<ServiceProxy> proxies = services.values();
if (!proxies.isEmpty()) {
List<ServiceProxy> candidates = new ArrayList<ServiceProxy>();
boolean isInterface = serviceType.isInterface();
for (ServiceProxy proxy : proxies) {
if (proxy.isMatching(serviceType) && proxy.isEnabled(context)) {
if (!isInterface) {
V service = (V)proxy.getService(unique);
service.setId(proxy.getId());
return service;
}
candidates.add(proxy);
}
}
V service = null;
if (!candidates.isEmpty()) {
service = (V)candidates.get(0).getService(unique);
service.setId(candidates.get(0).getId());
}
return service;
}
return null;
}
/**
* Get the service of the given type and the given id.
*
* @param id The service id or <code>null</code>.
* @param serviceType The service type the service should at least implement or extend.
* @param unique <code>true</code> if a new instance of the service is needed.
*
* @return The service or <code>null</code>.
*/
@SuppressWarnings("unchecked")
public <V extends IService> V getService(String id, Class<? extends V> serviceType, boolean unique) {
Assert.isNotNull(id);
Assert.isNotNull(serviceType);
Collection<ServiceProxy> proxies = services.values();
if (!proxies.isEmpty()) {
List<ServiceProxy> candidates = new ArrayList<ServiceProxy>();
boolean isInterface = serviceType.isInterface();
for (ServiceProxy proxy : proxies) {
if (proxy.isMatching(serviceType) && id.equals(proxy.getId())) {
if (!isInterface) {
V service = (V)proxy.getService(unique);
service.setId(proxy.getId());
return service;
}
candidates.add(proxy);
}
}
V service = null;
if (!candidates.isEmpty()) {
service = (V)candidates.get(0).getService(unique);
service.setId(candidates.get(0).getId());
}
return service;
}
return null;
}
/**
* Get a service list for the given service context that implements at least the needed service type.
*
* @param context The service context or <code>null</code>.
* @param serviceType The service type the service should at least implement or extend.
* @param unique <code>true</code> if a new instance of the service is needed.
*
* @return The service list or empty list.
*/
public IService[] getServices(Object context, Class<? extends IService> serviceType, boolean unique) {
Assert.isNotNull(serviceType);
Collection<ServiceProxy> proxies = services.values();
List<IService> services = new ArrayList<IService>();
if (!proxies.isEmpty()) {
List<ServiceProxy> candidates = new ArrayList<ServiceProxy>();
for (ServiceProxy proxy : proxies) {
if (proxy.isMatching(serviceType) && proxy.isEnabled(context)) {
candidates.add(proxy);
}
}
for (ServiceProxy serviceProxy : candidates) {
IService service = serviceProxy.getService(unique);
service.setId(serviceProxy.getId());
services.add(service);
}
}
return services.toArray(new IService[services.size()]);
}
/**
* Adds the given service to the service proxy map.
*/
protected void addService(ServiceProxy proxy) {
Assert.isNotNull(services);
Assert.isNotNull(proxy);
String id = proxy.getId();
Assert.isNotNull(id);
services.put(id, proxy);
}
/**
* Returns if or if not a service contribution for the given service context, implementing the
* given service type, exist.
*
* @param context The service context or <code>null</code>.
* @param serviceTypeName The name of a service type the service should at least implement or extend.
*
* @return <code>True</code> if a matching service contribution exist, <code>false</code> otherwise.
*/
public boolean hasService(Object context, String serviceTypeName) {
Assert.isNotNull(serviceTypeName);
// Get all service contributions
Collection<ServiceProxy> proxies = services.values();
if (!proxies.isEmpty()) {
for (ServiceProxy proxy : proxies) {
if (proxy.isMatching(serviceTypeName) && proxy.isEnabled(context)) {
return true;
}
}
}
return false;
}
/**
* Loads the contributed services into proxies (lazy loading!!) and adds them to this manager;
*/
protected abstract void loadServices();
}