| /********************************************************************* |
| * Copyright (c) 2009, 2010 SpringSource, a division of VMware, Inc. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| **********************************************************************/ |
| |
| package org.eclipse.virgo.ide.runtime.core.artefacts; |
| |
| 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.UnsupportedEncodingException; |
| import java.net.URI; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.locks.Lock; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipInputStream; |
| |
| 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.Platform; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.ui.statushandlers.StatusManager; |
| import org.eclipse.virgo.ide.bundlerepository.domain.BundleImport; |
| import org.eclipse.virgo.ide.bundlerepository.domain.OsgiVersion; |
| import org.eclipse.virgo.ide.bundlerepository.domain.PackageExport; |
| import org.eclipse.virgo.ide.bundlerepository.domain.PackageImport; |
| import org.eclipse.virgo.ide.bundlerepository.domain.SpringSourceApplicationPlatform; |
| import org.eclipse.virgo.ide.manifest.core.dependencies.IDependencyLocator; |
| import org.eclipse.virgo.ide.runtime.core.ServerCorePlugin; |
| import org.eclipse.virgo.ide.runtime.core.ServerUtils; |
| import org.eclipse.virgo.ide.runtime.core.provisioning.IBundleRepositoryChangeListener; |
| import org.eclipse.virgo.ide.runtime.internal.core.provisioning.IArtefactRepositoryLoader; |
| import org.eclipse.virgo.ide.runtime.internal.core.provisioning.JsonArtefactRepositoryLoader; |
| import org.eclipse.virgo.ide.runtime.internal.core.utils.StatusUtil; |
| import org.eclipse.virgo.ide.runtime.internal.core.utils.WebDownloadUtils; |
| import org.eclipse.virgo.kernel.repository.BundleDefinition; |
| import org.eclipse.virgo.kernel.repository.BundleRepository; |
| import org.eclipse.virgo.kernel.repository.LibraryDefinition; |
| import org.eclipse.virgo.repository.ArtifactDescriptor; |
| import org.eclipse.virgo.util.osgi.manifest.BundleManifest; |
| import org.eclipse.virgo.util.osgi.manifest.ExportedPackage; |
| import org.eclipse.virgo.util.osgi.manifest.VersionRange; |
| import org.eclipse.wst.server.core.IRuntime; |
| import org.eclipse.wst.server.core.util.PublishUtil; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.Version; |
| |
| /** |
| * Manages instances of {@link ArtefactRepository} to represent the current contents of the SpringSource Enterprise |
| * Bundle Repository and {@link BundleRepository}s indexed by {@link IRuntime} representing local bundle and library |
| * repositories in a Virgo Server instance. |
| * |
| * @author Christian Dupuis |
| * @since 1.0.0 |
| */ |
| public class ArtefactRepositoryManager { |
| |
| private ArtefactRepository artefactRepository = new ArtefactRepository(); |
| |
| private final Map<IRuntime, BundleRepository> bundleRepositories = new ConcurrentHashMap<IRuntime, BundleRepository>(); |
| |
| private Date repositoryDate = new Date(); |
| |
| private final SpringSourceApplicationPlatform applicationPlatform = new SpringSourceApplicationPlatform(); |
| |
| private final Set<IBundleRepositoryChangeListener> changeListeners = Collections.synchronizedSet(new HashSet<IBundleRepositoryChangeListener>()); |
| |
| private volatile boolean initialized = false; |
| |
| protected static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); |
| |
| protected static final Lock r = rwl.readLock(); |
| |
| protected static final Lock w = rwl.writeLock(); |
| |
| private List<OsgiVersion> findBundleVersions(String symbolicName) { |
| List<OsgiVersion> versions = new ArrayList<OsgiVersion>(); |
| try { |
| r.lock(); |
| for (IArtefact bundle : this.artefactRepository.getBundles()) { |
| if (bundle.getSymbolicName().equals(symbolicName)) { |
| versions.add(bundle.getVersion()); |
| } |
| } |
| } finally { |
| r.unlock(); |
| } |
| return versions; |
| } |
| |
| public BundleRepository getBundleRepository(IRuntime runtime) { |
| try { |
| r.lock(); |
| if (this.bundleRepositories.containsKey(runtime)) { |
| return this.bundleRepositories.get(runtime); |
| } |
| } finally { |
| r.unlock(); |
| } |
| IDependencyLocator locator = null; |
| try { |
| w.lock(); |
| if (this.bundleRepositories.containsKey(runtime)) { |
| return this.bundleRepositories.get(runtime); |
| } |
| |
| locator = ServerUtils.createDependencyLocator(runtime); |
| Set<BundleDefinition> bundles = new HashSet<BundleDefinition>(); |
| |
| for (BundleDefinition bundle : locator.getBundles()) { |
| if (bundle.getManifest() != null) { |
| bundles.add(new InitializedBundleDefinition(bundle.getManifest(), bundle.getLocation())); |
| } |
| } |
| |
| BundleRepository initializedBundleRepository = new InitializedBundleRepository(bundles, |
| new HashSet<LibraryDefinition>(locator.getLibraries())); |
| this.bundleRepositories.put(runtime, initializedBundleRepository); |
| return initializedBundleRepository; |
| } finally { |
| // Shutdown DependencyLocator |
| if (locator != null) { |
| locator.shutdown(); |
| } |
| |
| w.unlock(); |
| } |
| } |
| |
| public BundleRepository refreshBundleRepository(IRuntime runtime) { |
| try { |
| w.lock(); |
| this.bundleRepositories.remove(runtime); |
| } finally { |
| w.unlock(); |
| } |
| |
| BundleRepository bundleRepository = getBundleRepository(runtime); |
| fireBundleRepositoryChanged(runtime); |
| return bundleRepository; |
| } |
| |
| public List<BundleArtefact> findLibraryDependencies(LibraryArtefact library, boolean includeOptional) { |
| try { |
| r.lock(); |
| List<BundleArtefact> required = new ArrayList<BundleArtefact>(); |
| List<BundleImport> unsatisfiedBundleImports = new ArrayList<BundleImport>(); |
| List<BundleImport> bundleImports = library.getBundleImports(); |
| List<BundleArtefact> bundles = new ArrayList<BundleArtefact>(); |
| for (BundleImport imp : bundleImports) { |
| List<OsgiVersion> candidates = findBundleVersions(imp.getSymbolicName()); |
| OsgiVersion highestAvailable = new OsgiVersion("0.0.0"); |
| boolean foundMatch = false; |
| for (OsgiVersion foundVersion : candidates) { |
| if (imp.getVersionRange().contains(foundVersion)) { |
| if (foundVersion.compareTo(highestAvailable) > 0) { |
| highestAvailable = foundVersion; |
| foundMatch = true; |
| } |
| } |
| } |
| if (foundMatch) { |
| bundles.add(getBundle(imp.getSymbolicName(), highestAvailable)); |
| } else { |
| unsatisfiedBundleImports.add(imp); |
| } |
| } |
| required.addAll(bundles); |
| List<PackageImport> unsatisfiedImports = new ArrayList<PackageImport>(); |
| List<PackageImport> imports = new ArrayList<PackageImport>(); |
| for (BundleArtefact bundle : bundles) { |
| imports.addAll(bundle.getImports()); |
| } |
| collectExporters(required, includeOptional, imports, unsatisfiedImports); |
| return required; |
| } finally { |
| r.unlock(); |
| } |
| } |
| |
| public ArtefactRepository getArtefactRepository() { |
| try { |
| r.lock(); |
| if (!this.initialized) { |
| start(); |
| } |
| return this.artefactRepository; |
| } finally { |
| r.unlock(); |
| } |
| } |
| |
| public Date getArtefactRepositoryDate() { |
| try { |
| r.lock(); |
| if (!this.initialized) { |
| start(); |
| } |
| return this.repositoryDate; |
| } finally { |
| r.unlock(); |
| } |
| } |
| |
| public boolean isArtefactRepositoryInitialized() { |
| return this.initialized; |
| } |
| |
| private BundleArtefact getBundle(String symbolicName, OsgiVersion version) { |
| try { |
| r.lock(); |
| for (IArtefact bundle : this.artefactRepository.getBundles()) { |
| if (bundle.getSymbolicName().equals(symbolicName) && bundle.getVersion().equals(version)) { |
| return (BundleArtefact) bundle; |
| } |
| } |
| return null; |
| } finally { |
| r.unlock(); |
| } |
| } |
| |
| private void start() { |
| ArtefactRepositoryStartJob startJob = new ArtefactRepositoryStartJob(); |
| startJob.setPriority(Job.INTERACTIVE); |
| startJob.schedule(); |
| } |
| |
| public void stop() { |
| this.artefactRepository = null; |
| } |
| |
| public void update() { |
| ArtefactRepositoryUpdateJob updateJob = new ArtefactRepositoryUpdateJob(); |
| updateJob.setPriority(Job.INTERACTIVE); |
| updateJob.schedule(); |
| } |
| |
| private boolean alreadyInBundleList(List<BundleArtefact> required, BundleArtefact exporter) { |
| for (BundleArtefact b : required) { |
| if (b.getName().equals(exporter.getName())) { |
| if (b.getVersion().equals(exporter.getVersion())) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private void collectExporters(List<BundleArtefact> required, boolean includeOptional, List<PackageImport> imports, |
| List<PackageImport> unsatisfied) { |
| for (PackageImport imp : imports) { |
| if (includeOptional || !imp.isOptional()) { |
| if (!this.applicationPlatform.isSatisfiedViaSystemBundle(imp)) { |
| // (heuristic: we always prefer to get via system if we can) |
| BundleArtefact exporter = findBestExporter(imp); |
| if (exporter == null) { |
| unsatisfied.add(imp); |
| } else { |
| if (!alreadyInBundleList(required, exporter)) { |
| required.add(exporter); |
| collectExporters(required, includeOptional, exporter.getImports(), unsatisfied); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private BundleArtefact findBestExporter(PackageImport imp) { |
| List<PackageExport> candidates = findPackagesWithExactName(imp.getName()); |
| List<PackageExport> versionMatchedCandidates = new ArrayList<PackageExport>(); |
| for (PackageExport exp : candidates) { |
| if (imp.isSatisfiedBy(exp)) { |
| versionMatchedCandidates.add(exp); |
| } |
| } |
| if (versionMatchedCandidates.size() > 0) { |
| PackageExport withHighestVersion = null; |
| for (PackageExport versionMatchedExport : versionMatchedCandidates) { |
| if (withHighestVersion == null) { |
| withHighestVersion = versionMatchedExport; |
| } else { |
| if (withHighestVersion.getVersion().compareTo(versionMatchedExport.getVersion()) < 0) { |
| withHighestVersion = versionMatchedExport; |
| } |
| } |
| } |
| return withHighestVersion.getBundle(); |
| } |
| return null; |
| } |
| |
| private List<PackageExport> findPackagesWithExactName(String name) { |
| List<PackageExport> exports = new ArrayList<PackageExport>(); |
| for (IArtefactTyped bundle : this.artefactRepository.getBundles()) { |
| for (PackageExport e : ((BundleArtefact) bundle).getExports()) { |
| if (e.getName().equals(name)) { |
| exports.add(e); |
| } |
| } |
| } |
| return exports; |
| } |
| |
| public void addBundleRepositoryChangeListener(IBundleRepositoryChangeListener changeListener) { |
| this.changeListeners.add(changeListener); |
| } |
| |
| public void removeBundleRepositoryChangeListener(IBundleRepositoryChangeListener changeListener) { |
| this.changeListeners.remove(changeListener); |
| } |
| |
| private void fireBundleRepositoryChanged(IRuntime runtime) { |
| for (IBundleRepositoryChangeListener changeListener : this.changeListeners) { |
| changeListener.bundleRepositoryChanged(runtime); |
| } |
| } |
| |
| static class InitializedBundleDefinition implements BundleDefinition { |
| |
| private final URI file; |
| |
| private final BundleManifest manifest; |
| |
| public InitializedBundleDefinition(BundleManifest manifest, URI file) { |
| this.file = file; |
| this.manifest = manifest; |
| } |
| |
| public BundleManifest getManifest() { |
| return this.manifest; |
| } |
| |
| public URI getLocation() { |
| return this.file; |
| } |
| |
| } |
| |
| static class InitializedBundleRepository implements BundleRepository { |
| |
| private final Set<BundleDefinition> bundles; |
| |
| private final Set<LibraryDefinition> libraries; |
| |
| public InitializedBundleRepository(Set<BundleDefinition> bundles, Set<LibraryDefinition> libraries) { |
| this.bundles = bundles; |
| this.libraries = libraries; |
| } |
| |
| public Set<? extends BundleDefinition> findByExportedPackage(String packageName, VersionRange versionRange) { |
| Set<BundleDefinition> matchingBundles = new HashSet<BundleDefinition>(); |
| for (BundleDefinition bundle : this.bundles) { |
| if (bundle.getManifest() != null && bundle.getManifest().getExportPackage() != null) { |
| for (ExportedPackage header : bundle.getManifest().getExportPackage().getExportedPackages()) { |
| if (versionRange.includes(header.getVersion())) { |
| if (packageName.equals(header.getPackageName())) { |
| matchingBundles.add(bundle); |
| } |
| } |
| } |
| } |
| } |
| return matchingBundles; |
| } |
| |
| public Set<? extends BundleDefinition> findByFragmentHost(String bundleSymbolicName, Version version) { |
| return Collections.emptySet(); |
| } |
| |
| public BundleDefinition findBySymbolicName(String symbolicName, VersionRange versionRange) { |
| for (BundleDefinition bundle : this.bundles) { |
| if (bundle.getManifest() != null && bundle.getManifest().getBundleSymbolicName() != null |
| && bundle.getManifest().getBundleSymbolicName().getSymbolicName().equals(symbolicName)) { |
| Version version = bundle.getManifest().getBundleVersion(); |
| if (versionRange.includes(version)) { |
| return bundle; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public LibraryDefinition findLibrary(String libraryName, VersionRange versionRange) { |
| for (LibraryDefinition bundle : this.libraries) { |
| if (bundle.getName() != null && bundle.getName().equals(libraryName)) { |
| if (versionRange.includes(bundle.getVersion())) { |
| return bundle; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public Set<? extends BundleDefinition> getBundles() { |
| return this.bundles; |
| } |
| |
| public Set<? extends LibraryDefinition> getLibraries() { |
| return this.libraries; |
| } |
| |
| public void refresh() { |
| } |
| |
| public ArtifactDescriptor findSubsystem(String arg0) { |
| return null; |
| } |
| |
| } |
| |
| class ArtefactRepositoryStartJob extends Job { |
| |
| private final Bundle bundle = ServerCorePlugin.getDefault().getBundle(); |
| |
| public ArtefactRepositoryStartJob() { |
| super("Initializing Bundle Repository Index"); |
| } |
| |
| private void createArtefactDescriptorFromCurrentZipEntry(ZipInputStream zipInputStream, String newArtefactDescriptorPath) |
| throws IOException, FileNotFoundException { |
| File artefactDescriptor = new File(newArtefactDescriptorPath); |
| if (!artefactDescriptor.exists()) { |
| artefactDescriptor.createNewFile(); |
| FileOutputStream fileOutputStream = new FileOutputStream(artefactDescriptor); |
| byte[] buf = new byte[1024]; |
| int len; |
| while ((len = zipInputStream.read(buf)) > 0) { |
| fileOutputStream.write(buf, 0, len); |
| } |
| fileOutputStream.close(); |
| } |
| } |
| |
| private void createLocalFolderStructure() { |
| File localDirectory = getLocalDirectory(); |
| PublishUtil.deleteDirectory(localDirectory, null); |
| if (!localDirectory.exists()) { |
| localDirectory.mkdir(); |
| new File(localDirectory, "bundles").mkdirs(); |
| new File(localDirectory, "libraries").mkdirs(); |
| } |
| } |
| |
| protected File getLocalDirectory() { |
| IPath path = Platform.getStateLocation(this.bundle); |
| return new File(path.toString() + File.separator + "repository"); |
| } |
| |
| private File getRepositoryIndexFile() { |
| return new File(getLocalDirectory(), ".index"); |
| } |
| |
| protected void writeArchiveContentsToLocalRespositoryDirectory(InputStream zipFileInputStream) throws CoreException { |
| // clean folder |
| createLocalFolderStructure(); |
| |
| ZipInputStream zipInputStream = null; |
| try { |
| zipInputStream = new ZipInputStream(zipFileInputStream); |
| ZipEntry currentZipEntry = zipInputStream.getNextEntry(); |
| |
| while (currentZipEntry != null) { |
| String filePath = getLocalDirectory().getPath() + File.separator + currentZipEntry.getName(); |
| createArtefactDescriptorFromCurrentZipEntry(zipInputStream, filePath); |
| currentZipEntry = zipInputStream.getNextEntry(); |
| } |
| |
| zipInputStream.close(); |
| } catch (IOException e) { |
| StatusUtil.error(e); |
| } finally { |
| try { |
| if (zipInputStream != null) { |
| zipInputStream.close(); |
| } |
| } catch (IOException e) { |
| StatusUtil.error(e); |
| } |
| } |
| } |
| |
| protected IArtefactRepositoryLoader createArtefactRespositoryLoader() { |
| return new JsonArtefactRepositoryLoader(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| |
| // firstly make sure that we have the repository in the in the |
| // workspace |
| Enumeration<URL> urls = this.bundle.findEntries("/index", "repository-*.zip", false); |
| |
| // we will only bundle one repository zip in the plugin so it is |
| // safe to take the |
| // first element |
| if (urls.hasMoreElements()) { |
| URL url = urls.nextElement(); |
| if (!getRepositoryIndexFile().exists()) { |
| writeArchiveContentsToLocalRespositoryDirectory(url.openStream()); |
| } else { |
| // Check if the timestamp on the bundled repository is |
| // newer than the |
| // extracted |
| String path = url.getFile(); |
| int ix = path.lastIndexOf("repository-"); |
| Long timestamp = Long.valueOf(path.substring(ix + 11, path.length() - 4)); |
| Long localTimestamp = getLocalRepositoryTimestamp(); |
| if (timestamp.compareTo(localTimestamp) == 1) { |
| writeArchiveContentsToLocalRespositoryDirectory(url.openStream()); |
| } |
| } |
| } |
| |
| ArtefactRepositoryManager.this.initialized = true; |
| |
| // secondly load the repository in memory |
| ArtefactRepository newArtefactRepository = createArtefactRespositoryLoader().loadArtefactRepository(getLocalDirectory()); |
| try { |
| w.lock(); |
| ArtefactRepositoryManager.this.artefactRepository = newArtefactRepository; |
| ArtefactRepositoryManager.this.repositoryDate = new Date(getLocalRepositoryTimestamp()); |
| } finally { |
| w.unlock(); |
| } |
| } catch (Exception e) { |
| StatusUtil.error(e); |
| ArtefactRepositoryManager.this.initialized = true; |
| ArtefactRepositoryManager.this.artefactRepository = new ArtefactRepository(); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| protected Long getLocalRepositoryTimestamp() throws IOException, FileNotFoundException { |
| FileInputStream is = null; |
| try { |
| is = new FileInputStream(getRepositoryIndexFile()); |
| Properties properties = new Properties(); |
| properties.load(is); |
| Long localTimestamp = Long.valueOf(properties.getProperty("creation.timestamp")); |
| return localTimestamp; |
| } finally { |
| if (is != null) { |
| is.close(); |
| } |
| } |
| } |
| |
| } |
| |
| public class ArtefactRepositoryUpdateJob extends ArtefactRepositoryStartJob { |
| |
| /** |
| * The url under which the newest version of the repository index will be published |
| */ |
| private static final String REPOSITORY_INDEX_URL = "http://static.springsource.com/projects/sts-dm-server/index/repository.zip"; |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| |
| try { |
| // first download file from the online repository |
| Date lastModifiedDate = WebDownloadUtils.getLastModifiedDate(REPOSITORY_INDEX_URL, monitor); |
| if (lastModifiedDate != null && Long.valueOf(lastModifiedDate.getTime()).compareTo(getLocalRepositoryTimestamp()) == 1) { |
| File repositoryArchive = WebDownloadUtils.downloadFile(REPOSITORY_INDEX_URL, getLocalDirectory().getParentFile(), monitor); |
| |
| if (repositoryArchive != null) { |
| |
| // secondly extract it to the local repository location |
| if (repositoryArchive.exists() && repositoryArchive.canRead()) { |
| FileInputStream is = null; |
| try { |
| is = new FileInputStream(repositoryArchive); |
| writeArchiveContentsToLocalRespositoryDirectory(is); |
| } finally { |
| if (is != null) { |
| try { |
| is.close(); |
| } catch (Exception e) { |
| // ignore |
| } |
| } |
| } |
| } |
| |
| // thirdly load the repository in memory |
| ArtefactRepository newArtefactRepository = createArtefactRespositoryLoader().loadArtefactRepository(getLocalDirectory()); |
| try { |
| w.lock(); |
| ArtefactRepositoryManager.this.artefactRepository = newArtefactRepository; |
| ArtefactRepositoryManager.this.repositoryDate = new Date(getLocalRepositoryTimestamp()); |
| } finally { |
| w.unlock(); |
| } |
| } |
| } |
| } catch (FileNotFoundException e) { |
| StatusUtil.error(e); |
| } catch (IOException e) { |
| StatusUtil.error(e); |
| } catch (CoreException e) { |
| StatusUtil.error(e); |
| } |
| |
| return Status.OK_STATUS; |
| } |
| } |
| |
| // TODO why is this here? |
| public static byte[] convert(String string) { |
| if (string == null) { |
| return null; |
| } |
| String newString = new String(string); |
| try { |
| return newString.getBytes("UTF-8"); |
| } catch (UnsupportedEncodingException e) { |
| StatusManager.getManager().handle(new Status(IStatus.WARNING, ServerCorePlugin.PLUGIN_ID, "Couldn't convert string.", e)); |
| return string.getBytes(); |
| } |
| } |
| } |