/* * Copyright 2009 NCHOVY * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.krakenapps.pkg; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.Charset; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.JarFile; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; import org.apache.commons.httpclient.HttpException; import org.krakenapps.api.AlreadyInstalledPackageException; import org.krakenapps.api.BundleDescriptor; import org.krakenapps.api.BundleManager; import org.krakenapps.api.BundleRepository; import org.krakenapps.api.BundleRequirement; import org.krakenapps.api.KeyStoreManager; import org.krakenapps.api.MavenArtifact; import org.krakenapps.api.MavenResolveException; import org.krakenapps.api.PackageDescriptor; import org.krakenapps.api.PackageIndex; import org.krakenapps.api.PackageManager; import org.krakenapps.api.PackageMetadata; import org.krakenapps.api.PackageNotFoundException; import org.krakenapps.api.PackageRepository; import org.krakenapps.api.PackageUpdatePlan; import org.krakenapps.api.PackageVersionHistory; import org.krakenapps.api.ProgressMonitor; import org.krakenapps.api.Version; import org.krakenapps.api.VersionRange; import org.krakenapps.confdb.ConfigService; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.ServiceReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PackageManagerService implements PackageManager { private final Logger logger = LoggerFactory.getLogger(PackageManagerService.class.getName()); private PackageDatabase db; private BundleContext bc; private BundleManager bundleManager; public PackageManagerService(BundleContext bc) { this.bc = bc; ServiceReference ref = bc.getServiceReference(BundleManager.class.getName()); bundleManager = (BundleManager) bc.getService(ref); db = new PackageDatabase(getConfigService()); } @Override public List<PackageDescriptor> getInstalledPackages() { return db.getInstalledPackages(); } @Override public void updatePackage(String packageName, Version version, ProgressMonitor monitor) throws PackageNotFoundException, MavenResolveException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { PackageUpdatePlan dep = getUpdatePlan(packageName, version); PackageMetadata metadata = downloadMetadata(packageName); PackageVersionHistory choice = findPackageVersionHistory(version, metadata); PackageDescriptor newPkg = downloadPackageDesc(metadata, choice); if (dep.getRemovingBundles().size() > 0) monitor.writeln("Removing Bundles"); for (BundleDescriptor bundle : dep.getRemovingBundles()) { monitor.writeln(" -> Removing: " + bundle.getSymbolicName() + " " + bundle.getVersion()); bundleManager.uninstallBundle(bundle.getBundleId()); } monitor.writeln("Installing Bundles"); try { installMavenArtifacts(metadata, newPkg, monitor); // force bundle refresh bundleManager.refresh(); startBundles(newPkg, monitor); db.updatePackage(newPkg); } catch (BundleException e) { throw new RuntimeException(e); } } public void installPackage(String packageName, String version, ProgressMonitor monitor) throws AlreadyInstalledPackageException, PackageNotFoundException, MavenResolveException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { PackageDescriptor pkg = db.findInstalledPackage(packageName); if (pkg != null) throw new AlreadyInstalledPackageException(pkg); PackageMetadata metadata = downloadMetadata(packageName); if (metadata == null) throw new PackageNotFoundException(packageName); PackageVersionHistory ver = selectVersion(version, metadata); if (ver == null) throw new PackageNotFoundException(packageName); PackageDescriptor newPkg = downloadPackageDesc(metadata, ver); // download description and download maven artifacts logger.info("package manager: downloading {}", newPkg); try { // download maven bundles and inspect bundle metadata installMavenArtifacts(metadata, newPkg, monitor); // force bundle refresh bundleManager.refresh(); // start bundles startBundles(newPkg, monitor); db.installPackage(newPkg); } catch (BundleException e) { throw new RuntimeException(e); } } @Override public List<PackageIndex> getPackageIndexes() { List<PackageIndex> list = new ArrayList<PackageIndex>(); for (PackageRepository repo : db.getPackageRepositories()) { try { list.add(getPackageIndex(repo)); } catch (Throwable t) { logger.error("kraken core: cannot download package index", t); } } return list; } @Override public PackageIndex getPackageIndex(PackageRepository repo) { try { URL url = new URL(normalize(repo.getUrl()) + "kraken-package.index"); byte[] body = download(repo, url); return PackageIndexParser.parse(repo, body); } catch (Throwable t) { throw new IllegalStateException("cannot download package index from " + repo.getUrl(), t); } } private PackageVersionHistory selectVersion(String version, PackageMetadata metadata) { if (version == null) return metadata.getVersions().get(0); Version v = new Version(version); for (PackageVersionHistory h : metadata.getVersions()) if (h.getVersion().equals(v)) return h; return null; } private void startBundles(PackageDescriptor pkg, ProgressMonitor monitor) { monitor.writeln("Starting Bundles"); Map<String, Bundle> bundleMap = new HashMap<String, Bundle>(); for (Bundle bundle : bc.getBundles()) { bundleMap.put(bundle.getSymbolicName(), bundle); } // start bundles in order for (String symbolicName : pkg.getStartBundleNames()) { Bundle bundle = bundleMap.get(symbolicName); if (bundle == null) { monitor.writeln(String.format(" -> [FAIL] %s not found", symbolicName)); continue; } if (bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED) { try { bundle.start(); monitor.writeln(String.format(" -> [OK] %s %s", bundle.getSymbolicName(), bundle.getVersion())); } catch (BundleException e) { monitor.writeln(String.format(" -> [FAIL] %s %s: %s", bundle.getSymbolicName(), bundle.getVersion(), e.getMessage())); } } } } private void installMavenArtifacts(PackageMetadata metadata, PackageDescriptor newPkg, ProgressMonitor monitor) throws MavenResolveException, BundleException { // prepare repository set Set<String> urls = metadata.getMavenRepositories(); List<BundleRepository> configs = bundleManager.getRepositories(); List<BundleRepository> remotes = new ArrayList<BundleRepository>(); for (BundleRepository c : configs) { URL url = c.getUrl(); if (urls.contains(url)) urls.remove(url); remotes.add(c); } for (String url : urls) { try { remotes.add(new BundleRepository("", new URL(url))); } catch (MalformedURLException e) { } } File localRepository = new File(getDownloadRoot()); KeyStoreManager keyStoreManager = getKeyStoreManager(); for (MavenArtifact artifact : newPkg.getMavenArtifacts()) { monitor.writeln("Resolving " + artifact); File file = new MavenResolver(localRepository, remotes, monitor, keyStoreManager).resolve(artifact); monitor.writeln(" -> resolved"); String bundlePath = null; if (File.separatorChar == '\\') bundlePath = "file:///" + file.getAbsolutePath().replace('\\', '/'); else bundlePath = "file://" + file.getAbsolutePath(); if (isInstallRequired(file, newPkg)) { String symbolicName = getBundleSymbolicName(file); Version version = getBundleVersion(file); monitor.writeln(" -> installing: " + symbolicName + " " + version); bc.installBundle(bundlePath); } monitor.writeln(""); } } private String getDownloadRoot() { return new File(System.getProperty("kraken.download.dir")).getAbsolutePath(); } private String getBundleSymbolicName(File file) { JarFile jar = null; try { jar = new JarFile(file); Attributes attrs = jar.getManifest().getMainAttributes(); // metadata can be added followed by semicolon (e.g. ;singleton) return attrs.getValue("Bundle-SymbolicName").split(";")[0].trim(); } catch (IOException e) { logger.error("package manager: symbolic name not found", e); return null; } finally { if (jar != null) try { jar.close(); } catch (IOException e) { } } } private Version getBundleVersion(File file) { JarFile jar = null; try { jar = new JarFile(file); Attributes attrs = jar.getManifest().getMainAttributes(); return new Version(attrs.getValue("Bundle-Version")); } catch (IOException e) { logger.error("package manager: bundle version not found", e); return null; } finally { if (jar != null) try { jar.close(); } catch (IOException e) { } } } private boolean isInstallRequired(File file, PackageDescriptor packageDesc) { String bundleSymbolicName = getBundleSymbolicName(file); Version bundleVersion = getBundleVersion(file); // find related bundle requirement VersionRange requiredRange = null; for (BundleRequirement bundleDesc : packageDesc.getBundleRequirements()) { if (bundleSymbolicName.equals(bundleDesc.getName())) { requiredRange = bundleDesc.getVersionRange(); break; } } if (requiredRange == null) { String log = "package manager: cannot find version requirement for {}, check package syntax."; logger.warn(log, bundleSymbolicName); return false; } for (Bundle bundle : bc.getBundles()) { if (!bundle.getSymbolicName().equals(bundleSymbolicName)) continue; // found bundle, but is it satisfy requirement? Version current = new Version(bundle.getVersion().toString()); if (requiredRange.contains(current)) { logger.info("package manager: no install required - {} {}", bundle.getSymbolicName(), current); return false; } } // verify new bundle version if (!requiredRange.contains(bundleVersion)) { logger.error("package manager: new version also cannot satisfy requirement: {}", bundleVersion); return false; } return true; } @Override public Map<String, List<PackageDescriptor>> checkUninstallDependency(String packageName) throws PackageNotFoundException { PackageDescriptor pkg = getInstalledPackage(packageName); Set<BundleDescriptor> relatedBundles = findRelatedBundles(pkg); List<PackageDescriptor> packages = db.getInstalledPackages(); // bundle -> package desc list Map<String, List<PackageDescriptor>> dependMap = new HashMap<String, List<PackageDescriptor>>(); for (BundleDescriptor bundle : relatedBundles) { for (PackageDescriptor desc : packages) { // skip uninstall target package if (desc.getName().equals(packageName)) continue; Version version = new Version(bundle.getVersion().toString()); if (isUsedByPackage(bundle.getSymbolicName(), version, desc)) { List<PackageDescriptor> l = dependMap.get(bundle.getSymbolicName()); if (l == null) { l = new ArrayList<PackageDescriptor>(); dependMap.put(bundle.getSymbolicName(), l); } l.add(desc); } } } return dependMap; } @Override public Set<BundleDescriptor> findRelatedBundles(PackageDescriptor pkg) { Map<String, BundleRequirement> reqMap = new HashMap<String, BundleRequirement>(); for (BundleRequirement req : pkg.getBundleRequirements()) { reqMap.put(req.getName(), req); } Set<BundleDescriptor> relatedBundles = new HashSet<BundleDescriptor>(); for (Bundle bundle : bc.getBundles()) { BundleRequirement req = reqMap.get(bundle.getSymbolicName()); if (req == null) continue; if (req.getVersionRange().contains(new Version(bundle.getVersion().toString()))) relatedBundles.add(new BundleDescriptor(bundle.getBundleId(), bundle.getSymbolicName(), bundle.getVersion() .toString())); } return relatedBundles; } private boolean isUsedByPackage(String name, Version version, PackageDescriptor pkg) { for (BundleRequirement req : pkg.getBundleRequirements()) { if (!name.equals(req.getName())) continue; if (req.getVersionRange().contains(version)) return true; } return false; } @Override public PackageUpdatePlan getUpdatePlan(String packageName, Version version) throws PackageNotFoundException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { PackageUpdatePlan dep = new PackageUpdatePlan(); PackageMetadata metadata = downloadMetadata(packageName); if (metadata == null) throw new PackageNotFoundException(packageName); PackageVersionHistory choice = findPackageVersionHistory(version, metadata); PackageDescriptor newPkg = downloadPackageDesc(metadata, choice); PackageDescriptor oldPkg = getInstalledPackage(packageName); Set<BundleDescriptor> bundles = findRelatedBundles(oldPkg); // add all to removing bundles first for (BundleDescriptor bundle : bundles) if (isUsedByPackage(bundle, oldPkg.getName())) dep.getRemainingBundles().add(bundle); else dep.getRemovingBundles().add(bundle); // find install required bundles for (BundleRequirement req : newPkg.getBundleRequirements()) { BundleDescriptor bundle = findMatchedBundle(req, bundles); if (bundle != null) { dep.getRemovingBundles().remove(bundle); dep.getRemainingBundles().add(bundle); } else { dep.getInstallingBundles().add(req); } } return dep; } private boolean isUsedByPackage(BundleDescriptor bundle, String exceptPackageName) { for (PackageDescriptor pkg : getInstalledPackages()) { if (pkg.getName().equals(exceptPackageName)) continue; String name = bundle.getSymbolicName(); Version version = new Version(bundle.getVersion().toString()); if (isUsedByPackage(name, version, pkg)) return true; } return false; } private PackageVersionHistory findPackageVersionHistory(Version version, PackageMetadata metadata) { PackageVersionHistory choice = null; for (PackageVersionHistory it : metadata.getVersions()) if (it.getVersion().equals(version)) choice = it; return choice; } private BundleDescriptor findMatchedBundle(BundleRequirement req, Set<BundleDescriptor> bundles) { for (BundleDescriptor bundle : bundles) { if (req.getName().equals(bundle.getSymbolicName()) && req.getVersionRange().contains(new Version(bundle.getVersion().toString()))) return bundle; } return null; } @Override public void uninstallPackage(String packageName, ProgressMonitor monitor) throws PackageNotFoundException { PackageDescriptor pkg = findInstalledPackage(packageName); Map<String, List<PackageDescriptor>> dependMap = checkUninstallDependency(packageName); for (BundleDescriptor bundle : findRelatedBundles(pkg)) { if (!dependMap.containsKey(bundle.getSymbolicName())) { monitor.writeln(" Removing: " + bundle.getSymbolicName() + " " + bundle.getVersion()); bundleManager.uninstallBundle(bundle.getBundleId()); } } db.uninstallPackage(pkg.getName()); } @Override public List<PackageRepository> getRepositories() { return db.getPackageRepositories(); } @Override public PackageRepository getRepository(String alias) { return db.getPackageRepository(alias); } @Override public void createRepository(PackageRepository repo) { db.createPackageRepository(repo); } @Override public void updateRepository(PackageRepository repo) { db.updatePackageRepository(repo); } @Override public void addRepository(String alias, URL url) { createRepository(PackageRepository.create(alias, url)); } @Override public void addSecureRepository(String alias, URL url, String trustStoreAlias, String keyStoreAlias) { createRepository(PackageRepository.createHttps(alias, url, trustStoreAlias, keyStoreAlias)); } @Override public void removeRepository(String alias) { db.removePackageRepository(alias); } private PackageDescriptor getInstalledPackage(String packageName) throws PackageNotFoundException { PackageDescriptor installedPackage = db.findInstalledPackage(packageName); if (installedPackage == null) throw new PackageNotFoundException(packageName); return installedPackage; } private PackageDescriptor downloadPackageDesc(PackageMetadata metadata, PackageVersionHistory history) throws PackageNotFoundException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { PackageRepository repository = metadata.getRepository(); String pacakgeName = metadata.getName(); Version version = history.getVersion(); try { URL url = new URL(normalize(repository.getUrl()) + pacakgeName + "/" + version + "/kraken.package"); String body = downloadString(repository, url); return PackageDescParser.parse(metadata, history, body); } catch (HttpException e) { logger.error("package manager: http exception", e); } catch (IOException e) { logger.error("package manager: io exception", e); } throw new PackageNotFoundException(metadata.getName()); } private PackageMetadata downloadMetadata(String packageName) throws KeyStoreException, UnrecoverableKeyException, KeyManagementException { List<PackageRepository> repositories = getRepositories(); PackageMetadata metadata = null; for (PackageRepository repo : repositories) { metadata = downloadMetadata(repo, packageName); if (metadata != null) break; } return metadata; } private PackageMetadata downloadMetadata(PackageRepository repository, String packageName) throws KeyStoreException, UnrecoverableKeyException, KeyManagementException { try { URL url = new URL(normalize(repository.getUrl()) + packageName + "/kraken.package"); String body = downloadString(repository, url); PackageMetadata metadata = PackageMetadataParser.parse(body); metadata.setName(packageName); metadata.setRepository(repository); return metadata; } catch (IOException e) { return null; } } private byte[] download(PackageRepository repo, URL url) throws IOException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { if (repo.isLocalFilesystem()) { try { File file = new File(url.toURI()); long length = file.length(); FileInputStream stream = new FileInputStream(file); byte[] b = new byte[(int) length]; stream.read(b); return b; } catch (URISyntaxException e) { e.printStackTrace(); return new byte[0]; } } else if (repo.isHttps()) { ServiceReference ref = bc.getServiceReference(KeyStoreManager.class.getName()); KeyStoreManager keyman = (KeyStoreManager) bc.getService(ref); try { TrustManagerFactory tmf = keyman.getTrustManagerFactory(repo.getTrustStoreAlias(), "SunX509"); KeyManagerFactory kmf = keyman.getKeyManagerFactory(repo.getKeyStoreAlias(), "SunX509"); HttpWagon.download(url, tmf, kmf); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return HttpWagon.download(url); } else if (repo.isAuthRequired()) return HttpWagon.download(url, true, repo.getAccount(), repo.getPassword()); return HttpWagon.download(url); } private String downloadString(PackageRepository repo, URL url) throws IOException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { if (repo.isLocalFilesystem()) { try { File file = new File(url.toURI()); long length = file.length(); FileInputStream stream = new FileInputStream(file); byte[] b = new byte[(int) length]; stream.read(b); return new String(b, Charset.forName("UTF-8")); } catch (URISyntaxException e) { e.printStackTrace(); return new String(); } } else if (repo.isHttps()) { ServiceReference ref = bc.getServiceReference(KeyStoreManager.class.getName()); KeyStoreManager keyman = (KeyStoreManager) bc.getService(ref); try { TrustManagerFactory tmf = keyman.getTrustManagerFactory(repo.getTrustStoreAlias(), "SunX509"); KeyManagerFactory kmf = keyman.getKeyManagerFactory(repo.getKeyStoreAlias(), "SunX509"); HttpWagon.download(url, tmf, kmf); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return HttpWagon.downloadString(url); } else if (repo.isAuthRequired()) return HttpWagon.downloadString(url, repo.getAccount(), repo.getPassword()); return HttpWagon.downloadString(url); } private String normalize(URL repository) { String repoUrl = repository.toString(); char lastChar = repoUrl.charAt(repoUrl.length() - 1); if (lastChar != '/') return repoUrl + "/"; return repoUrl; } @Override public PackageVersionHistory getLatestVersion(String packageName) throws PackageNotFoundException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { PackageMetadata metadata = downloadMetadata(packageName); if (metadata == null) throw new PackageNotFoundException(packageName); return metadata.getVersions().get(0); } @Override public PackageVersionHistory checkUpdate(String packageName) throws PackageNotFoundException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { PackageMetadata metadata = downloadMetadata(packageName); if (metadata == null) throw new PackageNotFoundException(packageName); PackageVersionHistory history = metadata.getVersions().get(0); PackageDescriptor installedPackage = getInstalledPackage(packageName); if (needUpdate(installedPackage, history)) return history; return null; } private boolean needUpdate(PackageDescriptor installedPackage, PackageVersionHistory history) { int versionDiff = history.getVersion().compareTo(installedPackage.getVersion()); Date currentDate = installedPackage.getDate(); Date latestDate = history.getLastUpdated(); if (versionDiff > 0 || (versionDiff == 0 && currentDate.compareTo(latestDate) < 0)) return true; if (versionDiff == 0 && currentDate.compareTo(latestDate) > 0) { logger.warn("repository version is older. curious."); } return false; } @Override public PackageDescriptor findInstalledPackage(String name) { return db.findInstalledPackage(name); } private ConfigService getConfigService() { ServiceReference ref = bc.getServiceReference(ConfigService.class.getName()); return (ConfigService) bc.getService(ref); } private KeyStoreManager getKeyStoreManager() { ServiceReference ref = bc.getServiceReference(KeyStoreManager.class.getName()); KeyStoreManager keyman = (KeyStoreManager) bc.getService(ref); return keyman; } }