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.eclipse.aether.named.providers.FileLockNamedLockFactory; 029import org.eclipse.aether.util.ConfigUtils; 030 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034import java.util.ArrayDeque; 035import java.util.Collection; 036import java.util.Deque; 037import java.util.Objects; 038import java.util.concurrent.TimeUnit; 039 040/** 041 * Adapter to adapt {@link NamedLockFactory} and {@link NamedLock} to {@link SyncContext}. 042 */ 043public final class NamedLockFactoryAdapter 044{ 045 public static final String TIME_KEY = "aether.syncContext.named.time"; 046 047 public static final long DEFAULT_TIME = 30L; 048 049 public static final String TIME_UNIT_KEY = "aether.syncContext.named.time.unit"; 050 051 public static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS; 052 053 private final NameMapper nameMapper; 054 055 private final NamedLockFactory namedLockFactory; 056 057 public NamedLockFactoryAdapter( final NameMapper nameMapper, final NamedLockFactory namedLockFactory ) 058 { 059 this.nameMapper = Objects.requireNonNull( nameMapper ); 060 this.namedLockFactory = Objects.requireNonNull( namedLockFactory ); 061 // TODO: this is ad-hoc "validation", experimental and likely to change 062 if ( this.namedLockFactory instanceof FileLockNamedLockFactory 063 && !this.nameMapper.isFileSystemFriendly() ) 064 { 065 throw new IllegalArgumentException( 066 "Misconfiguration: FileLockNamedLockFactory lock factory requires FS friendly NameMapper" 067 ); 068 } 069 } 070 071 public SyncContext newInstance( final RepositorySystemSession session, final boolean shared ) 072 { 073 return new AdaptedLockSyncContext( session, shared, nameMapper, namedLockFactory ); 074 } 075 076 public void shutdown() 077 { 078 namedLockFactory.shutdown(); 079 } 080 081 public String toString() 082 { 083 return getClass().getSimpleName() 084 + "(nameMapper=" + nameMapper 085 + ", namedLockFactory=" + namedLockFactory 086 + ")"; 087 } 088 089 private static class AdaptedLockSyncContext implements SyncContext 090 { 091 private static final Logger LOGGER = LoggerFactory.getLogger( AdaptedLockSyncContext.class ); 092 093 private final RepositorySystemSession session; 094 095 private final boolean shared; 096 097 private final NameMapper lockNaming; 098 099 private final NamedLockFactory namedLockFactory; 100 101 private final long time; 102 103 private final TimeUnit timeUnit; 104 105 private final Deque<NamedLock> locks; 106 107 private AdaptedLockSyncContext( final RepositorySystemSession session, final boolean shared, 108 final NameMapper lockNaming, final NamedLockFactory namedLockFactory ) 109 { 110 this.session = session; 111 this.shared = shared; 112 this.lockNaming = lockNaming; 113 this.namedLockFactory = namedLockFactory; 114 this.time = getTime( session ); 115 this.timeUnit = getTimeUnit( session ); 116 this.locks = new ArrayDeque<>(); 117 118 if ( time < 0L ) 119 { 120 throw new IllegalArgumentException( "time cannot be negative" ); 121 } 122 } 123 124 private long getTime( final RepositorySystemSession session ) 125 { 126 return ConfigUtils.getLong( session, DEFAULT_TIME, TIME_KEY ); 127 } 128 129 private TimeUnit getTimeUnit( final RepositorySystemSession session ) 130 { 131 return TimeUnit.valueOf( ConfigUtils.getString( 132 session, DEFAULT_TIME_UNIT.name(), TIME_UNIT_KEY 133 ) ); 134 } 135 136 @Override 137 public void acquire( Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas ) 138 { 139 Collection<String> keys = lockNaming.nameLocks( session, artifacts, metadatas ); 140 if ( keys.isEmpty() ) 141 { 142 return; 143 } 144 145 LOGGER.trace( "Need {} {} lock(s) for {}", keys.size(), shared ? "read" : "write", keys ); 146 int acquiredLockCount = 0; 147 for ( String key : keys ) 148 { 149 NamedLock namedLock = namedLockFactory.getLock( key ); 150 try 151 { 152 LOGGER.trace( "Acquiring {} lock for '{}'", 153 shared ? "read" : "write", key ); 154 155 boolean locked; 156 if ( shared ) 157 { 158 locked = namedLock.lockShared( time, timeUnit ); 159 } 160 else 161 { 162 locked = namedLock.lockExclusively( time, timeUnit ); 163 } 164 165 if ( !locked ) 166 { 167 LOGGER.trace( "Failed to acquire {} lock for '{}'", 168 shared ? "read" : "write", key ); 169 170 namedLock.close(); 171 throw new IllegalStateException( 172 "Could not acquire " + ( shared ? "read" : "write" ) 173 + " lock for '" + namedLock.name() + "'" ); 174 } 175 176 locks.push( namedLock ); 177 acquiredLockCount++; 178 } 179 catch ( InterruptedException e ) 180 { 181 Thread.currentThread().interrupt(); 182 throw new RuntimeException( e ); 183 } 184 } 185 LOGGER.trace( "Total locks acquired: {}", acquiredLockCount ); 186 } 187 188 @Override 189 public void close() 190 { 191 if ( locks.isEmpty() ) 192 { 193 return; 194 } 195 196 // Release locks in reverse insertion order 197 int released = 0; 198 while ( !locks.isEmpty() ) 199 { 200 try ( NamedLock namedLock = locks.pop() ) 201 { 202 LOGGER.trace( "Releasing {} lock for '{}'", 203 shared ? "read" : "write", namedLock.name() ); 204 namedLock.unlock(); 205 released++; 206 } 207 } 208 LOGGER.trace( "Total locks released: {}", released ); 209 } 210 } 211}