package restx.plugins; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.io.Files; import org.apache.ivy.Ivy; import org.apache.ivy.core.LogOptions; import org.apache.ivy.core.module.descriptor.DefaultExcludeRule; import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor; import org.apache.ivy.core.module.id.ArtifactId; import org.apache.ivy.core.module.id.ModuleId; import org.apache.ivy.core.module.id.ModuleRevisionId; import org.apache.ivy.core.report.ArtifactDownloadReport; import org.apache.ivy.core.report.DownloadStatus; import org.apache.ivy.core.report.ResolveReport; import org.apache.ivy.core.resolve.ResolveOptions; import org.apache.ivy.plugins.matcher.ExactOrRegexpPatternMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.regex.Pattern; import static com.google.common.collect.Iterables.addAll; /** * User: xavierhanin * Date: 5/4/13 * Time: 2:46 PM */ public class ModulesManager { private static final Logger logger = LoggerFactory.getLogger(ModulesManager.class); private static final Pattern MRID_PATTERN = Pattern.compile("(.+):(.+):(.+)"); private final ObjectMapper mapper = new ObjectMapper(); private final URL url; private final Ivy ivy; public static class DownloadOptions { public static final DownloadOptions DEFAULT = new DownloadOptions(Collections.<String>emptyList(), true, false); private final ImmutableList<String> exclusions; private final boolean transitive; private final boolean changing; public DownloadOptions(List<String> exclusions, boolean transitive, boolean changing) { this.exclusions = ImmutableList.copyOf(exclusions); this.transitive = transitive; this.changing = changing; } public ImmutableList<String> getExclusions() { return exclusions; } public boolean isTransitive() { return transitive; } public boolean isChanging() { return changing; } public static class Builder { private List<String> exclusions; private boolean transitive; private boolean changing; public Builder(){ this.exclusions = new ArrayList<>(DEFAULT.exclusions); this.transitive = DEFAULT.transitive; this.changing = DEFAULT.changing; } public Builder transitive(boolean transitive) { this.transitive = transitive; return this; } public Builder changing(boolean changing) { this.changing = changing; return this; } public Builder exclusions(List<String> exclusions) { this.exclusions = exclusions; return this; } public DownloadOptions build(){ return new DownloadOptions(this.exclusions, this.transitive, this.changing); } } } public ModulesManager(URL url, Ivy ivy) { this.url = url; this.ivy = ivy; } public List<ModuleDescriptor> searchModules(String q) throws IOException { if (q.toLowerCase(Locale.ENGLISH).startsWith("category=")) { String category = q.substring("category=".length()).toLowerCase(Locale.ENGLISH); List<ModuleDescriptor> mds = loadAllRestxModuleDescriptors(); List<ModuleDescriptor> modules = new ArrayList<>(); for (ModuleDescriptor md : mds) { if (md.getCategory().equals(category)) { modules.add(md); } } return modules; } else { throw new UnsupportedOperationException("querying for modules other than by category is not supported yet."); } } public List<File> download(List<ModuleDescriptor> modules, File toDir, DownloadOptions opts) throws IOException { if (!toDir.exists()) { if (!toDir.mkdirs()) { throw new IOException("can't create directory " + toDir); } } List<File> files = new ArrayList<>(); for (ModuleDescriptor module : modules) { ivy.pushContext(); // DefaultModuleDescriptor access Ivy current context, so we need to push it. try { DefaultModuleDescriptor md = DefaultModuleDescriptor.newCallerInstance( toMrid(module.getId()), new String[]{"master", "runtime"}, opts.isTransitive(), opts.isChanging()); for (String exclude : opts.getExclusions()) { DefaultExcludeRule rule = new DefaultExcludeRule( new ArtifactId(toModuleId(exclude), ".*", ".*", ".*"), new ExactOrRegexpPatternMatcher(), null); rule.addConfiguration("master"); rule.addConfiguration("runtime"); md.addExcludeRule(rule); } ResolveReport report = ivy.resolve(md, (ResolveOptions) new ResolveOptions() .setLog(LogOptions.LOG_QUIET) ); if (!report.getAllProblemMessages().isEmpty()) { throw new IllegalStateException("plugin installation failed: " + module.getId() + "\n" + Joiner.on("\n").join(report.getAllProblemMessages())); } for (ArtifactDownloadReport artifactDownloadReport : report.getAllArtifactsReports()) { if (artifactDownloadReport.getDownloadStatus().equals(DownloadStatus.FAILED)) { logger.warn(String.format("artifact download %s.%s failed", artifactDownloadReport.getName(), artifactDownloadReport.getExt())); continue; } File localFile = artifactDownloadReport.getLocalFile(); File to = new File(toDir, artifactDownloadReport.getName() + "." + artifactDownloadReport.getExt()); Files.copy(localFile, to); files.add(to); } } catch (ParseException e) { throw new RuntimeException(e); } finally { ivy.popContext(); } } return files; } public static ModuleId toModuleId(String id) { String[] parts = id.split(":"); if (parts.length != 2) { throw new IllegalArgumentException("can't parse module id '" + id + "': it must be of the form groupId:artifactId"); } return ModuleId.newInstance(parts[0], parts[1]); } public static boolean isMrid(String id) { return MRID_PATTERN.matcher(id).matches(); } public static ModuleRevisionId toMrid(String id) { String[] parts = id.split(":"); if (parts.length != 3) { throw new IllegalArgumentException("can't parse module revision id '" + id + "': it must be of the form groupId:artifactId:version"); } return ModuleRevisionId.newInstance(parts[0], parts[1], parts[2]); } private List<ModuleDescriptor> loadAllRestxModuleDescriptors() throws IOException { List<ModuleDescriptor> modules = new ArrayList<>(); try (InputStreamReader reader = new InputStreamReader(url.openStream(), Charsets.UTF_8)) { addAll(modules, mapper .reader(new TypeReference<ArrayList<ModuleDescriptor>>() { }) .<Iterable<ModuleDescriptor>>readValue(reader)); } return modules; } }