| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.maven.artifact.repository.metadata; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.inject.Singleton; |
| import javax.xml.stream.XMLStreamException; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.nio.file.Files; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.maven.artifact.metadata.ArtifactMetadata; |
| import org.apache.maven.artifact.repository.ArtifactRepository; |
| import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy; |
| import org.apache.maven.artifact.repository.DefaultRepositoryRequest; |
| import org.apache.maven.artifact.repository.RepositoryRequest; |
| import org.apache.maven.artifact.repository.metadata.io.MetadataStaxReader; |
| import org.apache.maven.artifact.repository.metadata.io.MetadataStaxWriter; |
| import org.apache.maven.repository.legacy.UpdateCheckManager; |
| import org.apache.maven.repository.legacy.WagonManager; |
| import org.apache.maven.wagon.ResourceDoesNotExistException; |
| import org.apache.maven.wagon.TransferFailedException; |
| import org.codehaus.plexus.logging.AbstractLogEnabled; |
| |
| /** |
| */ |
| @Named |
| @Singleton |
| @Deprecated |
| public class DefaultRepositoryMetadataManager extends AbstractLogEnabled implements RepositoryMetadataManager { |
| @Inject |
| private WagonManager wagonManager; |
| |
| @Inject |
| private UpdateCheckManager updateCheckManager; |
| |
| public void resolve( |
| RepositoryMetadata metadata, |
| List<ArtifactRepository> remoteRepositories, |
| ArtifactRepository localRepository) |
| throws RepositoryMetadataResolutionException { |
| RepositoryRequest request = new DefaultRepositoryRequest(); |
| request.setLocalRepository(localRepository); |
| request.setRemoteRepositories(remoteRepositories); |
| resolve(metadata, request); |
| } |
| |
| public void resolve(RepositoryMetadata metadata, RepositoryRequest request) |
| throws RepositoryMetadataResolutionException { |
| ArtifactRepository localRepo = request.getLocalRepository(); |
| List<ArtifactRepository> remoteRepositories = request.getRemoteRepositories(); |
| |
| if (!request.isOffline()) { |
| Date localCopyLastModified = null; |
| if (metadata.getBaseVersion() != null) { |
| localCopyLastModified = getLocalCopyLastModified(localRepo, metadata); |
| } |
| |
| for (ArtifactRepository repository : remoteRepositories) { |
| ArtifactRepositoryPolicy policy = metadata.getPolicy(repository); |
| |
| File file = |
| new File(localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata(metadata, repository)); |
| boolean update; |
| |
| if (!policy.isEnabled()) { |
| update = false; |
| |
| if (getLogger().isDebugEnabled()) { |
| getLogger() |
| .debug("Skipping update check for " + metadata.getKey() + " (" + file |
| + ") from disabled repository " + repository.getId() + " (" |
| + repository.getUrl() + ")"); |
| } |
| } else if (request.isForceUpdate()) { |
| update = true; |
| } else if (localCopyLastModified != null && !policy.checkOutOfDate(localCopyLastModified)) { |
| update = false; |
| |
| if (getLogger().isDebugEnabled()) { |
| getLogger() |
| .debug("Skipping update check for " + metadata.getKey() + " (" + file |
| + ") from repository " + repository.getId() + " (" + repository.getUrl() |
| + ") in favor of local copy"); |
| } |
| } else { |
| update = updateCheckManager.isUpdateRequired(metadata, repository, file); |
| } |
| |
| if (update) { |
| getLogger().info(metadata.getKey() + ": checking for updates from " + repository.getId()); |
| try { |
| wagonManager.getArtifactMetadata(metadata, repository, file, policy.getChecksumPolicy()); |
| } catch (ResourceDoesNotExistException e) { |
| getLogger().debug(metadata + " could not be found on repository: " + repository.getId()); |
| |
| // delete the local copy so the old details aren't used. |
| if (file.exists()) { |
| if (!file.delete()) { |
| // sleep for 10ms just in case this is windows holding a file lock |
| try { |
| Thread.sleep(10); |
| } catch (InterruptedException ie) { |
| // ignore |
| } |
| file.delete(); // if this fails, forget about it |
| } |
| } |
| } catch (TransferFailedException e) { |
| getLogger() |
| .warn(metadata + " could not be retrieved from repository: " + repository.getId() |
| + " due to an error: " + e.getMessage()); |
| getLogger().debug("Exception", e); |
| } finally { |
| updateCheckManager.touch(metadata, repository, file); |
| } |
| } |
| |
| // TODO should this be inside the above check? |
| // touch file so that this is not checked again until interval has passed |
| if (file.exists()) { |
| file.setLastModified(System.currentTimeMillis()); |
| } |
| } |
| } |
| |
| try { |
| mergeMetadata(metadata, remoteRepositories, localRepo); |
| } catch (RepositoryMetadataStoreException e) { |
| throw new RepositoryMetadataResolutionException( |
| "Unable to store local copy of metadata: " + e.getMessage(), e); |
| } |
| } |
| |
| private Date getLocalCopyLastModified(ArtifactRepository localRepository, RepositoryMetadata metadata) { |
| String metadataPath = localRepository.pathOfLocalRepositoryMetadata(metadata, localRepository); |
| File metadataFile = new File(localRepository.getBasedir(), metadataPath); |
| return metadataFile.isFile() ? new Date(metadataFile.lastModified()) : null; |
| } |
| |
| private void mergeMetadata( |
| RepositoryMetadata metadata, |
| List<ArtifactRepository> remoteRepositories, |
| ArtifactRepository localRepository) |
| throws RepositoryMetadataStoreException { |
| // TODO currently this is first wins, but really we should take the latest by comparing either the |
| // snapshot timestamp, or some other timestamp later encoded into the metadata. |
| // TODO this needs to be repeated here so the merging doesn't interfere with the written metadata |
| // - we'd be much better having a pristine input, and an ongoing metadata for merging instead |
| |
| Map<ArtifactRepository, Metadata> previousMetadata = new HashMap<>(); |
| ArtifactRepository selected = null; |
| for (ArtifactRepository repository : remoteRepositories) { |
| ArtifactRepositoryPolicy policy = metadata.getPolicy(repository); |
| |
| if (policy.isEnabled() && loadMetadata(metadata, repository, localRepository, previousMetadata)) { |
| metadata.setRepository(repository); |
| selected = repository; |
| } |
| } |
| if (loadMetadata(metadata, localRepository, localRepository, previousMetadata)) { |
| metadata.setRepository(null); |
| selected = localRepository; |
| } |
| |
| updateSnapshotMetadata(metadata, previousMetadata, selected, localRepository); |
| } |
| |
| private void updateSnapshotMetadata( |
| RepositoryMetadata metadata, |
| Map<ArtifactRepository, Metadata> previousMetadata, |
| ArtifactRepository selected, |
| ArtifactRepository localRepository) |
| throws RepositoryMetadataStoreException { |
| // TODO this could be a lot nicer... should really be in the snapshot transformation? |
| if (metadata.isSnapshot()) { |
| Metadata prevMetadata = metadata.getMetadata(); |
| |
| for (ArtifactRepository repository : previousMetadata.keySet()) { |
| Metadata m = previousMetadata.get(repository); |
| if (repository.equals(selected)) { |
| if (m.getVersioning() == null) { |
| m.setVersioning(new Versioning()); |
| } |
| |
| if (m.getVersioning().getSnapshot() == null) { |
| m.getVersioning().setSnapshot(new Snapshot()); |
| } |
| } else { |
| if ((m.getVersioning() != null) |
| && (m.getVersioning().getSnapshot() != null) |
| && m.getVersioning().getSnapshot().isLocalCopy()) { |
| m.getVersioning().getSnapshot().setLocalCopy(false); |
| metadata.setMetadata(m); |
| metadata.storeInLocalRepository(localRepository, repository); |
| } |
| } |
| } |
| |
| metadata.setMetadata(prevMetadata); |
| } |
| } |
| |
| private boolean loadMetadata( |
| RepositoryMetadata repoMetadata, |
| ArtifactRepository remoteRepository, |
| ArtifactRepository localRepository, |
| Map<ArtifactRepository, Metadata> previousMetadata) { |
| boolean setRepository = false; |
| |
| File metadataFile = new File( |
| localRepository.getBasedir(), |
| localRepository.pathOfLocalRepositoryMetadata(repoMetadata, remoteRepository)); |
| |
| if (metadataFile.exists()) { |
| Metadata metadata; |
| |
| try { |
| metadata = readMetadata(metadataFile); |
| } catch (RepositoryMetadataReadException e) { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().warn(e.getMessage(), e); |
| } else { |
| getLogger().warn(e.getMessage()); |
| } |
| return setRepository; |
| } |
| |
| if (repoMetadata.isSnapshot() && (previousMetadata != null)) { |
| previousMetadata.put(remoteRepository, metadata); |
| } |
| |
| if (repoMetadata.getMetadata() != null) { |
| setRepository = repoMetadata.getMetadata().merge(metadata); |
| } else { |
| repoMetadata.setMetadata(metadata); |
| setRepository = true; |
| } |
| } |
| return setRepository; |
| } |
| |
| /* |
| * TODO share with DefaultPluginMappingManager. |
| */ |
| protected Metadata readMetadata(File mappingFile) throws RepositoryMetadataReadException { |
| |
| try (InputStream in = Files.newInputStream(mappingFile.toPath())) { |
| return new Metadata(new MetadataStaxReader().read(in, false)); |
| } catch (FileNotFoundException e) { |
| throw new RepositoryMetadataReadException("Cannot read metadata from '" + mappingFile + "'", e); |
| } catch (IOException | XMLStreamException e) { |
| throw new RepositoryMetadataReadException( |
| "Cannot read metadata from '" + mappingFile + "': " + e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Ensures the last updated timestamp of the specified metadata does not refer to the future and fixes the local |
| * metadata if necessary to allow proper merging/updating of metadata during deployment. |
| */ |
| private void fixTimestamp(File metadataFile, Metadata metadata, Metadata reference) { |
| boolean changed = false; |
| |
| if (metadata != null && reference != null) { |
| Versioning versioning = metadata.getVersioning(); |
| Versioning versioningRef = reference.getVersioning(); |
| if (versioning != null && versioningRef != null) { |
| String lastUpdated = versioning.getLastUpdated(); |
| String now = versioningRef.getLastUpdated(); |
| if (lastUpdated != null && now != null && now.compareTo(lastUpdated) < 0) { |
| getLogger() |
| .warn("The last updated timestamp in " + metadataFile + " refers to the future (now = " |
| + now |
| + ", lastUpdated = " + lastUpdated + "). Please verify that the clocks of all" |
| + " deploying machines are reasonably synchronized."); |
| versioning.setLastUpdated(now); |
| changed = true; |
| } |
| } |
| } |
| |
| if (changed) { |
| getLogger().debug("Repairing metadata in " + metadataFile); |
| |
| try (OutputStream out = Files.newOutputStream(metadataFile.toPath())) { |
| new MetadataStaxWriter().write(out, metadata.getDelegate()); |
| } catch (IOException | XMLStreamException e) { |
| String msg = "Could not write fixed metadata to " + metadataFile + ": " + e.getMessage(); |
| if (getLogger().isDebugEnabled()) { |
| getLogger().warn(msg, e); |
| } else { |
| getLogger().warn(msg); |
| } |
| } |
| } |
| } |
| |
| public void resolveAlways( |
| RepositoryMetadata metadata, ArtifactRepository localRepository, ArtifactRepository remoteRepository) |
| throws RepositoryMetadataResolutionException { |
| File file; |
| try { |
| file = getArtifactMetadataFromDeploymentRepository(metadata, localRepository, remoteRepository); |
| } catch (TransferFailedException e) { |
| throw new RepositoryMetadataResolutionException( |
| metadata + " could not be retrieved from repository: " + remoteRepository.getId() |
| + " due to an error: " + e.getMessage(), |
| e); |
| } |
| |
| try { |
| if (file.exists()) { |
| Metadata prevMetadata = readMetadata(file); |
| metadata.setMetadata(prevMetadata); |
| } |
| } catch (RepositoryMetadataReadException e) { |
| throw new RepositoryMetadataResolutionException(e.getMessage(), e); |
| } |
| } |
| |
| private File getArtifactMetadataFromDeploymentRepository( |
| ArtifactMetadata metadata, ArtifactRepository localRepo, ArtifactRepository remoteRepository) |
| throws TransferFailedException { |
| File file = |
| new File(localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata(metadata, remoteRepository)); |
| |
| try { |
| wagonManager.getArtifactMetadataFromDeploymentRepository( |
| metadata, remoteRepository, file, ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN); |
| } catch (ResourceDoesNotExistException e) { |
| getLogger() |
| .info(metadata + " could not be found on repository: " + remoteRepository.getId() |
| + ", so will be created"); |
| |
| // delete the local copy so the old details aren't used. |
| if (file.exists()) { |
| if (!file.delete()) { |
| // sleep for 10ms just in case this is windows holding a file lock |
| try { |
| Thread.sleep(10); |
| } catch (InterruptedException ie) { |
| // ignore |
| } |
| file.delete(); // if this fails, forget about it |
| } |
| } |
| } finally { |
| if (metadata instanceof RepositoryMetadata) { |
| updateCheckManager.touch((RepositoryMetadata) metadata, remoteRepository, file); |
| } |
| } |
| return file; |
| } |
| |
| public void deploy( |
| ArtifactMetadata metadata, ArtifactRepository localRepository, ArtifactRepository deploymentRepository) |
| throws RepositoryMetadataDeploymentException { |
| File file; |
| if (metadata instanceof RepositoryMetadata) { |
| getLogger().info("Retrieving previous metadata from " + deploymentRepository.getId()); |
| try { |
| file = getArtifactMetadataFromDeploymentRepository(metadata, localRepository, deploymentRepository); |
| } catch (TransferFailedException e) { |
| throw new RepositoryMetadataDeploymentException( |
| metadata + " could not be retrieved from repository: " + deploymentRepository.getId() |
| + " due to an error: " + e.getMessage(), |
| e); |
| } |
| |
| if (file.isFile()) { |
| try { |
| fixTimestamp(file, readMetadata(file), ((RepositoryMetadata) metadata).getMetadata()); |
| } catch (RepositoryMetadataReadException e) { |
| // will be reported via storeInlocalRepository |
| } |
| } |
| } else { |
| // It's a POM - we don't need to retrieve it first |
| file = new File( |
| localRepository.getBasedir(), |
| localRepository.pathOfLocalRepositoryMetadata(metadata, deploymentRepository)); |
| } |
| |
| try { |
| metadata.storeInLocalRepository(localRepository, deploymentRepository); |
| } catch (RepositoryMetadataStoreException e) { |
| throw new RepositoryMetadataDeploymentException("Error installing metadata: " + e.getMessage(), e); |
| } |
| |
| try { |
| wagonManager.putArtifactMetadata(file, metadata, deploymentRepository); |
| } catch (TransferFailedException e) { |
| throw new RepositoryMetadataDeploymentException("Error while deploying metadata: " + e.getMessage(), e); |
| } |
| } |
| |
| public void install(ArtifactMetadata metadata, ArtifactRepository localRepository) |
| throws RepositoryMetadataInstallationException { |
| try { |
| metadata.storeInLocalRepository(localRepository, localRepository); |
| } catch (RepositoryMetadataStoreException e) { |
| throw new RepositoryMetadataInstallationException("Error installing metadata: " + e.getMessage(), e); |
| } |
| } |
| } |