| /********************************************************************** |
| * This file is part of "Object Teams Development Tooling"-Software |
| * |
| * Copyright 2009 Germany and Technical University Berlin, Germany. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Please visit http://www.eclipse.org/objectteams for updates and contact. |
| * |
| * Contributors: |
| * Technical University Berlin - Initial API and implementation |
| **********************************************************************/ |
| package org.eclipse.objectteams.internal.osgi.weaving; |
| |
| import static org.eclipse.objectteams.otequinox.AspectPermission.DENY; |
| import static org.eclipse.objectteams.otequinox.AspectPermission.GRANT; |
| import static org.eclipse.objectteams.otequinox.AspectPermission.UNDEFINED; |
| import static org.eclipse.objectteams.otequinox.TransformerPlugin.log; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileReader; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.util.*; |
| |
| import org.eclipse.core.internal.runtime.InternalPlatform; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IExtensionRegistry; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.NonNullByDefault; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.objectteams.internal.osgi.weaving.AspectBinding.BaseBundle; |
| import org.eclipse.objectteams.internal.osgi.weaving.AspectBinding.TeamBinding; |
| import org.eclipse.objectteams.otequinox.ActivationKind; |
| import org.eclipse.objectteams.otequinox.AspectBindingRequestAnswer; |
| import org.eclipse.objectteams.otequinox.AspectPermission; |
| import org.eclipse.objectteams.otequinox.Constants; |
| import org.eclipse.objectteams.otequinox.IAspectRequestNegotiator; |
| import org.eclipse.objectteams.otequinox.TransformerPlugin; |
| import org.eclipse.osgi.internal.hookregistry.HookConfigurator; |
| import org.eclipse.osgi.service.datalocation.Location; |
| import org.objectteams.Team; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleException; |
| |
| /** |
| * Manage permissions of aspect bundles requesting to apply aspectBindings and forcedExports. |
| * The following pieces of information are checked: |
| * <ul> |
| * <li>properties set in installation-wide config.ini or as command line args (handled by {@link HookConfigurator} (plus internal class OTStorageHook))</li> |
| * <li>defaults set per workspace (file negotiationDefaults.txt)</li> |
| * <li>individual GRANT/DENY per workspace (files grantedForcedExports.txt, deniedForcedExports.txt)</li> |
| * <li>answers from registered negotiators (extension point org.eclipse.objectteams.otequinox.aspectBindingNegotiators, see {@link IAspectRequestNegotiator})</li> |
| * </ul> |
| * |
| * <p> |
| * The final answer for a given request is combined from all sources where the priority of any {@link #DENY} answer is highest, |
| * of {@link #UNDEFINED} is lowest. |
| * </p> |
| * <p> |
| * If a negotiator has determined a decision and its answer has the <code>persistent</code> flag set, |
| * this particular aspect permission is stored as per-workspace configuration. |
| * </p> |
| * @author stephan |
| * @since 1.2.6 |
| */ |
| @SuppressWarnings("restriction") |
| @NonNullByDefault |
| public class AspectPermissionManager { |
| |
| // property names for default configuration: |
| private static final String FORCED_EXPORT_DEFAULT = "forced.export.default"; |
| private static final String ASPECT_BINDING_DEFAULT = "aspect.binding.default"; |
| |
| // workspace files where negotiation configuration is stored: |
| private static final String NEGOTIATION_DEFAULTS_FILE = "negotiationDefaults.txt"; |
| private static final String GRANTED_FORCED_EXPORTS_FILE = "grantedForcedExports.txt"; |
| private static final String DENIED_FORCED_EXPORTS_FILE = "deniedForcedExports.txt"; |
| |
| private static final String GRANTED_TEAMS_FILE = "grantedTeams.txt"; |
| private static final String DENIED_TEAMS_FILE = "deniedTeams.txt"; |
| |
| // set of aspect plug-ins for which some permission has been denied: |
| private Set<String> deniedAspects = new HashSet<String>(); |
| // default permission for aspect bindings: |
| private AspectPermission defaultAspectBindingPermission = GRANT; |
| // default permission for forced exports: |
| private AspectPermission defaultForcedExportPermission = UNDEFINED; // not yet granted, but open for receiving a GRANT |
| // for negotiation of aspect binding requests (incl. forced export): |
| private List<IAspectRequestNegotiator> negotiators = new ArrayList<IAspectRequestNegotiator>(); |
| |
| |
| // collect all forced exports (denied/granted), granted should balance to an empty structure. |
| // structure is: aspect-id -> (base bundle x base package)* |
| private Map<String, List<@NonNull String[]>> deniedForcedExportsByAspect= new HashMap<>(); |
| private Map<String, List<@NonNull String[]>> grantedForcedExportsByAspect= new HashMap<>(); |
| |
| // key is aspectId+"->"+baseId, value is array of team names |
| private Map<String, Set<String>> deniedTeamsByAspectBinding = new HashMap<String, Set<String>>(); |
| private Map<String, Set<String>> grantedTeamsByAspectBinding = new HashMap<String, Set<String>>(); |
| |
| // the workspace directory for storing the state of this plugin |
| @Nullable private IPath otequinoxState; |
| // back link needed for accessing the state location: |
| private Bundle transformerBundle; |
| // helper instance needed to stop bundles by name |
| @SuppressWarnings("deprecation") |
| private org.osgi.service.packageadmin.@Nullable PackageAdmin packageAdmin; |
| |
| private ForcedExportsDelegate forcedExportsDelegate; |
| |
| public AspectPermissionManager(Bundle bundle, |
| @SuppressWarnings("deprecation") org.osgi.service.packageadmin.@Nullable PackageAdmin packageAdmin) |
| { |
| this.transformerBundle = bundle; |
| this.packageAdmin = packageAdmin; |
| this.forcedExportsDelegate = new ForcedExportsDelegate(); |
| } |
| |
| /* local cache for isReady(): */ |
| private boolean isWaitingForLocation = true; |
| |
| /** Before using this permission manager a client must check whether we're ready (instance location set). */ |
| public boolean isReady() { |
| if (!isWaitingForLocation) |
| return true; |
| try { |
| InternalPlatform platform = InternalPlatform.getDefault(); |
| Location instanceLocation = platform.getInstanceLocation(); |
| if (!instanceLocation.isSet()) |
| return false; // not yet capable |
| this.isWaitingForLocation = false; |
| fetchAspectBindingPermssionsFromWorkspace(); |
| } catch (NoClassDefFoundError ncdfe) { |
| log(IStatus.WARNING, "Optional class InternalPlatform not found, cannot access workspace location"); |
| this.isWaitingForLocation = false; |
| return true; |
| } |
| if (!this.obligations.isEmpty()) |
| for (Runnable job : this.obligations) |
| job.run(); |
| return true; |
| } |
| |
| /** |
| * Fetch stored permissions from this plugin's workspace state. |
| * |
| * @pre instance location should be set (see {@link #isReady()}), |
| * otherwise will silently return without accessing workspace settings. |
| */ |
| private void fetchAspectBindingPermssionsFromWorkspace() { |
| try { |
| IPath state = InternalPlatform.getDefault().getStateLocation(this.transformerBundle, true); |
| this.otequinoxState = state; |
| internalFetchAspectBindingPermssionsFromWorkspace(state); |
| } catch (NoClassDefFoundError ncdfe) { |
| log(IStatus.WARNING, "Optional class InternalPlatform not found, cannot access workspace location"); |
| return; |
| } |
| } |
| |
| /** Load extensions for EP org.eclipse.objectteams.otequinox.aspectBindingNegotiators. */ |
| public void loadAspectBindingNegotiators(IExtensionRegistry extensionRegistry) { |
| IConfigurationElement[] aspectBindingNegotiatorsConfigs = extensionRegistry.getConfigurationElementsFor( |
| Constants.TRANSFORMER_PLUGIN_ID, Constants.ASPECT_NEGOTIATOR_EXTPOINT_ID); |
| for (int i = 0; i < aspectBindingNegotiatorsConfigs.length; i++) { |
| IConfigurationElement currentNegotiatorConfig = aspectBindingNegotiatorsConfigs[i]; |
| try { |
| Object negotiator = currentNegotiatorConfig.createExecutableExtension("class"); |
| if (negotiator != null) |
| this.negotiators.add(((IAspectRequestNegotiator)negotiator)); |
| } catch (CoreException e) { |
| log(e, "Failed to instantiate extension "+currentNegotiatorConfig); |
| } |
| } |
| } |
| |
| /** Delegatee of internal API {@link TransformerPlugin#isDeniedAspectPlugin(String)}. */ |
| public boolean isDeniedAspectPlugin(String symbolicName) { |
| return this.deniedAspects.contains(symbolicName); |
| } |
| |
| |
| /** |
| * Check whether a given aspect requests forced exports from base, |
| * and whether these requests are granted/denied by checking all available sources. |
| * |
| * Clients should ask {@link #isReady()} (ie., instance location is set) before calling this method, |
| * otherwise workspace settings have to be silently ignored (any error should be signaled by client). |
| * |
| * @param aspectId symbolic name of the aspect bundle |
| * @param baseBundleId symbolic name of the bound base bundle |
| * @param forcedExports any forced exports requested in this aspect binding. |
| * @return whether all requests (if any) have been granted |
| */ |
| public boolean checkForcedExports(AspectBinding aspectBinding) { |
| switch (aspectBinding.forcedExportsPermission) { |
| case GRANT: return true; |
| case DENY: return false; |
| case UNDEFINED: |
| aspectBinding.forcedExportsPermission = internalCheckForcedExports(aspectBinding); |
| return aspectBinding.forcedExportsPermission == GRANT; |
| } |
| return true; |
| } |
| private AspectPermission internalCheckForcedExports(AspectBinding aspectBinding) { |
| IConfigurationElement[] forcedExports = aspectBinding.forcedExports; |
| if (forcedExports.length == 0) |
| return GRANT; |
| |
| String aspectId = aspectBinding.aspectPlugin; |
| String baseBundleId = aspectBinding.basePluginName; |
| List<String[]> deniedForcedExports = getConfiguredForcedExports(aspectId, DENY, deniedForcedExportsByAspect); |
| List<String[]> grantedForcedExports= getConfiguredForcedExports(aspectId, GRANT, grantedForcedExportsByAspect); |
| |
| // iterate all requested forcedExports to search for a matching permission: |
| for (IConfigurationElement forcedExport : forcedExports) { // [0..1] (as defined in the schema) |
| String forcedExportsRequest = forcedExport.getValue(); |
| if (forcedExportsRequest == null) |
| continue; |
| for (@NonNull String singleForcedExportRequest : forcedExportsRequest.split(",")) |
| { |
| singleForcedExportRequest = singleForcedExportRequest.trim(); |
| |
| String[] listEntry; |
| boolean grantReported = false; |
| AspectPermission negotiatedPermission = this.defaultForcedExportPermission; |
| |
| // DENY by default? |
| if (negotiatedPermission == DENY) { |
| log(IStatus.ERROR, "Default denial of forced export regarding package "+singleForcedExportRequest+ |
| " from bundle "+baseBundleId+" as requested by bundle "+aspectId+"; bundle not activated"); |
| this.deniedAspects.add(aspectId); // keep for answering the TransformerHook. |
| return DENY; // NOPE! |
| } |
| |
| // DENY from configuration? |
| listEntry = findRequestInList(baseBundleId, singleForcedExportRequest, deniedForcedExports); |
| if (listEntry != null) { |
| log(IStatus.ERROR, "Explicit denial of forced export regarding package "+singleForcedExportRequest+ |
| " from bundle "+baseBundleId+" as requested by bundle "+aspectId+"; bundle not activated"); |
| this.deniedAspects.add(aspectId); // keep for answering the TransformerHook. |
| return DENY; // NOPE! |
| } |
| |
| // GRANT from configuration? |
| listEntry = findRequestInList(baseBundleId, singleForcedExportRequest, grantedForcedExports); |
| if (listEntry != null) { |
| log(IStatus.INFO, "Forced export granted for "+aspectId+": "+singleForcedExportRequest+" (from bundle "+baseBundleId+")"); |
| grantReported = true; |
| grantedForcedExports.remove(listEntry); |
| negotiatedPermission = GRANT; |
| } |
| |
| // default and persistent configuration did not DENY, proceed to the negotiators: |
| boolean shouldPersist = false; |
| for (IAspectRequestNegotiator negotiator : this.negotiators) { |
| AspectBindingRequestAnswer answer = negotiator.checkForcedExport(aspectId, baseBundleId, singleForcedExportRequest, negotiatedPermission); |
| if (answer.permission.compareTo(negotiatedPermission) > 0) // increasing priority of answer? |
| { |
| shouldPersist = answer.persistent; |
| negotiatedPermission = answer.permission; |
| // locally store as default for subsequent requests (not persistent, see below): |
| if (answer.allRequests) |
| this.defaultForcedExportPermission = negotiatedPermission; |
| if (negotiatedPermission == DENY) |
| break; // end of discussion. |
| } |
| } |
| |
| // make decision persistent? |
| if (shouldPersist && negotiatedPermission != UNDEFINED) |
| // FIXME(SH): handle "allRequests": |
| persistForcedExportsAnswer(aspectId, baseBundleId, singleForcedExportRequest, negotiatedPermission); |
| |
| // report: |
| if (negotiatedPermission == GRANT) { |
| if (!grantReported) |
| log(IStatus.INFO, "Negotiation granted forced export for "+aspectId+ |
| ": "+singleForcedExportRequest+" (from bundle "+baseBundleId+')'); |
| } else { |
| String verb = "did not grant"; |
| if (negotiatedPermission == DENY) |
| verb = "denied"; |
| log(IStatus.ERROR, "Negotiation "+verb+" forced export for "+aspectId+ |
| ": "+singleForcedExportRequest+" (from bundle "+baseBundleId+")"+ |
| ". Aspect is not activated."); |
| this.deniedAspects.add(aspectId); // keep for answering the TransformerHook. |
| return DENY; // don't install illegal aspect |
| } |
| } |
| } |
| if (!grantedForcedExports.isEmpty()) |
| reportUnmatchForcedExports(aspectId, grantedForcedExports); |
| return GRANT; |
| } |
| |
| /** |
| * Get the forced exports configured for a given aspect bundle with permission <code>perm</code>. |
| * Consult {@link HookConfigurator} and store the result in <code>map</code>. |
| * |
| * @param aspectId symbolic name of the aspect in focus |
| * @param perm are we asking about DENY or GRANT? |
| * @param map in/out param for storing results from OTStorageHook |
| * @return list of pairs (base bundle x base package) |
| */ |
| private List<@NonNull String[]> getConfiguredForcedExports( String aspectId, |
| AspectPermission perm, |
| Map<String, List<@NonNull String[]>> map) |
| { |
| List<@NonNull String[]> forcedExports= map.get(aspectId); |
| if (forcedExports == null) { |
| // fetch declarations from config.ini or other locations. |
| forcedExports= forcedExportsDelegate.getForcedExportsByAspect(aspectId, perm); |
| map.put(aspectId, forcedExports); |
| } |
| return forcedExports; |
| } |
| |
| private String @Nullable[] findRequestInList(String baseBundleId, String basePackage, List<String[]> list) { |
| for (String[] singleExport : list) |
| if ( singleExport[0].equals(baseBundleId) |
| && singleExport[1].equals(basePackage)) |
| { |
| return singleExport; |
| } |
| return null; |
| } |
| |
| /** |
| * If the structure of grantedForcedExports is not empty we have mismatches between forced-export declarations. |
| * Report these mismatches as warnings. |
| */ |
| void reportUnmatchForcedExports(String aspectId, List<String[]> unmatchedForcedExports) |
| { |
| for (String[] export: unmatchedForcedExports) { |
| String baseId = export[0]; |
| String pack = export[1]; |
| log(IStatus.WARNING, "Aspect "+aspectId+ |
| " does not declare forced export of package "+ |
| pack+" from bundle "+baseId+ |
| " as declared in config.ini (or system property)"); |
| } |
| } |
| |
| /* Simple strategy to append a forced export to a file (existing or to be created). */ |
| private void persistForcedExportsAnswer(String aspectId, String baseBundleId, String basePackage, AspectPermission negotiatedPermission) |
| { |
| IPath state = this.otequinoxState; |
| if (state == null) { |
| log(IStatus.ERROR, "Can't persist forcedExports permission, no workspace location accessable."); |
| return; |
| } |
| try { |
| String fileName = (negotiatedPermission == DENY) ? DENIED_FORCED_EXPORTS_FILE : GRANTED_FORCED_EXPORTS_FILE; |
| IPath forcedExportsPath = state.append(fileName); |
| File forcedExportsFile = new File(forcedExportsPath.toOSString()); |
| if (!forcedExportsFile.exists()) |
| forcedExportsFile.createNewFile(); |
| try (FileWriter writer = new FileWriter(forcedExportsFile, true)) { // FIXME(SH): consider merge (after decision about file format) |
| writer.append('\n'); |
| writer.append(baseBundleId); |
| writer.append("\n[\n\t"); |
| writer.append(basePackage); |
| writer.append(";x-friends:=\""); |
| writer.append(aspectId); |
| writer.append("\"\n]\n"); |
| writer.flush(); |
| } |
| } catch (IOException ioe) { |
| log(ioe, "Failed to persist negotiation result"); |
| } |
| } |
| |
| /** |
| * Check the permissions for all given teams. |
| * @param teamsForBase the teams to check |
| * @return the set of denied teams |
| */ |
| Set<TeamBinding> checkAspectPermissionDenial(Collection<TeamBinding> teamsForBase) |
| { |
| Set<TeamBinding> deniedTeams = new HashSet<TeamBinding>(); |
| for (TeamBinding teamForBase : teamsForBase) { |
| AspectBinding aspectBinding = teamForBase.getAspectBinding(); |
| String aspectBundleName = aspectBinding.aspectPlugin; |
| if (aspectBinding.hasBeenDenied) { |
| deniedTeams.add(teamForBase); |
| } else { |
| if (!checkForcedExports(aspectBinding)) { |
| deniedTeams.add(teamForBase); |
| stopAspectBundle(aspectBinding, aspectBundleName, "requests unconfirmed forced export(s)."); |
| } else if (!checkTeamBinding(aspectBundleName, aspectBinding.basePluginName, teamForBase)) { |
| deniedTeams.add(teamForBase); |
| stopAspectBundle(aspectBinding, aspectBundleName, "requests unconfirmed aspect binding(s)."); |
| } |
| } |
| } |
| return deniedTeams; |
| } |
| |
| void stopAspectBundle(AspectBinding aspectBinding, String aspectBundleName, String reason) { |
| try { |
| aspectBinding.hasBeenDenied = true; |
| Bundle aspectBundle = aspectBinding.aspectBundle; |
| if (aspectBundle != null) { |
| aspectBundle.stop(); |
| log(IStatus.ERROR, "Stopped bundle "+aspectBundleName+" which "+reason); |
| } else { |
| log(IStatus.ERROR, "Cannot stop aspect bundle "+aspectBundleName); |
| } |
| } catch (Throwable t) { // don't let the aspect bundle get by by throwing an unexpected exception! |
| log(t, "Failed to stop bundle "+aspectBundleName+" which "+reason); |
| } |
| } |
| |
| /** |
| * Check permission for the aspect binding of one specific team. |
| * |
| * Clients should ask {@link #isReady()} (ie., instance location is set) before calling this method, |
| * otherwise workspace settings have to be silently ignored (any error should be signaled by client). |
| * |
| * @param aspectBundleId |
| * @param baseBundleId |
| * @param teamBinding |
| * @return whether this team is permitted to adapt classes from the given base bundle. |
| */ |
| boolean checkTeamBinding(String aspectBundleId, String baseBundleId, TeamBinding teamBinding) { |
| if (teamBinding.checkedPermission != null) |
| return teamBinding.checkedPermission == AspectPermission.GRANT; |
| |
| boolean isGranted = internalCheckTeamBinding(aspectBundleId, baseBundleId, teamBinding.teamName); |
| teamBinding.checkedPermission = isGranted ? AspectPermission.GRANT : AspectPermission.DENY; |
| return isGranted; |
| } |
| |
| boolean internalCheckTeamBinding(String aspectBundleId, String baseBundleId, String teamClass) |
| { |
| boolean shouldReportGrant = false; // grant by default should not be reported |
| AspectPermission negotiatedPermission = this.defaultAspectBindingPermission; |
| |
| // DENY by default? |
| if (negotiatedPermission == DENY) { |
| log(IStatus.ERROR, "Default denial of aspect binding regarding base bundle "+baseBundleId+ |
| " as requested by bundle "+aspectBundleId+"; bundle not activated"); |
| this.deniedAspects.add(aspectBundleId); // keep for answering the TransformerHook. |
| return false; // NOPE! |
| } |
| |
| |
| String key = aspectBundleId+"->"+baseBundleId; |
| |
| // denied from configuration? |
| Set<String> deniedTeams = deniedTeamsByAspectBinding.get(key); |
| if (deniedTeams != null && !deniedTeams.isEmpty()) { |
| if (deniedTeams.contains(teamClass)) { |
| log(IStatus.ERROR, "Configured denial of aspect binding regarding base bundle "+baseBundleId+ |
| " as requested by bundle "+aspectBundleId+"; bundle not activated"); |
| deniedAspects.add(aspectBundleId); |
| return false; |
| } |
| } |
| |
| // granted from configuration? |
| Set<String> grantedTeams = grantedTeamsByAspectBinding.get(key); |
| if (grantedTeams != null && grantedTeams.contains(teamClass)) { |
| negotiatedPermission = GRANT; |
| shouldReportGrant = true; |
| } |
| |
| // default and persistent configuration did not DENY, proceed to the negotiators: |
| boolean shouldPersist = false; |
| String denyingNegotiator = null; |
| for (IAspectRequestNegotiator negotiator : this.negotiators) { |
| AspectBindingRequestAnswer answer = negotiator.checkAspectBinding(aspectBundleId, baseBundleId, teamClass, negotiatedPermission); |
| if (answer.permission.compareTo(negotiatedPermission) > 0) // increasing priority of answer? |
| { |
| shouldPersist = answer.persistent; |
| negotiatedPermission = answer.permission; |
| shouldReportGrant = negotiatedPermission == GRANT; |
| // locally store as default for subsequent requests: |
| if (answer.allRequests) |
| this.defaultAspectBindingPermission = negotiatedPermission; // FIXME: differentiate: apply to all / all of same aspect bundle |
| |
| if (negotiatedPermission == DENY) { |
| denyingNegotiator = negotiator.getClass().getName(); |
| break; // end of discussion. |
| } |
| } |
| } |
| |
| // make decision persistent? |
| if (shouldPersist && negotiatedPermission != UNDEFINED) |
| persistTeamBindingAnswer(aspectBundleId, baseBundleId, teamClass, negotiatedPermission); |
| |
| // report: |
| if (negotiatedPermission == GRANT) { |
| if (shouldReportGrant) |
| log(IStatus.INFO, "Negotiation granted aspect binding for "+aspectBundleId+ |
| " to base bundle "+baseBundleId+" by means of team "+teamClass+'.'); |
| } else { |
| String front = (negotiatedPermission == DENY) |
| ? "Negotiator "+denyingNegotiator + " denied" |
| : "Negotiation did not grant"; |
| log(IStatus.ERROR, front+" aspect binding for "+aspectBundleId+ |
| " to base bundle "+baseBundleId+" by means of team "+teamClass+ |
| ". Aspect is not activated."); |
| this.deniedAspects.add(aspectBundleId); // keep for answering the TransformerPlugin. |
| return false; // don't install illegal aspect |
| } |
| return true; |
| } |
| |
| List<Runnable> obligations = new ArrayList<Runnable>(); |
| public void addBaseBundleObligations(final List<Team> teamInstances, final Collection<TeamBinding> teamClasses, final BaseBundle baseBundle) { |
| schedule(new Runnable() { |
| public void run() { |
| List<TeamBinding> teamsToRevert = new ArrayList<TeamBinding>(); |
| // aspect bindings: |
| for (TeamBinding teamClass : teamClasses) |
| if (!checkTeamBinding(teamClass.getAspectBinding().aspectPlugin, baseBundle.bundleName, teamClass)) |
| teamsToRevert.add(teamClass); |
| if (!teamsToRevert.isEmpty()) |
| revert(teamsToRevert); |
| } |
| void revert(List<TeamBinding> teamsToRevert) { |
| try { |
| Set<Bundle> bundlesToStop = new HashSet<Bundle>(); |
| for (TeamBinding teamClass : teamClasses) { |
| if (teamClass.getActivation() != ActivationKind.NONE) { |
| for (Team teamInstance : teamInstances) |
| if (teamInstance.getClass() == teamClass.teamClass) |
| teamInstance.deactivate(Team.ALL_THREADS); |
| // could also check if roles are present already ... |
| } |
| Bundle aspectBundle = teamClass.getAspectBinding().aspectBundle; |
| if (aspectBundle != null) |
| bundlesToStop.add(aspectBundle); |
| } |
| for (Bundle bundle : bundlesToStop) { |
| if ((bundle.getState() & (Bundle.STARTING|Bundle.ACTIVE)) != 0) { |
| log(IStatus.ERROR, "Stopping aspect bundle "+bundle.getSymbolicName()+" with denied aspect binding(s)"); |
| bundle.stop(); |
| } |
| } |
| } catch (Exception e) { |
| log(e, "Failed to revert aspect bundle with denied aspect bindings."); |
| } |
| } |
| }); |
| } |
| |
| void schedule(Runnable job) { |
| if (isReady()) // became ready since last query? |
| job.run(); |
| else |
| synchronized(obligations) { |
| obligations.add(job); |
| } |
| } |
| |
| void stopIllegalBundle(String symbolicName) { |
| String msgCore = "stop bundle "+symbolicName+" whose requests for forced exports have been denied"; |
| @SuppressWarnings("deprecation") |
| org.osgi.service.packageadmin.PackageAdmin packAdmin = this.packageAdmin; |
| if (packAdmin == null) { |
| log(IStatus.ERROR, "Needing to "+msgCore+" but package admin is not available"); |
| } else { |
| @SuppressWarnings("deprecation") |
| Bundle[] bundles = packAdmin.getBundles(symbolicName, null); |
| if (bundles == null) |
| log(IStatus.ERROR, "Needing to "+msgCore+" but bundle cannot be retrieved"); |
| else |
| try { |
| bundles[0].stop(); |
| } catch (BundleException e) { |
| log(e, "Failed to " + msgCore); |
| } |
| } |
| } |
| |
| // ==== File I/O: ==== |
| |
| private void internalFetchAspectBindingPermssionsFromWorkspace(IPath state) { |
| // defaults: |
| IPath configFilePath = state.append(NEGOTIATION_DEFAULTS_FILE); |
| File configFile = new File(configFilePath.toOSString()); |
| if (configFile.exists()) { |
| Properties props = new Properties(); |
| try { |
| try (FileInputStream inStream = new FileInputStream(configFile)) { |
| props.load(inStream); |
| } |
| String value = (String) props.get(ASPECT_BINDING_DEFAULT); |
| if (value != null) |
| try { |
| defaultAspectBindingPermission = AspectPermission.valueOf(value); |
| } catch (IllegalArgumentException iae) { |
| defaultAspectBindingPermission = AspectPermission.DENY; |
| log(iae, "Cannot set default aspect permission from file "+NEGOTIATION_DEFAULTS_FILE+", assuming DENY."); |
| } |
| value = (String) props.get(FORCED_EXPORT_DEFAULT); |
| if (value != null) |
| try { |
| defaultForcedExportPermission = AspectPermission.valueOf(value); |
| } catch (IllegalArgumentException iae) { |
| defaultForcedExportPermission = AspectPermission.DENY; |
| log(iae, "Cannot set default forced exports permission from file "+NEGOTIATION_DEFAULTS_FILE+", assuming DENY."); |
| } |
| } catch (IOException ioex) { |
| log(ioex, "Failed to read configuration file "+configFilePath.toOSString()); |
| } |
| } else { |
| try { |
| File stateDir = new File(state.toOSString()); |
| if (!stateDir.exists()) |
| stateDir.mkdirs(); |
| configFile.createNewFile(); |
| writeNegotiationDefaults(configFile); |
| } catch (IOException ioex) { |
| log(ioex, "Failed to create configuration file "+configFilePath.toOSString()); |
| } |
| } |
| |
| // configured grant / deny per team: |
| |
| configFilePath = state.append(GRANTED_TEAMS_FILE); |
| configFile = new File(configFilePath.toOSString()); |
| if (configFile.exists()) |
| parseTeamPermissionFile(grantedTeamsByAspectBinding, configFile); |
| |
| configFilePath = state.append(DENIED_TEAMS_FILE); |
| configFile = new File(configFilePath.toOSString()); |
| if (configFile.exists()) |
| parseTeamPermissionFile(deniedTeamsByAspectBinding, configFile); |
| |
| // configured grant / denied for forced exports: |
| configFilePath = state.append(DENIED_FORCED_EXPORTS_FILE); |
| configFile = new File(configFilePath.toOSString()); |
| if (configFile.exists()) |
| forcedExportsDelegate.parseForcedExportsFile(configFile, DENY); |
| |
| configFilePath = state.append(GRANTED_FORCED_EXPORTS_FILE); |
| configFile = new File(configFilePath.toOSString()); |
| if (configFile.exists()) |
| forcedExportsDelegate.parseForcedExportsFile(configFile, GRANT); |
| } |
| |
| private void writeNegotiationDefaults(File configFile) |
| throws IOException |
| { |
| try (FileWriter writer = new FileWriter(configFile)) { |
| writer.append(ASPECT_BINDING_DEFAULT+'='+defaultAspectBindingPermission.toString()+'\n'); |
| writer.append(FORCED_EXPORT_DEFAULT+'='+defaultForcedExportPermission.toString()+'\n'); |
| writer.flush(); |
| } |
| log(IStatus.INFO, "Created aspect binding defaults file "+configFile.getCanonicalPath()); |
| } |
| |
| private void parseTeamPermissionFile(Map<String, Set<String>> teamsByAspectBinding, File configFile) { |
| try (BufferedReader reader = new BufferedReader(new FileReader(configFile))) { |
| String line; |
| while ((line = reader.readLine()) != null) { |
| if (line.length() > 0 && line.charAt(0) == '#') continue; |
| @NonNull String[] parts = line.split("="); |
| if (parts.length == 2) { |
| Set<String> teams = new HashSet<String>(); |
| StringTokenizer teamToks = new StringTokenizer(parts[1], ","); |
| while (teamToks.hasMoreElements()) |
| teams.add(teamToks.nextToken()); |
| teamsByAspectBinding.put(parts[0], teams); |
| } |
| } |
| } catch (IOException e) { |
| log(e, "Failed to read permission file "+configFile.getAbsolutePath()); |
| } |
| } |
| |
| private void persistTeamBindingAnswer(String aspectBundleId, String baseBundleId, String teamClass, AspectPermission negotiatedPermission) |
| { |
| IPath state = this.otequinoxState; |
| if (state != null) { |
| Map<String, Set<String>> teamsByAspect = null; |
| IPath configFilePath = null; |
| switch (negotiatedPermission) { |
| case GRANT: |
| teamsByAspect = this.grantedTeamsByAspectBinding; |
| configFilePath = state.append(GRANTED_TEAMS_FILE); |
| break; |
| case DENY: |
| teamsByAspect = this.deniedTeamsByAspectBinding; |
| configFilePath = state.append(DENIED_TEAMS_FILE); |
| break; |
| default: return; // TODO: also persist UNDEFINED (just to avoid asking again?) |
| } |
| |
| // in fact we store the entire state for the given category (grant / deny) |
| // so first insert the new answer into the existing map: |
| String key = aspectBundleId+"->"+baseBundleId; |
| Set<String> teams = teamsByAspect.get(key); |
| if (teams == null) |
| teamsByAspect.put(key, teams = new HashSet<String>()); |
| teams.add(teamClass); |
| |
| // now dump the entire map: |
| File configFile = new File(configFilePath.toOSString()); |
| try { |
| if (!configFile.exists()) |
| configFile.createNewFile(); |
| try (FileWriter writer = new FileWriter(configFile, false)) { |
| writer.write("# Aspect permission file generated from aspect negotiation results.\n"); |
| for (Map.Entry<String, Set<String>> entry : teamsByAspect.entrySet()) { |
| writer.append(entry.getKey()).append('='); |
| String sep = ""; |
| for (String t : entry.getValue()) { |
| writer.append(sep).append(t); |
| sep = ","; |
| } |
| writer.append('\n'); |
| } |
| writer.flush(); |
| } |
| } catch (IOException ioe) { |
| log(ioe, "Failed to persist negotiation result"); |
| } |
| } |
| } |
| } |