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.ArrayDeque;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.LinkedHashMap;
25  import java.util.Map;
26  import java.util.concurrent.TimeUnit;
27  
28  import org.eclipse.aether.named.NamedLock;
29  import org.eclipse.aether.named.NamedLockKey;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  /**
34   * Implementation of composite lock when "composition" is needed for locks that are naturally mapped as 1:1 name
35   * vs some backing implementation. Instances of these locks are "unique per call" and are not ref counted.
36   *
37   * @since 2.0.0
38   */
39  public final class CompositeNamedLock extends NamedLockSupport {
40      private static final Logger LOGGER = LoggerFactory.getLogger(CompositeNamedLock.class);
41  
42      private final Map<NamedLockKey, NamedLock> locks;
43  
44      private final ArrayDeque<ArrayDeque<NamedLock>> steps = new ArrayDeque<>();
45  
46      public CompositeNamedLock(NamedLockKey key, NamedLockFactorySupport factory, Collection<NamedLock> namedLocks) {
47          super(key, factory);
48          LinkedHashMap<NamedLockKey, NamedLock> map = new LinkedHashMap<>();
49          namedLocks.forEach(l -> map.put(l.key(), l));
50          this.locks = Collections.unmodifiableMap(map);
51      }
52  
53      @Override
54      protected boolean doLockShared(long time, TimeUnit unit) throws InterruptedException {
55          return lock(time, unit, true);
56      }
57  
58      @Override
59      protected boolean doLockExclusively(long time, TimeUnit unit) throws InterruptedException {
60          return lock(time, unit, false);
61      }
62  
63      private boolean lock(long time, TimeUnit timeUnit, boolean shared) throws InterruptedException {
64          final ArrayDeque<NamedLock> step = new ArrayDeque<>(locks.size());
65          final String timeStr = time + " " + timeUnit;
66          final String lockKind = shared ? "shared" : "exclusive";
67          LOGGER.trace(
68                  "{}: Need {} {} lock(s) of {} in {}", key().name(), locks.size(), lockKind, key().resources(), timeStr);
69          try {
70              for (NamedLock namedLock : locks.values()) {
71                  LOGGER.trace("{}: Acquiring {} lock for '{}'", key().name(), lockKind, namedLock.key());
72  
73                  boolean locked;
74                  if (shared) {
75                      locked = namedLock.lockShared(time, timeUnit);
76                  } else {
77                      locked = namedLock.lockExclusively(time, timeUnit);
78                  }
79  
80                  if (!locked) {
81                      LOGGER.trace(
82                              "{}: Failed to acquire {} lock for '{}' in {}",
83                              key().name(),
84                              lockKind,
85                              namedLock.key(),
86                              timeStr);
87  
88                      unlockAll(step);
89                      return false;
90                  } else {
91                      step.push(namedLock);
92                  }
93              }
94          } catch (Throwable t) {
95              unlockAll(step);
96              throw t;
97          }
98          steps.push(step);
99          return true;
100     }
101 
102     @Override
103     protected void doUnlock() {
104         unlockAll(steps.pop());
105     }
106 
107     @Override
108     protected void doClose() {
109         locks.values().forEach(NamedLock::close);
110     }
111 
112     private void unlockAll(final ArrayDeque<NamedLock> locks) {
113         if (locks.isEmpty()) {
114             return;
115         }
116 
117         // Release locks in reverse locking order
118         while (!locks.isEmpty()) {
119             NamedLock namedLock = locks.pop();
120             LOGGER.trace("{}: Releasing lock for '{}'", key().name(), namedLock.key());
121             namedLock.unlock();
122         }
123     }
124 }