blob: 209e02562d3e465bceefc9e6a61588fd7da66b54 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2020 IBM Corporation and others.
*
* 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
*
* Contributors:
* IBM Corporation - initial API and implementation
* Alexander Fedorov <alexander.fedorov@arsysop.ru> - Bug 541067
*******************************************************************************/
package org.eclipse.pde.internal.core.target;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.equinox.frameworkadmin.BundleInfo;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.osgi.service.datalocation.Location;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.core.target.ITargetDefinition;
import org.eclipse.pde.core.target.ITargetHandle;
import org.eclipse.pde.core.target.ITargetLocation;
import org.eclipse.pde.core.target.ITargetPlatformService;
import org.eclipse.pde.core.target.NameVersionDescriptor;
import org.eclipse.pde.core.target.TargetBundle;
import org.eclipse.pde.core.target.TargetEvents;
import org.eclipse.pde.internal.core.ICoreConstants;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.PDEPreferencesManager;
import org.eclipse.pde.internal.core.TargetDefinitionManager;
import org.osgi.service.prefs.BackingStoreException;
/**
* Target platform service implementation.
*
* @since 3.5
*/
public class TargetPlatformService implements ITargetPlatformService {
/**
* Service instance
*/
private static ITargetPlatformService fgDefault;
/**
* External File Targets
*/
private static Map<URI, ExternalFileTargetHandle> fExtTargetHandles;
/**
* The target definition currently being used as the target platform for
* the workspace.
*/
private final AtomicReference<ITargetDefinition> fWorkspaceTarget;
/**
* vm arguments for default target
*/
private StringBuilder fVMArguments;
private final EventDispatcher eventSendingJob;
/**
* Collects target files in the workspace
*/
static class ResourceProxyVisitor implements IResourceProxyVisitor {
private List<IResource> fList;
protected ResourceProxyVisitor(List<IResource> list) {
fList = list;
}
/**
* @see org.eclipse.core.resources.IResourceProxyVisitor#visit(org.eclipse.core.resources.IResourceProxy)
*/
@Override
public boolean visit(IResourceProxy proxy) {
if (proxy.getType() == IResource.FILE) {
if (ICoreConstants.TARGET_FILE_EXTENSION.equalsIgnoreCase(new Path(proxy.getName()).getFileExtension())) {
fList.add(proxy.requestResource());
}
return false;
}
return true;
}
}
private TargetPlatformService() {
fWorkspaceTarget = new AtomicReference<>();
eventSendingJob = new EventDispatcher("Sending 'workspace target changed' event", TargetPlatformService.class); //$NON-NLS-1$
}
/**
* The target service should be obtained by requesting the {@link ITargetPlatformService} from OSGi. This
* method should only be used internally be PDE.
*
* @return The singleton implementation of this service
*/
public synchronized static ITargetPlatformService getDefault() {
if (fgDefault == null) {
fgDefault = new TargetPlatformService();
}
return fgDefault;
}
@Override
public void deleteTarget(ITargetHandle handle) throws CoreException {
if (handle instanceof ExternalFileTargetHandle) {
fExtTargetHandles.remove(((ExternalFileTargetHandle) handle).getLocation());
}
((AbstractTargetHandle) handle).delete();
}
@Override
public ITargetHandle getTarget(IFile file) {
return new WorkspaceFileTargetHandle(file);
}
@Override
public ITargetHandle getTarget(String memento) throws CoreException {
try {
URI uri = new URI(memento);
String scheme = uri.getScheme();
if (scheme != null) {
switch (scheme) {
case WorkspaceFileTargetHandle.SCHEME:
return WorkspaceFileTargetHandle.restoreHandle(uri);
case LocalTargetHandle.SCHEME:
return LocalTargetHandle.restoreHandle(uri);
case ExternalFileTargetHandle.SCHEME:
return ExternalFileTargetHandle.restoreHandle(uri);
case RemoteTargetHandle.SCHEME:
return RemoteTargetHandle.get(memento.substring(RemoteTargetHandle.SCHEME.length() + 1));
default:
break;
}
}
} catch (URISyntaxException e) {
throw new CoreException(Status.error(Messages.TargetPlatformService_0, e));
}
throw new CoreException(
Status.error(NLS.bind(Messages.TargetPlatformService_1, memento,
Stream.of(WorkspaceFileTargetHandle.SCHEME, LocalTargetHandle.SCHEME,
ExternalFileTargetHandle.SCHEME, RemoteTargetHandle.SCHEME)
.collect(Collectors.joining(", "))), //$NON-NLS-1$
null));
}
@Override
public ITargetHandle getTarget(URI uri) {
if (fExtTargetHandles == null) {
fExtTargetHandles = new LinkedHashMap<>(10);
}
if (fExtTargetHandles.containsKey(uri)) {
return fExtTargetHandles.get(uri);
}
ExternalFileTargetHandle externalTarget = new ExternalFileTargetHandle(uri);
fExtTargetHandles.put(uri, externalTarget);
return externalTarget;
}
@Override
public ITargetHandle[] getTargets(IProgressMonitor monitor) {
List<ITargetHandle> local = findLocalTargetDefinitions();
List<WorkspaceFileTargetHandle> ws = findWorkspaceTargetDefinitions();
local.addAll(ws);
if (fExtTargetHandles != null) {
// If an external target is inaccessible then don't show it. But keep the reference in case it becomes accessible later
Collection<ExternalFileTargetHandle> externalTargets = fExtTargetHandles.values();
for (ExternalFileTargetHandle target : externalTargets) {
if (target.exists()) {
local.add(target);
}
}
} else {
PDEPreferencesManager preferences = PDECore.getDefault().getPreferencesManager();
String memento = preferences.getString(ICoreConstants.WORKSPACE_TARGET_HANDLE);
if (memento != null && memento.length() != 0 && !memento.equals(ICoreConstants.NO_TARGET)) {
try {
URI uri = new URI(memento);
String scheme = uri.getScheme();
if (ExternalFileTargetHandle.SCHEME.equals(scheme)) {
ITargetHandle target = getTarget(uri);
local.add(target);
}
} catch (URISyntaxException e) {
// ignore
}
}
}
return local.toArray(new ITargetHandle[local.size()]);
}
/**
* Finds and returns all local target definition handles
*
* @return all local target definition handles
*/
private List<ITargetHandle> findLocalTargetDefinitions() {
IPath containerPath = LocalTargetHandle.LOCAL_TARGET_CONTAINER_PATH;
List<ITargetHandle> handles = new ArrayList<>(10);
final File directory = containerPath.toFile();
if (directory.isDirectory()) {
FilenameFilter filter = (dir, name) -> dir.equals(directory) && name.endsWith(ICoreConstants.TARGET_FILE_EXTENSION);
File[] files = directory.listFiles(filter);
for (File file : files) {
try {
handles.add(LocalTargetHandle.restoreHandle(file.toURI()));
} catch (CoreException e) {
PDECore.log(e);
}
}
}
return handles;
}
/**
* Finds and returns all target definition handles defined by workspace files
*
* @return all target definition handles in the workspace
*/
private List<WorkspaceFileTargetHandle> findWorkspaceTargetDefinitions() {
List<IResource> files = new ArrayList<>(10);
ResourceProxyVisitor visitor = new ResourceProxyVisitor(files);
try {
ResourcesPlugin.getWorkspace().getRoot().accept(visitor, IResource.NONE);
} catch (CoreException e) {
PDECore.log(e);
return new ArrayList<>(0);
}
Iterator<IResource> iter = files.iterator();
List<WorkspaceFileTargetHandle> handles = new ArrayList<>(files.size());
while (iter.hasNext()) {
IFile file = (IFile) iter.next();
handles.add(new WorkspaceFileTargetHandle(file));
}
return handles;
}
@Override
public ITargetLocation newDirectoryLocation(String path) {
return new DirectoryBundleContainer(path);
}
@Override
public ITargetLocation newProfileLocation(String home, String configurationLocation) {
return new ProfileBundleContainer(home, configurationLocation);
}
@Override
public ITargetDefinition newTarget() {
return new TargetDefinition(new LocalTargetHandle());
}
@Override
public void saveTargetDefinition(ITargetDefinition definition) throws CoreException {
((AbstractTargetHandle) definition.getHandle()).save(definition);
}
@Override
public ITargetLocation newFeatureLocation(String home, String id, String version) {
return new FeatureBundleContainer(home, id, version);
}
@Override
public ITargetHandle getWorkspaceTargetHandle() throws CoreException {
PDEPreferencesManager preferences = PDECore.getDefault().getPreferencesManager();
String memento = preferences.getString(ICoreConstants.WORKSPACE_TARGET_HANDLE);
if (memento != null && memento.length() != 0 && !memento.equals(ICoreConstants.NO_TARGET)) {
return getTarget(memento);
}
return null;
}
@Override
public synchronized ITargetDefinition getWorkspaceTargetDefinition() throws CoreException {
ITargetDefinition workspaceTarget = fWorkspaceTarget.get();
if (workspaceTarget != null && workspaceTarget.getHandle().equals(getWorkspaceTargetHandle())) {
return workspaceTarget;
}
// If no target definition has been chosen before, try using preferences
initDefaultTargetPlatformDefinition();
// Load and resolve
String memento = PDECore.getDefault().getPreferencesManager().getString(ICoreConstants.WORKSPACE_TARGET_HANDLE);
ITargetDefinition target = null;
if (memento == null || memento.equals("") || memento.equals(ICoreConstants.NO_TARGET)) { //$NON-NLS-1$
target = newTarget();
} else {
ITargetHandle handle = getTarget(memento);
target = handle.getTargetDefinition();
}
setWorkspaceTargetDefinition(target, true);
return target;
}
/**
* Updates the current stored target. Provided to allow the LoadTargetDefinitionJob
* to pass along a possibly resolved target rather than force it to be resolved again.
* This method will not update the stored {@link ICoreConstants#WORKSPACE_TARGET_HANDLE},
* as it should only be called from LoadTargetDefinitionJob which does additional
* steps to reset the target.
*
* @param target
* the new workspace target definition
* @param asyncEvents
* to notify listener asynchronously
*/
public void setWorkspaceTargetDefinition(ITargetDefinition target, boolean asyncEvents) {
ITargetDefinition oldTarget = fWorkspaceTarget.getAndSet(target);
boolean changed = !Objects.equals(oldTarget, target);
if (changed) {
if (asyncEvents) {
eventSendingJob.schedule(target);
} else {
notifyTargetChanged(target);
}
}
}
static void notifyTargetChanged(ITargetDefinition target) {
IEclipseContext context = EclipseContextFactory.getServiceContext(PDECore.getDefault().getBundleContext());
IEventBroker broker = context.get(IEventBroker.class);
if (broker != null) {
broker.send(TargetEvents.TOPIC_WORKSPACE_TARGET_CHANGED, target);
}
}
static class EventDispatcher extends Job {
private final ConcurrentLinkedQueue<ITargetDefinition> queue;
private final Object myFamily;
/**
* @param jobName
* descriptive job name
* @param family
* non null object to control this job execution
**/
public EventDispatcher(String jobName, Object family) {
super(jobName);
Assert.isNotNull(family);
this.myFamily = family;
this.queue = new ConcurrentLinkedQueue<>();
setSystem(true);
}
@Override
public boolean belongsTo(Object family) {
return myFamily == family;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
ITargetDefinition target;
while ((target = queue.poll()) != null && !monitor.isCanceled()) {
notifyTargetChanged(target);
}
if (!queue.isEmpty() && !monitor.isCanceled()) {
// in case actions got faster scheduled then processed
schedule();
}
if (monitor.isCanceled()) {
queue.clear();
return Status.CANCEL_STATUS;
}
return Status.OK_STATUS;
}
/**
* Enqueue a task asynchronously.
**/
public void schedule(ITargetDefinition target) {
queue.offer(target);
schedule(); // will reschedule if already running
}
}
/**
* Sets active target definition handle if not yet set. If an existing target
* definition corresponds to workspace target settings, it is selected as the
* active target. If there are no targets that correspond to workspace settings
* a new definition is created.
*/
private void initDefaultTargetPlatformDefinition() {
PDEPreferencesManager preferenceManager = PDECore.getDefault().getPreferencesManager();
String memento = preferenceManager.getString(ICoreConstants.WORKSPACE_TARGET_HANDLE);
if (removeInvalidTargetMementoInPreference(preferenceManager, memento)) {
memento = preferenceManager.getString(ICoreConstants.WORKSPACE_TARGET_HANDLE);
}
if (memento == null || memento.equals("")) { //$NON-NLS-1$
try {
if (PDECore.DEBUG_MODEL) {
System.out.println("No target platform memento, add default target."); //$NON-NLS-1$
}
// Add default target
ITargetDefinition defaultTarget = newDefaultTarget();
defaultTarget.setName(Messages.TargetPlatformService_7);
saveTargetDefinition(defaultTarget);
// Set active platform
PDEPreferencesManager preferences = PDECore.getDefault().getPreferencesManager();
ITargetHandle active = defaultTarget.getHandle();
preferences.setValue(ICoreConstants.WORKSPACE_TARGET_HANDLE, active.getMemento());
} catch (CoreException e) {
PDECore.log(e);
}
}
}
private boolean removeInvalidTargetMementoInPreference(PDEPreferencesManager preferenceManager, String memento) {
// check if preference entry points to valid target
if (memento != null && !memento.equals("") && !memento.equals(ICoreConstants.NO_TARGET)) { //$NON-NLS-1$
ITargetHandle handle;
try {
handle = getTarget(memento);
if (!handle.exists()) {
// preferences points to invalid target definition remove preference entry
preferenceManager.setValueOrRemove(ICoreConstants.WORKSPACE_TARGET_HANDLE,
preferenceManager.getDefaultString(ICoreConstants.WORKSPACE_TARGET_HANDLE));
preferenceManager.flush();
return true;
}
} catch (CoreException | BackingStoreException e) {
PDECore.log(e);
}
}
return false;
}
@Override
public void copyTargetDefinition(ITargetDefinition from, ITargetDefinition to) throws CoreException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
((TargetDefinition) from).write(outputStream);
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
((TargetDefinition) to).setContents(inputStream);
}
@Override
public void loadTargetDefinition(ITargetDefinition definition, String targetExtensionId) throws CoreException {
IConfigurationElement elem = PDECore.getDefault().getTargetProfileManager().getTarget(targetExtensionId);
if (elem == null) {
throw new CoreException(Status.error(NLS.bind(Messages.TargetPlatformService_2, targetExtensionId)));
}
String path = elem.getAttribute("definition"); //$NON-NLS-1$
String symbolicName = elem.getDeclaringExtension().getContributor().getName();
URL url = TargetDefinitionManager.getResourceURL(symbolicName, path);
if (url != null) {
try {
((TargetDefinition) definition).setContents(new BufferedInputStream(url.openStream()));
} catch (IOException e) {
throw new CoreException(Status.error(NLS.bind(Messages.TargetPlatformService_3, path), e));
}
} else {
throw new CoreException(Status.error(NLS.bind(Messages.TargetPlatformService_4, path)));
}
}
@Override
public ITargetDefinition newDefaultTarget() {
ITargetDefinition target = newTarget();
Location configArea = Platform.getConfigurationLocation();
String configLocation = null;
if (configArea != null) {
configLocation = configArea.getURL().getFile();
}
if (configLocation != null) {
Location location = Platform.getInstallLocation();
if (location != null) {
URL url = location.getURL();
if (url != null) {
IPath installPath = new Path(url.getFile());
IPath configPath = new Path(configLocation);
if (installPath.isPrefixOf(configPath)) {
// if it is the default configuration area, do not specify explicitly
configPath = configPath.removeFirstSegments(installPath.segmentCount());
configPath = configPath.setDevice(null);
if (configPath.segmentCount() == 1 && configPath.lastSegment().equals("configuration")) { //$NON-NLS-1$
configLocation = null;
}
}
}
}
}
ITargetLocation container = newProfileLocation("${eclipse_home}", configLocation); //$NON-NLS-1$
target.setTargetLocations(new ITargetLocation[] {container});
target.setName(Messages.TargetPlatformService_7);
// initialize environment with default settings
target.setArch(Platform.getOSArch());
target.setOS(Platform.getOS());
target.setWS(Platform.getWS());
target.setNL(Platform.getNL());
// initialize vm arguments from the default container
ITargetLocation[] containers = target.getTargetLocations();
Job job = new Job(Messages.TargetPlatformService_6) {
@Override
public IStatus run(IProgressMonitor monitor) {
fVMArguments = getVMArguments(containers);
return Status.OK_STATUS;
}
};
job.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(org.eclipse.core.runtime.jobs.IJobChangeEvent event) {
if (fVMArguments != null) {
target.setVMArguments(fVMArguments.toString().trim());
}
}
});
job.schedule();
return target;
}
private StringBuilder getVMArguments(ITargetLocation[] containers) {
StringBuilder arguments = new StringBuilder(""); //$NON-NLS-1$
if (containers != null) {
for (ITargetLocation container : containers) {
String[] vmargs = container.getVMArguments();
if (vmargs == null) {
continue;
}
for (String vmarg : vmargs) {
arguments.append(vmarg).append(' ');
}
}
}
return arguments;
}
@Override
public IStatus compareWithTargetPlatform(ITargetDefinition target) throws CoreException {
if (!target.isResolved()) {
return null;
}
// Get the current models from the target platform
IPluginModelBase[] models = PDECore.getDefault().getModelManager().getExternalModels();
Set<File> allFilesAtLocations = new HashSet<>(models.length);
Map<File, IPluginModelBase> stateLocations = new LinkedHashMap<>(models.length);
for (IPluginModelBase base : models) {
allFilesAtLocations.add(new File(base.getInstallLocation()));
stateLocations.put(new File(base.getInstallLocation()), base);
}
// Compare the platform bundles against the definition ones and collect any missing bundles
MultiStatus multi = new MultiStatus(PDECore.PLUGIN_ID, 0, "", null); //$NON-NLS-1$
TargetBundle[] bundles = target.getAllBundles();
Set<NameVersionDescriptor> alreadyConsidered = new HashSet<>(bundles.length);
for (TargetBundle bundle : bundles) {
BundleInfo info = bundle.getBundleInfo();
File file = URIUtil.toFile(info.getLocation());
String location = file.getAbsolutePath();
File fileAtLocation = new File(location);
stateLocations.remove(fileAtLocation);
NameVersionDescriptor desc = new NameVersionDescriptor(info.getSymbolicName(), info.getVersion());
if (!alreadyConsidered.contains(desc)) {
alreadyConsidered.add(desc);
// ignore duplicates (symbolic name & version)
if (!allFilesAtLocations.contains(fileAtLocation)) {
// it's not in the state... if it's not really in the target either (missing) this
// is not an error
IStatus status = bundle.getStatus();
if (status.isOK() || (status.getCode() != TargetBundle.STATUS_PLUGIN_DOES_NOT_EXIST && status.getCode() != TargetBundle.STATUS_FEATURE_DOES_NOT_EXIST && status.getCode() != TargetBundle.STATUS_VERSION_DOES_NOT_EXIST)) {
// its in the target, missing in the state
IStatus s = new Status(IStatus.WARNING, PDECore.PLUGIN_ID, ITargetPlatformService.STATUS_MISSING_FROM_TARGET_PLATFORM, bundle.getBundleInfo().getSymbolicName(), null);
multi.add(s);
}
}
}
}
// Anything left over is in the state and not the target (have been removed from the target)
Iterator<IPluginModelBase> iterator = stateLocations.values().iterator();
while (iterator.hasNext()) {
IPluginModelBase model = iterator.next();
IStatus status = new Status(IStatus.WARNING, PDECore.PLUGIN_ID, ITargetPlatformService.STATUS_MISSING_FROM_TARGET_DEFINITION, model.getPluginBase().getId(), null);
multi.add(status);
}
if (multi.isOK()) {
return Status.OK_STATUS;
}
return multi;
}
@Override
public ITargetLocation newIULocation(IInstallableUnit[] units, URI[] repositories, int resolutionFlags) {
return new IUBundleContainer(units, repositories, resolutionFlags);
}
@Override
public ITargetLocation newIULocation(String[] unitIds, String[] versions, URI[] repositories, int resolutionFlags) {
return new IUBundleContainer(unitIds, versions, repositories, resolutionFlags);
}
}