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.internal.impl; 020 021import java.io.File; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.OutputStream; 025import java.io.UncheckedIOException; 026import java.nio.file.Files; 027import java.nio.file.NoSuchFileException; 028import java.nio.file.Path; 029import java.nio.file.StandardOpenOption; 030import java.util.Map; 031import java.util.Properties; 032import java.util.concurrent.TimeUnit; 033 034import org.eclipse.aether.named.NamedLock; 035import org.eclipse.aether.named.NamedLockFactory; 036import org.eclipse.aether.named.NamedLockKey; 037import org.eclipse.aether.util.StringDigestUtil; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041/** 042 * Manages access to a properties file using named locks. 043 * <p> 044 * This implementation uses {@link NamedLock} to protect tracking files from concurrent access, and can be used 045 * when it is single "modern" Maven version used to access same local repository concurrently. 046 * 047 * @since 2.0.17 048 * @see LegacyTrackingFileManager 049 * @see TrackingFileManagerProvider 050 */ 051public final class NamedLocksTrackingFileManager implements TrackingFileManager { 052 private static final Logger LOGGER = LoggerFactory.getLogger(NamedLocksTrackingFileManager.class); 053 054 private final NamedLockFactory namedLockFactory; 055 private final long time; 056 private final TimeUnit unit; 057 058 public NamedLocksTrackingFileManager(NamedLockFactory namedLockFactory, long time, TimeUnit unit) { 059 this.namedLockFactory = namedLockFactory; 060 this.time = time; 061 this.unit = unit; 062 } 063 064 @Deprecated 065 @Override 066 public Properties read(File file) { 067 return read(file.toPath()); 068 } 069 070 @Override 071 public Properties read(Path path) { 072 try (NamedLock namedLock = namedLock(path)) { 073 if (namedLock.lockShared(time, unit)) { 074 try { 075 Properties props = new Properties(); 076 try (InputStream in = Files.newInputStream(path)) { 077 props.load(in); 078 } 079 return props; 080 } catch (NoSuchFileException e) { 081 LOGGER.debug("No such file to read {}: {}", path, e.getMessage()); 082 return null; 083 } catch (IOException e) { 084 LOGGER.warn("Failed to read tracking file '{}'", path, e); 085 throw new UncheckedIOException(e); 086 } finally { 087 namedLock.unlock(); 088 } 089 } 090 throw new IllegalStateException("Failed to lock for read the tracking file " + path); 091 } catch (InterruptedException e) { 092 Thread.currentThread().interrupt(); 093 throw new IllegalStateException("Interrupted while reading tracking file " + path, e); 094 } 095 } 096 097 @Deprecated 098 @Override 099 public Properties update(File file, Map<String, String> updates) { 100 return update(file.toPath(), updates); 101 } 102 103 @Override 104 public Properties update(Path path, Map<String, String> updates) { 105 try { 106 Path parent = path.getParent(); 107 if (parent != null) { 108 Files.createDirectories(parent); 109 } 110 } catch (IOException e) { 111 LOGGER.warn("Failed to create tracking file parent '{}'", path, e); 112 throw new UncheckedIOException(e); 113 } 114 try (NamedLock lock = namedLock(path)) { 115 if (lock.lockExclusively(time, unit)) { 116 try { 117 Properties props = new Properties(); 118 if (Files.isRegularFile(path)) { 119 try (InputStream stream = Files.newInputStream(path, StandardOpenOption.READ)) { 120 props.load(stream); 121 } 122 } 123 for (Map.Entry<String, String> update : updates.entrySet()) { 124 if (update.getValue() == null) { 125 props.remove(update.getKey()); 126 } else { 127 props.setProperty(update.getKey(), update.getValue()); 128 } 129 } 130 LOGGER.debug("Writing tracking file '{}'", path); 131 try (OutputStream out = Files.newOutputStream(path)) { 132 props.store( 133 out, 134 "NOTE: This is a Maven Resolver internal implementation file" 135 + ", its format can be changed without prior notice."); 136 } 137 return props; 138 } catch (IOException e) { 139 LOGGER.warn("Failed to write tracking file '{}'", path, e); 140 throw new UncheckedIOException(e); 141 } finally { 142 lock.unlock(); 143 } 144 } 145 throw new IllegalStateException("Failed to lock for update the tracking file " + path); 146 } catch (InterruptedException e) { 147 Thread.currentThread().interrupt(); 148 throw new IllegalStateException("Interrupted while updating tracking file " + path, e); 149 } 150 } 151 152 @Deprecated 153 @Override 154 public boolean delete(File file) { 155 return delete(file.toPath()); 156 } 157 158 @Override 159 public boolean delete(Path path) { 160 try (NamedLock lock = namedLock(path)) { 161 if (lock.lockExclusively(time, unit)) { 162 try { 163 return Files.deleteIfExists(path); 164 } catch (NoSuchFileException e) { 165 LOGGER.debug("No such file to delete {}: {}", path, e.getMessage()); 166 return false; 167 } catch (IOException e) { 168 LOGGER.warn("Failed to delete tracking file '{}'", path, e); 169 throw new UncheckedIOException(e); 170 } finally { 171 lock.unlock(); 172 } 173 } 174 throw new IllegalStateException("Failed to lock for delete the tracking file " + path); 175 } catch (InterruptedException e) { 176 Thread.currentThread().interrupt(); 177 throw new IllegalStateException("Interrupted while deleting tracking file " + path, e); 178 } 179 } 180 181 /** 182 * Creates unique named lock for given path with name that is unique for paths, but carries some extra 183 * information useful for debugging. 184 * <p> 185 * Note: it is important that created named lock names remain (and carry) file friendly URLs, as this makes it 186 * work with all lock factories, even the file one. Using non-file friendly names would make it work it with 187 * all <strong>except the file lock factory</strong>. 188 */ 189 private NamedLock namedLock(Path path) { 190 Path canonical = canonicalPath(path); 191 // Place lock file next to the tracking file, using its hash as filename 192 Path lockPath = canonical.resolveSibling("tracking-" + StringDigestUtil.sha1(canonical.toString()) + ".lock"); 193 return namedLockFactory.getLock( 194 NamedLockKey.of(lockPath.toAbsolutePath().toUri().toASCIIString(), path.toString())); 195 } 196 197 /** 198 * Tries the best it can to figure out actual file the workload is about, while resolving cases like symlinked 199 * local repository etc. 200 */ 201 private static Path canonicalPath(Path path) { 202 try { 203 return path.toRealPath(); 204 } catch (IOException e) { 205 return path.getParent() != null 206 ? canonicalPath(path.getParent()).resolve(path.getFileName()) 207 : path.toAbsolutePath(); 208 } 209 } 210}