View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.named.support;
20  
21  import java.util.Collection;
22  import java.util.Deque;
23  import java.util.HashMap;
24  import java.util.Map;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.concurrent.ConcurrentMap;
27  import java.util.concurrent.atomic.AtomicBoolean;
28  import java.util.concurrent.atomic.AtomicInteger;
29  import java.util.function.Supplier;
30  import java.util.stream.Collectors;
31  
32  import org.eclipse.aether.named.NamedLock;
33  import org.eclipse.aether.named.NamedLockFactory;
34  import org.eclipse.aether.named.NamedLockKey;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  import static java.util.Objects.requireNonNull;
39  
40  /**
41   * Support class for {@link NamedLockFactory} implementations providing reference counting.
42   */
43  public abstract class NamedLockFactorySupport implements NamedLockFactory {
44      /**
45       * System property key to enable locking diagnostic collection.
46       *
47       * @since 1.9.11
48       * @configurationSource {@link System#getProperty(String, String)}
49       * @configurationType {@link java.lang.Boolean}
50       * @configurationDefaultValue false
51       */
52      public static final String SYSTEM_PROP_DIAGNOSTIC_ENABLED = "aether.named.diagnostic.enabled";
53  
54      private static final boolean DIAGNOSTIC_ENABLED = Boolean.getBoolean(SYSTEM_PROP_DIAGNOSTIC_ENABLED);
55  
56      protected final Logger logger = LoggerFactory.getLogger(getClass());
57  
58      private final ConcurrentMap<NamedLockKey, NamedLockHolder> locks;
59  
60      private final AtomicInteger compositeCounter;
61  
62      private final boolean diagnosticEnabled;
63  
64      private final AtomicBoolean shutdown = new AtomicBoolean(false);
65  
66      public NamedLockFactorySupport() {
67          this(DIAGNOSTIC_ENABLED);
68      }
69  
70      public NamedLockFactorySupport(boolean diagnosticEnabled) {
71          this.locks = new ConcurrentHashMap<>();
72          this.compositeCounter = new AtomicInteger(0);
73          this.diagnosticEnabled = diagnosticEnabled;
74      }
75  
76      /**
77       * Returns {@code true} if factory diagnostic collection is enabled.
78       *
79       * @since 1.9.11
80       */
81      public boolean isDiagnosticEnabled() {
82          return diagnosticEnabled;
83      }
84  
85      @Override
86      public final NamedLock getLock(final Collection<NamedLockKey> keys) {
87          requireNonNull(keys, "keys");
88          if (shutdown.get()) {
89              throw new IllegalStateException("factory already shut down");
90          }
91          if (keys.isEmpty()) {
92              throw new IllegalArgumentException("empty keys");
93          } else {
94              return doGetLock(keys);
95          }
96      }
97  
98      protected NamedLock doGetLock(final Collection<NamedLockKey> keys) {
99          if (keys.size() == 1) {
100             NamedLockKey key = keys.iterator().next();
101             return getLockAndRefTrack(key, () -> createLock(key));
102         } else {
103             return new CompositeNamedLock(
104                     NamedLockKey.of(
105                             "composite-" + compositeCounter.incrementAndGet(),
106                             keys.stream()
107                                     .map(NamedLockKey::resources)
108                                     .flatMap(Collection::stream)
109                                     .collect(Collectors.toList())),
110                     this,
111                     keys.stream()
112                             .map(k -> getLockAndRefTrack(k, () -> createLock(k)))
113                             .collect(Collectors.toList()));
114         }
115     }
116 
117     protected NamedLock getLockAndRefTrack(final NamedLockKey key, Supplier<NamedLockSupport> supplier) {
118         return locks.compute(key, (k, v) -> {
119                     if (v == null) {
120                         v = new NamedLockHolder(supplier.get());
121                     }
122                     return v.incRef();
123                 })
124                 .namedLock;
125     }
126 
127     @Override
128     public void shutdown() {
129         if (shutdown.compareAndSet(false, true)) {
130             doShutdown();
131         }
132     }
133 
134     protected void doShutdown() {
135         // override if needed
136     }
137 
138     @Override
139     public <E extends Throwable> E onFailure(E failure) {
140         if (isDiagnosticEnabled()) {
141             Map<NamedLockKey, NamedLockHolder> locks = new HashMap<>(this.locks); // copy
142             int activeLocks = locks.size();
143             logger.info("Diagnostic dump of lock factory");
144             logger.info("===============================");
145             logger.info("Implementation: {}", getClass().getName());
146             logger.info("Active locks: {}", activeLocks);
147             logger.info("");
148             if (activeLocks > 0) {
149                 for (Map.Entry<NamedLockKey, NamedLockHolder> entry : locks.entrySet()) {
150                     NamedLockKey key = entry.getKey();
151                     int refCount = entry.getValue().referenceCount.get();
152                     NamedLockSupport lock = entry.getValue().namedLock;
153                     logger.info("Name: {}", key.name());
154                     logger.info("RefCount: {}", refCount);
155                     logger.info("Resources:");
156                     key.resources().forEach(r -> logger.info(" - {}", r));
157                     Map<Thread, Deque<String>> diag = lock.diagnosticState();
158                     logger.info("State:");
159                     diag.forEach((k, v) -> logger.info("  {} -> {}", k, v));
160                 }
161                 logger.info("");
162             }
163         }
164         return failure;
165     }
166 
167     public void closeLock(final NamedLockKey key) {
168         locks.compute(key, (k, v) -> {
169             if (v != null && v.decRef() == 0) {
170                 destroyLock(v.namedLock);
171                 return null;
172             }
173             return v;
174         });
175     }
176 
177     /**
178      * Implementations shall create and return {@link NamedLockSupport} for given {@code name}, this method must never
179      * return {@code null}.
180      */
181     protected abstract NamedLockSupport createLock(NamedLockKey key);
182 
183     /**
184      * Implementation may override this (empty) method to perform some sort of implementation specific cleanup for
185      * given lock name. Invoked when reference count for given name drops to zero and named lock was removed.
186      */
187     protected void destroyLock(final NamedLock namedLock) {
188         // override if needed
189     }
190 
191     private static final class NamedLockHolder {
192         private final NamedLockSupport namedLock;
193 
194         private final AtomicInteger referenceCount;
195 
196         private NamedLockHolder(final NamedLockSupport namedLock) {
197             this.namedLock = requireNonNull(namedLock);
198             this.referenceCount = new AtomicInteger(0);
199         }
200 
201         private NamedLockHolder incRef() {
202             referenceCount.incrementAndGet();
203             return this;
204         }
205 
206         private int decRef() {
207             return referenceCount.decrementAndGet();
208         }
209 
210         @Override
211         public String toString() {
212             return "[refCount=" + referenceCount.get() + ", lock=" + namedLock + "]";
213         }
214     }
215 }