001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.named.support;
020
021import java.util.ArrayDeque;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.LinkedHashMap;
025import java.util.Map;
026import java.util.concurrent.TimeUnit;
027
028import org.eclipse.aether.named.NamedLock;
029import org.eclipse.aether.named.NamedLockKey;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * Implementation of composite lock when "composition" is needed for locks that are naturally mapped as 1:1 name
035 * vs some backing implementation. Instances of these locks are "unique per call" and are not ref counted.
036 *
037 * @since 2.0.0
038 */
039public final class CompositeNamedLock extends NamedLockSupport {
040    private static final Logger LOGGER = LoggerFactory.getLogger(CompositeNamedLock.class);
041
042    private final Map<NamedLockKey, NamedLock> locks;
043
044    private final ArrayDeque<ArrayDeque<NamedLock>> steps = new ArrayDeque<>();
045
046    public CompositeNamedLock(NamedLockKey key, NamedLockFactorySupport factory, Collection<NamedLock> namedLocks) {
047        super(key, factory);
048        LinkedHashMap<NamedLockKey, NamedLock> map = new LinkedHashMap<>();
049        namedLocks.forEach(l -> map.put(l.key(), l));
050        this.locks = Collections.unmodifiableMap(map);
051    }
052
053    @Override
054    protected boolean doLockShared(long time, TimeUnit unit) throws InterruptedException {
055        return lock(time, unit, true);
056    }
057
058    @Override
059    protected boolean doLockExclusively(long time, TimeUnit unit) throws InterruptedException {
060        return lock(time, unit, false);
061    }
062
063    private boolean lock(long time, TimeUnit timeUnit, boolean shared) throws InterruptedException {
064        final ArrayDeque<NamedLock> step = new ArrayDeque<>(locks.size());
065        final String timeStr = time + " " + timeUnit;
066        final String lockKind = shared ? "shared" : "exclusive";
067        LOGGER.trace(
068                "{}: Need {} {} lock(s) of {} in {}", key().name(), locks.size(), lockKind, key().resources(), timeStr);
069        for (NamedLock namedLock : locks.values()) {
070            LOGGER.trace("{}: Acquiring {} lock for '{}'", key().name(), lockKind, namedLock.key());
071
072            boolean locked;
073            if (shared) {
074                locked = namedLock.lockShared(time, timeUnit);
075            } else {
076                locked = namedLock.lockExclusively(time, timeUnit);
077            }
078
079            if (!locked) {
080                LOGGER.trace(
081                        "{}: Failed to acquire {} lock for '{}' in {}",
082                        key().name(),
083                        lockKind,
084                        namedLock.key(),
085                        timeStr);
086
087                unlockAll(step);
088                break;
089            } else {
090                step.push(namedLock);
091            }
092        }
093        if (step.size() == locks.size()) {
094            steps.push(step);
095            return true;
096        }
097        unlockAll(step);
098        return false;
099    }
100
101    @Override
102    protected void doUnlock() {
103        unlockAll(steps.pop());
104    }
105
106    @Override
107    protected void doClose() {
108        locks.values().forEach(NamedLock::close);
109    }
110
111    private void unlockAll(final ArrayDeque<NamedLock> locks) {
112        if (locks.isEmpty()) {
113            return;
114        }
115
116        // Release locks in reverse locking order
117        while (!locks.isEmpty()) {
118            NamedLock namedLock = locks.pop();
119            LOGGER.trace("{}: Releasing lock for '{}'", key().name(), namedLock.key());
120            namedLock.unlock();
121        }
122    }
123}