View Javadoc
1   package org.eclipse.aether.internal.impl.synccontext.named;
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 org.eclipse.aether.RepositorySystemSession;
23  import org.eclipse.aether.SyncContext;
24  import org.eclipse.aether.artifact.Artifact;
25  import org.eclipse.aether.metadata.Metadata;
26  import org.eclipse.aether.named.NamedLock;
27  import org.eclipse.aether.named.NamedLockFactory;
28  import org.eclipse.aether.named.providers.FileLockNamedLockFactory;
29  import org.eclipse.aether.util.ConfigUtils;
30  
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  import java.util.ArrayDeque;
35  import java.util.Collection;
36  import java.util.Deque;
37  import java.util.Objects;
38  import java.util.concurrent.TimeUnit;
39  
40  /**
41   * Adapter to adapt {@link NamedLockFactory} and {@link NamedLock} to {@link SyncContext}.
42   */
43  public final class NamedLockFactoryAdapter
44  {
45      public static final String TIME_KEY = "aether.syncContext.named.time";
46  
47      public static final long DEFAULT_TIME = 30L;
48  
49      public static final String TIME_UNIT_KEY = "aether.syncContext.named.time.unit";
50  
51      public static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;
52  
53      private final NameMapper nameMapper;
54  
55      private final NamedLockFactory namedLockFactory;
56  
57      public NamedLockFactoryAdapter( final NameMapper nameMapper, final NamedLockFactory namedLockFactory )
58      {
59          this.nameMapper = Objects.requireNonNull( nameMapper );
60          this.namedLockFactory = Objects.requireNonNull( namedLockFactory );
61          // TODO: this is ad-hoc "validation", experimental and likely to change
62          if ( this.namedLockFactory instanceof FileLockNamedLockFactory
63                  && !this.nameMapper.isFileSystemFriendly() )
64          {
65              throw new IllegalArgumentException(
66                      "Misconfiguration: FileLockNamedLockFactory lock factory requires FS friendly NameMapper"
67              );
68          }
69      }
70  
71      public SyncContext newInstance( final RepositorySystemSession session, final boolean shared )
72      {
73          return new AdaptedLockSyncContext( session, shared, nameMapper, namedLockFactory );
74      }
75  
76      /**
77       * @since 1.9.1
78       */
79      public NameMapper getNameMapper()
80      {
81          return nameMapper;
82      }
83  
84      /**
85       * @since 1.9.1
86       */
87      public NamedLockFactory getNamedLockFactory()
88      {
89          return namedLockFactory;
90      }
91  
92      public String toString()
93      {
94          return getClass().getSimpleName()
95                  + "(nameMapper=" + nameMapper
96                  + ", namedLockFactory=" + namedLockFactory
97                  + ")";
98      }
99  
100     private static class AdaptedLockSyncContext implements SyncContext
101     {
102         private static final Logger LOGGER = LoggerFactory.getLogger( AdaptedLockSyncContext.class );
103 
104         private final RepositorySystemSession session;
105 
106         private final boolean shared;
107 
108         private final NameMapper lockNaming;
109 
110         private final NamedLockFactory namedLockFactory;
111 
112         private final long time;
113 
114         private final TimeUnit timeUnit;
115 
116         private final Deque<NamedLock> locks;
117 
118         private AdaptedLockSyncContext( final RepositorySystemSession session, final boolean shared,
119                                         final NameMapper lockNaming, final NamedLockFactory namedLockFactory )
120         {
121             this.session = session;
122             this.shared = shared;
123             this.lockNaming = lockNaming;
124             this.namedLockFactory = namedLockFactory;
125             this.time = getTime( session );
126             this.timeUnit = getTimeUnit( session );
127             this.locks = new ArrayDeque<>();
128 
129             if ( time < 0L )
130             {
131                 throw new IllegalArgumentException( "time cannot be negative" );
132             }
133         }
134 
135         private long getTime( final RepositorySystemSession session )
136         {
137             return ConfigUtils.getLong( session, DEFAULT_TIME, TIME_KEY );
138         }
139 
140         private TimeUnit getTimeUnit( final RepositorySystemSession session )
141         {
142             return TimeUnit.valueOf( ConfigUtils.getString(
143                 session, DEFAULT_TIME_UNIT.name(), TIME_UNIT_KEY
144             ) );
145         }
146 
147         @Override
148         public void acquire( Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas )
149         {
150             Collection<String> keys = lockNaming.nameLocks( session, artifacts, metadatas );
151             if ( keys.isEmpty() )
152             {
153                 return;
154             }
155 
156             LOGGER.trace( "Need {} {} lock(s) for {}", keys.size(), shared ? "read" : "write", keys );
157             int acquiredLockCount = 0;
158             for ( String key : keys )
159             {
160                 NamedLock namedLock = namedLockFactory.getLock( key );
161                 try
162                 {
163                      LOGGER.trace( "Acquiring {} lock for '{}'",
164                              shared ? "read" : "write", key );
165 
166                     boolean locked;
167                     if ( shared )
168                     {
169                         locked = namedLock.lockShared( time, timeUnit );
170                     }
171                     else
172                     {
173                         locked = namedLock.lockExclusively( time, timeUnit );
174                     }
175 
176                     if ( !locked )
177                     {
178                         LOGGER.trace( "Failed to acquire {} lock for '{}'",
179                                 shared ? "read" : "write", key );
180 
181                         namedLock.close();
182                         throw new IllegalStateException(
183                                 "Could not acquire " + ( shared ? "read" : "write" )
184                                 + " lock for '" + namedLock.name() + "'" );
185                     }
186 
187                     locks.push( namedLock );
188                     acquiredLockCount++;
189                 }
190                 catch ( InterruptedException e )
191                 {
192                     Thread.currentThread().interrupt();
193                     throw new RuntimeException( e );
194                 }
195             }
196             LOGGER.trace( "Total locks acquired: {}", acquiredLockCount );
197         }
198 
199         @Override
200         public void close()
201         {
202             if ( locks.isEmpty() )
203             {
204                 return;
205             }
206 
207             // Release locks in reverse insertion order
208             int released = 0;
209             while ( !locks.isEmpty() )
210             {
211                 try ( NamedLock namedLock = locks.pop() )
212                 {
213                     LOGGER.trace( "Releasing {} lock for '{}'",
214                             shared ? "read" : "write", namedLock.name() );
215                     namedLock.unlock();
216                     released++;
217                 }
218             }
219             LOGGER.trace( "Total locks released: {}", released );
220         }
221     }
222 }