/*******************************************************************************
* Copyright (c) 2004, 2006
* Thomas Hallgren, Kenneth Olwing, Mitch Sonies
* Pontus Rydin, Nils Unden, Peer Torngren
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the individual
* copyright holders listed above, as Initial Contributors under such license.
* The text of such license is available at www.eclipse.org.
*
* Contributors:
* Lorenzo Bettini - https://bugs.eclipse.org/bugs/show_bug.cgi?id=428301
*******************************************************************************/
package org.eclipse.buckminster.pde.internal;
import static org.eclipse.buckminster.core.helpers.MapUtils.getString;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.eclipse.buckminster.core.RMContext;
import org.eclipse.buckminster.core.cspec.IComponentRequest;
import org.eclipse.buckminster.core.cspec.model.ComponentIdentifier;
import org.eclipse.buckminster.core.ctype.IComponentType;
import org.eclipse.buckminster.core.helpers.FileUtils;
import org.eclipse.buckminster.core.helpers.TextUtils;
import org.eclipse.buckminster.core.materializer.IMaterializer;
import org.eclipse.buckminster.core.materializer.MaterializationContext;
import org.eclipse.buckminster.core.metadata.model.Resolution;
import org.eclipse.buckminster.core.mspec.ConflictResolution;
import org.eclipse.buckminster.core.reader.CatalogReaderType;
import org.eclipse.buckminster.core.reader.IComponentReader;
import org.eclipse.buckminster.core.reader.IReaderType;
import org.eclipse.buckminster.core.reader.IVersionFinder;
import org.eclipse.buckminster.core.reader.P2ReaderType;
import org.eclipse.buckminster.core.resolver.NodeQuery;
import org.eclipse.buckminster.core.rmap.model.Provider;
import org.eclipse.buckminster.core.version.ProviderMatch;
import org.eclipse.buckminster.core.version.VersionHelper;
import org.eclipse.buckminster.core.version.VersionMatch;
import org.eclipse.buckminster.download.DownloadManager;
import org.eclipse.buckminster.pde.IPDEConstants;
import org.eclipse.buckminster.pde.Messages;
import org.eclipse.buckminster.pde.PDEPlugin;
import org.eclipse.buckminster.pde.internal.EclipseImportBase.Key;
import org.eclipse.buckminster.pde.internal.imports.PluginImportOperation;
import org.eclipse.buckminster.pde.mapfile.MapFile;
import org.eclipse.buckminster.pde.mapfile.MapFileEntry;
import org.eclipse.buckminster.runtime.BuckminsterException;
import org.eclipse.buckminster.runtime.IOUtils;
import org.eclipse.buckminster.runtime.MonitorUtils;
import org.eclipse.buckminster.runtime.URLUtils;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
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.Status;
import org.eclipse.ecf.core.security.IConnectContext;
import org.eclipse.equinox.internal.p2.artifact.repository.ArtifactRequest;
import org.eclipse.equinox.internal.p2.repository.Transport;
import org.eclipse.equinox.internal.provisional.p2.artifact.repository.processing.ProcessingStepHandler;
import org.eclipse.equinox.p2.metadata.IArtifactKey;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.metadata.VersionRange;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.equinox.p2.repository.artifact.IArtifactDescriptor;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRequest;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.PDEState;
import org.eclipse.pde.internal.core.feature.ExternalFeatureModel;
import org.eclipse.pde.internal.core.ifeature.IFeatureModel;
import org.osgi.framework.Constants;
@SuppressWarnings({ "restriction" })
public class EclipseImportReaderType extends CatalogReaderType implements IPDEConstants {
/**
* A request to mirror (copy) an artifact into a given destination artifact
* repository.
*/
public static class CopyRequest extends ArtifactRequest {
private final File destination;
public CopyRequest(IArtifactKey key, Transport transport, File destination) {
super(key, transport);
this.destination = destination;
}
@Override
public void perform(IArtifactRepository sourceRepository, IProgressMonitor monitor) {
monitor.subTask(NLS.bind(Messages.downloading_0, getArtifactKey().getId()));
setSourceRepository(sourceRepository);
// if the request does not have a descriptor then try to fill one in
// by getting
// the list of all and randomly picking one that appears to be
// optimized.
IArtifactDescriptor optimized = null;
IArtifactDescriptor canonical = null;
IArtifactDescriptor descriptor = null;
IArtifactDescriptor[] descriptors = source.getArtifactDescriptors(getArtifactKey());
if (descriptors.length > 0) {
for (int i = 0; i < descriptors.length; i++) {
if (descriptors[i].getProperty(IArtifactDescriptor.FORMAT) == null)
canonical = descriptors[i];
else if (ProcessingStepHandler.canProcess(descriptors[i]))
optimized = descriptors[i];
}
boolean chooseCanonical = source.getLocation().equals("file"); //$NON-NLS-1$
// If the source repo is local then look for a canonical
// descriptor so we don't waste processing
// time.
descriptor = chooseCanonical ? canonical : optimized;
// if the descriptor is still null then we could not find our
// first choice of format so switch the
// logic.
if (descriptor == null)
descriptor = !chooseCanonical ? canonical : optimized;
}
// if the descriptor is not set now then the repo does not have the
// requested artifact
if (descriptor == null) {
setResult(new Status(IStatus.ERROR, PDEPlugin.getPluginId(), NLS.bind(Messages.artifact_not_found_0, getArtifactKey())));
return;
}
IStatus status = transfer(descriptor, monitor);
if (monitor.isCanceled()) {
setResult(Status.CANCEL_STATUS);
return;
}
if (status.isOK() || status.getSeverity() == IStatus.CANCEL) {
setResult(status);
return;
}
if (descriptor == canonical || canonical == null) {
setResult(status);
return;
}
// try with canonical
setResult(transfer(canonical, monitor));
}
private IStatus transfer(IArtifactDescriptor descriptor, IProgressMonitor monitor) {
IStatus status;
do {
status = transferSingle(descriptor, monitor);
} while (status.getSeverity() == IStatus.ERROR && status.getCode() == IArtifactRepository.CODE_RETRY);
return status;
}
private IStatus transferSingle(IArtifactDescriptor descriptor, IProgressMonitor monitor) {
OutputStream output = null;
IStatus status = null;
try {
output = new FileOutputStream(destination);
status = source.getArtifact(descriptor, output, monitor);
} catch (IOException e) {
status = BuckminsterException.createStatus(e);
} finally {
IOUtils.close(output);
}
return status;
}
}
private static final UUID CACHE_KEY_SITE_CACHE = UUID.randomUUID();
public static File getTempSite(Map<UUID, Object> ucache) throws CoreException {
Map<String, File> siteCache = getSiteCache(ucache);
synchronized (siteCache) {
String key = EclipseImportReaderType.class.getSimpleName() + ":tempSite"; //$NON-NLS-1$
File tempSite = siteCache.get(key);
if (tempSite != null)
return tempSite;
tempSite = FileUtils.createTempFolder("bmsite", ".tmp"); //$NON-NLS-1$ //$NON-NLS-2$
new File(tempSite, PLUGINS_FOLDER).mkdir();
new File(tempSite, FEATURES_FOLDER).mkdir();
siteCache.put(key, tempSite);
return tempSite;
}
}
static URL createRemoteComponentURL(URL remoteLocation, IConnectContext cctx, ComponentIdentifier cid, String subDir) throws CoreException {
if (remoteLocation.getPath().endsWith(".jar")) //$NON-NLS-1$
return remoteLocation;
if (remoteLocation.getPath().endsWith(".map")) //$NON-NLS-1$
{
for (MapFileEntry entry : getMapEntries(remoteLocation, cctx)) {
ComponentIdentifier entryCid = entry.getComponentIdentifier();
if (!(entryCid.getName().equals(cid.getName()) && entryCid.getComponentTypeID().equals(cid.getComponentTypeID())))
continue;
if (VersionHelper.equalsUnqualified(cid.getVersion(), entryCid.getVersion()))
try {
return new URL(getString(entry.getProperties(), "src")); //$NON-NLS-1$
} catch (MalformedURLException e) {
// Just skip
}
}
throw BuckminsterException.fromMessage(NLS.bind(Messages.unable_to_find_0_in_map_1, cid.getName(), remoteLocation));
}
try {
return new URL(remoteLocation, subDir + '/' + cid.getName() + '_' + cid.getVersion() + ".jar"); //$NON-NLS-1$
} catch (MalformedURLException e) {
throw BuckminsterException.wrap(e);
}
}
static List<MapFileEntry> getMapEntries(URL location, IConnectContext cctx) throws CoreException {
InputStream input = null;
try {
ArrayList<MapFileEntry> mapEntries = new ArrayList<MapFileEntry>();
input = DownloadManager.read(location, cctx);
MapFile.parse(input, location.toString(), null, null, mapEntries);
ArrayList<MapFileEntry> binaryEntries = new ArrayList<MapFileEntry>();
for (MapFileEntry entry : mapEntries) {
if (!IReaderType.URL.equals(entry.getReaderType().getId()))
continue;
Map<String, Object> props = entry.getProperties();
String src = getString(props, "src"); //$NON-NLS-1$
if (src == null || !(src.endsWith(".jar") || src.endsWith(".zip"))) //$NON-NLS-1$ //$NON-NLS-2$
continue;
binaryEntries.add(entry);
}
return binaryEntries;
} catch (FileNotFoundException e) {
return Collections.emptyList();
} catch (IOException e) {
throw BuckminsterException.wrap(e);
} finally {
IOUtils.close(input);
}
}
@SuppressWarnings("unchecked")
static Map<String, File> getSiteCache(Map<UUID, Object> ctxUserCache) {
synchronized (ctxUserCache) {
Map<String, File> siteCache = (Map<String, File>) ctxUserCache.get(CACHE_KEY_SITE_CACHE);
if (siteCache == null) {
siteCache = Collections.synchronizedMap(new HashMap<String, File>());
ctxUserCache.put(CACHE_KEY_SITE_CACHE, siteCache);
}
return siteCache;
}
}
private final Map<IProject, IClasspathEntry[]> classpaths = new HashMap<IProject, IClasspathEntry[]>();
private final HashMap<File, IFeatureModel[]> featureCache = new HashMap<File, IFeatureModel[]>();
public EclipseImportReaderType() {
}
public synchronized void addProjectClasspath(IProject project, IClasspathEntry[] classPath) {
classpaths.put(project, classPath);
}
@Override
public URI getArtifactURL(Resolution resolution, RMContext context) throws CoreException {
try {
URL siteURL = new URL(resolution.getRepository());
String sitePath = siteURL.getPath();
if (!(sitePath.endsWith(".map") || sitePath.endsWith(".xml") || sitePath.endsWith(".jar"))) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
siteURL = URLUtils.appendTrailingSlash(siteURL);
boolean isFeature = IComponentType.ECLIPSE_FEATURE.equals(resolution.getComponentTypeId());
String subDir = isFeature ? FEATURES_FOLDER : PLUGINS_FOLDER;
return createRemoteComponentURL(siteURL, null, resolution.getComponentIdentifier(), subDir).toURI();
} catch (MalformedURLException e) {
throw BuckminsterException.wrap(e);
} catch (URISyntaxException e) {
throw BuckminsterException.wrap(e);
}
}
@Override
public IComponentReader getReader(ProviderMatch providerMatch, IProgressMonitor monitor) throws CoreException {
MonitorUtils.complete(monitor);
return new EclipseImportReader(this, providerMatch);
}
@Override
public String getRecommendedMaterializer() {
return IMaterializer.P2;
}
@Override
public IVersionFinder getVersionFinder(Provider provider, IComponentType ctype, NodeQuery nodeQuery, IProgressMonitor monitor)
throws CoreException {
throw new UnsupportedOperationException("import reader can not be used for resolution"); //$NON-NLS-1$
}
@Override
public synchronized void postMaterialization(MaterializationContext context, IProgressMonitor monitor) throws CoreException {
// Create needed classpath entries in each project
//
PluginImportOperation.setClasspaths(monitor, classpaths);
// Clear cached entries
//
featureCache.clear();
classpaths.clear();
}
IInstallableUnit getCachedInstallableUnit(IMetadataRepository mdr, ProviderMatch providerMatch) throws CoreException {
NodeQuery query = providerMatch.getNodeQuery();
Version bv = providerMatch.getVersionMatch().getVersion();
if (bv == null)
return null;
VersionRange vr = new VersionRange(bv, true, bv, true);
IComponentRequest cr = query.getComponentRequest();
String name = cr.getName();
if (IComponentType.ECLIPSE_FEATURE.equals(cr.getComponentTypeID()) && !name.endsWith(IPDEConstants.FEATURE_GROUP))
name += IPDEConstants.FEATURE_GROUP;
IQueryResult<IInstallableUnit> c = mdr.query(QueryUtil.createIUQuery(name, vr), null);
if (c.isEmpty())
return null;
return c.iterator().next();
}
IFeatureModel getFeatureModel(ProviderMatch rInfo, IProgressMonitor monitor) throws CoreException {
IFeatureModel model = null;
EclipseImportBase localBase = localizeContents(rInfo, false, MonitorUtils.subMonitor(monitor, 90));
String version = rInfo.getVersionMatch().getVersion().toString();
for (IFeatureModel candidate : localBase.getFeatureModels(this, monitor)) {
if (version.equals(candidate.getFeature().getVersion())) {
model = candidate;
break;
}
}
return model;
}
List<IFeatureModel> getFeatureModels(File location, String featureName, IProgressMonitor monitor) throws CoreException {
ArrayList<IFeatureModel> candidates = new ArrayList<IFeatureModel>();
for (IFeatureModel model : getSiteFeatures(location, monitor)) {
if (model.getFeature().getId().equals(featureName))
candidates.add(model);
}
return candidates;
}
IPluginModelBase getPluginModel(ProviderMatch rInfo, IProgressMonitor monitor) throws CoreException {
IPluginModelBase model = null;
EclipseImportBase localBase = localizeContents(rInfo, true, MonitorUtils.subMonitor(monitor, 90));
String version = rInfo.getVersionMatch().getVersion().toString();
for (IPluginModelBase candidate : localBase.getPluginModels(this, MonitorUtils.subMonitor(monitor, 10))) {
if (version.equals(candidate.getBundleDescription().getVersion().toString())) {
model = candidate;
break;
}
}
return model;
}
List<IPluginModelBase> getPluginModels(File location, String pluginName, IProgressMonitor monitor) throws CoreException {
ArrayList<IPluginModelBase> candidates = new ArrayList<IPluginModelBase>();
for (IPluginModelBase model : getSitePlugins(location, monitor)) {
if (model.getPluginBase().getId().equals(pluginName))
candidates.add(model);
}
return candidates;
}
// Must be synchronized since we materialize to a common state
synchronized EclipseImportBase localizeContents(ProviderMatch rInfo, boolean isPlugin, IProgressMonitor monitor) throws CoreException {
NodeQuery query = rInfo.getNodeQuery();
EclipseImportBase base = EclipseImportBase.obtain(query, rInfo.getRepositoryURI());
if (base.isLocal() && rInfo.getVersionMatch().getArtifactInfo() == null)
return base;
Map<UUID, Object> userCache = query.getContext().getUserCache();
String name = base.getComponentName();
monitor.beginTask(null, 1000);
monitor.subTask(NLS.bind(Messages.localizing_0, name));
try {
IConnectContext cctx = rInfo.getConnectContext();
String typeDir = isPlugin ? PLUGINS_FOLDER : FEATURES_FOLDER;
File tempSite = getTempSite(userCache);
File subDir = new File(tempSite, typeDir);
String jarName = null;
File jarFile = null;
VersionMatch vm = rInfo.getVersionMatch();
if (vm.getArtifactInfo() != null) {
// This is a P2 artifact. Copy it from the artifact repository
//
IInstallableUnit iu = P2ReaderType.getIU(rInfo, monitor);
IArtifactRepository ar = P2ReaderType.getArtifactRepository(rInfo, monitor);
Transport transport = (Transport) ar.getProvisioningAgent().getService(Transport.SERVICE_NAME);
for (IArtifactKey ak : iu.getArtifacts()) {
jarName = ak.getId() + '_' + ak.getVersion() + ".jar"; //$NON-NLS-1$
jarFile = new File(subDir, jarName);
IStatus status = ar.getArtifacts(new IArtifactRequest[] { new CopyRequest(ak, transport, jarFile) }, monitor);
if (!status.isOK())
throw new CoreException(status);
}
if (jarFile == null)
throw BuckminsterException.fromMessage(NLS.bind(Messages.IU_0_1_has_no_artifacts, iu.getId(), iu.getVersion()));
} else {
URL pluginURL = createRemoteComponentURL(base.getRemoteLocation(), cctx, new ComponentIdentifier(rInfo.getComponentName(), rInfo
.getComponentType().getId(), vm.getVersion()), typeDir);
// Use a temporary local site
//
IPath path = Path.fromPortableString(pluginURL.getPath());
jarName = path.lastSegment();
if (!(jarName.endsWith(".jar") || jarName.endsWith(".zip"))) //$NON-NLS-1$ //$NON-NLS-2$
throw BuckminsterException.fromMessage(NLS.bind(Messages.invalid_url_fore_remote_import_0, pluginURL));
jarFile = new File(subDir, jarName);
InputStream input = null;
try {
input = DownloadManager.getCache().open(pluginURL, cctx, null, null, MonitorUtils.subMonitor(monitor, 900));
FileUtils.copyFile(input, subDir, jarName, MonitorUtils.subMonitor(monitor, 900));
} finally {
IOUtils.close(input);
}
}
Key remoteKey = base.getKey();
base = EclipseImportBase.obtain(query, new URI("file", null, tempSite.toURI().getPath(), base //$NON-NLS-1$
.getQuery(), name).toString());
File destDir = null;
boolean unpack = true;
ConflictResolution cres = ConflictResolution.REPLACE;
if (jarName.endsWith(".zip")) //$NON-NLS-1$
{
// Special orbit packaging. Just unzip into the plug-ins folder
//
destDir = subDir;
cres = ConflictResolution.UPDATE;
} else {
if (!base.isFeature()) {
// Guess unpack based on classpath
//
JarFile jf = new JarFile(jarFile);
Manifest mf = jf.getManifest();
if (mf != null) {
String[] classPath = TextUtils.split(mf.getMainAttributes().getValue(Constants.BUNDLE_CLASSPATH), ","); //$NON-NLS-1$
int top = classPath.length;
unpack = (top > 0);
for (int idx = 0; idx < top; ++idx) {
if (classPath[idx].trim().equals(".")) //$NON-NLS-1$
{
unpack = false;
break;
}
}
}
jf.close();
}
if (unpack) {
String vcName = jarName.substring(0, jarName.length() - 4);
destDir = new File(subDir, vcName);
}
}
if (unpack) {
InputStream input = new FileInputStream(jarFile);
try {
FileUtils.unzip(input, null, destDir, cres, MonitorUtils.subMonitor(monitor, 100));
} finally {
IOUtils.close(input);
}
jarFile.delete();
}
base.setUnpack(unpack);
// Cache this using the remote key also so that the next time
// someone asks for it, the local
// version is returned
//
EclipseImportBase.getImportBaseCacheCache(userCache).put(remoteKey, base);
return base;
} catch (URISyntaxException e) {
throw BuckminsterException.wrap(e);
} catch (IOException e) {
throw BuckminsterException.wrap(e);
} finally {
monitor.done();
}
}
private CoreException createException(ArrayList<IStatus> resultStatus) {
int errCount = resultStatus.size();
if (errCount == 1)
return new CoreException(resultStatus.get(0));
IStatus[] children = resultStatus.toArray(new IStatus[errCount]);
MultiStatus multiStatus = new MultiStatus(PDEPlugin.getPluginId(), IStatus.OK, children, Messages.problems_loading_feature, null);
return new CoreException(multiStatus);
}
private IFeatureModel[] getPlatformFeatures() {
return PDECore.getDefault().getFeatureModelManager().getModels();
}
private IPluginModelBase[] getPlatformPlugins() {
return PDECore.getDefault().getModelManager().getExternalModels();
}
private synchronized IFeatureModel[] getSiteFeatures(File location, IProgressMonitor monitor) throws CoreException {
if (location == null)
return getPlatformFeatures();
IFeatureModel[] result = featureCache.get(location);
if (result != null)
return result;
location = new File(location, FEATURES_FOLDER);
File[] dirs = location.listFiles();
if (dirs == null || dirs.length == 0)
return new IFeatureModel[0];
monitor.beginTask(null, dirs.length);
monitor.subTask(NLS.bind(Messages.building_feature_list_for_site_0, location));
ArrayList<IFeatureModel> models = new ArrayList<IFeatureModel>(dirs.length);
ArrayList<IStatus> resultStatus = null;
for (File dir : dirs) {
File manifest = new File(dir, FEATURE_MANIFEST);
InputStream manifestInput = null;
try {
manifestInput = new FileInputStream(manifest);
ExternalFeatureModel model = new ExternalFeatureModel();
model.setInstallLocation(dir.getAbsolutePath());
model.load(manifestInput, false);
if (!model.isValid())
throw new CoreException(new Status(IStatus.WARNING, PDEPlugin.getPluginId(), IStatus.OK, NLS.bind(
Messages.import_location_0_contains_invalid_feature, dir), null));
models.add(model);
} catch (FileNotFoundException e) {
// This is expected. No feature.xml means not a feature.
} catch (CoreException e) {
if (resultStatus == null)
resultStatus = new ArrayList<IStatus>();
resultStatus.add(e.getStatus());
} finally {
IOUtils.close(manifestInput);
MonitorUtils.worked(monitor, 1);
}
}
if (resultStatus != null)
throw createException(resultStatus);
result = models.toArray(new IFeatureModel[models.size()]);
featureCache.put(location, result);
return result;
}
private synchronized IPluginModelBase[] getSitePlugins(File location, IProgressMonitor monitor) throws CoreException {
if (location == null)
return getPlatformPlugins();
monitor.beginTask(null, 2);
monitor.subTask(NLS.bind(Messages.building_plugin_list_for_site_0, location));
try {
File pluginsRoot = new File(location, PLUGINS_FOLDER);
if (!pluginsRoot.isDirectory())
return new IPluginModelBase[0];
File[] files = pluginsRoot.listFiles();
int idx = files.length;
URL[] pluginURLs = new URL[idx];
while (--idx >= 0)
pluginURLs[idx] = files[idx].toURI().toURL();
MonitorUtils.worked(monitor, 1);
PDEState state = new PDEState(pluginURLs, false, false, MonitorUtils.subMonitor(monitor, 1));
return state.getTargetModels();
} catch (IOException e) {
throw BuckminsterException.wrap(e);
} finally {
monitor.done();
}
}
}