blob: d2241fff081874d4bc9d69aae0665f9e66a9ea4b [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2002, 2007 IBM Corporation 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:
* IBM Corporation - initial API and implementation
****************************************************************************/
package org.eclipse.gmf.runtime.common.core.service;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.gmf.runtime.common.core.internal.CommonCoreDebugOptions;
import org.eclipse.gmf.runtime.common.core.internal.CommonCorePlugin;
import org.eclipse.gmf.runtime.common.core.internal.CommonCoreStatusCodes;
import org.eclipse.gmf.runtime.common.core.internal.l10n.CommonCoreMessages;
import org.eclipse.gmf.runtime.common.core.util.Log;
import org.eclipse.gmf.runtime.common.core.util.Trace;
/**
*
* A <code>Service</code> does some specific piece of work for clients by
* delegating the actual work done to one or more service providers. Client
* requests are made using {@link org.eclipse.gmf.runtime.common.core.service.IOperation}
* s.
* <P>
* Modeling platform services should subclass this class.
* <P>
* Each service provider has a
* {@link org.eclipse.gmf.runtime.common.core.service.ProviderPriority} that is
* declared in its extension descriptor. It is the
* {@link org.eclipse.gmf.runtime.common.core.service.ExecutionStrategy} that
* determines how service provider priorities are used to select a provider to
* service each client request. For example, if the
* {@link org.eclipse.gmf.runtime.common.core.service.ExecutionStrategy#FIRST}
* is used, the provider with the highest priority will give an answer to the
* request.
* <P>
* A <code>Service</code> may choose to have the following performance
* optimizations:
* <UL>
* <LI>optimized, so that providers that provide for an operation are cached
* the first time they are retrieved and the cache used when an operation is
* executed. If the service is not optimized, all of the service providers may
* be considered each time an operation is executed.</LI>
* <LI>optmistic, so that an optimized service always trusts the contents of
* its cache to contain providers that provide for the given operation. If the
* optimized service is not optimistic, it double-checks the contents of the
* cache to make sure that the cached providers still provide for the operation.
* </LI>
* </UL>
*
* @see org.eclipse.gmf.runtime.common.core.service
*
* @author khussey
*/
public abstract class Service
extends AbstractProvider
implements IProvider, IProviderChangeListener {
/**
* A descriptor for providers defined by a configuration element.
*
* @author khussey
*/
public static class ProviderDescriptor
extends AbstractProvider
implements IProvider, IProviderChangeListener {
protected boolean policyInitialized = false;
private String providerClassName;
/**
* The name of the 'class' XML attribute.
*/
protected static final String A_CLASS = "class"; //$NON-NLS-1$
/**
* The name of the 'plugin' XML attribute.
*
*/
protected static final String A_PLUGIN = "plugin"; //$NON-NLS-1$
/**
* The name of the 'Policy' XML element.
*/
protected static final String E_POLICY = "Policy"; //$NON-NLS-1$
/**
* The configuration element describing this descriptor's provider.
*/
private final IConfigurationElement element;
/**
* The provider for which this object is a descriptor.
*/
protected IProvider provider;
/**
* The policy associated with this descriptor's provider (if specified).
*/
protected IProviderPolicy policy;
/**
* Tracks the failure of the provider class intantiation, so that a
* failure to create the class is logged only once.
*/
private boolean providerClassInstantiationFailed = false;
/**
* Constructs a new provider descriptor for the specified configuration
* element.
*
* @param element The configuration element describing the provider.
*/
protected ProviderDescriptor(IConfigurationElement element) {
super();
this.element = element;
}
/**
* Retrieves the configuration element describing this descriptor's
* provider.
*
* @return The configuration element describing this descriptor's
* provider.
*/
protected final IConfigurationElement getElement() {
return element;
}
/**
* Retrieves the provider for which this object is a descriptor.
* Lazy-initializes the value by instantiating the class described by
* this provider descriptor's configuration element.
*
* @return The provider for which this object is a descriptor.
*/
public IProvider getProvider() {
if (null == provider && !providerClassInstantiationFailed) {
CommonCorePlugin corePlugin = CommonCorePlugin.getDefault();
try {
Log.info(corePlugin, CommonCoreStatusCodes.OK, "Activating provider '" + element.getAttribute(A_CLASS) + "'..."); //$NON-NLS-1$ //$NON-NLS-2$
provider = (IProvider)element.createExecutableExtension(A_CLASS);
provider.addProviderChangeListener(this);
Trace.trace(corePlugin, CommonCoreDebugOptions.SERVICES_ACTIVATE, "Provider '" + provider + "' activated."); //$NON-NLS-1$ //$NON-NLS-2$
} catch (CoreException ce) {
if (provider == null) {
// remember that the provider class could not be instantiated
providerClassInstantiationFailed = true;
}
Trace.catching(corePlugin, CommonCoreDebugOptions.EXCEPTIONS_CATCHING, getClass(), "getProvider", ce); //$NON-NLS-1$
IStatus status = ce.getStatus();
Log.log(
corePlugin,
status.getSeverity(),
CommonCoreStatusCodes.SERVICE_FAILURE,
CommonCoreMessages.bind(CommonCoreMessages.serviceProviderNotActivated, element.getAttribute(A_CLASS)),
status.getException());
}
}
return provider;
}
/**
* Retrieves the policy associated with this descriptor's provider (if
* specified). Lazy-initializes the value by instantiating the class
* described by this provider descriptor's configuration element, if
* specified.
*
* @return The policy associated with this descriptor's provider (if
* specified).
*/
protected IProviderPolicy getPolicy() {
if (!policyInitialized) {
policyInitialized = true;
IConfigurationElement[] elements = element.getChildren(E_POLICY);
working: {
if (elements.length == 0)
break working; // no child elements
CommonCorePlugin corePlugin = CommonCorePlugin.getDefault();
try {
Log.info(corePlugin, CommonCoreStatusCodes.OK, "Activating provider policy '" + elements[0].getAttribute(A_CLASS) + "'..."); //$NON-NLS-1$ //$NON-NLS-2$
// the following results in a core dump on Solaris if
// the policy plug-in cannot be found
policy = (IProviderPolicy)element.createExecutableExtension(E_POLICY);
Trace.trace(corePlugin, CommonCoreDebugOptions.SERVICES_ACTIVATE, "Provider policy '" + policy + "' activated."); //$NON-NLS-1$ //$NON-NLS-2$
} catch (CoreException ce) {
Trace.catching(corePlugin, CommonCoreDebugOptions.EXCEPTIONS_CATCHING, getClass(), "getPolicy", ce); //$NON-NLS-1$
IStatus status = ce.getStatus();
Log.log(
corePlugin,
status.getSeverity(),
CommonCoreStatusCodes.SERVICE_FAILURE,
status.getMessage(),
status.getException());
}
}
}
return policy;
}
/**
* Indicates whether this provider descriptor can provide the
* functionality described by the specified <code>operation</code>.
*
* @param operation
* The operation in question.
* @return <code>true</code> if this descriptor's policy or provider
* provides the operation; <code>false</code> otherwise.
*/
public boolean provides(IOperation operation) {
if (!policyInitialized){
policy = getPolicy();
policyInitialized = true;
}
if (null != policy) {
try {
return policy.provides(operation);
}
catch (Throwable e) {
Log.log(
CommonCorePlugin.getDefault(),
IStatus.ERROR,
CommonCoreStatusCodes.SERVICE_FAILURE,
"Ignoring provider since policy " + policy + " threw an exception or error in the provides() method", //$NON-NLS-1$ //$NON-NLS-2$
e);
// re-throw fatal errors
if (e instanceof ThreadDeath) {
throw (ThreadDeath) e;
}
if (e instanceof VirtualMachineError) {
throw (VirtualMachineError) e;
}
return false;
}
}
IProvider theProvider = getProvider();
return (theProvider != null) ?
safeProvides(theProvider, operation) : false;
}
/**
* Handles an event indicating that a provider has changed.
*
* @param event The provider change event to be handled.
*/
public void providerChanged(ProviderChangeEvent event) {
fireProviderChange(event);
}
/**
* Returns the provider's class name, if it can be found.
*/
public String toString() {
if (providerClassName == null) {
if (getElement() != null && getElement().isValid()) {
// get the provider class name
providerClassName = getElement().getAttribute(A_CLASS);
}
if (providerClassName == null) {
// use the object ID if no provider class name can be found
providerClassName = super.toString();
}
}
return providerClassName;
}
}
/**
* A pattern for error messages indicating an invalid XML element.
*
*/
protected static final String INVALID_ELEMENT_MESSAGE_PATTERN = "Invalid XML element ({0})."; //$NON-NLS-1$
/**
* The name of the 'name' XML attribute.
*/
private static final String A_NAME = "name"; //$NON-NLS-1$
/**
* The name of the 'Priority' XML element.
*/
private static final String E_PRIORITY = "Priority"; //$NON-NLS-1$
/**
* The size of a cache which is indexed by {@link ProviderPriority} ordinals.
*/
private static final int priorityCount;
// Initialize priorityCount.
static {
// any priority will do to get the list of values
List priorities = ProviderPriority.HIGHEST.getValues();
int maxOrdinal = 0;
for (Iterator i = priorities.iterator(); i.hasNext();) {
int ordinal = ((ProviderPriority) i.next()).getOrdinal();
if (maxOrdinal < ordinal)
maxOrdinal = ordinal;
}
priorityCount = maxOrdinal + 1;
}
/**
* List of providers class names that have thrown exceptions in the provides() method.
* Used to prevent logging repeatedly for the same failed provider.
*/
private static final List ignoredProviders = new ArrayList();
/**
* The cache of providers (for optimization) indexed by
* {@link ProviderPriority} ordinals.
*/
private final Map[] cache;
/**
* The lists of registered providers.
*/
private final ArrayList[] providers;
/**
* Whether the service uses optimistic caching.
*/
private final boolean optimistic;
/**
* Constructs a new service that is not optimized.
*/
protected Service() {
this(false);
}
/**
* Constructs a new service that is (not) optimized as specified.
* <P>
* If the service is optimized, the service providers that provide for an
* operation are cached the first time they are retrieved. When an operation
* is executed, this cache is used to find the service providers for the
* execution. If the service is not optimized, all of the service providers
* may be considered each time an operation is executed.
*
* @param optimized
* <code>true</code> if the new service is optimized,
* <code>false</code> otherwise.
*/
protected Service(boolean optimized) {
this(optimized, true);
}
/**
* Constructs a new service that is (not) optimized as specified.
* <P>
* If the service is optimized, the service providers that provide for an
* operation are cached the first time they are retrieved. When an operation
* is executed, this cache is used to find the service providers for the
* execution. If the service is not optimized, all of the service providers
* may be considered each time an operation is executed.
* <P>
* If the optimized service is optimistic, it always trusts the contents of
* its cache to contain providers that provide for the given operation. If
* the optimized service is not optimistic, it double-checks the contents of
* the cache to make sure that the cached providers still provide for the
* operation.
* <P>
* The value of <code>optimistic</code> is meaningless if
* <code>optimized</code> is false.
*
* @param optimized
* <code>true</code> if the new service is optimized,
* <code>false</code> otherwise.
* @param optimistic
* <code>true</code> if the new service uses optmistic caching,
* <code>false</code> otherwise.
*/
protected Service(boolean optimized, boolean optimistic) {
super();
if (optimized) {
cache = new Map[priorityCount];
for (int ordinal = priorityCount; --ordinal >= 0;) {
cache[ordinal] = createPriorityCache();
}
} else {
cache = null;
}
this.optimistic = optimistic;
providers = new ArrayList[priorityCount];
for (int ordinal = priorityCount; --ordinal >= 0;)
providers[ordinal] = new ArrayList(0);
}
/**
* Creates a map for caching service providers keyed by
* the values returned in {@link #getCachingKey(IOperation)}.
*
* @return the new map
*/
protected Map createPriorityCache() {
return new WeakHashMap();
}
/**
* Gets the key used to cache service providers that provide for
* <code>operation</code> in the map created by
* {@link #createPriorityCache()}.
*
* @param operation <code>IOperation</code> for which the key will be retrieved
* @return the key into the service providers cache
*/
protected Object getCachingKey(IOperation operation) {
return operation;
}
/**
* Answers whether or not this service is optimized by caching its service
* providers.
* <P>
* If the service is optimized, the service providers that provide for an
* operation are cached the first time they are retrieved. When an operation
* is executed, this cache is used to find the service providers for the
* execution. If the service is not optimized, all of the service providers
* may be considered each time an operation is executed.
*
* @return <code>true</code> if the new service is optimized,
* <code>false</code> otherwise.
*/
protected final boolean isOptimized() {
return null != cache;
}
/**
* Answers whether or not this service uses optimistic caching. This value
* is only meaningful if {@link #isOptimized()}returns <code>true</code>.
* <P>
* If the optimized service is optimistic, it always trusts the contents of
* its cache to contain providers that provide for the given operation. If
* the optimized service is not optimistic, it double-checks the contents of
* the cache to make sure that the cached providers still provide for the
* operation.
*
* @return <code>true</code> if the new service uses optmistic caching,
* <code>false</code> otherwise.
*/
protected final boolean isOptimistic() {
return optimistic;
}
/**
* Clears the service provider cache (if this service is optimized).
*/
protected final void clearCache() {
if (null != cache) {
for (int ordinal = priorityCount; --ordinal >= 0;) {
cache[ordinal].clear();
}
}
}
/**
* Retrieves a complete list of all the providers registered with this
* service that have the specified <code>priority</code>.
* <P>
* This method does not consider the optimized state of the service.
* @param priority
* The priority of providers to be retrieved.
* @return A complete list of providers of the specified priority.
*/
final List getProviders(ProviderPriority priority) {
return providers[priority.getOrdinal()];
}
/**
* Retrieves a list of providers of the specified <code>priority</code>
* that provide for the specified <code>operation</code>.
* <P>
* If the service is optimized, the result will be cached the first time it
* is retrieved. If caching is not optimistic, the providers from the cache
* will be asked again if they still provide for the operation.
*
* @param strategy
* The strategy used by the service.
* @param priority
* The priority of providers to be retrieved.
* @param operation
* The operation that the provides must provide.
* @return A list of providers that provide for the operation (from the
* cache, if appropriate).
*/
protected final List getProviders(
ExecutionStrategy strategy,
ProviderPriority priority,
IOperation operation) {
assert null != priority : "getProviders received null priority as argument"; //$NON-NLS-1$
assert null != operation : "getproviders received null operation as argument"; //$NON-NLS-1$
List providerList;
if (null == cache) {
providerList = strategy.getUncachedProviders(this, priority, operation);
} else {
Object cachingKey = getCachingKey(operation);
Map map = cache[priority.getOrdinal()];
providerList = (List)map.get(cachingKey);
if (null != providerList) {
if (optimistic)
return providerList;
int n = providerList.size();
if (n != 0) {
for (int i = 0;;) {
IProvider provider = (IProvider)providerList.get(i);
if (!safeProvides(provider, operation))
break;
if (++i == n)
return providerList;
}
}
}
providerList = strategy.getUncachedProviders(this, priority, operation);
map.put(cachingKey, providerList);
}
return providerList;
}
/**
* Retrieves a list of all providers of all priorities for this service.
*
* @return A list of all providers of all priorities.
*/
protected final List getAllProviders() {
int i;
int n = priorityCount;
int total;
for (i = n, total = 0; --i >= 0;)
total += providers[i].size();
List allProviders = new ArrayList(total);
for (i = 0; i < n; ++i)
allProviders.addAll(providers[i]);
return allProviders;
}
/**
* Registers the <code>provider</code> as a provider for this service,
* with the specified <code>priority</code>.
*
* @param priority
* The priority at which to add the provider.
* @param provider
* The provider to be added.
*/
protected final void addProvider(
ProviderPriority priority,
ProviderDescriptor provider) {
assert null != priority : "null ProviderPriority"; //$NON-NLS-1$
assert null != provider : "null ProviderDescriptor"; //$NON-NLS-1$
int ordinal = priority.getOrdinal();
if (null != cache) {
cache[ordinal].clear();
}
providers[ordinal].add(provider);
provider.addProviderChangeListener(this);
}
/**
* Removes the <code>provider</code> as a provider for this service.
*
* @param provider
* The provider to be removed.
*/
protected final void removeProvider(ProviderDescriptor provider) {
assert null != provider : "null provider"; //$NON-NLS-1$
for (int i = 0, n = priorityCount; i < n; ++i) {
if (providers[i].remove(provider)) {
provider.removeProviderChangeListener(this);
clearCache();
break;
}
}
}
/**
* Executes the <code>operation</code> based on the specified execution
* <code>strategy</code>.
*
* @param strategy
* The execution strategy to use.
* @param operation
* The operation to be executed.
* @return The list of results.
*/
protected final List execute(
ExecutionStrategy strategy,
IOperation operation) {
assert null != strategy : "null strategy"; //$NON-NLS-1$
assert null != operation : "null operation"; //$NON-NLS-1$
List results = strategy.execute(this, operation);
if (Trace.shouldTrace(CommonCorePlugin.getDefault(), CommonCoreDebugOptions.SERVICES_EXECUTE)) {
Trace.trace(
CommonCorePlugin.getDefault(),
CommonCoreDebugOptions.SERVICES_EXECUTE,
"Operation '" + operation + "' executed using strategy '" + strategy + "'."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
return results;
}
/**
* Executes the <code>operation</code> based on the specified execution
* <code>strategy</code>. If the result is a single object, return it.
* Otherwise return <code>null</code>.
*
* @param strategy
* The execution strategy to use.
* @param operation
* The operation to be executed.
* @return The unique result.
*/
protected final Object executeUnique(
ExecutionStrategy strategy,
IOperation operation) {
List results = execute(strategy, operation);
return results.size() == 1 ? results.get(0) : null;
}
/**
* Indicates whether or not this service can provide the functionality
* described by the specified <code>operation</code>.
* <P>
* This method does not consider the optimized state of the service. All of
* the providers registered with the service are consulted to determine if
* they provide for the operation.
*
* @param operation
* The operation that describes the requested functionality.
* @return <code>true</code> if any of this service's providers provide
* the operation; <code>false</code> otherwise.
*/
public final boolean provides(IOperation operation) {
assert null != operation : "null operation passed to provides(IOperation)"; //$NON-NLS-1$
for (int priority = 0, n = priorityCount; priority < n; ++priority)
{
List providerList = providers[priority];
int providerCount = providerList.size();
for (int provider = 0; provider < providerCount; ++provider)
if (safeProvides(((IProvider)providerList.get(provider)), operation))
return true;
}
return false;
}
/**
* Indicates whether or not this service can provide the functionality
* described by the specified <code>operation</code> using the given
* execution <code>strategy</code>.
* <P>
* This method considers the optimized state of the service. If the service
* is optimized, it will consult only those providers that have been cached.
*
* @param operation
* The operation in question.
* @param strategy
* The strategy to be used.
* @return <code>true</code> if any of this service's providers provide
* the operation; <code>false</code> otherwise.
*/
protected final boolean provides(ExecutionStrategy strategy, IOperation operation) {
assert null != strategy : "null strategy"; //$NON-NLS-1$
assert null != operation : "null operation"; //$NON-NLS-1$
for (int i = 0; i < ExecutionStrategy.PRIORITIES.length; ++i) {
ProviderPriority priority = ExecutionStrategy.PRIORITIES[i];
List providerList = getProviders(strategy, priority, operation);
int providerCount = providerList.size();
for (int provider = 0; provider < providerCount; ++provider)
if (safeProvides (((IProvider)providerList.get(provider)), operation))
return true;
}
return false;
}
/**
* Handles an event indicating that a provider has changed.
*
* @param event
* The provider change event to be handled.
*/
public final void providerChanged(ProviderChangeEvent event) {
assert null != event : "null event"; //$NON-NLS-1$
event.setSource(this);
fireProviderChange(event);
}
/**
* Registers the service providers described by the extensions of the
* specified namespace and extension point name with this service.
*
* @param namespace the namespace for the given extension point
* (e.g. <code>"org.eclipse.gmf.runtime.common.core"</code>)
* @param extensionPointName the simple identifier of the
* extension point (e.g. <code>"parserProviders"</code>)
*/
public final void configureProviders(String namespace, String extensionPointName) {
configureProviders(Platform.getExtensionRegistry()
.getExtensionPoint(namespace, extensionPointName)
.getConfigurationElements());
}
/**
* Registers the service providers described by the specified configuration
* <code>elements</code> with this service.
*
* @param elements
* The configuration elements describing the providers.
*/
public final void configureProviders(IConfigurationElement[] elements) {
assert null != elements : "null elements"; //$NON-NLS-1$
for (int i = 0; i < elements.length; ++i)
{
IConfigurationElement element = elements[i];
try
{
addProvider(ProviderPriority.parse(getPriority(element)),
newProviderDescriptor(element));
}
finally
{
if (Trace.shouldTrace(CommonCorePlugin.getDefault(), CommonCoreDebugOptions.SERVICES_CONFIG))
{
IExtension extension = element.getDeclaringExtension();
String identifier = extension.getUniqueIdentifier();
if (identifier == null)
identifier = String.valueOf(extension.getNamespaceIdentifier());
extension.getExtensionPointUniqueIdentifier();
Trace.trace(CommonCorePlugin.getDefault(), CommonCoreDebugOptions.SERVICES_CONFIG,
"Provider of '" + extension.getExtensionPointUniqueIdentifier() //$NON-NLS-1$
+ "' configured from extension '" + identifier + "'."); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
for (int i = priorityCount; --i >= 0;)
providers[i].trimToSize();
}
/**
* Get the priority of the Provider's configuration element
*
* @param element
* The configuration elements describing the provider.
* @return the priority of the specified configuration element
*/
public String getPriority(IConfigurationElement element) {
return element.getChildren(E_PRIORITY)[0].getAttribute(A_NAME);
}
/**
* Creates a new provider descriptor for the specified configuration
* <code>element</code>.
*
* @param element
* The configuration element from which to create the descriptor.
* @return A new provider descriptor.
*/
protected ProviderDescriptor newProviderDescriptor(IConfigurationElement element) {
return new ProviderDescriptor(element);
}
/**
* Safely calls a provider's provides() method.
*
* The provider must not be null.
*
* Returns true if there were no exceptions thrown and the provides() method
* returns true. Returns false if an exception was thrown or the provides()
* method returns false.
*
* An entry is added to the log if the provider threw an exception.
*
* @param provider to safely execute the provides() method
* @param operation passed into the provider's provides() method
* @return true if there were no exceptions thrown and the provides() method
* returns true. Returns false if an exception was thrown or the provides()
* method returns false.
*/
static boolean safeProvides(IProvider provider, IOperation operation) {
assert provider != null;
try {
return provider.provides(operation);
}
catch (Throwable e) {
String providerClassName = provider.getClass().getName();
if (!ignoredProviders.contains(providerClassName)) {
// remember the ignored provider so that the error is only logged once per provider
ignoredProviders.add(providerClassName);
Log.log(
CommonCorePlugin.getDefault(),
IStatus.ERROR,
CommonCoreStatusCodes.SERVICE_FAILURE,
"Ignoring provider " + provider + " since it threw an exception or error in the provides() method", //$NON-NLS-1$ //$NON-NLS-2$
e);
}
// re-throw fatal errors
if (e instanceof ThreadDeath) {
throw (ThreadDeath) e;
}
if (e instanceof VirtualMachineError) {
throw (VirtualMachineError) e;
}
return false;
}
}
/**
* Package private access to the list of ignored providers. Providers are
* ignored when they cause a runtime exception or error to be thrown in their {{@link #provides(IOperation)}}
* method.
*
* @return the list of ignored providers.
*/
static List getIgnoredProviders() {
return ignoredProviders;
}
}