/* * Copyright 2015 MovingBlocks * * 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.terasology.web.artifactory; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.Writer; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.io.Files; import com.google.gson.Gson; import com.google.gson.GsonBuilder; /** * Implements {@link ArtifactRepository} for Artifactory. */ public final class ArtifactoryRepo implements ArtifactRepository { private static final Logger logger = LoggerFactory.getLogger(ArtifactoryRepo.class); private static final Gson GSON = new GsonBuilder() .setPrettyPrinting() .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") .create(); private final Map<String, Collection<ArtifactoryArtifactInfo>> artifactInfo = new LinkedHashMap<>(); private final String baseUrl; private final Path cacheFolder; private final String repoName; private final String group; private final RepoType type; private ArtifactoryRepo(String uri, String repoName, String group, Path cacheFolder, RepoType type) throws IOException { this.cacheFolder = cacheFolder; this.repoName = repoName; this.type = type; this.group = group; String repoPath = group.replaceAll("\\.", "/"); baseUrl = uri + "/api/storage" + "/" + repoName + "/" + repoPath; ArtifactoryItem folder = readItem(baseUrl); for (ArtifactoryItem.Entry child : folder.children) { if (child.folder) { String moduleName = child.uri.substring(1); artifactInfo.put(moduleName, Collections.emptySet()); } } } public static ArtifactoryRepo snapshot(String uri, String repoName, String group, Path cacheFolder) throws IOException { return new ArtifactoryRepo(uri, repoName, group, cacheFolder, RepoType.SNAPSHOT); } public static ArtifactoryRepo release(String uri, String repoName, String group, Path cacheFolder) throws IOException { return new ArtifactoryRepo(uri, repoName, group, cacheFolder, RepoType.RELEASE); } private ArtifactoryModule loadModuleFromCache(String moduleName) throws IOException { File cacheFile = getCacheFile(moduleName); if (cacheFile.exists()) { try (Reader reader = Files.newReader(cacheFile, StandardCharsets.UTF_8)) { ArtifactoryModule meta = GSON.fromJson(reader, ArtifactoryModule.class); return meta; } catch (RuntimeException e) { logger.warn("Could not read {}", cacheFile, e); cacheFile.delete(); } } return null; } private File getCacheFile(String moduleName) { return cacheFolder.resolve(moduleName).resolve("artifactory.json").toFile(); } @Override public String getName() { return repoName; } @Override public RepoType getType() { return type; } @Override public Set<String> getModuleNames() { return artifactInfo.keySet(); } @Override public void updateModule(String moduleName) throws IOException { String moduleUrl = baseUrl + "/" + moduleName; try { ArtifactoryItem mavenMeta = readItem(moduleUrl + "/maven-metadata.xml"); ArtifactoryModule module = loadModuleFromCache(moduleName); if (module == null) { module = new ArtifactoryModule(); module.lastModified = new Date(0); module.items = new ArrayList<>(); } if (mavenMeta.lastModified.after(module.lastModified)) { module.lastModified = mavenMeta.lastModified; module.items.clear(); ArtifactoryItem moduleFolder = readItem(moduleUrl); for (ArtifactoryItem.Entry child2 : moduleFolder.children) { if (child2.folder) { String versionUrl = moduleUrl + child2.uri; Set<ArtifactoryArtifactInfo> entries = findArtifacts(versionUrl); artifactInfo.put(moduleName, entries); module.items.addAll(entries); } } logger.info("Updated " + moduleName); } else { logger.debug("No updates for " + moduleName); } artifactInfo.put(moduleName, module.items); File cacheFile = getCacheFile(moduleName); cacheFile.getParentFile().mkdirs(); try (Writer writer = Files.newWriter(cacheFile, StandardCharsets.UTF_8)) { GSON.toJson(module, writer); } } catch (FileNotFoundException e) { logger.info("No entries for '{}.{}' in '{}'", group, moduleName, repoName); } } private Set<ArtifactoryArtifactInfo> findArtifacts(String versionUrl) throws IOException { Set<ArtifactoryArtifactInfo> hits = new HashSet<>(); ArtifactoryItem versionFolder = readItem(versionUrl); for (ArtifactoryItem.Entry child3 : versionFolder.children) { if (matches(child3.uri)) { String artifactUrl = versionUrl + child3.uri; ArtifactoryItem artifact = readItem(artifactUrl); hits.add(new ArtifactoryArtifactInfo(artifact)); logger.debug("Added " + artifactUrl); } } return hits; } @Override public Collection<? extends ArtifactInfo> getModuleArtifacts(String moduleName) { return Collections.unmodifiableCollection(artifactInfo.getOrDefault(moduleName, Collections.emptySet())); } private static ArtifactoryItem readItem(String url) throws IOException { try (Reader reader = new InputStreamReader(new URL(url).openStream(), StandardCharsets.UTF_8)) { ArtifactoryItem folder = GSON.fromJson(reader, ArtifactoryItem.class); return folder; } } private static boolean matches(String uri) { if (uri.endsWith(".jar")) { if (!uri.endsWith("-sources.jar") && !uri.endsWith("-javadoc.jar")) { return true; } } return false; } }