/** * Copyright (c) 2011 Cloudsmith Inc. and other contributors, as listed below. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cloudsmith * */ package org.cloudsmith.geppetto.forge.impl; import static org.cloudsmith.geppetto.diagnostic.Diagnostic.ERROR; import static org.cloudsmith.geppetto.diagnostic.Diagnostic.INFO; import static org.cloudsmith.geppetto.diagnostic.Diagnostic.WARNING; import static org.cloudsmith.geppetto.forge.Forge.FORGE; import static org.cloudsmith.geppetto.forge.Forge.METADATA_JSON_NAME; import static org.cloudsmith.geppetto.forge.Forge.MODULE_FILE_FILTER; import static org.cloudsmith.geppetto.forge.Forge.PUBLISHER; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.zip.GZIPInputStream; import org.apache.http.HttpStatus; import org.apache.http.client.HttpResponseException; import org.cloudsmith.geppetto.common.os.FileUtils; import org.cloudsmith.geppetto.common.os.StreamUtil; import org.cloudsmith.geppetto.diagnostic.Diagnostic; import org.cloudsmith.geppetto.diagnostic.ExceptionDiagnostic; import org.cloudsmith.geppetto.forge.AlreadyPublishedException; import org.cloudsmith.geppetto.forge.Cache; import org.cloudsmith.geppetto.forge.Forge; import org.cloudsmith.geppetto.forge.ForgeService; import org.cloudsmith.geppetto.forge.client.ForgeException; import org.cloudsmith.geppetto.forge.util.ModuleUtils; import org.cloudsmith.geppetto.forge.util.TarUtils; import org.cloudsmith.geppetto.forge.v1.model.ModuleInfo; import org.cloudsmith.geppetto.forge.v2.MetadataRepository; import org.cloudsmith.geppetto.forge.v2.model.Dependency; import org.cloudsmith.geppetto.forge.v2.model.Metadata; import org.cloudsmith.geppetto.forge.v2.model.Module; import org.cloudsmith.geppetto.forge.v2.model.ModuleName; import org.cloudsmith.geppetto.forge.v2.model.Release; import org.cloudsmith.geppetto.forge.v2.service.ModuleService; import org.cloudsmith.geppetto.forge.v2.service.ReleaseService; import org.cloudsmith.geppetto.semver.VersionRange; import com.google.inject.Inject; import com.google.inject.name.Named; class ForgeServiceImpl implements ForgeService { @Inject private Cache cache; @Inject private ModuleService moduleService; @Inject private org.cloudsmith.geppetto.forge.v1.service.ModuleService moduleServiceV1; @Inject private ReleaseService releaseService; @Inject private MetadataRepository metadataRepo; @Inject @Named(MODULE_FILE_FILTER) private FileFilter moduleFileFilter; @Inject private Forge forgeUtil; @Override public Collection<File> downloadDependencies(Iterable<Metadata> metadatas, File importedModulesDir, Diagnostic result) throws IOException { Set<Dependency> unresolvedCollector = new HashSet<Dependency>(); Set<Release> releasesToDownload = resolveDependencies(metadatas, unresolvedCollector); for(Dependency unresolved : unresolvedCollector) result.addChild(new Diagnostic(WARNING, FORGE, String.format( "Unable to resolve dependency: %s:%s", unresolved.getName(), unresolved.getVersionRequirement().toString()))); if(!releasesToDownload.isEmpty()) { importedModulesDir.mkdirs(); List<File> importedModuleLocations = new ArrayList<File>(); for(Release release : releasesToDownload) { result.addChild(new Diagnostic(INFO, FORGE, "Installing dependent module " + release.getFullName() + ':' + release.getVersion())); StringBuilder bld = new StringBuilder(); ModuleUtils.buildFileName(release.getFullName(), release.getVersion(), bld); File moduleDir = new File(importedModulesDir, bld.toString()); install(release, moduleDir, true, false); importedModuleLocations.add(moduleDir); } return importedModuleLocations; } if(unresolvedCollector.isEmpty()) result.addChild(new Diagnostic(INFO, FORGE, "No additional dependencies were detected")); return Collections.emptyList(); } @Override public Metadata install(ModuleName moduleName, VersionRange range, File destination, boolean destinationIncludesTopFolder, boolean force) throws IOException { if(moduleService == null || cache == null) throw new UnsupportedOperationException( "Unable to install since no module service is configured. Was a serviceURL provided in the preferences?"); List<Release> releases = moduleService.getReleases(moduleName.getOwner(), moduleName.getName(), null); if(releases.isEmpty()) throw new FileNotFoundException("No releases found for module '" + moduleName + '\''); Release best = null; for(Release release : releases) if((best == null || release.getVersion().compareTo(best.getVersion()) > 0) && (range == null || range.isIncluded(release.getVersion()))) best = release; if(best == null) throw new FileNotFoundException("No releases matching '" + range + "' found for module '" + moduleName + '\''); if(!destinationIncludesTopFolder) // Use module name as the default destination = new File(destination, moduleName.getName()); if(destination.exists()) { if(!force) throw new IOException("Destination folder is not empty: " + destination.getAbsolutePath()); // Don't remove .project, .settings, .git, .svn, etc. if they are present. FileUtils.rmR(destination, FileUtils.DEFAULT_EXCLUDES); } File moduleFile = cache.retrieve(best.getFullName(), best.getVersion()); // Unpack closes its input. TarUtils.unpack(new GZIPInputStream(new FileInputStream(moduleFile)), destination, true, null); return forgeUtil.loadJSONMetadata(new File(destination, METADATA_JSON_NAME)); } @Override public Metadata install(Release release, File destination, boolean destinationIncludesTopFolder, boolean force) throws IOException { return install( release.getFullName(), VersionRange.exact(release.getVersion()), destination, destinationIncludesTopFolder, force); } @Override public void publish(File moduleArchive, boolean dryRun, Diagnostic result) throws IOException { if(releaseService == null) throw new UnsupportedOperationException( "Unable to publish since no release service is configured. Was a serviceURL provided in the preferences?"); Metadata metadata = forgeUtil.getMetadataFromPackage(moduleArchive); if(metadata == null) throw new ForgeException("No \"metadata.json\" found in archive: " + moduleArchive.getAbsolutePath()); if(metadata.getName() == null) throw new ForgeException("The \"metadata.json\" found in archive: " + moduleArchive.getAbsolutePath() + " has no name"); if(metadata.getVersion() == null) throw new ForgeException("The \"metadata.json\" found in archive: " + moduleArchive.getAbsolutePath() + " has no version"); try { if(metadataRepo.resolve(metadata.getName(), metadata.getVersion()) != null) throw new AlreadyPublishedException("Module " + metadata.getName() + ':' + metadata.getVersion() + " has already been published"); } catch(HttpResponseException e) { // A SC_NOT_FOUND can be expected and is OK. if(e.getStatusCode() != HttpStatus.SC_NOT_FOUND) throw new ForgeException("Unable to check module existence on the forge: " + e.getMessage()); } if(dryRun) { result.addChild(new Diagnostic(INFO, PUBLISHER, "Module file " + moduleArchive.getName() + " would have been uploaded (but wasn't since this is a dry run)")); return; } InputStream gzInput = new FileInputStream(moduleArchive); try { ModuleName name = metadata.getName(); releaseService.create( name.getOwner(), name.getName(), "Published using GitHub trigger", gzInput, moduleArchive.length()); result.addChild(new Diagnostic(INFO, PUBLISHER, "Module file " + moduleArchive.getName() + " has been uploaded")); } finally { StreamUtil.close(gzInput); } } public void publishAll(File[] builtModules, boolean dryRun, Diagnostic result) { boolean noPublishingMade = true; for(File builtModule : builtModules) { String name = builtModule.getName(); if(!(name.endsWith(".tar.gz") || name.endsWith(".tgz"))) continue; try { publish(builtModule, dryRun, result); noPublishingMade = false; continue; } catch(AlreadyPublishedException e) { result.addChild(new Diagnostic(WARNING, PUBLISHER, e.getMessage())); continue; } catch(ForgeException e) { result.addChild(new Diagnostic(ERROR, PUBLISHER, e.getMessage())); } catch(Exception e) { result.addChild(new ExceptionDiagnostic(ERROR, PUBLISHER, "Unable to publish module " + builtModule.getName(), e)); } return; } if(noPublishingMade) { result.addChild(new Diagnostic( INFO, PUBLISHER, "All modules have already been published at their current version")); } } @Override public Set<Release> resolveDependencies(Iterable<Metadata> metadatas, Set<Dependency> unresolvedCollector) throws IOException { // Resolve missing dependencies Set<Dependency> deps = new HashSet<Dependency>(); for(Metadata metadata : metadatas) deps.addAll(metadata.getDependencies()); // Remove the dependencies that appoints modules that we have in the // workspace Iterator<Dependency> depsItor = deps.iterator(); nextDep: while(depsItor.hasNext()) { Dependency dep = depsItor.next(); for(Metadata metadata : metadatas) if(dep.matches(metadata)) { depsItor.remove(); continue nextDep; } } // Resolve remaining dependencies Set<Release> releasesToDownload = new HashSet<Release>(); if(!deps.isEmpty()) { if(metadataRepo == null) throw new UnsupportedOperationException( "Unable to resolve dependencies since no forge service is configured. Was a serviceURL provided in the preferences?"); for(Dependency dep : deps) releasesToDownload.addAll(metadataRepo.deepResolve(dep, unresolvedCollector)); } return releasesToDownload; } @Override public List<Module> search(String term) throws IOException { return moduleService.search(term, null); } @Override public List<ModuleInfo> search_v1(String term) throws IOException { return moduleServiceV1.search(term); } }