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 /** 077 * @since 1.9.1 078 */ 079 public NameMapper getNameMapper() 080 { 081 return nameMapper; 082 } 083 084 /** 085 * @since 1.9.1 086 */ 087 public NamedLockFactory getNamedLockFactory() 088 { 089 return namedLockFactory; 090 } 091 092 public String toString() 093 { 094 return getClass().getSimpleName() 095 + "(nameMapper=" + nameMapper 096 + ", namedLockFactory=" + namedLockFactory 097 + ")"; 098 } 099 100 private static class AdaptedLockSyncContext implements SyncContext 101 { 102 private static final Logger LOGGER = LoggerFactory.getLogger( AdaptedLockSyncContext.class ); 103 104 private final RepositorySystemSession session; 105 106 private final boolean shared; 107 108 private final NameMapper lockNaming; 109 110 private final NamedLockFactory namedLockFactory; 111 112 private final long time; 113 114 private final TimeUnit timeUnit; 115 116 private final Deque<NamedLock> locks; 117 118 private AdaptedLockSyncContext( final RepositorySystemSession session, final boolean shared, 119 final NameMapper lockNaming, final NamedLockFactory namedLockFactory ) 120 { 121 this.session = session; 122 this.shared = shared; 123 this.lockNaming = lockNaming; 124 this.namedLockFactory = namedLockFactory; 125 this.time = getTime( session ); 126 this.timeUnit = getTimeUnit( session ); 127 this.locks = new ArrayDeque<>(); 128 129 if ( time < 0L ) 130 { 131 throw new IllegalArgumentException( "time cannot be negative" ); 132 } 133 } 134 135 private long getTime( final RepositorySystemSession session ) 136 { 137 return ConfigUtils.getLong( session, DEFAULT_TIME, TIME_KEY ); 138 } 139 140 private TimeUnit getTimeUnit( final RepositorySystemSession session ) 141 { 142 return TimeUnit.valueOf( ConfigUtils.getString( 143 session, DEFAULT_TIME_UNIT.name(), TIME_UNIT_KEY 144 ) ); 145 } 146 147 @Override 148 public void acquire( Collection<? extends Artifact> artifacts, Collection<? extends Metadata> metadatas ) 149 { 150 Collection<String> keys = lockNaming.nameLocks( session, artifacts, metadatas ); 151 if ( keys.isEmpty() ) 152 { 153 return; 154 } 155 156 LOGGER.trace( "Need {} {} lock(s) for {}", keys.size(), shared ? "read" : "write", keys ); 157 int acquiredLockCount = 0; 158 for ( String key : keys ) 159 { 160 NamedLock namedLock = namedLockFactory.getLock( key ); 161 try 162 { 163 LOGGER.trace( "Acquiring {} lock for '{}'", 164 shared ? "read" : "write", key ); 165 166 boolean locked; 167 if ( shared ) 168 { 169 locked = namedLock.lockShared( time, timeUnit ); 170 } 171 else 172 { 173 locked = namedLock.lockExclusively( time, timeUnit ); 174 } 175 176 if ( !locked ) 177 { 178 LOGGER.trace( "Failed to acquire {} lock for '{}'", 179 shared ? "read" : "write", key ); 180 181 namedLock.close(); 182 throw new IllegalStateException( 183 "Could not acquire " + ( shared ? "read" : "write" ) 184 + " lock for '" + namedLock.name() + "'" ); 185 } 186 187 locks.push( namedLock ); 188 acquiredLockCount++; 189 } 190 catch ( InterruptedException e ) 191 { 192 Thread.currentThread().interrupt(); 193 throw new RuntimeException( e ); 194 } 195 } 196 LOGGER.trace( "Total locks acquired: {}", acquiredLockCount ); 197 } 198 199 @Override 200 public void close() 201 { 202 if ( locks.isEmpty() ) 203 { 204 return; 205 } 206 207 // Release locks in reverse insertion order 208 int released = 0; 209 while ( !locks.isEmpty() ) 210 { 211 try ( NamedLock namedLock = locks.pop() ) 212 { 213 LOGGER.trace( "Releasing {} lock for '{}'", 214 shared ? "read" : "write", namedLock.name() ); 215 namedLock.unlock(); 216 released++; 217 } 218 } 219 LOGGER.trace( "Total locks released: {}", released ); 220 } 221 } 222}