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