View Javadoc
1   package org.eclipse.aether.internal.impl;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   * 
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   * 
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Map;
28  import static java.util.Objects.requireNonNull;
29  
30  import java.util.Objects;
31  import java.util.Properties;
32  
33  import org.eclipse.aether.RepositorySystemSession;
34  import org.eclipse.aether.artifact.Artifact;
35  import org.eclipse.aether.repository.LocalArtifactRegistration;
36  import org.eclipse.aether.repository.LocalArtifactRequest;
37  import org.eclipse.aether.repository.LocalArtifactResult;
38  import org.eclipse.aether.repository.RemoteRepository;
39  import org.eclipse.aether.util.ConfigUtils;
40  
41  /**
42   * These are implementation details for enhanced local repository manager, subject to change without prior notice.
43   * Repositories from which a cached artifact was resolved are tracked in a properties file named
44   * <code>_remote.repositories</code>, with content key as filename&gt;repo_id and value as empty string. If a file has
45   * been installed in the repository, but not downloaded from a remote repository, it is tracked as empty repository id
46   * and always resolved. For example:
47   * 
48   * <pre>
49   * artifact-1.0.pom>=
50   * artifact-1.0.jar>=
51   * artifact-1.0.pom>central=
52   * artifact-1.0.jar>central=
53   * artifact-1.0.zip>central=
54   * artifact-1.0-classifier.zip>central=
55   * artifact-1.0.pom>my_repo_id=
56   * </pre>
57   * 
58   * @see EnhancedLocalRepositoryManagerFactory
59   */
60  class EnhancedLocalRepositoryManager
61      extends SimpleLocalRepositoryManager
62  {
63  
64      private static final String LOCAL_REPO_ID = "";
65  
66      private final String trackingFilename;
67  
68      private final TrackingFileManager trackingFileManager;
69  
70      EnhancedLocalRepositoryManager( File basedir,
71                                      RepositorySystemSession session,
72                                      TrackingFileManager trackingFileManager )
73      {
74          super( basedir, "enhanced" );
75          String filename = ConfigUtils.getString( session, "", "aether.enhancedLocalRepository.trackingFilename" );
76          if ( filename.isEmpty() || filename.contains( "/" ) || filename.contains( "\\" )
77              || filename.contains( ".." ) )
78          {
79              filename = "_remote.repositories";
80          }
81          this.trackingFilename = filename;
82          this.trackingFileManager = Objects.requireNonNull( trackingFileManager );
83      }
84  
85      @Override
86      public LocalArtifactResult find( RepositorySystemSession session, LocalArtifactRequest request )
87      {
88          String path = getPathForArtifact( request.getArtifact(), false );
89          File file = new File( getRepository().getBasedir(), path );
90  
91          LocalArtifactResult result = new LocalArtifactResult( request );
92  
93          if ( file.isFile() )
94          {
95              result.setFile( file );
96  
97              Properties props = readRepos( file );
98  
99              if ( props.get( getKey( file, LOCAL_REPO_ID ) ) != null )
100             {
101                 // artifact installed into the local repo is always accepted
102                 result.setAvailable( true );
103             }
104             else
105             {
106                 String context = request.getContext();
107                 for ( RemoteRepository repository : request.getRepositories() )
108                 {
109                     if ( props.get( getKey( file, getRepositoryKey( repository, context ) ) ) != null )
110                     {
111                         // artifact downloaded from remote repository is accepted only downloaded from request
112                         // repositories
113                         result.setAvailable( true );
114                         result.setRepository( repository );
115                         break;
116                     }
117                 }
118                 if ( !result.isAvailable() && !isTracked( props, file ) )
119                 {
120                     /*
121                      * NOTE: The artifact is present but not tracked at all, for inter-op with simple local repo, assume
122                      * the artifact was locally installed.
123                      */
124                     result.setAvailable( true );
125                 }
126             }
127         }
128 
129         return result;
130     }
131 
132     @Override
133     public void add( RepositorySystemSession session, LocalArtifactRegistration request )
134     {
135         Collection<String> repositories;
136         if ( request.getRepository() == null )
137         {
138             repositories = Collections.singleton( LOCAL_REPO_ID );
139         }
140         else
141         {
142             repositories = getRepositoryKeys( request.getRepository(), request.getContexts() );
143         }
144         addArtifact( request.getArtifact(), repositories, request.getRepository() == null );
145     }
146 
147     private Collection<String> getRepositoryKeys( RemoteRepository repository, Collection<String> contexts )
148     {
149         Collection<String> keys = new HashSet<>();
150 
151         if ( contexts != null )
152         {
153             for ( String context : contexts )
154             {
155                 keys.add( getRepositoryKey( repository, context ) );
156             }
157         }
158 
159         return keys;
160     }
161 
162     private void addArtifact( Artifact artifact, Collection<String> repositories, boolean local )
163     {
164         String path = getPathForArtifact( requireNonNull( artifact, "artifact cannot be null" ), local );
165         File file = new File( getRepository().getBasedir(), path );
166         addRepo( file, repositories );
167     }
168 
169     private Properties readRepos( File artifactFile )
170     {
171         File trackingFile = getTrackingFile( artifactFile );
172 
173         Properties props = trackingFileManager.read( trackingFile );
174 
175         return ( props != null ) ? props : new Properties();
176     }
177 
178     private void addRepo( File artifactFile, Collection<String> repositories )
179     {
180         Map<String, String> updates = new HashMap<>();
181         for ( String repository : repositories )
182         {
183             updates.put( getKey( artifactFile, repository ), "" );
184         }
185 
186         File trackingFile = getTrackingFile( artifactFile );
187 
188         trackingFileManager.update( trackingFile, updates );
189     }
190 
191     private File getTrackingFile( File artifactFile )
192     {
193         return new File( artifactFile.getParentFile(), trackingFilename );
194     }
195 
196     private String getKey( File file, String repository )
197     {
198         return file.getName() + '>' + repository;
199     }
200 
201     private boolean isTracked( Properties props, File file )
202     {
203         if ( props != null )
204         {
205             String keyPrefix = file.getName() + '>';
206             for ( Object key : props.keySet() )
207             {
208                 if ( key.toString().startsWith( keyPrefix ) )
209                 {
210                     return true;
211                 }
212             }
213         }
214         return false;
215     }
216 
217 }