001package org.eclipse.aether.internal.impl.synccontext.named;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.eclipse.aether.RepositorySystemSession;
023import org.eclipse.aether.SyncContext;
024import org.eclipse.aether.artifact.Artifact;
025import org.eclipse.aether.metadata.Metadata;
026import org.eclipse.aether.named.NamedLock;
027import org.eclipse.aether.named.NamedLockFactory;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031import java.util.ArrayDeque;
032import java.util.Collection;
033import java.util.Deque;
034import java.util.Objects;
035import java.util.concurrent.TimeUnit;
036
037/**
038 * Adapter to adapt {@link NamedLockFactory} and {@link NamedLock} to {@link SyncContext}.
039 */
040public final class NamedLockFactoryAdapter
041{
042    private final NameMapper nameMapper;
043
044    private final NamedLockFactory namedLockFactory;
045
046    private final long time;
047
048    private final TimeUnit timeUnit;
049
050    public NamedLockFactoryAdapter( final NameMapper nameMapper, final NamedLockFactory namedLockFactory,
051                                    final long time, final TimeUnit timeUnit )
052    {
053        this.nameMapper = Objects.requireNonNull( nameMapper );
054        this.namedLockFactory = Objects.requireNonNull( namedLockFactory );
055        if ( time < 0L )
056        {
057            throw new IllegalArgumentException( "time cannot be negative" );
058        }
059        this.time = time;
060        this.timeUnit = Objects.requireNonNull( timeUnit );
061    }
062
063    public SyncContext newInstance( final RepositorySystemSession session, final boolean shared )
064    {
065        return new AdaptedLockSyncContext( session, shared, nameMapper, namedLockFactory, time, timeUnit );
066    }
067
068    public void shutdown()
069    {
070        namedLockFactory.shutdown();
071    }
072
073    private static class AdaptedLockSyncContext implements SyncContext
074    {
075        private static final Logger LOGGER = LoggerFactory.getLogger( AdaptedLockSyncContext.class );
076
077        private final RepositorySystemSession session;
078
079        private final boolean shared;
080
081        private final NameMapper lockNaming;
082
083        private final SessionAwareNamedLockFactory sessionAwareNamedLockFactory;
084
085        private final NamedLockFactory namedLockFactory;
086
087        private final long time;
088
089        private final TimeUnit timeUnit;
090
091        private final Deque<NamedLock> locks;
092
093        private AdaptedLockSyncContext( final RepositorySystemSession session, final boolean shared,
094                                        final NameMapper lockNaming, final NamedLockFactory namedLockFactory,
095                                        final long time, final TimeUnit timeUnit )
096        {
097            this.session = session;
098            this.shared = shared;
099            this.lockNaming = lockNaming;
100            this.sessionAwareNamedLockFactory = namedLockFactory instanceof SessionAwareNamedLockFactory
101                    ? (SessionAwareNamedLockFactory) namedLockFactory : null;
102            this.namedLockFactory = namedLockFactory;
103            this.time = time;
104            this.timeUnit = timeUnit;
105            this.locks = new ArrayDeque<>();
106        }
107
108        @Override
109        public void acquire( Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas )
110        {
111            Collection<String> keys = lockNaming.nameLocks( session, artifacts, metadatas );
112            if ( keys.isEmpty() )
113            {
114                return;
115            }
116
117            LOGGER.trace( "Need {} {} lock(s) for {}", keys.size(), shared ? "read" : "write", keys );
118            int acquiredLockCount = 0;
119            for ( String key : keys )
120            {
121                NamedLock namedLock = sessionAwareNamedLockFactory != null ? sessionAwareNamedLockFactory
122                        .getLock( session, key ) : namedLockFactory.getLock( key );
123                try
124                {
125                     LOGGER.trace( "Acquiring {} lock for '{}'",
126                             shared ? "read" : "write", key );
127
128                    boolean locked;
129                    if ( shared )
130                    {
131                        locked = namedLock.lockShared( time, timeUnit );
132                    }
133                    else
134                    {
135                        locked = namedLock.lockExclusively( time, timeUnit );
136                    }
137
138                    if ( !locked )
139                    {
140                        LOGGER.trace( "Failed to acquire {} lock for '{}'",
141                                shared ? "read" : "write", key );
142
143                        namedLock.close();
144                        throw new IllegalStateException(
145                                "Could not acquire " + ( shared ? "read" : "write" )
146                                + " lock for '" + namedLock.name() + "'" );
147                    }
148
149                    locks.push( namedLock );
150                    acquiredLockCount++;
151                }
152                catch ( InterruptedException e )
153                {
154                    Thread.currentThread().interrupt();
155                    throw new RuntimeException( e );
156                }
157            }
158            LOGGER.trace( "Total locks acquired: {}", acquiredLockCount );
159        }
160
161        @Override
162        public void close()
163        {
164            if ( locks.isEmpty() )
165            {
166                return;
167            }
168
169            // Release locks in reverse insertion order
170            int released = 0;
171            while ( !locks.isEmpty() )
172            {
173                try ( NamedLock namedLock = locks.pop() )
174                {
175                    LOGGER.trace( "Releasing {} lock for '{}'",
176                            shared ? "read" : "write", namedLock.name() );
177                    namedLock.unlock();
178                    released++;
179                }
180            }
181            LOGGER.trace( "Total locks released: {}", released );
182        }
183    }
184}