001/* 002 * Extra Bnd Repository Plugins 003 * Copyright (C) 2019-2021 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Affero General Public License as published by 007 * the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package de.mnl.osgi.bnd.repository.maven.provider; 020 021import aQute.bnd.build.Workspace; 022import aQute.bnd.http.HttpClient; 023import static aQute.bnd.osgi.Constants.BSN_SOURCE_SUFFIX; 024import aQute.bnd.osgi.repository.BaseRepository; 025import aQute.bnd.osgi.repository.BridgeRepository; 026import aQute.bnd.osgi.repository.BridgeRepository.ResourceInfo; 027import aQute.bnd.service.Plugin; 028import aQute.bnd.service.Refreshable; 029import aQute.bnd.service.Registry; 030import aQute.bnd.service.RegistryPlugin; 031import aQute.bnd.service.RepositoryListenerPlugin; 032import aQute.bnd.service.RepositoryPlugin; 033import aQute.bnd.util.repository.DownloadListenerPromise; 034import aQute.bnd.version.Version; 035import aQute.lib.converter.Converter; 036import aQute.lib.io.IO; 037import aQute.libg.reporter.slf4j.Slf4jReporter; 038import aQute.maven.api.Archive; 039import aQute.service.reporter.Reporter; 040import de.mnl.osgi.bnd.maven.RepositoryUtils; 041import de.mnl.osgi.bnd.repository.maven.idxmvn.IndexedMavenConfiguration; 042import de.mnl.osgi.bnd.repository.maven.idxmvn.IndexedMavenRepository; 043import java.io.File; 044import java.io.InputStream; 045import java.net.MalformedURLException; 046import java.net.URL; 047import java.util.Collection; 048import java.util.List; 049import java.util.Map; 050import java.util.SortedSet; 051import java.util.stream.Collectors; 052import org.osgi.resource.Capability; 053import org.osgi.resource.Requirement; 054import org.osgi.service.repository.Repository; 055import org.osgi.util.promise.Promise; 056 057/** 058 * Maintains an index of a subset of one or more maven repositories 059 * and provides it as an OSGi repository. 060 */ 061public class IndexedMavenRepositoryProvider extends BaseRepository 062 implements Repository, Plugin, RegistryPlugin, RepositoryPlugin, 063 Refreshable { 064 private static final String MAVEN_REPO_LOCAL 065 = System.getProperty("maven.repo.local", "~/.m2/repository"); 066 067 private boolean initialized; 068 private IndexedMavenConfiguration configuration; 069 private String name = "Indexed Maven"; 070 private String location; 071 private Registry registry; 072 private Reporter reporter 073 = new Slf4jReporter(IndexedMavenRepositoryProvider.class); 074 private IndexedMavenRepository osgiRepository; 075 private BridgeRepository bridge; 076 private boolean logIndexing; 077 078 @Override 079 @SuppressWarnings({ "PMD.UseLocaleWithCaseConversions", "restriction" }) 080 public void setProperties(Map<String, String> properties) throws Exception { 081 configuration 082 = Converter.cnv(IndexedMavenConfiguration.class, properties); 083 name = configuration.name(name); 084 location = configuration.location( 085 "cnf/" + name.toLowerCase().replace(' ', '-').replace('/', ':')); 086 logIndexing = configuration.logIndexing(); 087 } 088 089 @Override 090 public void setRegistry(Registry registry) { 091 this.registry = registry; 092 } 093 094 @Override 095 public void setReporter(Reporter reporter) { 096 this.reporter = reporter; 097 } 098 099 @Override 100 public String getName() { 101 return name; 102 } 103 104 @Override 105 public PutResult put(InputStream stream, PutOptions options) 106 throws Exception { 107 throw new IllegalStateException("Read-only repository"); 108 } 109 110 @Override 111 public boolean canWrite() { 112 return false; 113 } 114 115 /** 116 * Performs initialization. Initialization must be delayed because the 117 * precise sequence of injecting dependencies seems to be undefined. 118 * 119 * @throws MalformedURLException 120 */ 121 @SuppressWarnings({ "PMD.AvoidCatchingGenericException", 122 "PMD.AvoidThrowingRawExceptionTypes" }) 123 private void init() { 124 synchronized (this) { 125 if (initialized) { 126 return; 127 } 128 initialized = true; 129 Workspace workspace = registry.getPlugin(Workspace.class); 130 HttpClient client = registry.getPlugin(HttpClient.class); 131 File indexDb = workspace.getFile(getLocation()); 132 File localRepo = IO.getFile(configuration.local(MAVEN_REPO_LOCAL)); 133 try { 134 Thread.currentThread() 135 .setContextClassLoader(getClass().getClassLoader()); 136 osgiRepository = new IndexedMavenRepository(name, 137 RepositoryUtils.itemizeList(configuration.releaseUrls()) 138 .map(ru -> stringToUrl(ru)) 139 .collect(Collectors.toList()), 140 RepositoryUtils.itemizeList(configuration.snapshotUrls()) 141 .map(ru -> stringToUrl(ru)) 142 .collect(Collectors.toList()), 143 localRepo, indexDb, reporter, client, logIndexing); 144 bridge = new BridgeRepository(osgiRepository); 145 } catch (Exception e) { 146 throw new RuntimeException(e); 147 } 148 } 149 } 150 151 private URL stringToUrl(String url) { 152 try { 153 return new URL(url); 154 } catch (MalformedURLException e) { 155 throw new IllegalArgumentException(e); 156 } 157 } 158 159 @Override 160 public File getRoot() throws Exception { 161 return osgiRepository.location(); 162 } 163 164 @Override 165 @SuppressWarnings("PMD.AvoidCatchingGenericException") 166 public boolean refresh() throws Exception { 167 init(); 168 if (!osgiRepository.refresh()) { 169 return false; 170 } 171 bridge = new BridgeRepository(osgiRepository); 172 for (RepositoryListenerPlugin listener : registry 173 .getPlugins(RepositoryListenerPlugin.class)) { 174 try { 175 listener.repositoryRefreshed(this); 176 } catch (Exception e) { 177 reporter.exception(e, "Updating listener plugin %s", listener); 178 } 179 } 180 return true; 181 } 182 183 @Override 184 public File get(String bsn, Version version, Map<String, String> properties, 185 DownloadListener... listeners) throws Exception { 186 init(); 187 Archive archive; 188 ResourceInfo resource = bridge.getInfo(bsn, version); 189 if (resource == null) { 190 archive = trySources(bsn, version); 191 if (archive == null) { 192 return null; 193 } 194 } else { 195 String from = resource.getInfo().from(); 196 archive = Archive.valueOf(from); 197 } 198 199 Promise<File> prmse 200 = osgiRepository.mavenRepository().retrieve(archive); 201 202 if (listeners.length == 0) { 203 return prmse.getValue(); 204 } 205 new DownloadListenerPromise(reporter, 206 name + ": get " + bsn + ";" + version, prmse, listeners); 207 return osgiRepository.mavenRepository().toLocalFile(archive); 208 } 209 210 /** 211 * The Eclipse bndtools plugin attempts to retrieve a bundle's sources 212 * by calling {@link #get(String, Version, Map, 213 * aQute.bnd.service.RepositoryPlugin.DownloadListener...)} with the 214 * bundle symbol name and ".source" appended as suffix. Check if the 215 * given bsn matches this pattern and return an archive specification 216 * for the artifact containing the sources using maven conventions. 217 * 218 * @param bsn the bsn 219 * @param version the version 220 * @return the archive 221 * @throws Exception the exception 222 */ 223 private Archive trySources(String bsn, Version version) throws Exception { 224 if (!bsn.endsWith(BSN_SOURCE_SUFFIX)) { 225 return null; 226 } 227 String baseBsn 228 = bsn.substring(0, bsn.length() - BSN_SOURCE_SUFFIX.length()); 229 ResourceInfo resource = bridge.getInfo(baseBsn, version); 230 if (resource == null) { 231 return null; 232 } 233 String from = resource.getInfo().from(); 234 return Archive.valueOf(from) 235 .getOther(Archive.JAR_EXTENSION, Archive.SOURCES_CLASSIFIER); 236 } 237 238 @Override 239 public List<String> list(String pattern) throws Exception { 240 init(); 241 return bridge.list(pattern); 242 } 243 244 @Override 245 public SortedSet<Version> versions(String bsn) throws Exception { 246 init(); 247 return bridge.versions(bsn); 248 } 249 250 @Override 251 public String getLocation() { 252 return location; 253 } 254 255 @Override 256 public Map<Requirement, Collection<Capability>> findProviders( 257 Collection<? extends Requirement> requirements) { 258 init(); 259 return osgiRepository.findProviders(requirements); 260 } 261 262 @Override 263 public String toString() { 264 return name; 265 } 266 267}