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.Collection;
022import java.util.Deque;
023import java.util.HashMap;
024import java.util.Map;
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.concurrent.ConcurrentMap;
027import java.util.concurrent.atomic.AtomicBoolean;
028import java.util.concurrent.atomic.AtomicInteger;
029import java.util.function.Supplier;
030import java.util.stream.Collectors;
031
032import org.eclipse.aether.named.NamedLock;
033import org.eclipse.aether.named.NamedLockFactory;
034import org.eclipse.aether.named.NamedLockKey;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038import static java.util.Objects.requireNonNull;
039
040/**
041 * Support class for {@link NamedLockFactory} implementations providing reference counting.
042 */
043public abstract class NamedLockFactorySupport implements NamedLockFactory {
044    /**
045     * System property key to enable locking diagnostic collection.
046     *
047     * @since 1.9.11
048     * @configurationSource {@link System#getProperty(String, String)}
049     * @configurationType {@link java.lang.Boolean}
050     * @configurationDefaultValue false
051     */
052    public static final String SYSTEM_PROP_DIAGNOSTIC_ENABLED = "aether.named.diagnostic.enabled";
053
054    private static final boolean DIAGNOSTIC_ENABLED = Boolean.getBoolean(SYSTEM_PROP_DIAGNOSTIC_ENABLED);
055
056    protected final Logger logger = LoggerFactory.getLogger(getClass());
057
058    private final ConcurrentMap<NamedLockKey, NamedLockHolder> locks;
059
060    private final AtomicInteger compositeCounter;
061
062    private final boolean diagnosticEnabled;
063
064    private final AtomicBoolean shutdown = new AtomicBoolean(false);
065
066    public NamedLockFactorySupport() {
067        this(DIAGNOSTIC_ENABLED);
068    }
069
070    public NamedLockFactorySupport(boolean diagnosticEnabled) {
071        this.locks = new ConcurrentHashMap<>();
072        this.compositeCounter = new AtomicInteger(0);
073        this.diagnosticEnabled = diagnosticEnabled;
074    }
075
076    /**
077     * Returns {@code true} if factory diagnostic collection is enabled.
078     *
079     * @since 1.9.11
080     */
081    public boolean isDiagnosticEnabled() {
082        return diagnosticEnabled;
083    }
084
085    @Override
086    public final NamedLock getLock(final Collection<NamedLockKey> keys) {
087        requireNonNull(keys, "keys");
088        if (shutdown.get()) {
089            throw new IllegalStateException("factory already shut down");
090        }
091        if (keys.isEmpty()) {
092            throw new IllegalArgumentException("empty keys");
093        } else {
094            return doGetLock(keys);
095        }
096    }
097
098    protected NamedLock doGetLock(final Collection<NamedLockKey> keys) {
099        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}