You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Maven2RepositoryStorage.java 40KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  1. package org.apache.archiva.metadata.repository.storage.maven2;
  2. /*
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing,
  14. * software distributed under the License is distributed on an
  15. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  16. * KIND, either express or implied. See the License for the
  17. * specific language governing permissions and limitations
  18. * under the License.
  19. */
  20. import org.apache.archiva.checksum.ChecksumAlgorithm;
  21. import org.apache.archiva.checksum.ChecksummedFile;
  22. import org.apache.archiva.common.Try;
  23. import org.apache.archiva.common.utils.VersionUtil;
  24. import org.apache.archiva.filter.Filter;
  25. import org.apache.archiva.maven2.metadata.MavenMetadataReader;
  26. import org.apache.archiva.metadata.model.ArtifactMetadata;
  27. import org.apache.archiva.metadata.model.ProjectMetadata;
  28. import org.apache.archiva.metadata.model.ProjectVersionMetadata;
  29. import org.apache.archiva.metadata.model.facets.RepositoryProblemFacet;
  30. import org.apache.archiva.metadata.repository.storage.*;
  31. import org.apache.archiva.model.ArchivaRepositoryMetadata;
  32. import org.apache.archiva.model.ArtifactReference;
  33. import org.apache.archiva.model.SnapshotVersion;
  34. import org.apache.archiva.policies.ProxyDownloadException;
  35. import org.apache.archiva.proxy.ProxyRegistry;
  36. import org.apache.archiva.proxy.maven.WagonFactory;
  37. import org.apache.archiva.proxy.model.NetworkProxy;
  38. import org.apache.archiva.proxy.model.ProxyConnector;
  39. import org.apache.archiva.proxy.model.RepositoryProxyHandler;
  40. import org.apache.archiva.repository.*;
  41. import org.apache.archiva.repository.content.PathParser;
  42. import org.apache.archiva.repository.maven2.MavenSystemManager;
  43. import org.apache.archiva.repository.storage.StorageAsset;
  44. import org.apache.archiva.xml.XMLException;
  45. import org.apache.commons.lang.ArrayUtils;
  46. import org.apache.commons.lang.StringUtils;
  47. import org.apache.maven.model.*;
  48. import org.apache.maven.model.building.*;
  49. import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
  50. import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
  51. import org.slf4j.Logger;
  52. import org.slf4j.LoggerFactory;
  53. import org.springframework.context.ApplicationContext;
  54. import org.springframework.stereotype.Service;
  55. import javax.annotation.PostConstruct;
  56. import javax.inject.Inject;
  57. import javax.inject.Named;
  58. import java.io.FileNotFoundException;
  59. import java.io.IOException;
  60. import java.io.Reader;
  61. import java.nio.channels.Channels;
  62. import java.nio.charset.Charset;
  63. import java.nio.file.NoSuchFileException;
  64. import java.util.*;
  65. import java.util.function.Predicate;
  66. import java.util.stream.Collectors;
  67. // import java.io.FileNotFoundException;
  68. /**
  69. * <p>
  70. * Maven 2 repository format storage implementation. This class currently takes parameters to indicate the repository to
  71. * deal with rather than being instantiated per-repository.
  72. * FIXME: instantiate one per repository and allocate permanently from a factory (which can be obtained within the session).
  73. * </p>
  74. * <p>
  75. * The session is passed in as an argument to obtain any necessary resources, rather than the class being instantiated
  76. * within the session in the context of a single managed repository's resolution needs.
  77. * </p>
  78. */
  79. @Service("repositoryStorage#maven2")
  80. public class Maven2RepositoryStorage
  81. implements RepositoryStorage {
  82. private static final Logger LOGGER = LoggerFactory.getLogger(Maven2RepositoryStorage.class);
  83. private ModelBuilder builder;
  84. @Inject
  85. RepositoryRegistry repositoryRegistry;
  86. @Inject
  87. @Named("repositoryPathTranslator#maven2")
  88. private RepositoryPathTranslator pathTranslator;
  89. @Inject
  90. private WagonFactory wagonFactory;
  91. @Inject
  92. private ApplicationContext applicationContext;
  93. @Inject
  94. @Named("pathParser#default")
  95. private PathParser pathParser;
  96. @Inject
  97. private ProxyRegistry proxyRegistry;
  98. @Inject
  99. private MavenSystemManager mavenSystemManager;
  100. private static final String METADATA_FILENAME_START = "maven-metadata";
  101. private static final String METADATA_FILENAME = METADATA_FILENAME_START + ".xml";
  102. // This array must be lexically sorted
  103. private static final String[] IGNORED_FILES = {METADATA_FILENAME, "resolver-status.properties"};
  104. private static final MavenXpp3Reader MAVEN_XPP_3_READER = new MavenXpp3Reader();
  105. @PostConstruct
  106. public void initialize() {
  107. builder = new DefaultModelBuilderFactory().newInstance();
  108. }
  109. @Override
  110. public ProjectMetadata readProjectMetadata(String repoId, String namespace, String projectId) {
  111. // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
  112. return null;
  113. }
  114. @Override
  115. public ProjectVersionMetadata readProjectVersionMetadata(ReadMetadataRequest readMetadataRequest)
  116. throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException,
  117. RepositoryStorageRuntimeException {
  118. ManagedRepository managedRepository = repositoryRegistry.getManagedRepository(readMetadataRequest.getRepositoryId());
  119. boolean isReleases = managedRepository.getActiveReleaseSchemes().contains(ReleaseScheme.RELEASE);
  120. boolean isSnapshots = managedRepository.getActiveReleaseSchemes().contains(ReleaseScheme.SNAPSHOT);
  121. String artifactVersion = readMetadataRequest.getProjectVersion();
  122. // olamy: in case of browsing via the ui we can mix repos (parent of a SNAPSHOT can come from release repo)
  123. if (!readMetadataRequest.isBrowsingRequest()) {
  124. if (VersionUtil.isSnapshot(artifactVersion)) {
  125. // skygo trying to improve speed by honoring managed configuration MRM-1658
  126. if (isReleases && !isSnapshots) {
  127. throw new RepositoryStorageRuntimeException("lookforsnaponreleaseonly",
  128. "managed repo is configured for release only");
  129. }
  130. } else {
  131. if (!isReleases && isSnapshots) {
  132. throw new RepositoryStorageRuntimeException("lookforsreleaseonsneponly",
  133. "managed repo is configured for snapshot only");
  134. }
  135. }
  136. }
  137. StorageAsset basedir = managedRepository.getAsset("");
  138. if (VersionUtil.isSnapshot(artifactVersion)) {
  139. StorageAsset metadataFile = pathTranslator.toFile(basedir, readMetadataRequest.getNamespace(),
  140. readMetadataRequest.getProjectId(), artifactVersion,
  141. METADATA_FILENAME);
  142. try {
  143. ArchivaRepositoryMetadata metadata = MavenMetadataReader.read(metadataFile);
  144. // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
  145. SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
  146. if (snapshotVersion != null) {
  147. artifactVersion =
  148. artifactVersion.substring(0, artifactVersion.length() - 8); // remove SNAPSHOT from end
  149. artifactVersion =
  150. artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
  151. }
  152. } catch (XMLException | IOException e) {
  153. // unable to parse metadata - LOGGER it, and continue with the version as the original SNAPSHOT version
  154. LOGGER.warn("Invalid metadata: {} - {}", metadataFile, e.getMessage());
  155. }
  156. }
  157. // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator
  158. String id = readMetadataRequest.getProjectId() + "-" + artifactVersion + ".pom";
  159. StorageAsset file =
  160. pathTranslator.toFile(basedir, readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
  161. readMetadataRequest.getProjectVersion(), id);
  162. if (!file.exists()) {
  163. // metadata could not be resolved
  164. throw new RepositoryStorageMetadataNotFoundException(
  165. "The artifact's POM file '" + file.getPath() + "' was missing");
  166. }
  167. // TODO: this is a workaround until we can properly resolve using proxies as well - this doesn't cache
  168. // anything locally!
  169. List<RemoteRepository> remoteRepositories = new ArrayList<>();
  170. Map<String, NetworkProxy> networkProxies = new HashMap<>();
  171. Map<String, List<ProxyConnector>> proxyConnectorsMap = proxyRegistry.getProxyConnectorAsMap();
  172. List<ProxyConnector> proxyConnectors = proxyConnectorsMap.get(readMetadataRequest.getRepositoryId());
  173. if (proxyConnectors != null) {
  174. for (ProxyConnector proxyConnector : proxyConnectors) {
  175. RemoteRepository remoteRepoConfig =
  176. repositoryRegistry.getRemoteRepository(proxyConnector.getTargetRepository().getId());
  177. if (remoteRepoConfig != null) {
  178. remoteRepositories.add(remoteRepoConfig);
  179. NetworkProxy networkProxyConfig =
  180. proxyRegistry.getNetworkProxy(proxyConnector.getProxyId());
  181. if (networkProxyConfig != null) {
  182. // key/value: remote repo ID/proxy info
  183. networkProxies.put(proxyConnector.getTargetRepository().getId(), networkProxyConfig);
  184. }
  185. }
  186. }
  187. }
  188. // That's a browsing request so we can a mix of SNAPSHOT and release artifacts (especially with snapshots which
  189. // can have released parent pom
  190. if (readMetadataRequest.isBrowsingRequest()) {
  191. remoteRepositories.addAll(repositoryRegistry.getRemoteRepositories());
  192. }
  193. ModelBuildingRequest req =
  194. new DefaultModelBuildingRequest().setProcessPlugins(false).setPomFile(file.getFilePath().toFile()).setTwoPhaseBuilding(
  195. false).setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
  196. //MRM-1607. olamy this will resolve jdk profiles on the current running archiva jvm
  197. req.setSystemProperties(System.getProperties());
  198. // MRM-1411
  199. req.setModelResolver(
  200. new RepositoryModelResolver(managedRepository, pathTranslator, wagonFactory, remoteRepositories,
  201. networkProxies, managedRepository, mavenSystemManager));
  202. Model model;
  203. try {
  204. model = builder.build(req).getEffectiveModel();
  205. } catch (ModelBuildingException e) {
  206. String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage();
  207. List<ModelProblem> modelProblems = e.getProblems();
  208. for (ModelProblem problem : modelProblems) {
  209. // MRM-1411, related to MRM-1335
  210. // this means that the problem was that the parent wasn't resolved!
  211. // olamy really hackhish but fail with java profile so use error message
  212. // || ( StringUtils.startsWith( problem.getMessage(), "Failed to determine Java version for profile" ) )
  213. // but setTwoPhaseBuilding(true) fix that
  214. if (((problem.getException() instanceof FileNotFoundException
  215. || problem.getException() instanceof NoSuchFileException
  216. ) && e.getModelId() != null &&
  217. !e.getModelId().equals(problem.getModelId()))) {
  218. LOGGER.warn("The artifact's parent POM file '{}' cannot be resolved. "
  219. + "Using defaults for project version metadata..", file);
  220. ProjectVersionMetadata metadata = new ProjectVersionMetadata();
  221. metadata.setId(readMetadataRequest.getProjectVersion());
  222. MavenProjectFacet facet = new MavenProjectFacet();
  223. facet.setGroupId(readMetadataRequest.getNamespace());
  224. facet.setArtifactId(readMetadataRequest.getProjectId());
  225. facet.setPackaging("jar");
  226. metadata.addFacet(facet);
  227. String errMsg =
  228. "Error in resolving artifact's parent POM file. " + (problem.getException() == null
  229. ? problem.getMessage()
  230. : problem.getException().getMessage());
  231. RepositoryProblemFacet repoProblemFacet = new RepositoryProblemFacet();
  232. repoProblemFacet.setRepositoryId(readMetadataRequest.getRepositoryId());
  233. repoProblemFacet.setId(readMetadataRequest.getRepositoryId());
  234. repoProblemFacet.setMessage(errMsg);
  235. repoProblemFacet.setProblem(errMsg);
  236. repoProblemFacet.setProject(readMetadataRequest.getProjectId());
  237. repoProblemFacet.setVersion(readMetadataRequest.getProjectVersion());
  238. repoProblemFacet.setNamespace(readMetadataRequest.getNamespace());
  239. metadata.addFacet(repoProblemFacet);
  240. return metadata;
  241. }
  242. }
  243. throw new RepositoryStorageMetadataInvalidException("invalid-pom", msg, e);
  244. }
  245. // Check if the POM is in the correct location
  246. boolean correctGroupId = readMetadataRequest.getNamespace().equals(model.getGroupId());
  247. boolean correctArtifactId = readMetadataRequest.getProjectId().equals(model.getArtifactId());
  248. boolean correctVersion = readMetadataRequest.getProjectVersion().equals(model.getVersion());
  249. if (!correctGroupId || !correctArtifactId || !correctVersion) {
  250. StringBuilder message = new StringBuilder("Incorrect POM coordinates in '" + file + "':");
  251. if (!correctGroupId) {
  252. message.append("\nIncorrect group ID: ").append(model.getGroupId());
  253. }
  254. if (!correctArtifactId) {
  255. message.append("\nIncorrect artifact ID: ").append(model.getArtifactId());
  256. }
  257. if (!correctVersion) {
  258. message.append("\nIncorrect version: ").append(model.getVersion());
  259. }
  260. throw new RepositoryStorageMetadataInvalidException("mislocated-pom", message.toString());
  261. }
  262. ProjectVersionMetadata metadata = new ProjectVersionMetadata();
  263. metadata.setCiManagement(convertCiManagement(model.getCiManagement()));
  264. metadata.setDescription(model.getDescription());
  265. metadata.setId(readMetadataRequest.getProjectVersion());
  266. metadata.setIssueManagement(convertIssueManagement(model.getIssueManagement()));
  267. metadata.setLicenses(convertLicenses(model.getLicenses()));
  268. metadata.setMailingLists(convertMailingLists(model.getMailingLists()));
  269. metadata.setDependencies(convertDependencies(model.getDependencies()));
  270. metadata.setName(model.getName());
  271. metadata.setOrganization(convertOrganization(model.getOrganization()));
  272. metadata.setScm(convertScm(model.getScm()));
  273. metadata.setUrl(model.getUrl());
  274. metadata.setProperties(model.getProperties());
  275. MavenProjectFacet facet = new MavenProjectFacet();
  276. facet.setGroupId(model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId());
  277. facet.setArtifactId(model.getArtifactId());
  278. facet.setPackaging(model.getPackaging());
  279. if (model.getParent() != null) {
  280. MavenProjectParent parent = new MavenProjectParent();
  281. parent.setGroupId(model.getParent().getGroupId());
  282. parent.setArtifactId(model.getParent().getArtifactId());
  283. parent.setVersion(model.getParent().getVersion());
  284. facet.setParent(parent);
  285. }
  286. metadata.addFacet(facet);
  287. return metadata;
  288. }
  289. public void setWagonFactory(WagonFactory wagonFactory) {
  290. this.wagonFactory = wagonFactory;
  291. }
  292. private List<org.apache.archiva.metadata.model.Dependency> convertDependencies(List<Dependency> dependencies) {
  293. List<org.apache.archiva.metadata.model.Dependency> l = new ArrayList<>();
  294. for (Dependency dependency : dependencies) {
  295. org.apache.archiva.metadata.model.Dependency newDependency =
  296. new org.apache.archiva.metadata.model.Dependency();
  297. newDependency.setArtifactId(dependency.getArtifactId());
  298. newDependency.setClassifier(dependency.getClassifier());
  299. newDependency.setGroupId(dependency.getGroupId());
  300. newDependency.setOptional(dependency.isOptional());
  301. newDependency.setScope(dependency.getScope());
  302. newDependency.setSystemPath(dependency.getSystemPath());
  303. newDependency.setType(dependency.getType());
  304. newDependency.setVersion(dependency.getVersion());
  305. l.add(newDependency);
  306. }
  307. return l;
  308. }
  309. private org.apache.archiva.metadata.model.Scm convertScm(Scm scm) {
  310. org.apache.archiva.metadata.model.Scm newScm = null;
  311. if (scm != null) {
  312. newScm = new org.apache.archiva.metadata.model.Scm();
  313. newScm.setConnection(scm.getConnection());
  314. newScm.setDeveloperConnection(scm.getDeveloperConnection());
  315. newScm.setUrl(scm.getUrl());
  316. }
  317. return newScm;
  318. }
  319. private org.apache.archiva.metadata.model.Organization convertOrganization(Organization organization) {
  320. org.apache.archiva.metadata.model.Organization org = null;
  321. if (organization != null) {
  322. org = new org.apache.archiva.metadata.model.Organization();
  323. org.setName(organization.getName());
  324. org.setUrl(organization.getUrl());
  325. }
  326. return org;
  327. }
  328. private List<org.apache.archiva.metadata.model.License> convertLicenses(List<License> licenses) {
  329. List<org.apache.archiva.metadata.model.License> l = new ArrayList<>();
  330. for (License license : licenses) {
  331. org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
  332. newLicense.setName(license.getName());
  333. newLicense.setUrl(license.getUrl());
  334. l.add(newLicense);
  335. }
  336. return l;
  337. }
  338. private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists(List<MailingList> mailingLists) {
  339. List<org.apache.archiva.metadata.model.MailingList> l = new ArrayList<>();
  340. for (MailingList mailingList : mailingLists) {
  341. org.apache.archiva.metadata.model.MailingList newMailingList =
  342. new org.apache.archiva.metadata.model.MailingList();
  343. newMailingList.setName(mailingList.getName());
  344. newMailingList.setMainArchiveUrl(mailingList.getArchive());
  345. newMailingList.setPostAddress(mailingList.getPost());
  346. newMailingList.setSubscribeAddress(mailingList.getSubscribe());
  347. newMailingList.setUnsubscribeAddress(mailingList.getUnsubscribe());
  348. newMailingList.setOtherArchives(mailingList.getOtherArchives());
  349. l.add(newMailingList);
  350. }
  351. return l;
  352. }
  353. private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement(IssueManagement issueManagement) {
  354. org.apache.archiva.metadata.model.IssueManagement im = null;
  355. if (issueManagement != null) {
  356. im = new org.apache.archiva.metadata.model.IssueManagement();
  357. im.setSystem(issueManagement.getSystem());
  358. im.setUrl(issueManagement.getUrl());
  359. }
  360. return im;
  361. }
  362. private org.apache.archiva.metadata.model.CiManagement convertCiManagement(CiManagement ciManagement) {
  363. org.apache.archiva.metadata.model.CiManagement ci = null;
  364. if (ciManagement != null) {
  365. ci = new org.apache.archiva.metadata.model.CiManagement();
  366. ci.setSystem(ciManagement.getSystem());
  367. ci.setUrl(ciManagement.getUrl());
  368. }
  369. return ci;
  370. }
  371. @Override
  372. public Collection<String> listRootNamespaces(String repoId, Filter<String> filter)
  373. throws RepositoryStorageRuntimeException {
  374. StorageAsset dir = getRepositoryBasedir(repoId);
  375. return getSortedFiles(dir, filter);
  376. }
  377. private static Collection<String> getSortedFiles(StorageAsset dir, Filter<String> filter) {
  378. final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
  379. return dir.list().stream().filter(f -> f.isContainer())
  380. .filter(dFilter)
  381. .map(path -> path.getName().toString())
  382. .sorted().collect(Collectors.toList());
  383. }
  384. private StorageAsset getRepositoryBasedir(String repoId)
  385. throws RepositoryStorageRuntimeException {
  386. ManagedRepository repositoryConfiguration = repositoryRegistry.getManagedRepository(repoId);
  387. return repositoryConfiguration.getAsset("");
  388. }
  389. @Override
  390. public Collection<String> listNamespaces(String repoId, String namespace, Filter<String> filter)
  391. throws RepositoryStorageRuntimeException {
  392. StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace);
  393. if (!(dir.exists()) && !dir.isContainer()) {
  394. return Collections.emptyList();
  395. }
  396. // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
  397. Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
  398. return dir.list().stream().filter(dFilter).filter(path -> !isProject(path, filter)).map(path -> path.getName().toString())
  399. .sorted().collect(Collectors.toList());
  400. }
  401. @Override
  402. public Collection<String> listProjects(String repoId, String namespace, Filter<String> filter)
  403. throws RepositoryStorageRuntimeException {
  404. StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace);
  405. if (!(dir.exists() && dir.isContainer())) {
  406. return Collections.emptyList();
  407. }
  408. // scan all directories in the namespace, and only include those that are known to be projects
  409. final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
  410. return dir.list().stream().filter(dFilter).filter(path -> isProject(path, filter)).map(path -> path.getName().toString())
  411. .sorted().collect(Collectors.toList());
  412. }
  413. @Override
  414. public Collection<String> listProjectVersions(String repoId, String namespace, String projectId,
  415. Filter<String> filter)
  416. throws RepositoryStorageRuntimeException {
  417. StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace, projectId);
  418. if (!(dir.exists() && dir.isContainer())) {
  419. return Collections.emptyList();
  420. }
  421. // all directories in a project directory can be considered a version
  422. return getSortedFiles(dir, filter);
  423. }
  424. @Override
  425. public Collection<ArtifactMetadata> readArtifactsMetadata(ReadMetadataRequest readMetadataRequest)
  426. throws RepositoryStorageRuntimeException {
  427. StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(readMetadataRequest.getRepositoryId()),
  428. readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
  429. readMetadataRequest.getProjectVersion());
  430. if (!(dir.exists() && dir.isContainer())) {
  431. return Collections.emptyList();
  432. }
  433. // all files that are not metadata and not a checksum / signature are considered artifacts
  434. final Predicate<StorageAsset> dFilter = new ArtifactDirectoryFilter(readMetadataRequest.getFilter());
  435. // Returns a map TRUE -> (success values), FALSE -> (Exceptions)
  436. Map<Boolean, List<Try<ArtifactMetadata>>> result = dir.list().stream().filter(dFilter).map(path -> {
  437. try {
  438. return Try.success(getArtifactFromFile(readMetadataRequest.getRepositoryId(), readMetadataRequest.getNamespace(),
  439. readMetadataRequest.getProjectId(), readMetadataRequest.getProjectVersion(),
  440. path));
  441. } catch (Exception e) {
  442. LOGGER.debug("Could not create metadata for {}: {}", path, e.getMessage(), e);
  443. return Try.<ArtifactMetadata>failure(e);
  444. }
  445. }
  446. ).collect(Collectors.groupingBy(Try::isSuccess));
  447. if (result.containsKey(Boolean.FALSE) && result.get(Boolean.FALSE).size() > 0 && (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE).size() == 0)) {
  448. LOGGER.error("Could not get artifact metadata. Directory: {}. Number of errors {}.", dir, result.get(Boolean.FALSE).size());
  449. Try<ArtifactMetadata> failure = result.get(Boolean.FALSE).get(0);
  450. LOGGER.error("Sample exception {}", failure.getError().getMessage(), failure.getError());
  451. throw new RepositoryStorageRuntimeException(readMetadataRequest.getRepositoryId(), "Could not retrieve metadata of the files");
  452. } else {
  453. if (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE) == null) {
  454. return Collections.emptyList();
  455. }
  456. return result.get(Boolean.TRUE).stream().map(tr -> tr.get()).collect(Collectors.toList());
  457. }
  458. }
  459. @Override
  460. public ArtifactMetadata readArtifactMetadataFromPath(String repoId, String path)
  461. throws RepositoryStorageRuntimeException {
  462. ArtifactMetadata metadata = pathTranslator.getArtifactForPath(repoId, path);
  463. try {
  464. populateArtifactMetadataFromFile(metadata, getRepositoryBasedir(repoId).resolve(path));
  465. } catch (IOException e) {
  466. throw new RepositoryStorageRuntimeException(repoId, "Error during metadata retrieval of " + path + " :" + e.getMessage(), e);
  467. }
  468. return metadata;
  469. }
  470. private ArtifactMetadata getArtifactFromFile(String repoId, String namespace, String projectId,
  471. String projectVersion, StorageAsset file) throws IOException {
  472. ArtifactMetadata metadata =
  473. pathTranslator.getArtifactFromId(repoId, namespace, projectId, projectVersion, file.getName());
  474. populateArtifactMetadataFromFile(metadata, file);
  475. return metadata;
  476. }
  477. @Override
  478. public void applyServerSideRelocation(ManagedRepositoryContent managedRepository, ArtifactReference artifact)
  479. throws ProxyDownloadException {
  480. if ("pom".equals(artifact.getType())) {
  481. return;
  482. }
  483. // Build the artifact POM reference
  484. ArtifactReference pomReference = new ArtifactReference();
  485. pomReference.setGroupId(artifact.getGroupId());
  486. pomReference.setArtifactId(artifact.getArtifactId());
  487. pomReference.setVersion(artifact.getVersion());
  488. pomReference.setType("pom");
  489. RepositoryType repositoryType = managedRepository.getRepository().getType();
  490. if (!proxyRegistry.hasHandler(repositoryType)) {
  491. throw new ProxyDownloadException("No proxy handler found for repository type " + repositoryType, new HashMap<>());
  492. }
  493. RepositoryProxyHandler proxyHandler = proxyRegistry.getHandler(repositoryType).get(0);
  494. // Get the artifact POM from proxied repositories if needed
  495. proxyHandler.fetchFromProxies(managedRepository, pomReference);
  496. // Open and read the POM from the managed repo
  497. StorageAsset pom = managedRepository.toFile(pomReference);
  498. if (!pom.exists()) {
  499. return;
  500. }
  501. try {
  502. // MavenXpp3Reader leaves the file open, so we need to close it ourselves.
  503. Model model;
  504. try (Reader reader = Channels.newReader(pom.getReadChannel(), Charset.defaultCharset().name())) {
  505. model = MAVEN_XPP_3_READER.read(reader);
  506. }
  507. DistributionManagement dist = model.getDistributionManagement();
  508. if (dist != null) {
  509. Relocation relocation = dist.getRelocation();
  510. if (relocation != null) {
  511. // artifact is relocated : update the repositoryPath
  512. if (relocation.getGroupId() != null) {
  513. artifact.setGroupId(relocation.getGroupId());
  514. }
  515. if (relocation.getArtifactId() != null) {
  516. artifact.setArtifactId(relocation.getArtifactId());
  517. }
  518. if (relocation.getVersion() != null) {
  519. artifact.setVersion(relocation.getVersion());
  520. }
  521. }
  522. }
  523. } catch (IOException e) {
  524. // Unable to read POM : ignore.
  525. } catch (XmlPullParserException e) {
  526. // Invalid POM : ignore
  527. }
  528. }
  529. @Override
  530. public String getFilePath(String requestPath, org.apache.archiva.repository.ManagedRepository managedRepository) {
  531. // managedRepository can be null
  532. // extract artifact reference from url
  533. // groupId:artifactId:version:packaging:classifier
  534. //org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
  535. String logicalResource = null;
  536. String requestPathInfo = StringUtils.defaultString(requestPath);
  537. //remove prefix ie /repository/blah becomes /blah
  538. requestPathInfo = removePrefix(requestPathInfo);
  539. // Remove prefixing slash as the repository id doesn't contain it;
  540. if (requestPathInfo.startsWith("/")) {
  541. requestPathInfo = requestPathInfo.substring(1);
  542. }
  543. int slash = requestPathInfo.indexOf('/');
  544. if (slash > 0) {
  545. logicalResource = requestPathInfo.substring(slash);
  546. if (logicalResource.endsWith("/..")) {
  547. logicalResource += "/";
  548. }
  549. if (logicalResource != null && logicalResource.startsWith("//")) {
  550. logicalResource = logicalResource.substring(1);
  551. }
  552. if (logicalResource == null) {
  553. logicalResource = "/";
  554. }
  555. } else {
  556. logicalResource = "/";
  557. }
  558. return logicalResource;
  559. }
  560. @Override
  561. public String getFilePathWithVersion(final String requestPath, ManagedRepositoryContent managedRepositoryContent)
  562. throws RelocationException, XMLException, IOException {
  563. if (StringUtils.endsWith(requestPath, METADATA_FILENAME)) {
  564. return getFilePath(requestPath, managedRepositoryContent.getRepository());
  565. }
  566. String filePath = getFilePath(requestPath, managedRepositoryContent.getRepository());
  567. ArtifactReference artifactReference = null;
  568. try {
  569. artifactReference = pathParser.toArtifactReference(filePath);
  570. } catch (LayoutException e) {
  571. return filePath;
  572. }
  573. if (StringUtils.endsWith(artifactReference.getVersion(), VersionUtil.SNAPSHOT)) {
  574. // read maven metadata to get last timestamp
  575. StorageAsset metadataDir = managedRepositoryContent.getRepository().getAsset(filePath).getParent();
  576. if (!metadataDir.exists()) {
  577. return filePath;
  578. }
  579. StorageAsset metadataFile = metadataDir.resolve(METADATA_FILENAME);
  580. if (!metadataFile.exists()) {
  581. return filePath;
  582. }
  583. ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read(metadataFile);
  584. int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber();
  585. String timestamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp();
  586. // MRM-1846
  587. if (buildNumber < 1 && timestamp == null) {
  588. return filePath;
  589. }
  590. // org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
  591. // -> archiva-checksum-1.4-M4-20130425.081822-1.jar
  592. filePath = StringUtils.replace(filePath, //
  593. artifactReference.getArtifactId() //
  594. + "-" + artifactReference.getVersion(), //
  595. artifactReference.getArtifactId() //
  596. + "-" + StringUtils.remove(artifactReference.getVersion(),
  597. "-" + VersionUtil.SNAPSHOT) //
  598. + "-" + timestamp //
  599. + "-" + buildNumber);
  600. throw new RelocationException("/repository/" + managedRepositoryContent.getRepository().getId() +
  601. (StringUtils.startsWith(filePath, "/") ? "" : "/") + filePath,
  602. RelocationException.RelocationType.TEMPORARY);
  603. }
  604. return filePath;
  605. }
  606. //-----------------------------
  607. // internal
  608. //-----------------------------
  609. /**
  610. * FIXME remove
  611. *
  612. * @param href
  613. * @return
  614. */
  615. private static String removePrefix(final String href) {
  616. String[] parts = StringUtils.split(href, '/');
  617. parts = (String[]) ArrayUtils.subarray(parts, 1, parts.length);
  618. if (parts == null || parts.length == 0) {
  619. return "/";
  620. }
  621. String joinedString = StringUtils.join(parts, '/');
  622. if (href.endsWith("/")) {
  623. joinedString = joinedString + "/";
  624. }
  625. return joinedString;
  626. }
  627. private static void populateArtifactMetadataFromFile(ArtifactMetadata metadata, StorageAsset file) throws IOException {
  628. metadata.setWhenGathered(new Date());
  629. metadata.setFileLastModified(file.getModificationTime().toEpochMilli());
  630. ChecksummedFile checksummedFile = new ChecksummedFile(file.getFilePath());
  631. try {
  632. metadata.setMd5(checksummedFile.calculateChecksum(ChecksumAlgorithm.MD5));
  633. } catch (IOException e) {
  634. LOGGER.error("Unable to checksum file {}: {},MD5", file, e.getMessage());
  635. }
  636. try {
  637. metadata.setSha1(checksummedFile.calculateChecksum(ChecksumAlgorithm.SHA1));
  638. } catch (IOException e) {
  639. LOGGER.error("Unable to checksum file {}: {},SHA1", file, e.getMessage());
  640. }
  641. metadata.setSize(file.getSize());
  642. }
  643. private boolean isProject(StorageAsset dir, Filter<String> filter) {
  644. // scan directories for a valid project version subdirectory, meaning this must be a project directory
  645. final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter);
  646. boolean projFound = dir.list().stream().filter(dFilter)
  647. .anyMatch(path -> isProjectVersion(path));
  648. if (projFound) {
  649. return true;
  650. }
  651. // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
  652. ArchivaRepositoryMetadata metadata = readMetadata(dir);
  653. if (metadata != null && dir.getName().toString().equals(metadata.getArtifactId())) {
  654. return true;
  655. }
  656. return false;
  657. }
  658. private boolean isProjectVersion(StorageAsset dir) {
  659. final String artifactId = dir.getParent().getName();
  660. final String projectVersion = dir.getName();
  661. // check if there is a POM artifact file to ensure it is a version directory
  662. Predicate<StorageAsset> filter;
  663. if (VersionUtil.isSnapshot(projectVersion)) {
  664. filter = new PomFilenameFilter(artifactId, projectVersion);
  665. } else {
  666. final String pomFile = artifactId + "-" + projectVersion + ".pom";
  667. filter = new PomFileFilter(pomFile);
  668. }
  669. if (dir.list().stream().filter(f -> !f.isContainer()).anyMatch(filter)) {
  670. return true;
  671. }
  672. // if a metadata file is present, check if this is the "version" directory, marking it as a project version
  673. ArchivaRepositoryMetadata metadata = readMetadata(dir);
  674. if (metadata != null && projectVersion.equals(metadata.getVersion())) {
  675. return true;
  676. }
  677. return false;
  678. }
  679. private ArchivaRepositoryMetadata readMetadata(StorageAsset directory) {
  680. ArchivaRepositoryMetadata metadata = null;
  681. StorageAsset metadataFile = directory.resolve(METADATA_FILENAME);
  682. if (metadataFile.exists()) {
  683. try {
  684. metadata = MavenMetadataReader.read(metadataFile);
  685. } catch (XMLException | IOException e) {
  686. // ignore missing or invalid metadata
  687. }
  688. }
  689. return metadata;
  690. }
  691. private static class DirectoryFilter
  692. implements Predicate<StorageAsset> {
  693. private final Filter<String> filter;
  694. public DirectoryFilter(Filter<String> filter) {
  695. this.filter = filter;
  696. }
  697. @Override
  698. public boolean test(StorageAsset dir) {
  699. final String name = dir.getName();
  700. if (!filter.accept(name)) {
  701. return false;
  702. } else if (name.startsWith(".")) {
  703. return false;
  704. } else if (!dir.isContainer()) {
  705. return false;
  706. }
  707. return true;
  708. }
  709. }
  710. private static class ArtifactDirectoryFilter
  711. implements Predicate<StorageAsset> {
  712. private final Filter<String> filter;
  713. private ArtifactDirectoryFilter(Filter<String> filter) {
  714. this.filter = filter;
  715. }
  716. @Override
  717. public boolean test(StorageAsset file) {
  718. final Set<String> checksumExts = ChecksumAlgorithm.getAllExtensions();
  719. final String path = file.getPath();
  720. final String name = file.getName();
  721. final String extension = StringUtils.substringAfterLast(name, ".").toLowerCase();
  722. // TODO compare to logic in maven-repository-layer
  723. if (file.isContainer()) {
  724. return false;
  725. } else if (!filter.accept(name)) {
  726. return false;
  727. } else if (name.startsWith(".") || path.contains("/.") ) {
  728. return false;
  729. } else if (checksumExts.contains(extension)) {
  730. return false;
  731. } else if (Arrays.binarySearch(IGNORED_FILES, name) >= 0) {
  732. return false;
  733. }
  734. // some files from remote repositories can have name like maven-metadata-archiva-vm-all-public.xml
  735. else if (StringUtils.startsWith(name, METADATA_FILENAME_START) && StringUtils.endsWith(name, ".xml")) {
  736. return false;
  737. }
  738. return true;
  739. }
  740. }
  741. private static final class PomFilenameFilter
  742. implements Predicate<StorageAsset> {
  743. private final String artifactId, projectVersion;
  744. private PomFilenameFilter(String artifactId, String projectVersion) {
  745. this.artifactId = artifactId;
  746. this.projectVersion = projectVersion;
  747. }
  748. @Override
  749. public boolean test(StorageAsset dir) {
  750. final String name = dir.getName();
  751. if (name.startsWith(artifactId + "-") && name.endsWith(".pom")) {
  752. String v = name.substring(artifactId.length() + 1, name.length() - 4);
  753. v = VersionUtil.getBaseVersion(v);
  754. if (v.equals(projectVersion)) {
  755. return true;
  756. }
  757. }
  758. return false;
  759. }
  760. }
  761. private static class PomFileFilter
  762. implements Predicate<StorageAsset> {
  763. private final String pomFile;
  764. private PomFileFilter(String pomFile) {
  765. this.pomFile = pomFile;
  766. }
  767. @Override
  768. public boolean test(StorageAsset dir) {
  769. return pomFile.equals(dir.getName());
  770. }
  771. }
  772. public PathParser getPathParser() {
  773. return pathParser;
  774. }
  775. public void setPathParser(PathParser pathParser) {
  776. this.pathParser = pathParser;
  777. }
  778. }