blob: 989a83bb0a862a238cdf4b2360fb2b122718c6f5 [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Development Tooling"-Software
*
* Copyright 2004, 2006 Fraunhofer Gesellschaft, Munich, Germany,
* for its Fraunhofer Institute for Computer Architecture and Software
* Technology (FIRST), Berlin, Germany and Technical University Berlin,
* Germany.
*
* 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
* $Id: MasterTeamLoader.java 23468 2010-02-04 22:34:27Z stephan $
*
* Please visit http://www.eclipse.org/objectteams for updates and contact.
*
* Contributors:
* Fraunhofer FIRST - Initial API and implementation
* Technical University Berlin - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otequinox.internal;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.internal.registry.osgi.OSGIUtils;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.objectteams.otequinox.ActivationKind;
import org.eclipse.objectteams.otequinox.TransformerPlugin;
import org.eclipse.osgi.framework.internal.core.BundleHost;
import org.eclipse.objectteams.otequinox.hook.ClassScanner;
import org.eclipse.objectteams.otequinox.hook.ILogger;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
/**
* Each instance of this class is responsible for loading all teams adapting a specific base plugin.
* It stores the team classes loaded successfully.
* After team loading a call to instantiateLoadedTeams() shall be issued to instantiate
* those teams, but that call pre-assumes that adapted base classes have already been loaded.
*
* @author stephan
* @version $Id: MasterTeamLoader.java 23468 2010-02-04 22:34:27Z stephan $
*/
@SuppressWarnings("restriction")
public class MasterTeamLoader {
/** A simple record describing a configured and loaded team class: */
static class TeamClassRecord {
// store these info to check consistency among different aspectBindings mentioning the same team class:
static Set<Class<?>> allInstantiatedTeams = new HashSet<Class<?>>();
static Map<Class<?>, ActivationKind> allActivations = new HashMap<Class<?>, ActivationKind>();
/** The bundle providing this team classs. */
Bundle aspectBundle;
/** The qualified team name. */
String teamName;
/** The loaded team class. */
Class<?> clazz;
/** The activation kind which was requested from the extension. */
ActivationKind activation;
/**
* @param clazz the loaded team class
* @param activation the activation kind which was requested from the extension
*/
TeamClassRecord(Class<?> clazz, ActivationKind activation) {
this.clazz = clazz;
this.activation = activation;
}
public TeamClassRecord(Bundle aspectBundle, String teamName, ActivationKind activation) throws Exception {
this.activation = activation;
this.aspectBundle = aspectBundle;
this.teamName = teamName;
// enable to revert to early loading:
//this.clazz = this.aspectBundle.loadClass(this.teamName);
}
/** Constant from org.objectteams.Team acquired via reflection. */
static Thread ALL_THREADS = null;
/** Retrieve org.objectteams.Team.ALL_THREADS. */
static synchronized Thread get_ALL_THREADS()
throws Exception
{
if (ALL_THREADS == null) {
Class<?> ooTeam = Class.forName("org.objectteams.Team");
Field constantField = ooTeam.getDeclaredField("ALL_THREADS");
ALL_THREADS = (Thread)constantField.get(null);
}
return ALL_THREADS;
}
void readOTAttributes(ClassScanner scanner)
throws Exception, IOException
{
readOTAttributes(scanner, this.teamName);
}
void readOTAttributes(ClassScanner scanner, String teamName)
throws Exception, IOException
{
TransformerPlugin.getDefault().log(IStatus.OK, "reading attributes of team "+teamName);
ClassLoader loader = (ClassLoader) ((BundleHost)aspectBundle).getLoaderProxy().getBundleLoader().createClassLoader();
scanner.readOTAttributes(aspectBundle, teamName, loader);
Collection<String> baseClassNames = scanner.getCollectedBaseClassNames(teamName);
if (baseClassNames != null && !baseClassNames.isEmpty())
TransformerPlugin.getDefault().storeAdaptedBaseClassNames(this.aspectBundle.getSymbolicName(), teamName, baseClassNames);
}
public boolean isAlreadyHandled() throws ClassNotFoundException {
if (this.clazz == null)
this.clazz = this.aspectBundle.loadClass(this.teamName);
return allInstantiatedTeams.contains(this.clazz);
}
public Object newInstance() throws Exception {
if (this.clazz == null)
this.clazz = this.aspectBundle.loadClass(this.teamName);
allInstantiatedTeams.add(this.clazz); // do this before accessing the constructor to avoid circularity (see Trac #257).
Object newInstance = this.clazz.newInstance();
return newInstance;
}
public void markAsActivated() {
allActivations.put(this.clazz, this.activation);
}
public ActivationKind getActualActivation() {
ActivationKind kind = allActivations.get(this.clazz);
if (kind == null)
return ActivationKind.NONE;
return kind;
}
}
Bundle baseBundle;
/** Team classes waiting for activation. */
private List<TeamClassRecord> teamClasses = new ArrayList<TeamClassRecord>();
private HashMap<Bundle,List<String>> baseBundleToAspectName = new HashMap<Bundle, List<String>>();
public MasterTeamLoader(Bundle baseBundle) {
this.baseBundle = baseBundle;
}
/**
* Load all teams adapting baseBundle and the adapted base classes.
* TODO(SH): No checks are yet performed, whether the teams found in extensions actually
* match the given baseBundle.
*
* @param baseBundle an adaptable base bundle which was just activated.
* @param classScanner helper for reading OT bytecode attributes.
* @param bindings declared aspect bindings for this bundle
* @return whether or not teams have been loaded successfully
*/
public boolean loadTeams(Bundle baseBundle, ClassScanner classScanner, ArrayList<AspectBinding> bindings)
{
for (final AspectBinding binding : bindings) {
String aspectBundleName = binding.aspectPlugin;
log(IStatus.OK, ">>> TransformerPlugin loading aspect plugin "+aspectBundleName+" <<<");
final Bundle aspectBundle = OSGIUtils.getDefault().getBundle(aspectBundleName);
if (aspectBundle == null) {
Throwable t = new Exception("Aspect bundle "+aspectBundleName+" does not exist.");
t.fillInStackTrace();
log(t, "Failed to load teams for an aspect plugin.");
}
// load and store the team classes:
for (int i = 0; i < binding.teamClasses.length; i++) {
try {
TeamClassRecord teamClassRecord = new TeamClassRecord(
aspectBundle, binding.teamClasses[i],
binding.activations[i]
);
this.teamClasses.add(teamClassRecord);
teamClassRecord.readOTAttributes(classScanner); // disable to revert to early loading
if (binding.subTeamClasses[i] != null)
for (String subTeamName : binding.subTeamClasses[i])
teamClassRecord.readOTAttributes(classScanner, subTeamName); // FIXME(SH): really use the same TeamClassRecord??
} catch (Throwable t) {
log(t, "Exception occurred while loading team class"); //$NON-NLS-1$
}
}
// store aspectBundleName for PHASE 2:
List<String> aspectNames = baseBundleToAspectName.get(baseBundle);
if (aspectNames == null)
baseBundleToAspectName.put(baseBundle, aspectNames = new ArrayList<String>());
aspectNames.add(aspectBundleName);
}
return !this.teamClasses.isEmpty();
}
/**
* Instantiate all team classes loaded before.
* Before doing so all adapted base classes have to be loaded, too.
*
* @pre the AspectPermissionManager should be ready (ie., instance location is set), otherwise
* workspace settings have to be ignored (error should be signaled by client).
*
* @param baseBundle only teams adapting this base bundle should be instantiated
* @param triggerClassname loading of this class triggered this instantiation (may be null)
* @param permissionManager helper for checking permissions of aspectBinding / forcedExport
* @return list of team instances (maybe empty). Null signal that instantiation had to be deferred.
*/
public List<Object> instantiateLoadedTeams(Bundle baseBundle, String triggerClassname, AspectPermissionManager permissionManager)
{
List<Object> teamInstances = new ArrayList<Object>(this.teamClasses.size());
// permission checking can be performed now or later, depending on readiness:
boolean permissionManagerReady = permissionManager.isReady();
// ==== check permissions before we start activating:
if (permissionManagerReady) { // otherwise we will register pending obligations below.
boolean hasDenial = false;
for (TeamClassRecord teamClass : this.teamClasses)
if (!permissionManager.checkTeamBinding(teamClass.aspectBundle.getSymbolicName(), baseBundle.getSymbolicName(), teamClass.teamName))
{
hasDenial = true;
try {
teamClass.aspectBundle.stop();
log(ILogger.ERROR, "Stopped bundle "+teamClass.aspectBundle.getSymbolicName()+" which requests unconfirmed aspect binding(s).");
} catch (BundleException e) {
log(e, "Failed to stop bundle "+teamClass.aspectBundle.getSymbolicName()+" which requests unconfirmed aspect binding(s).");
}
}
if (hasDenial)
return teamInstances; // still empty list
}
// ==== instantiate the teams:
for (TeamClassRecord teamClass : this.teamClasses) {
if (teamClass.activation == ActivationKind.NONE)
continue;
try {
if (teamClass.isAlreadyHandled()) { // previously instantiated due to a different aspectBinding/basePlugin?
ActivationKind actualActivation = teamClass.getActualActivation();
if ( actualActivation != teamClass.activation
&& actualActivation != ActivationKind.NONE
&& teamClass.activation != ActivationKind.NONE)
{
log(IStatus.WARNING, "Conflicting activation requests in aspect bindings for team class "+teamClass.teamName);
}
} else {
Object newTeam = teamClass.newInstance();
teamInstances.add(newTeam);
Class<?>[] activationArgumentTypes = new Class[0];
Object[] activationArguments = null;
switch(teamClass.activation) {
case ALL_THREADS:
activationArgumentTypes = new Class[]{Thread.class};
activationArguments = new Object[] {TeamClassRecord.get_ALL_THREADS()};
// fall through
case THREAD:
Method activationMethod = teamClass.clazz.getMethod("activate", activationArgumentTypes);
activationMethod.invoke(newTeam, activationArguments);
log(IStatus.OK, ">>> instantiated team: "+teamClass.clazz+", activation: "+teamClass.activation+" <<<");
teamClass.markAsActivated();
break;
case NONE:
// nothing ;-)
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
if (!permissionManagerReady)
permissionManager.addBaseBundleObligations(teamInstances, new ArrayList<TeamClassRecord>(this.teamClasses), baseBundle);
return teamInstances;
}
void log(int status, String msg) {
TransformerPlugin.getDefault().log(status, msg);
}
void log(Throwable t, String msg) {
TransformerPlugin.getDefault().log(t, msg);
}
}