001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.internal.impl; 020 021import javax.inject.Inject; 022import javax.inject.Named; 023import javax.inject.Singleton; 024 025import java.io.IOException; 026 027import org.eclipse.aether.ConfigurationProperties; 028import org.eclipse.aether.RepositorySystemSession; 029import org.eclipse.aether.repository.LocalRepository; 030import org.eclipse.aether.repository.LocalRepositoryManager; 031import org.eclipse.aether.repository.NoLocalRepositoryManagerException; 032import org.eclipse.aether.spi.localrepo.LocalRepositoryManagerFactory; 033import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory; 034import org.eclipse.aether.util.ConfigUtils; 035 036import static java.util.Objects.requireNonNull; 037 038/** 039 * Creates enhanced local repository managers for repository types {@code "default"} or {@code "" (automatic)}. Enhanced 040 * local repository manager is built upon the classical Maven 2.0 local repository structure but additionally keeps 041 * track of from what repositories a cached artifact was resolved. Resolution of locally cached artifacts will be 042 * rejected in case the current resolution request does not match the known source repositories of an artifact, thereby 043 * emulating physically separated artifact caches per remote repository. 044 */ 045@Singleton 046@Named(EnhancedLocalRepositoryManagerFactory.NAME) 047public class EnhancedLocalRepositoryManagerFactory implements LocalRepositoryManagerFactory { 048 public static final String NAME = "enhanced"; 049 050 static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_LRM + NAME + "."; 051 052 /** 053 * Filename of the file in which to track the remote repositories. 054 * 055 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 056 * @configurationType {@link java.lang.String} 057 * @configurationDefaultValue {@link #DEFAULT_TRACKING_FILENAME} 058 */ 059 public static final String CONFIG_PROP_TRACKING_FILENAME = CONFIG_PROPS_PREFIX + "trackingFilename"; 060 061 public static final String DEFAULT_TRACKING_FILENAME = "_remote.repositories"; 062 063 /** 064 * Whether to verify that the real (on-disk) path of a locally cached artifact matches the requested path 065 * spelling before the artifact is used. On case-insensitive or case/normalization-preserving filesystems (the 066 * macOS and Windows defaults) a file cached for one set of coordinates also answers lookups for coordinates 067 * that differ only in case or Unicode normalization, while the repository tracking data is compared exactly: 068 * such an aliased file is treated as present-but-untracked and accepted with no download and no checksum 069 * verification, letting case-colliding coordinates poison distinct GAVs. When enabled (the default), an 070 * artifact whose on-disk path spelling differs from the requested one is treated as not present, forcing a 071 * proper download. Disable only if the local repository intentionally contains symbolic links below its base 072 * directory (a symlinked base directory itself is supported either way). 073 * 074 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 075 * @configurationType {@link java.lang.Boolean} 076 * @configurationDefaultValue {@link #DEFAULT_VERIFY_REAL_PATH} 077 * @since 2.0.23 078 */ 079 public static final String CONFIG_PROP_VERIFY_REAL_PATH = CONFIG_PROPS_PREFIX + "verifyRealPath"; 080 081 public static final boolean DEFAULT_VERIFY_REAL_PATH = true; 082 083 /** 084 * Marks whether the local repository is meant to be shared (or was shared) with legacy Maven 3.9 or older 085 * versions. Maven 3.9 and older versions suffer from "impostor" problem, where artifact and metadata origin was 086 * tracked only by the remote repository ID, where two remote repositories may share same ID but different URLs, 087 * in fact they may be completely unrelated to each other (ID clash by mistake), or, it may be due some sort of 088 * "impostor" attempt, where a malicious repository may pretend like some other repository. 089 * Right now, we intentionally default to {@code true} to ease users transitioning, and Resolver 2 will retain 090 * this "old" behavior (will observe legacy tracking entries and will store remote metadata as before). But, 091 * at some point in the future, the default value will be changed to {@code false} (and same change is warmly 092 * recommended for modern Maven users, who do not intend to share local repository with older Maven versions). 093 * When this configuration set to {@code false}, the "repository key" is not ID only anymore, but is changed 094 * to {@code $id-sha1($url)} form, and this key is used in "origin tracking" entries and in caching remote 095 * Maven Repository Metadata XML files as well, guaranteeing they are not mixed in case of same IDs. 096 * 097 * @see ConfigurationProperties#REPOSITORY_SYSTEM_REPOSITORY_KEY_FUNCTION 098 * @see ConfigurationProperties#REPOSITORY_TRACKING_REPOSITORY_KEY_FUNCTION 099 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 100 * @configurationType {@link java.lang.Boolean} 101 * @configurationDefaultValue {@link #DEFAULT_LEGACY_LOCAL_REPOSITORY} 102 * @since 2.0.23 103 */ 104 public static final String CONFIG_PROP_LEGACY_LOCAL_REPOSITORY = CONFIG_PROPS_PREFIX + "legacyLocalRepository"; 105 106 public static final boolean DEFAULT_LEGACY_LOCAL_REPOSITORY = true; 107 108 private float priority = 10.0f; 109 110 private final LocalPathComposer localPathComposer; 111 112 private final TrackingFileManager trackingFileManager; 113 114 private final LocalPathPrefixComposerFactory localPathPrefixComposerFactory; 115 116 private final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory; 117 118 @Inject 119 public EnhancedLocalRepositoryManagerFactory( 120 final LocalPathComposer localPathComposer, 121 final TrackingFileManager trackingFileManager, 122 final LocalPathPrefixComposerFactory localPathPrefixComposerFactory, 123 final RepositoryKeyFunctionFactory repositoryKeyFunctionFactory) { 124 this.localPathComposer = requireNonNull(localPathComposer); 125 this.trackingFileManager = requireNonNull(trackingFileManager); 126 this.localPathPrefixComposerFactory = requireNonNull(localPathPrefixComposerFactory); 127 this.repositoryKeyFunctionFactory = requireNonNull(repositoryKeyFunctionFactory); 128 } 129 130 @Override 131 public LocalRepositoryManager newInstance(RepositorySystemSession session, LocalRepository repository) 132 throws NoLocalRepositoryManagerException { 133 requireNonNull(session, "session cannot be null"); 134 requireNonNull(repository, "repository cannot be null"); 135 136 String trackingFilename = ConfigUtils.getString(session, "", CONFIG_PROP_TRACKING_FILENAME); 137 if (trackingFilename.isEmpty() 138 || trackingFilename.contains("/") 139 || trackingFilename.contains("\\") 140 || trackingFilename.contains("..")) { 141 trackingFilename = DEFAULT_TRACKING_FILENAME; 142 } 143 boolean legacyLocalRepository = 144 ConfigUtils.getBoolean(session, DEFAULT_LEGACY_LOCAL_REPOSITORY, CONFIG_PROP_LEGACY_LOCAL_REPOSITORY); 145 146 if ("".equals(repository.getContentType()) || "default".equals(repository.getContentType())) { 147 try { 148 return new EnhancedLocalRepositoryManager( 149 repository.getBasePath(), 150 localPathComposer, 151 repositoryKeyFunctionFactory.trackingRepositoryKeyFunction(session), 152 trackingFilename, 153 legacyLocalRepository, 154 trackingFileManager, 155 localPathPrefixComposerFactory.createComposer(session)); 156 } catch (IOException e) { 157 throw new NoLocalRepositoryManagerException(repository, e); 158 } 159 } else { 160 throw new NoLocalRepositoryManagerException(repository); 161 } 162 } 163 164 @Override 165 public float getPriority() { 166 return priority; 167 } 168 169 /** 170 * Sets the priority of this component. 171 * 172 * @param priority The priority. 173 * @return This component for chaining, never {@code null}. 174 */ 175 public EnhancedLocalRepositoryManagerFactory setPriority(float priority) { 176 this.priority = priority; 177 return this; 178 } 179}