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.support.FileSystemFriendly;
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 FileSystemFriendly
63                  && !( this.nameMapper instanceof FileSystemFriendly ) )
64          {
65              throw new IllegalArgumentException(
66                      "Misconfiguration: FS friendly lock factory requires FS friendly name mapper"
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      public void shutdown()
77      {
78          namedLockFactory.shutdown();
79      }
80  
81      private static class AdaptedLockSyncContext implements SyncContext
82      {
83          private static final Logger LOGGER = LoggerFactory.getLogger( AdaptedLockSyncContext.class );
84  
85          private final RepositorySystemSession session;
86  
87          private final boolean shared;
88  
89          private final NameMapper lockNaming;
90  
91          private final NamedLockFactory namedLockFactory;
92  
93          private final long time;
94  
95          private final TimeUnit timeUnit;
96  
97          private final Deque<NamedLock> locks;
98  
99          private AdaptedLockSyncContext( final RepositorySystemSession session, final boolean shared,
100                                         final NameMapper lockNaming, final NamedLockFactory namedLockFactory )
101         {
102             this.session = session;
103             this.shared = shared;
104             this.lockNaming = lockNaming;
105             this.namedLockFactory = namedLockFactory;
106             this.time = getTime( session );
107             this.timeUnit = getTimeUnit( session );
108             this.locks = new ArrayDeque<>();
109 
110             if ( time < 0L )
111             {
112                 throw new IllegalArgumentException( "time cannot be negative" );
113             }
114         }
115 
116         private long getTime( final RepositorySystemSession session )
117         {
118             return ConfigUtils.getLong( session, DEFAULT_TIME, TIME_KEY );
119         }
120 
121         private TimeUnit getTimeUnit( final RepositorySystemSession session )
122         {
123             return TimeUnit.valueOf( ConfigUtils.getString(
124                 session, DEFAULT_TIME_UNIT.name(), TIME_UNIT_KEY
125             ) );
126         }
127 
128         @Override
129         public void acquire( Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas )
130         {
131             Collection<String> keys = lockNaming.nameLocks( session, artifacts, metadatas );
132             if ( keys.isEmpty() )
133             {
134                 return;
135             }
136 
137             LOGGER.trace( "Need {} {} lock(s) for {}", keys.size(), shared ? "read" : "write", keys );
138             int acquiredLockCount = 0;
139             for ( String key : keys )
140             {
141                 NamedLock namedLock = namedLockFactory.getLock( key );
142                 try
143                 {
144                      LOGGER.trace( "Acquiring {} lock for '{}'",
145                              shared ? "read" : "write", key );
146 
147                     boolean locked;
148                     if ( shared )
149                     {
150                         locked = namedLock.lockShared( time, timeUnit );
151                     }
152                     else
153                     {
154                         locked = namedLock.lockExclusively( time, timeUnit );
155                     }
156 
157                     if ( !locked )
158                     {
159                         LOGGER.trace( "Failed to acquire {} lock for '{}'",
160                                 shared ? "read" : "write", key );
161 
162                         namedLock.close();
163                         throw new IllegalStateException(
164                                 "Could not acquire " + ( shared ? "read" : "write" )
165                                 + " lock for '" + namedLock.name() + "'" );
166                     }
167 
168                     locks.push( namedLock );
169                     acquiredLockCount++;
170                 }
171                 catch ( InterruptedException e )
172                 {
173                     Thread.currentThread().interrupt();
174                     throw new RuntimeException( e );
175                 }
176             }
177             LOGGER.trace( "Total locks acquired: {}", acquiredLockCount );
178         }
179 
180         @Override
181         public void close()
182         {
183             if ( locks.isEmpty() )
184             {
185                 return;
186             }
187 
188             // Release locks in reverse insertion order
189             int released = 0;
190             while ( !locks.isEmpty() )
191             {
192                 try ( NamedLock namedLock = locks.pop() )
193                 {
194                     LOGGER.trace( "Releasing {} lock for '{}'",
195                             shared ? "read" : "write", namedLock.name() );
196                     namedLock.unlock();
197                     released++;
198                 }
199             }
200             LOGGER.trace( "Total locks released: {}", released );
201         }
202     }
203 }