update : updates.entrySet()) {
92 | if (update.getValue() == null) {
93 | props.remove(update.getKey());
94 | } else {
95 | props.setProperty(update.getKey(), update.getValue());
96 | }
97 | }
98 |
99 | ByteArrayOutputStream stream = new ByteArrayOutputStream(1024 * 2);
100 |
101 | logger.debug("Writing tracking file " + file);
102 | props.store(stream, "NOTE: This is an Aether internal implementation file" + ", its format can be changed without prior notice.");
103 | raf.seek(0);
104 | raf.write(stream.toByteArray());
105 | raf.setLength(raf.getFilePointer());
106 | } catch (IOException e) {
107 | logger.warn("Failed to write tracking file " + file, e);
108 | } finally {
109 | release(lock, file);
110 | close(raf, file);
111 | }
112 | }
113 |
114 | return props;
115 | }
116 |
117 | private void release(FileLock lock, File file) {
118 | if (lock != null) {
119 | try {
120 | lock.release();
121 | } catch (IOException e) {
122 | logger.warn("Error releasing lock for tracking file " + file, e);
123 | }
124 | }
125 | }
126 |
127 | private void close(Closeable closeable, File file) {
128 | if (closeable != null) {
129 | try {
130 | closeable.close();
131 | } catch (IOException e) {
132 | logger.warn("Error closing tracking file " + file, e);
133 | }
134 | }
135 | }
136 |
137 | private Object getLock(File file) {
138 | /*
139 | * NOTE: Locks held by one JVM must not overlap and using the canonical path is our best bet, still another piece of code might have locked the same file (unlikely though) or the canonical path
140 | * fails to capture file identity sufficiently as is the case with Java 1.6 and symlinks on Windows.
141 | */
142 | try {
143 | return file.getCanonicalPath().intern();
144 | } catch (IOException e) {
145 | logger.warn("Failed to canonicalize path " + file + ": " + e.getMessage());
146 | return file.getAbsolutePath().intern();
147 | }
148 | }
149 |
150 | private FileLock lock(FileChannel channel, long size, boolean shared) throws IOException {
151 | FileLock lock = null;
152 |
153 | for (int attempts = 8; attempts >= 0; attempts--) {
154 | try {
155 | lock = channel.lock(0, size, shared);
156 | break;
157 | } catch (OverlappingFileLockException e) {
158 | if (attempts <= 0) {
159 | throw (IOException) new IOException().initCause(e);
160 | }
161 | try {
162 | Thread.sleep(50);
163 | } catch (InterruptedException e1) {
164 | Thread.currentThread().interrupt();
165 | }
166 | }
167 | }
168 |
169 | if (lock == null) {
170 | throw new IOException("Could not lock file");
171 | }
172 |
173 | return lock;
174 | }
175 |
176 | }
177 |
--------------------------------------------------------------------------------
/src/main/java/io/takari/filemanager/FileManager.java:
--------------------------------------------------------------------------------
1 | package io.takari.filemanager;
2 |
3 | /*******************************************************************************
4 | * Copyright (c) 2010-2013 Sonatype, Inc.
5 | * All rights reserved. This program and the accompanying materials
6 | * are made available under the terms of the Eclipse Public License v1.0
7 | * which accompanies this distribution, and is available at
8 | * http://www.eclipse.org/legal/epl-v10.html
9 | *******************************************************************************/
10 |
11 | import io.takari.filemanager.FileManager.ProgressListener;
12 |
13 | import java.io.File;
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.nio.ByteBuffer;
17 |
18 | /**
19 | * A LockManager holding external locks, locking files between OS processes (e.g. via {@link Lock}.
20 | *
21 | * @author Benjamin Hanzelmann
22 | */
23 | public interface FileManager {
24 |
25 | /**
26 | * Obtain a lock object that may be used to lock the target file for reading. This method must not lock that file
27 | * right immediately (see {@link Lock#lock()}).
28 | *
29 | * @param target the file to lock, never {@code null}.
30 | * @return a lock object, never {@code null}.
31 | */
32 | Lock readLock(File target);
33 |
34 | /**
35 | * Obtain a lock object that may be used to lock the target file for writing. This method must not lock that file
36 | * right immediately (see {@link Lock#lock()}).
37 | *
38 | * @param target the file to lock, never {@code null}.
39 | * @return a lock object, never {@code null}.
40 | */
41 | Lock writeLock(File target);
42 |
43 | //
44 | // This will become a concurrent/process safe file manager and we'll move many of the methods from the LockingFileProcessor and then
45 | // make the LockingFileProcessor a thin wrapper around our default FileManager
46 | //
47 | boolean mkdirs(File directory);
48 |
49 | void write(File target, String data) throws IOException;
50 |
51 | void write(File target, InputStream source) throws IOException;
52 |
53 | void move(File source, File target) throws IOException;
54 |
55 | void copy(File source, File target) throws IOException;
56 |
57 | long copy(File source, File target, ProgressListener listener) throws IOException;
58 |
59 | /**
60 | * A listener object that is notified for every progress made while copying files.
61 | *
62 | * @see FileProcessor#copy(File, File, ProgressListener)
63 | */
64 | public interface ProgressListener {
65 | void progressed(ByteBuffer buffer) throws IOException;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/io/takari/filemanager/Lock.java:
--------------------------------------------------------------------------------
1 | package io.takari.filemanager;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.io.RandomAccessFile;
6 | import java.nio.channels.FileLock;
7 |
8 | /**
9 | * This lock object adds the ability to directly access the contents of the locked file.
10 | *
11 | * @author Benjamin Hanzelmann
12 | */
13 | public interface Lock {
14 |
15 | /**
16 | * Gets the random access file used to read/write the contents of the locked file.
17 | *
18 | * @return The random access file used to read/write or {@code null} if the lock isn't acquired.
19 | * @throws IOException
20 | */
21 | RandomAccessFile getRandomAccessFile() throws IOException;
22 |
23 | /**
24 | * Tells whether the lock is shared or exclusive.
25 | *
26 | * @return {@code true} if the lock is shared, {@code false} if the lock is exclusive.
27 | */
28 | boolean isShared();
29 |
30 | /**
31 | * Lock the file this Lock was obtained for.
32 | *
33 | * Multiple {@link #lock()} invocations on the same or other lock objects using the same (canonical) file as
34 | * target are possible and non-blocking from the same caller thread.
35 | *
36 | * @throws IOException if an error occurs while locking the file.
37 | */
38 | void lock() throws IOException;
39 |
40 | /**
41 | * Unlock the file this Lock was obtained for.
42 | *
43 | * @throws IOException if an error occurs while locking the file.
44 | */
45 | void unlock() throws IOException;
46 |
47 | /**
48 | * Get the file this Lock was obtained for.
49 | *
50 | * @return The file this lock was obtained for, never {@code null}.
51 | */
52 | File getFile();
53 |
54 | // I added this to remove having to reference the concrete implementation in the test case
55 | FileLock getLock();
56 | }
--------------------------------------------------------------------------------
/src/main/java/io/takari/filemanager/internal/DefaultFileManager.java:
--------------------------------------------------------------------------------
1 | package io.takari.filemanager.internal;
2 |
3 | /*******************************************************************************
4 | * Copyright (c) 2010-2013 Sonatype, Inc.
5 | * All rights reserved. This program and the accompanying materials
6 | * are made available under the terms of the Eclipse Public License v1.0
7 | * which accompanies this distribution, and is available at
8 | * http://www.eclipse.org/legal/epl-v10.html
9 | *******************************************************************************/
10 |
11 | import io.takari.filemanager.FileManager;
12 | import io.takari.filemanager.Lock;
13 |
14 | import java.io.Closeable;
15 | import java.io.File;
16 | import java.io.IOException;
17 | import java.io.InputStream;
18 | import java.io.RandomAccessFile;
19 | import java.nio.ByteBuffer;
20 | import java.nio.channels.FileLock;
21 | import java.nio.channels.FileLockInterruptionException;
22 | import java.util.HashMap;
23 | import java.util.Locale;
24 | import java.util.Map;
25 | import java.util.concurrent.ConcurrentHashMap;
26 | import java.util.concurrent.ConcurrentMap;
27 | import java.util.concurrent.atomic.AtomicInteger;
28 |
29 | import javax.inject.Named;
30 | import javax.inject.Singleton;
31 |
32 | import org.slf4j.Logger;
33 | import org.slf4j.LoggerFactory;
34 |
35 | /**
36 | * Offers advisory file locking independently of the platform. With regard to concurrent readers that don't use any file
37 | * locking (i.e. 3rd party code accessing files), mandatory locking (as seen on Windows) must be avoided as this would
38 | * immediately kill the unaware readers. To emulate advisory locking, this implementation uses a dedicated lock file
39 | * (*.aetherlock) next to the actual file. The inter-process file locking is performed on this lock file, thereby
40 | * keeping the data file free from locking.
41 | *
42 | * @author Benjamin Bentmann
43 | */
44 | @Named
45 | @Singleton
46 | public class DefaultFileManager implements FileManager {
47 |
48 | private static Boolean IS_SET_LAST_MODIFIED_SAFE;
49 |
50 | private Logger logger = LoggerFactory.getLogger(DefaultFileManager.class);
51 | private static final ConcurrentMap lockFiles = new ConcurrentHashMap(64);
52 |
53 | public Lock readLock(File target) {
54 | return new IndirectFileLock(normalize(target), false);
55 | }
56 |
57 | public Lock writeLock(File target) {
58 | return new IndirectFileLock(normalize(target), true);
59 | }
60 |
61 | private File normalize(File file) {
62 | try {
63 | return file.getCanonicalFile();
64 | } catch (IOException e) {
65 | logger.warn("Failed to normalize pathname for lock on " + file + ": " + e);
66 | return file.getAbsoluteFile();
67 | }
68 | }
69 |
70 | /**
71 | * Null-safe variant of {@link File#mkdirs()}.
72 | *
73 | * @param directory The directory to create, may be {@code null}.
74 | * @return {@code true} if and only if the directory was created, along with all necessary parent
75 | * directories; {@code false} otherwise
76 | */
77 | public boolean mkdirs(File directory) {
78 | if (directory == null) {
79 | return false;
80 | }
81 |
82 | return directory.mkdirs();
83 | }
84 |
85 | private RandomAccessFile open(File file, String mode) throws IOException {
86 | boolean interrupted = false;
87 |
88 | try {
89 | mkdirs(file.getParentFile());
90 |
91 | return new RandomAccessFile(file, mode);
92 | } catch (IOException e) {
93 | /*
94 | * NOTE: I've seen failures (on Windows) when opening the file which I can't really explain ("access denied", "locked"). Assuming those are bad interactions with OS-level processes (e.g.
95 | * indexing, anti-virus), let's just retry before giving up due to a potentially spurious problem.
96 | */
97 | for (int i = 3; i >= 0; i--) {
98 | try {
99 | Thread.sleep(10);
100 | } catch (InterruptedException e1) {
101 | interrupted = true;
102 | }
103 | try {
104 | return new RandomAccessFile(file, mode);
105 | } catch (IOException ie) {
106 | // ignored, we eventually rethrow the original error
107 | }
108 | }
109 |
110 | throw e;
111 | } finally {
112 | if (interrupted) {
113 | Thread.currentThread().interrupt();
114 | }
115 | }
116 | }
117 |
118 | private void close(Closeable closeable) {
119 | if (closeable != null) {
120 | try {
121 | closeable.close();
122 | } catch (IOException e) {
123 | if (logger != null) {
124 | logger.warn("Failed to close file: " + e);
125 | }
126 | }
127 | }
128 | }
129 |
130 | class IndirectFileLock implements Lock {
131 |
132 | private final File file;
133 | private final boolean write;
134 | private final Throwable stackTrace;
135 | private RandomAccessFile raFile;
136 | private LockFile lockFile;
137 | private int nesting;
138 |
139 | public IndirectFileLock(File file, boolean write) {
140 | this.file = file;
141 | this.write = write;
142 | this.stackTrace = new IllegalStateException();
143 | }
144 |
145 | public synchronized void lock() throws IOException {
146 | if (lockFile == null) {
147 | open();
148 | nesting = 1;
149 | } else {
150 | nesting++;
151 | }
152 | }
153 |
154 | public synchronized void unlock() throws IOException {
155 | nesting--;
156 | if (nesting <= 0) {
157 | close();
158 | }
159 | }
160 |
161 | public RandomAccessFile getRandomAccessFile() throws IOException {
162 | if (raFile == null && lockFile != null && lockFile.getFileLock().isValid()) {
163 | raFile = DefaultFileManager.this.open(file, write ? "rw" : "r");
164 | }
165 | return raFile;
166 | }
167 |
168 | public boolean isShared() {
169 | if (lockFile == null) {
170 | throw new IllegalStateException("lock not acquired");
171 | }
172 | return lockFile.isShared();
173 | }
174 |
175 | public FileLock getLock() {
176 | if (lockFile == null) {
177 | return null;
178 | }
179 | return lockFile.getFileLock();
180 | }
181 |
182 | public File getFile() {
183 | return file;
184 | }
185 |
186 | @Override
187 | protected void finalize() throws Throwable {
188 | try {
189 | if (lockFile != null) {
190 | logger.warn("Lock on file " + file + " has not been properly released", stackTrace);
191 | }
192 | close();
193 | } finally {
194 | super.finalize();
195 | }
196 | }
197 |
198 | private void open() throws IOException {
199 | lockFile = lock(file, write);
200 | }
201 |
202 | private void close() throws IOException {
203 | try {
204 | if (raFile != null) {
205 | try {
206 | raFile.close();
207 | } finally {
208 | raFile = null;
209 | }
210 | }
211 | } finally {
212 | if (lockFile != null) {
213 | try {
214 | unlock(lockFile);
215 | } catch (IOException e) {
216 | logger.warn("Failed to release lock for " + file + ": " + e);
217 | } finally {
218 | lockFile = null;
219 | }
220 | }
221 | }
222 | }
223 |
224 | private LockFile lock(File file, boolean write) throws IOException {
225 | boolean interrupted = false;
226 |
227 | try {
228 | while (true) {
229 | LockFile lockFile = lockFiles.get(file);
230 |
231 | if (lockFile == null) {
232 | lockFile = new LockFile(file);
233 |
234 | LockFile existing = lockFiles.putIfAbsent(file, lockFile);
235 | if (existing != null) {
236 | lockFile = existing;
237 | }
238 | }
239 |
240 | synchronized (lockFile) {
241 | if (lockFile.isInvalid()) {
242 | continue;
243 | } else if (lockFile.lock(write)) {
244 | return lockFile;
245 | }
246 |
247 | try {
248 | lockFile.wait();
249 | } catch (InterruptedException e) {
250 | interrupted = true;
251 | }
252 | }
253 | }
254 | } finally {
255 | /*
256 | * NOTE: We want to ignore the interrupt but other code might want/need to react to it, so restore the interrupt flag.
257 | */
258 | if (interrupted) {
259 | Thread.currentThread().interrupt();
260 | }
261 | }
262 | }
263 |
264 | private void unlock(LockFile lockFile) throws IOException {
265 | synchronized (lockFile) {
266 | try {
267 | lockFile.unlock();
268 | } finally {
269 | if (lockFile.isInvalid()) {
270 | lockFiles.remove(lockFile.getDataFile(), lockFile);
271 | lockFile.notifyAll();
272 | }
273 | }
274 | }
275 | }
276 | }
277 |
278 | // LockFile
279 |
280 | /**
281 | * Manages an {@code *.aetherlock} file. Note: This class is not thread-safe and requires external
282 | * synchronization.
283 | */
284 | class LockFile {
285 |
286 | private final File dataFile;
287 | private final File lockFile;
288 | private FileLock fileLock;
289 | private RandomAccessFile raFile;
290 | private int refCount;
291 | private Thread owner;
292 | private final Map clients = new HashMap();
293 |
294 | public LockFile(File dataFile) {
295 | this.dataFile = dataFile;
296 | if (dataFile.isDirectory()) {
297 | lockFile = new File(dataFile, ".aetherlock");
298 | } else {
299 | lockFile = new File(dataFile.getPath() + ".aetherlock");
300 | }
301 | }
302 |
303 | public File getDataFile() {
304 | return dataFile;
305 | }
306 |
307 | public boolean lock(boolean write) throws IOException {
308 | if (isInvalid()) {
309 | throw new IllegalStateException("lock for " + dataFile + " has been invalidated");
310 | }
311 |
312 | if (isClosed()) {
313 | open(write);
314 |
315 | return true;
316 | } else if (isReentrant(write)) {
317 | incRefCount();
318 |
319 | return true;
320 | } else if (isAlreadyHoldByCurrentThread()) {
321 | throw new IllegalStateException("Cannot acquire " + (write ? "write" : "read") + " lock on " + dataFile + " for thread " + Thread.currentThread() + " which already holds incompatible lock");
322 | }
323 |
324 | return false;
325 | }
326 |
327 | public void unlock() throws IOException {
328 | if (decRefCount() <= 0) {
329 | close();
330 | }
331 | }
332 |
333 | FileLock getFileLock() {
334 | return fileLock;
335 | }
336 |
337 | public boolean isInvalid() {
338 | return refCount < 0;
339 | }
340 |
341 | public boolean isShared() {
342 | if (fileLock == null) {
343 | throw new IllegalStateException("lock not acquired");
344 | }
345 | return fileLock.isShared();
346 | }
347 |
348 | private boolean isClosed() {
349 | return fileLock == null;
350 | }
351 |
352 | private boolean isReentrant(boolean write) {
353 | if (isShared()) {
354 | return !write;
355 | } else {
356 | return Thread.currentThread() == owner;
357 | }
358 | }
359 |
360 | private boolean isAlreadyHoldByCurrentThread() {
361 | return clients.get(Thread.currentThread()) != null;
362 | }
363 |
364 | private void open(boolean write) throws IOException {
365 | refCount = 1;
366 |
367 | owner = write ? Thread.currentThread() : null;
368 |
369 | clients.put(Thread.currentThread(), new AtomicInteger(1));
370 |
371 | RandomAccessFile raf = null;
372 | FileLock lock = null;
373 | boolean interrupted = false;
374 |
375 | try {
376 | while (true) {
377 | raf = DefaultFileManager.this.open(lockFile, "rw");
378 |
379 | try {
380 | lock = raf.getChannel().lock(0, 1, !write);
381 |
382 | if (lock == null) {
383 | /*
384 | * Probably related to http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6979009, lock() erroneously returns null when the thread got interrupted and the channel silently closed.
385 | */
386 | throw new FileLockInterruptionException();
387 | }
388 |
389 | break;
390 | } catch (FileLockInterruptionException e) {
391 | /*
392 | * NOTE: We want to lock that file and this isn't negotiable, so whatever felt like interrupting our thread, try again later, we have work to get done. And since the interrupt closed the
393 | * channel, we need to start with a fresh file handle.
394 | */
395 |
396 | interrupted |= Thread.interrupted();
397 |
398 | DefaultFileManager.this.close(raf);
399 | } catch (IOException e) {
400 | DefaultFileManager.this.close(raf);
401 |
402 | // EVIL: parse message of IOException to find out if it's a (probably erroneous) 'deadlock
403 | // detection' (linux kernel does not account for different threads holding the locks for the
404 | // same process)
405 | if (isPseudoDeadlock(e)) {
406 | logger.debug("OS detected pseudo deadlock for " + lockFile + ", retrying locking");
407 | try {
408 | Thread.sleep(100);
409 | } catch (InterruptedException e1) {
410 | interrupted = true;
411 | }
412 | } else {
413 | delete();
414 | throw e;
415 | }
416 | }
417 | }
418 | } finally {
419 | /*
420 | * NOTE: We want to ignore the interrupt but other code might want/need to react to it, so restore the interrupt flag.
421 | */
422 | if (interrupted) {
423 | Thread.currentThread().interrupt();
424 | }
425 | }
426 |
427 | raFile = raf;
428 | fileLock = lock;
429 | }
430 |
431 | private boolean isPseudoDeadlock(IOException e) {
432 | String msg = e.getMessage();
433 | return msg != null && msg.toLowerCase(Locale.ENGLISH).contains("deadlock");
434 | }
435 |
436 | private void close() throws IOException {
437 | refCount = -1;
438 |
439 | if (fileLock != null) {
440 | try {
441 | if (fileLock.isValid()) {
442 | fileLock.release();
443 | }
444 | } catch (IOException e) {
445 | logger.warn("Failed to release lock on " + lockFile + ": " + e);
446 | } finally {
447 | fileLock = null;
448 | }
449 | }
450 |
451 | if (raFile != null) {
452 | try {
453 | raFile.close();
454 | } finally {
455 | raFile = null;
456 | delete();
457 | }
458 | }
459 | }
460 |
461 | private void delete() {
462 | if (lockFile != null) {
463 | if (!lockFile.delete() && lockFile.exists()) {
464 | // NOTE: This happens naturally when some other thread locked it in the meantime
465 | lockFile.deleteOnExit();
466 | }
467 | }
468 | }
469 |
470 | private int incRefCount() {
471 | AtomicInteger clientRefCount = clients.get(Thread.currentThread());
472 | if (clientRefCount == null) {
473 | clients.put(Thread.currentThread(), new AtomicInteger(1));
474 | } else {
475 | clientRefCount.incrementAndGet();
476 | }
477 |
478 | return ++refCount;
479 | }
480 |
481 | private int decRefCount() {
482 | AtomicInteger clientRefCount = clients.get(Thread.currentThread());
483 | if (clientRefCount != null && clientRefCount.decrementAndGet() <= 0) {
484 | clients.remove(Thread.currentThread());
485 | }
486 |
487 | return --refCount;
488 | }
489 |
490 | @Override
491 | protected void finalize() throws Throwable {
492 | try {
493 | close();
494 | } finally {
495 | super.finalize();
496 | }
497 | }
498 | }
499 |
500 | // FileManager
501 |
502 | private void unlock(Lock lock) {
503 | if (lock != null) {
504 | try {
505 | lock.unlock();
506 | } catch (IOException e) {
507 | logger.warn("Failed to unlock file " + lock.getFile() + ": " + e);
508 | }
509 | }
510 | }
511 |
512 | public void copy(File source, File target) throws IOException {
513 | copy(source, target, null);
514 | }
515 |
516 | /**
517 | * Copy src- to target-file. Creates the necessary directories for the target file. In case of an error, the created
518 | * directories will be left on the file system.
519 | *
520 | * This method performs R/W-locking on the given files to provide concurrent access to files without data
521 | * corruption, and will honor {@link FileLock}s from an external process.
522 | *
523 | * @param source the file to copy from, must not be {@code null}.
524 | * @param target the file to copy to, must not be {@code null}.
525 | * @param listener the listener to notify about the copy progress, may be {@code null}.
526 | * @return the number of copied bytes.
527 | * @throws IOException if an I/O error occurs.
528 | */
529 | public long copy(File source, File target, ProgressListener listener) throws IOException {
530 | Lock sourceLock = readLock(source);
531 | Lock targetLock = writeLock(target);
532 |
533 | try {
534 | mkdirs(target.getParentFile());
535 |
536 | sourceLock.lock();
537 | targetLock.lock();
538 |
539 | return copy(sourceLock.getRandomAccessFile(), targetLock.getRandomAccessFile(), listener);
540 | } finally {
541 | target.setLastModified(source.lastModified());
542 | unlock(sourceLock);
543 | unlock(targetLock);
544 | }
545 | }
546 |
547 | private long copy(RandomAccessFile rafIn, RandomAccessFile rafOut, ProgressListener listener) throws IOException {
548 | ByteBuffer buffer = ByteBuffer.allocate(1024 * 32);
549 | byte[] array = buffer.array();
550 | long total = 0;
551 | for (;;) {
552 | int bytes = rafIn.read(array);
553 | if (bytes < 0) {
554 | rafOut.setLength(rafOut.getFilePointer());
555 | break;
556 | }
557 | rafOut.write(array, 0, bytes);
558 | total += bytes;
559 | if (listener != null && bytes > 0) {
560 | try {
561 | buffer.rewind();
562 | buffer.limit(bytes);
563 | listener.progressed(buffer);
564 | } catch (Exception e) {
565 | logger.debug("Failed to invoke copy progress listener", e);
566 | }
567 | }
568 | }
569 | return total;
570 | }
571 |
572 | public void write(File file, InputStream source) throws IOException {
573 |
574 | Lock lock = writeLock(file);
575 |
576 | try {
577 | mkdirs(file.getParentFile());
578 |
579 | lock.lock();
580 |
581 | RandomAccessFile raf = lock.getRandomAccessFile();
582 |
583 | raf.seek(0);
584 | if (source != null) {
585 | byte[] buffer = new byte[1024];
586 | int len;
587 | while ((len = source.read(buffer)) != -1) {
588 | raf.write(buffer, 0, len);
589 | }
590 | }
591 |
592 | raf.setLength(raf.getFilePointer());
593 | } finally {
594 | unlock(lock);
595 | }
596 | }
597 |
598 | /**
599 | * Write the given data to a file. UTF-8 is assumed as encoding for the data.
600 | *
601 | * @param file The file to write to, must not be {@code null}. This file will be truncated.
602 | * @param data The data to write, may be {@code null}.
603 | * @throws IOException if an I/O error occurs.
604 | */
605 | public void write(File file, String data) throws IOException {
606 | Lock lock = writeLock(file);
607 |
608 | try {
609 | mkdirs(file.getParentFile());
610 |
611 | lock.lock();
612 |
613 | RandomAccessFile raf = lock.getRandomAccessFile();
614 |
615 | raf.seek(0);
616 | if (data != null) {
617 | raf.write(data.getBytes("UTF-8"));
618 | }
619 |
620 | raf.setLength(raf.getFilePointer());
621 | } finally {
622 | unlock(lock);
623 | }
624 | }
625 |
626 | public void move(File source, File target) throws IOException {
627 | /*
628 | * NOTE: For graceful collaboration with concurrent readers don't attempt to delete the target file, if it already exists, it's safer to just overwrite it, especially when the contents doesn't
629 | * actually change.
630 | */
631 |
632 | /*
633 | * NOTE: We're about to remove/delete the source file so be sure to acquire an exclusive lock for the source.
634 | */
635 |
636 | Lock sourceLock = writeLock(source);
637 | Lock targetLock = writeLock(target);
638 |
639 | try {
640 | mkdirs(target.getParentFile());
641 |
642 | sourceLock.lock();
643 | targetLock.lock();
644 |
645 | if (!source.renameTo(target)) {
646 | copy(sourceLock.getRandomAccessFile(), targetLock.getRandomAccessFile(), null);
647 |
648 | /*
649 | * NOTE: On Windows and before JRE 1.7, File.setLastModified() opens the file without any sharing enabled (cf. evaluation of Sun bug 6357599). This means while setLastModified() is executing,
650 | * no other thread/process can open the file "because it is being used by another process". The read accesses to files can't always be guarded by locks, take for instance class loaders reading
651 | * JARs, so we must avoid calling setLastModified() completely on the affected platforms to enable safe concurrent IO. The setLastModified() call below while the file is still open is
652 | * generally ineffective as the OS will update the timestamp after closing the file (at least Windows does so). But its failure allows us to detect the problematic platforms. The destination
653 | * file not having the same timestamp as the source file isn't overly beauty but shouldn't actually matter in real life either.
654 | */
655 | if (IS_SET_LAST_MODIFIED_SAFE == null) {
656 | IS_SET_LAST_MODIFIED_SAFE = Boolean.valueOf(target.setLastModified(source.lastModified()));
657 | logger.debug("Updates of file modification timestamp are safe: " + IS_SET_LAST_MODIFIED_SAFE);
658 | }
659 |
660 | close(targetLock.getRandomAccessFile());
661 |
662 | if (IS_SET_LAST_MODIFIED_SAFE.booleanValue()) {
663 | target.setLastModified(source.lastModified());
664 | }
665 |
666 | // NOTE: Close the file handle to enable its deletion but don't release the lock yet.
667 | close(sourceLock.getRandomAccessFile());
668 |
669 | source.delete();
670 | }
671 | } finally {
672 | unlock(sourceLock);
673 | unlock(targetLock);
674 | }
675 | }
676 | }
677 |
--------------------------------------------------------------------------------
/src/main/java/org/eclipse/aether/internal/impl/DefaultUpdateCheckManager.java:
--------------------------------------------------------------------------------
1 | package org.eclipse.aether.internal.impl;
2 |
3 | import io.takari.aether.localrepo.TakariUpdateCheckManager;
4 |
5 | import javax.inject.Named;
6 | import javax.inject.Singleton;
7 |
8 | @Named
9 | @Singleton
10 | public class DefaultUpdateCheckManager extends TakariUpdateCheckManager {
11 | }
12 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/aether/localrepo/BaseLocalRepositoryManagerTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2010, 2013 Sonatype, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * http://www.eclipse.org/legal/epl-v10.html
7 | *
8 | * Contributors:
9 | * Sonatype, Inc. - initial API and implementation
10 | *******************************************************************************/
11 | package io.takari.aether.localrepo;
12 |
13 | import static org.junit.Assert.*;
14 |
15 | import java.io.File;
16 | import java.io.IOException;
17 |
18 | import org.eclipse.aether.RepositorySystemSession;
19 | import org.eclipse.aether.artifact.Artifact;
20 | import org.eclipse.aether.artifact.DefaultArtifact;
21 | import org.eclipse.aether.internal.test.util.TestUtils;
22 | import org.eclipse.aether.repository.LocalArtifactRequest;
23 | import org.eclipse.aether.repository.LocalArtifactResult;
24 | import org.eclipse.aether.repository.RemoteRepository;
25 | import org.junit.After;
26 | import org.junit.Before;
27 | import org.junit.Test;
28 |
29 | import com.google.common.collect.Lists;
30 |
31 | public class BaseLocalRepositoryManagerTest {
32 |
33 | private File basedir;
34 |
35 | private TakariLocalRepositoryManager manager;
36 |
37 | private RepositorySystemSession session;
38 |
39 | @Before
40 | public void setup() throws IOException {
41 | basedir = TestFileUtils.createTempDir("simple-repo");
42 | session = TestUtils.newSession();
43 | manager = new TakariLocalRepositoryManager(basedir, session, Lists.newArrayList());
44 | }
45 |
46 | @After
47 | public void tearDown() throws Exception {
48 | TestFileUtils.delete(basedir);
49 | manager = null;
50 | session = null;
51 | }
52 |
53 | @Test
54 | public void testGetPathForLocalArtifact() throws Exception {
55 | Artifact artifact = new DefaultArtifact("g.i.d:a.i.d:1.0-SNAPSHOT");
56 | assertEquals("1.0-SNAPSHOT", artifact.getBaseVersion());
57 | assertEquals("g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-SNAPSHOT.jar", manager.getPathForLocalArtifact(artifact));
58 |
59 | artifact = new DefaultArtifact("g.i.d:a.i.d:1.0-20110329.221805-4");
60 | assertEquals("1.0-SNAPSHOT", artifact.getBaseVersion());
61 | assertEquals("g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-SNAPSHOT.jar", manager.getPathForLocalArtifact(artifact));
62 |
63 | artifact = new DefaultArtifact("g.i.d", "a.i.d", "", "", "1.0-SNAPSHOT");
64 | assertEquals("g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-SNAPSHOT", manager.getPathForLocalArtifact(artifact));
65 | }
66 |
67 | @Test
68 | public void testGetPathForRemoteArtifact() throws Exception {
69 | RemoteRepository remoteRepo = new RemoteRepository.Builder("repo", "default", "ram:/void").build();
70 |
71 | Artifact artifact = new DefaultArtifact("g.i.d:a.i.d:1.0-SNAPSHOT");
72 | assertEquals("1.0-SNAPSHOT", artifact.getBaseVersion());
73 | assertEquals("g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-SNAPSHOT.jar", manager.getPathForRemoteArtifact(artifact, remoteRepo, ""));
74 |
75 | artifact = new DefaultArtifact("g.i.d:a.i.d:1.0-20110329.221805-4");
76 | assertEquals("1.0-SNAPSHOT", artifact.getBaseVersion());
77 | assertEquals("g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-20110329.221805-4.jar", manager.getPathForRemoteArtifact(artifact, remoteRepo, ""));
78 | }
79 |
80 | @Test
81 | public void testFindArtifactUsesTimestampedVersion() throws Exception {
82 | Artifact artifact = new DefaultArtifact("g.i.d:a.i.d:1.0-SNAPSHOT");
83 | File file = new File(basedir, manager.getPathForLocalArtifact(artifact));
84 | TestFileUtils.write("test", file);
85 |
86 | artifact = artifact.setVersion("1.0-20110329.221805-4");
87 | LocalArtifactRequest request = new LocalArtifactRequest();
88 | request.setArtifact(artifact);
89 | LocalArtifactResult result = manager.find(session, request);
90 | assertNull(result.toString(), result.getFile());
91 | assertFalse(result.toString(), result.isAvailable());
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/aether/localrepo/SimpleResolutionErrorPolicy.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2012 Sonatype, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * http://www.eclipse.org/legal/epl-v10.html
7 | *
8 | * Contributors:
9 | * Sonatype, Inc. - initial API and implementation
10 | *******************************************************************************/
11 | package io.takari.aether.localrepo;
12 |
13 | import org.eclipse.aether.RepositorySystemSession;
14 | import org.eclipse.aether.artifact.Artifact;
15 | import org.eclipse.aether.metadata.Metadata;
16 | import org.eclipse.aether.resolution.ResolutionErrorPolicy;
17 | import org.eclipse.aether.resolution.ResolutionErrorPolicyRequest;
18 |
19 | /**
20 | * A resolution error policy that allows to control caching for artifacts and metadata at a global level.
21 | */
22 | public final class SimpleResolutionErrorPolicy
23 | implements ResolutionErrorPolicy
24 | {
25 |
26 | private final int artifactPolicy;
27 |
28 | private final int metadataPolicy;
29 |
30 | /**
31 | * Creates a new error policy with the specified behavior for both artifacts and metadata.
32 | *
33 | * @param cacheNotFound {@code true} to enable caching of missing items, {@code false} to disable it.
34 | * @param cacheTransferErrors {@code true} to enable chaching of transfer errors, {@code false} to disable it.
35 | */
36 | public SimpleResolutionErrorPolicy( boolean cacheNotFound, boolean cacheTransferErrors )
37 | {
38 | this( ( cacheNotFound ? CACHE_NOT_FOUND : 0 ) | ( cacheTransferErrors ? CACHE_TRANSFER_ERROR : 0 ) );
39 | }
40 |
41 | /**
42 | * Creates a new error policy with the specified bit mask for both artifacts and metadata.
43 | *
44 | * @param policy The bit mask describing the policy for artifacts and metadata.
45 | */
46 | public SimpleResolutionErrorPolicy( int policy )
47 | {
48 | this( policy, policy );
49 | }
50 |
51 | /**
52 | * Creates a new error policy with the specified bit masks for artifacts and metadata.
53 | *
54 | * @param artifactPolicy The bit mask describing the policy for artifacts.
55 | * @param metadataPolicy The bit mask describing the policy for metadata.
56 | */
57 | public SimpleResolutionErrorPolicy( int artifactPolicy, int metadataPolicy )
58 | {
59 | this.artifactPolicy = artifactPolicy;
60 | this.metadataPolicy = metadataPolicy;
61 | }
62 |
63 | public int getArtifactPolicy( RepositorySystemSession session, ResolutionErrorPolicyRequest request )
64 | {
65 | return artifactPolicy;
66 | }
67 |
68 | public int getMetadataPolicy( RepositorySystemSession session, ResolutionErrorPolicyRequest request )
69 | {
70 | return metadataPolicy;
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/aether/localrepo/TakariLocalRepositoryManagerTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2010, 2013 Sonatype, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * http://www.eclipse.org/legal/epl-v10.html
7 | *
8 | * Contributors:
9 | * Sonatype, Inc. - initial API and implementation
10 | *******************************************************************************/
11 | package io.takari.aether.localrepo;
12 |
13 | import static org.junit.Assert.assertEquals;
14 | import static org.junit.Assert.assertFalse;
15 | import static org.junit.Assert.assertNotNull;
16 | import static org.junit.Assert.assertNull;
17 | import static org.junit.Assert.assertTrue;
18 |
19 | import java.io.File;
20 | import java.io.IOException;
21 | import java.net.URI;
22 | import java.util.Arrays;
23 | import java.util.Collection;
24 | import java.util.Collections;
25 |
26 | import org.eclipse.aether.RepositorySystemSession;
27 | import org.eclipse.aether.artifact.Artifact;
28 | import org.eclipse.aether.artifact.DefaultArtifact;
29 | import org.eclipse.aether.internal.test.util.TestUtils;
30 | import org.eclipse.aether.metadata.DefaultMetadata;
31 | import org.eclipse.aether.metadata.Metadata;
32 | import org.eclipse.aether.metadata.Metadata.Nature;
33 | import org.eclipse.aether.repository.LocalArtifactRegistration;
34 | import org.eclipse.aether.repository.LocalArtifactRequest;
35 | import org.eclipse.aether.repository.LocalArtifactResult;
36 | import org.eclipse.aether.repository.LocalMetadataRequest;
37 | import org.eclipse.aether.repository.LocalMetadataResult;
38 | import org.eclipse.aether.repository.RemoteRepository;
39 | import org.junit.After;
40 | import org.junit.Before;
41 | import org.junit.Test;
42 |
43 | import com.google.common.collect.Lists;
44 |
45 | public class TakariLocalRepositoryManagerTest {
46 |
47 | private Artifact artifact;
48 | private Artifact snapshot;
49 | private File basedir;
50 | private TakariLocalRepositoryManager manager;
51 | private File artifactFile;
52 | private RemoteRepository repository;
53 | private String testContext = "project/compile";
54 | private RepositorySystemSession session;
55 | private Metadata metadata;
56 | private Metadata noVerMetadata;
57 |
58 | @Before
59 | public void setup() throws Exception {
60 | String url = TestFileUtils.createTempDir("enhanced-remote-repo").toURI().toURL().toString();
61 | repository = new RemoteRepository.Builder("enhanced-remote-repo", "default", url).setRepositoryManager(true).build();
62 | artifact = new DefaultArtifact("gid", "aid", "", "jar", "1-test", Collections. emptyMap(), TestFileUtils.createTempFile("artifact"));
63 | snapshot = new DefaultArtifact("gid", "aid", "", "jar", "1.0-20120710.231549-9", Collections. emptyMap(), TestFileUtils.createTempFile("artifact"));
64 | metadata = new DefaultMetadata("gid", "aid", "1-test", "maven-metadata.xml", Nature.RELEASE, TestFileUtils.createTempFile("metadata"));
65 | noVerMetadata = new DefaultMetadata("gid", "aid", null, "maven-metadata.xml", Nature.RELEASE, TestFileUtils.createTempFile("metadata"));
66 | basedir = TestFileUtils.createTempDir("enhanced-repo");
67 | session = TestUtils.newSession();
68 | manager = new TakariLocalRepositoryManager(basedir, session, Lists. newArrayList());
69 | artifactFile = new File(basedir, manager.getPathForLocalArtifact(artifact));
70 | }
71 |
72 | @After
73 | public void tearDown() throws Exception {
74 | TestFileUtils.delete(basedir);
75 | TestFileUtils.delete(new File(new URI(repository.getUrl())));
76 |
77 | session = null;
78 | manager = null;
79 | repository = null;
80 | artifact = null;
81 | }
82 |
83 | private long addLocalArtifact(Artifact artifact) throws IOException {
84 | manager.add(session, new LocalArtifactRegistration(artifact));
85 | String path = manager.getPathForLocalArtifact(artifact);
86 |
87 | return copy(artifact, path);
88 | }
89 |
90 | private long addRemoteArtifact(Artifact artifact) throws IOException {
91 | Collection contexts = Arrays.asList(testContext);
92 | manager.add(session, new LocalArtifactRegistration(artifact, repository, contexts));
93 | String path = manager.getPathForRemoteArtifact(artifact, repository, testContext);
94 | return copy(artifact, path);
95 | }
96 |
97 | private long copy(Metadata metadata, String path) throws IOException {
98 | if (metadata.getFile() == null) {
99 | return -1;
100 | }
101 | return TestFileUtils.copy(metadata.getFile(), new File(basedir, path));
102 | }
103 |
104 | private long copy(Artifact artifact, String path) throws IOException {
105 | if (artifact.getFile() == null) {
106 | return -1;
107 | }
108 | File artifactFile = new File(basedir, path);
109 | return TestFileUtils.copy(artifact.getFile(), artifactFile);
110 | }
111 |
112 | @Test
113 | public void testGetPathForLocalArtifact() {
114 | Artifact artifact = new DefaultArtifact("g.i.d:a.i.d:1.0-SNAPSHOT");
115 | assertEquals("1.0-SNAPSHOT", artifact.getBaseVersion());
116 | assertEquals("g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-SNAPSHOT.jar", manager.getPathForLocalArtifact(artifact));
117 |
118 | artifact = new DefaultArtifact("g.i.d:a.i.d:1.0-20110329.221805-4");
119 | assertEquals("1.0-SNAPSHOT", artifact.getBaseVersion());
120 | assertEquals("g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-SNAPSHOT.jar", manager.getPathForLocalArtifact(artifact));
121 | }
122 |
123 | @Test
124 | public void testGetPathForRemoteArtifact() {
125 | RemoteRepository remoteRepo = new RemoteRepository.Builder("repo", "default", "ram:/void").build();
126 |
127 | Artifact artifact = new DefaultArtifact("g.i.d:a.i.d:1.0-SNAPSHOT");
128 | assertEquals("1.0-SNAPSHOT", artifact.getBaseVersion());
129 | assertEquals("g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-SNAPSHOT.jar", manager.getPathForRemoteArtifact(artifact, remoteRepo, ""));
130 |
131 | artifact = new DefaultArtifact("g.i.d:a.i.d:1.0-20110329.221805-4");
132 | assertEquals("1.0-SNAPSHOT", artifact.getBaseVersion());
133 | assertEquals("g/i/d/a.i.d/1.0-SNAPSHOT/a.i.d-1.0-20110329.221805-4.jar", manager.getPathForRemoteArtifact(artifact, remoteRepo, ""));
134 | }
135 |
136 | @Test
137 | public void testFindLocalArtifact() throws Exception {
138 | addLocalArtifact(artifact);
139 |
140 | LocalArtifactRequest request = new LocalArtifactRequest(artifact, null, null);
141 | LocalArtifactResult result = manager.find(session, request);
142 | assertTrue(result.isAvailable());
143 | assertEquals(null, result.getRepository());
144 |
145 | snapshot = snapshot.setVersion(snapshot.getBaseVersion());
146 | addLocalArtifact(snapshot);
147 |
148 | request = new LocalArtifactRequest(snapshot, null, null);
149 | result = manager.find(session, request);
150 | assertTrue(result.isAvailable());
151 | assertEquals(null, result.getRepository());
152 | }
153 |
154 | @Test
155 | public void testFindRemoteArtifact() throws Exception {
156 | addRemoteArtifact(artifact);
157 |
158 | LocalArtifactRequest request = new LocalArtifactRequest(artifact, Arrays.asList(repository), testContext);
159 | LocalArtifactResult result = manager.find(session, request);
160 | assertTrue(result.isAvailable());
161 | assertEquals(repository, result.getRepository());
162 |
163 | addRemoteArtifact(snapshot);
164 |
165 | request = new LocalArtifactRequest(snapshot, Arrays.asList(repository), testContext);
166 | result = manager.find(session, request);
167 | assertTrue(result.isAvailable());
168 | assertEquals(repository, result.getRepository());
169 | }
170 |
171 | /*
172 |
173 | So this makes sure that if the artifact is found in a different repo it fails, but we want to start providing better checks such
174 | that we will accept the artifact if it has the same SHA1.
175 |
176 | @Test
177 | public void testDoNotFindDifferentContext() throws Exception {
178 | addRemoteArtifact(artifact);
179 |
180 | LocalArtifactRequest request = new LocalArtifactRequest(artifact, Arrays.asList(repository), "different");
181 | LocalArtifactResult result = manager.find(session, request);
182 | assertFalse(result.isAvailable());
183 | }
184 |
185 | */
186 |
187 | @Test
188 | public void testDoNotFindNullFile() throws Exception {
189 | artifact = artifact.setFile(null);
190 | addLocalArtifact(artifact);
191 |
192 | LocalArtifactRequest request = new LocalArtifactRequest(artifact, Arrays.asList(repository), testContext);
193 | LocalArtifactResult result = manager.find(session, request);
194 | assertFalse(result.isAvailable());
195 | }
196 |
197 | @Test
198 | public void testDoNotFindDeletedFile() throws Exception {
199 | addLocalArtifact(artifact);
200 | assertTrue("could not delete artifact file", artifactFile.delete());
201 |
202 | LocalArtifactRequest request = new LocalArtifactRequest(artifact, Arrays.asList(repository), testContext);
203 | LocalArtifactResult result = manager.find(session, request);
204 | assertFalse(result.isAvailable());
205 | }
206 |
207 | @Test
208 | public void testFindUntrackedFile() throws Exception {
209 | copy(artifact, manager.getPathForLocalArtifact(artifact));
210 |
211 | LocalArtifactRequest request = new LocalArtifactRequest(artifact, Arrays.asList(repository), testContext);
212 | LocalArtifactResult result = manager.find(session, request);
213 | assertTrue(result.isAvailable());
214 | }
215 |
216 | private long addMetadata(Metadata metadata, RemoteRepository repo) throws IOException {
217 | String path;
218 | if (repo == null) {
219 | path = manager.getPathForLocalMetadata(metadata);
220 | } else {
221 | path = manager.getPathForRemoteMetadata(metadata, repo, testContext);
222 | }
223 | System.err.println(path);
224 |
225 | return copy(metadata, path);
226 | }
227 |
228 | @Test
229 | public void testFindLocalMetadata() throws Exception {
230 | addMetadata(metadata, null);
231 |
232 | LocalMetadataRequest request = new LocalMetadataRequest(metadata, null, testContext);
233 | LocalMetadataResult result = manager.find(session, request);
234 |
235 | assertNotNull(result.getFile());
236 | }
237 |
238 | @Test
239 | public void testFindLocalMetadataNoVersion() throws Exception {
240 | addMetadata(noVerMetadata, null);
241 |
242 | LocalMetadataRequest request = new LocalMetadataRequest(noVerMetadata, null, testContext);
243 | LocalMetadataResult result = manager.find(session, request);
244 |
245 | assertNotNull(result.getFile());
246 | }
247 |
248 | @Test
249 | public void testDoNotFindRemoteMetadataDifferentContext() throws Exception {
250 | addMetadata(noVerMetadata, repository);
251 | addMetadata(metadata, repository);
252 |
253 | LocalMetadataRequest request = new LocalMetadataRequest(noVerMetadata, repository, "different");
254 | LocalMetadataResult result = manager.find(session, request);
255 | assertNull(result.getFile());
256 |
257 | request = new LocalMetadataRequest(metadata, repository, "different");
258 | result = manager.find(session, request);
259 | assertNull(result.getFile());
260 | }
261 |
262 | @Test
263 | public void testFindArtifactUsesTimestampedVersion() throws Exception {
264 | Artifact artifact = new DefaultArtifact("g.i.d:a.i.d:1.0-SNAPSHOT");
265 | File file = new File(basedir, manager.getPathForLocalArtifact(artifact));
266 | TestFileUtils.write("test", file);
267 | addLocalArtifact(artifact);
268 |
269 | artifact = artifact.setVersion("1.0-20110329.221805-4");
270 | LocalArtifactRequest request = new LocalArtifactRequest();
271 | request.setArtifact(artifact);
272 | LocalArtifactResult result = manager.find(session, request);
273 | assertNull(result.toString(), result.getFile());
274 | assertFalse(result.toString(), result.isAvailable());
275 | }
276 |
277 | }
278 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/aether/localrepo/TakariUpdateCheckManagerTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2010, 2013 Sonatype, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * http://www.eclipse.org/legal/epl-v10.html
7 | *
8 | * Contributors:
9 | * Sonatype, Inc. - initial API and implementation
10 | *******************************************************************************/
11 | package io.takari.aether.localrepo;
12 |
13 | import static org.junit.Assert.assertEquals;
14 | import static org.junit.Assert.assertFalse;
15 | import static org.junit.Assert.assertNotNull;
16 | import static org.junit.Assert.assertNull;
17 | import static org.junit.Assert.assertTrue;
18 |
19 | import java.io.File;
20 | import java.net.URI;
21 | import java.util.Calendar;
22 | import java.util.Date;
23 | import java.util.TimeZone;
24 |
25 | import org.eclipse.aether.DefaultRepositorySystemSession;
26 | import org.eclipse.aether.RepositorySystemSession;
27 | import org.eclipse.aether.artifact.Artifact;
28 | import org.eclipse.aether.artifact.DefaultArtifact;
29 | import org.eclipse.aether.impl.UpdateCheck;
30 | import org.eclipse.aether.impl.UpdateCheckManager;
31 | import org.eclipse.aether.internal.impl.DefaultUpdatePolicyAnalyzer;
32 | import org.eclipse.aether.internal.test.util.TestUtils;
33 | import org.eclipse.aether.metadata.DefaultMetadata;
34 | import org.eclipse.aether.metadata.Metadata;
35 | import org.eclipse.aether.repository.RemoteRepository;
36 | import org.eclipse.aether.repository.RepositoryPolicy;
37 | import org.eclipse.aether.transfer.ArtifactNotFoundException;
38 | import org.eclipse.aether.transfer.ArtifactTransferException;
39 | import org.eclipse.aether.transfer.MetadataNotFoundException;
40 | import org.eclipse.aether.transfer.MetadataTransferException;
41 | import org.junit.After;
42 | import org.junit.Before;
43 | import org.junit.Test;
44 |
45 | /**
46 | */
47 | public class TakariUpdateCheckManagerTest {
48 |
49 | private static final int HOUR = 60 * 60 * 1000;
50 |
51 | private UpdateCheckManager manager;
52 |
53 | private DefaultRepositorySystemSession session;
54 |
55 | private Metadata metadata;
56 |
57 | private RemoteRepository repository;
58 |
59 | private Artifact artifact;
60 |
61 | @Before
62 | public void setup() throws Exception {
63 | File dir = TestFileUtils.createTempFile("");
64 | TestFileUtils.delete(dir);
65 |
66 | File metadataFile = new File(dir, "metadata.txt");
67 | TestFileUtils.write("metadata", metadataFile);
68 | File artifactFile = new File(dir, "artifact.txt");
69 | TestFileUtils.write("artifact", artifactFile);
70 |
71 | session = TestUtils.newSession();
72 | repository = new RemoteRepository.Builder("id", "default", TestFileUtils.createTempDir().toURI().toURL().toString()).build();
73 | manager = new TakariUpdateCheckManager().setUpdatePolicyAnalyzer(new DefaultUpdatePolicyAnalyzer());
74 | metadata = new DefaultMetadata("gid", "aid", "ver", "maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT, metadataFile);
75 | artifact = new DefaultArtifact("gid", "aid", "", "ext", "ver").setFile(artifactFile);
76 | }
77 |
78 | @After
79 | public void teardown() throws Exception {
80 | new File(metadata.getFile().getParent(), "resolver-status.properties").delete();
81 | new File(artifact.getFile().getPath() + ".lastUpdated").delete();
82 | metadata.getFile().delete();
83 | artifact.getFile().delete();
84 | TestFileUtils.delete(new File(new URI(repository.getUrl())));
85 | }
86 |
87 | static void resetSessionData(RepositorySystemSession session) {
88 | session.getData().set("updateCheckManager.checks", null);
89 | }
90 |
91 | private UpdateCheck newMetadataCheck() {
92 | UpdateCheck check = new UpdateCheck();
93 | check.setItem(metadata);
94 | check.setFile(metadata.getFile());
95 | check.setRepository(repository);
96 | check.setAuthoritativeRepository(repository);
97 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_INTERVAL + ":10");
98 | return check;
99 | }
100 |
101 | private UpdateCheck newArtifactCheck() {
102 | UpdateCheck check = new UpdateCheck();
103 | check.setItem(artifact);
104 | check.setFile(artifact.getFile());
105 | check.setRepository(repository);
106 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_INTERVAL + ":10");
107 | return check;
108 | }
109 |
110 | @Test(expected = Exception.class)
111 | public void testCheckMetadataFailOnNoFile() throws Exception {
112 | UpdateCheck check = newMetadataCheck();
113 | check.setItem(metadata.setFile(null));
114 | check.setFile(null);
115 |
116 | manager.checkMetadata(session, check);
117 | }
118 |
119 | @Test
120 | public void testCheckMetadataUpdatePolicyRequired() throws Exception {
121 | UpdateCheck check = newMetadataCheck();
122 |
123 | Calendar cal = Calendar.getInstance();
124 | cal.add(Calendar.DATE, -1);
125 | check.setLocalLastUpdated(cal.getTimeInMillis());
126 |
127 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS);
128 | manager.checkMetadata(session, check);
129 | assertNull(check.getException());
130 | assertTrue(check.isRequired());
131 |
132 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
133 | manager.checkMetadata(session, check);
134 | assertNull(check.getException());
135 | assertTrue(check.isRequired());
136 |
137 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_INTERVAL + ":60");
138 | manager.checkMetadata(session, check);
139 | assertNull(check.getException());
140 | assertTrue(check.isRequired());
141 | }
142 |
143 | @Test
144 | public void testCheckMetadataUpdatePolicyNotRequired() throws Exception {
145 | UpdateCheck check = newMetadataCheck();
146 |
147 | check.setLocalLastUpdated(System.currentTimeMillis());
148 |
149 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_NEVER);
150 | manager.checkMetadata(session, check);
151 | assertFalse(check.isRequired());
152 |
153 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
154 | manager.checkMetadata(session, check);
155 | assertFalse(check.isRequired());
156 |
157 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_INTERVAL + ":61");
158 | manager.checkMetadata(session, check);
159 | assertFalse(check.isRequired());
160 |
161 | check.setPolicy("no particular policy");
162 | manager.checkMetadata(session, check);
163 | assertFalse(check.isRequired());
164 | }
165 |
166 | @Test
167 | public void testCheckMetadata() throws Exception {
168 | UpdateCheck check = newMetadataCheck();
169 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
170 |
171 | // existing file, never checked before
172 | manager.checkMetadata(session, check);
173 | assertEquals(true, check.isRequired());
174 |
175 | // just checked
176 | manager.touchMetadata(session, check);
177 | resetSessionData(session);
178 |
179 | check = newMetadataCheck();
180 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_INTERVAL + ":60");
181 |
182 | manager.checkMetadata(session, check);
183 | assertEquals(false, check.isRequired());
184 |
185 | // no local file
186 | check.getFile().delete();
187 | manager.checkMetadata(session, check);
188 | assertEquals(true, check.isRequired());
189 | // (! file.exists && ! repoKey) -> no timestamp
190 | }
191 |
192 | @Test
193 | public void testCheckMetadataNoLocalFile() throws Exception {
194 | metadata.getFile().delete();
195 |
196 | UpdateCheck check = newMetadataCheck();
197 |
198 | long lastUpdate = new Date().getTime() - HOUR;
199 | check.setLocalLastUpdated(lastUpdate);
200 |
201 | // ! file.exists && updateRequired -> check in remote repo
202 | check.setLocalLastUpdated(lastUpdate);
203 | manager.checkMetadata(session, check);
204 | assertEquals(true, check.isRequired());
205 | }
206 |
207 | @Test
208 | public void testCheckMetadataNotFoundInRepoCachingEnabled() throws Exception {
209 | metadata.getFile().delete();
210 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(true, false));
211 |
212 | UpdateCheck check = newMetadataCheck();
213 |
214 | check.setException(new MetadataNotFoundException(metadata, repository, ""));
215 | manager.touchMetadata(session, check);
216 | resetSessionData(session);
217 |
218 | // ! file.exists && ! updateRequired -> artifact not found in remote repo
219 | check = newMetadataCheck().setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
220 | manager.checkMetadata(session, check);
221 | assertEquals(false, check.isRequired());
222 | assertNotNull(check.getException());
223 | }
224 |
225 | @Test
226 | public void testCheckMetadataNotFoundInRepoCachingDisabled() throws Exception {
227 | metadata.getFile().delete();
228 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(false, false));
229 |
230 | UpdateCheck check = newMetadataCheck();
231 |
232 | check.setException(new MetadataNotFoundException(metadata, repository, ""));
233 | manager.touchMetadata(session, check);
234 | resetSessionData(session);
235 |
236 | // ! file.exists && updateRequired -> check in remote repo
237 | check = newMetadataCheck().setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
238 | manager.checkMetadata(session, check);
239 | assertEquals(true, check.isRequired());
240 | assertNull(check.getException());
241 | }
242 |
243 | @Test
244 | public void testCheckMetadataErrorFromRepo() throws Exception {
245 | metadata.getFile().delete();
246 |
247 | UpdateCheck check = newMetadataCheck();
248 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
249 |
250 | check.setException(new MetadataTransferException(metadata, repository, "some error"));
251 | manager.touchMetadata(session, check);
252 | resetSessionData(session);
253 |
254 | // ! file.exists && ! updateRequired && previousError -> depends on transfer error caching
255 | check = newMetadataCheck();
256 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(false, true));
257 | manager.checkMetadata(session, check);
258 | assertEquals(false, check.isRequired());
259 | assertTrue(String.valueOf(check.getException()), check.getException().getMessage().contains("some error"));
260 | }
261 |
262 | @Test
263 | public void testCheckMetadataErrorFromRepoNoCaching() throws Exception {
264 | metadata.getFile().delete();
265 |
266 | UpdateCheck check = newMetadataCheck();
267 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
268 |
269 | check.setException(new MetadataTransferException(metadata, repository, "some error"));
270 | manager.touchMetadata(session, check);
271 | resetSessionData(session);
272 |
273 | // ! file.exists && ! updateRequired && previousError -> depends on transfer error caching
274 | check = newMetadataCheck();
275 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(false, false));
276 | manager.checkMetadata(session, check);
277 | assertEquals(true, check.isRequired());
278 | assertNull(check.getException());
279 | }
280 |
281 | @Test
282 | public void testCheckMetadataAtMostOnceDuringSessionEvenIfUpdatePolicyAlways() throws Exception {
283 | UpdateCheck check = newMetadataCheck();
284 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS);
285 |
286 | // first check
287 | manager.checkMetadata(session, check);
288 | assertEquals(true, check.isRequired());
289 |
290 | manager.touchMetadata(session, check);
291 |
292 | // second check in same session
293 | manager.checkMetadata(session, check);
294 | assertEquals(false, check.isRequired());
295 | }
296 |
297 | @Test
298 | public void testCheckMetadataWhenLocallyMissingEvenIfUpdatePolicyIsNever() throws Exception {
299 | UpdateCheck check = newMetadataCheck();
300 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_NEVER);
301 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(true, false));
302 |
303 | check.getFile().delete();
304 | assertEquals(check.getFile().getAbsolutePath(), false, check.getFile().exists());
305 |
306 | manager.checkMetadata(session, check);
307 | assertEquals(true, check.isRequired());
308 | }
309 |
310 | @Test
311 | public void testCheckMetadataWhenLocallyPresentButInvalidEvenIfUpdatePolicyIsNever() throws Exception {
312 | UpdateCheck check = newMetadataCheck();
313 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_NEVER);
314 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(true, false));
315 |
316 | manager.touchMetadata(session, check);
317 | resetSessionData(session);
318 |
319 | check.setFileValid(false);
320 |
321 | manager.checkMetadata(session, check);
322 | assertEquals(true, check.isRequired());
323 | }
324 |
325 | @Test
326 | public void testCheckMetadataWhenLocallyDeletedEvenIfTimestampUpToDate() throws Exception {
327 | UpdateCheck check = newMetadataCheck();
328 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(true, false));
329 |
330 | manager.touchMetadata(session, check);
331 | resetSessionData(session);
332 |
333 | check.getFile().delete();
334 | assertEquals(check.getFile().getAbsolutePath(), false, check.getFile().exists());
335 |
336 | manager.checkMetadata(session, check);
337 | assertEquals(true, check.isRequired());
338 | }
339 |
340 | @Test
341 | public void testCheckMetadataNotWhenUpdatePolicyIsNeverAndTimestampIsUnavailable() throws Exception {
342 | UpdateCheck check = newMetadataCheck();
343 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_NEVER);
344 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(true, false));
345 |
346 | manager.checkMetadata(session, check);
347 | assertEquals(false, check.isRequired());
348 | }
349 |
350 | @Test(expected = IllegalArgumentException.class)
351 | public void testCheckArtifactFailOnNoFile() throws Exception {
352 | UpdateCheck check = newArtifactCheck();
353 | check.setItem(artifact.setFile(null));
354 | check.setFile(null);
355 |
356 | manager.checkArtifact(session, check);
357 | assertNotNull(check.getException());
358 | }
359 |
360 | @Test
361 | public void testCheckArtifactUpdatePolicyRequired() throws Exception {
362 | UpdateCheck check = newArtifactCheck();
363 | check.setItem(artifact);
364 | check.setFile(artifact.getFile());
365 |
366 | Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
367 | cal.add(Calendar.DATE, -1);
368 | long lastUpdate = cal.getTimeInMillis();
369 | artifact.getFile().setLastModified(lastUpdate);
370 | check.setLocalLastUpdated(lastUpdate);
371 |
372 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS);
373 | manager.checkArtifact(session, check);
374 | assertNull(check.getException());
375 | assertTrue(check.isRequired());
376 |
377 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
378 | manager.checkArtifact(session, check);
379 | assertNull(check.getException());
380 | assertTrue(check.isRequired());
381 |
382 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_INTERVAL + ":60");
383 | manager.checkArtifact(session, check);
384 | assertNull(check.getException());
385 | assertTrue(check.isRequired());
386 | }
387 |
388 | @Test
389 | public void testCheckArtifactUpdatePolicyNotRequired() throws Exception {
390 | UpdateCheck check = newArtifactCheck();
391 | check.setItem(artifact);
392 | check.setFile(artifact.getFile());
393 |
394 | Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
395 | cal.add(Calendar.HOUR_OF_DAY, -1);
396 | check.setLocalLastUpdated(cal.getTimeInMillis());
397 |
398 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_NEVER);
399 | manager.checkArtifact(session, check);
400 | assertFalse(check.isRequired());
401 |
402 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
403 | manager.checkArtifact(session, check);
404 | assertFalse(check.isRequired());
405 |
406 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_INTERVAL + ":61");
407 | manager.checkArtifact(session, check);
408 | assertFalse(check.isRequired());
409 |
410 | check.setPolicy("no particular policy");
411 | manager.checkArtifact(session, check);
412 | assertFalse(check.isRequired());
413 | }
414 |
415 | @Test
416 | public void testCheckArtifact() throws Exception {
417 | UpdateCheck check = newArtifactCheck();
418 | long fifteenMinutes = new Date().getTime() - (15 * 60 * 1000);
419 | check.getFile().setLastModified(fifteenMinutes);
420 | // time is truncated on setLastModfied
421 | fifteenMinutes = check.getFile().lastModified();
422 |
423 | // never checked before
424 | manager.checkArtifact(session, check);
425 | assertEquals(true, check.isRequired());
426 |
427 | // just checked
428 | check.setLocalLastUpdated(0);
429 | long lastUpdate = new Date().getTime();
430 | check.getFile().setLastModified(lastUpdate);
431 | lastUpdate = check.getFile().lastModified();
432 |
433 | manager.checkArtifact(session, check);
434 | assertEquals(false, check.isRequired());
435 |
436 | // no local file, no repo timestamp
437 | check.setLocalLastUpdated(0);
438 | check.getFile().delete();
439 | manager.checkArtifact(session, check);
440 | assertEquals(true, check.isRequired());
441 | }
442 |
443 | @Test
444 | public void testCheckArtifactNoLocalFile() throws Exception {
445 | artifact.getFile().delete();
446 | UpdateCheck check = newArtifactCheck();
447 |
448 | long lastUpdate = new Date().getTime() - HOUR;
449 |
450 | // ! file.exists && updateRequired -> check in remote repo
451 | check.setLocalLastUpdated(lastUpdate);
452 | manager.checkArtifact(session, check);
453 | assertEquals(true, check.isRequired());
454 | }
455 |
456 | @Test
457 | public void testCheckArtifactNotFoundInRepoCachingEnabled() throws Exception {
458 | artifact.getFile().delete();
459 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(true, false));
460 |
461 | UpdateCheck check = newArtifactCheck();
462 | check.setException(new ArtifactNotFoundException(artifact, repository));
463 | manager.touchArtifact(session, check);
464 | resetSessionData(session);
465 |
466 | // ! file.exists && ! updateRequired -> artifact not found in remote repo
467 | check = newArtifactCheck().setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
468 | manager.checkArtifact(session, check);
469 | assertEquals(false, check.isRequired());
470 | assertTrue(check.getException() instanceof ArtifactNotFoundException);
471 | }
472 |
473 | @Test
474 | public void testCheckArtifactNotFoundInRepoCachingDisabled() throws Exception {
475 | artifact.getFile().delete();
476 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(false, false));
477 |
478 | UpdateCheck check = newArtifactCheck();
479 | check.setException(new ArtifactNotFoundException(artifact, repository));
480 | manager.touchArtifact(session, check);
481 | resetSessionData(session);
482 |
483 | // ! file.exists && updateRequired -> check in remote repo
484 | check = newArtifactCheck().setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
485 | manager.checkArtifact(session, check);
486 | assertEquals(true, check.isRequired());
487 | assertNull(check.getException());
488 | }
489 |
490 | @Test
491 | public void testCheckArtifactErrorFromRepoCachingEnabled() throws Exception {
492 | artifact.getFile().delete();
493 |
494 | UpdateCheck check = newArtifactCheck();
495 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
496 | check.setException(new ArtifactTransferException(artifact, repository, "some error"));
497 | manager.touchArtifact(session, check);
498 | resetSessionData(session);
499 |
500 | // ! file.exists && ! updateRequired && previousError -> depends on transfer error caching
501 | check = newArtifactCheck();
502 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(false, true));
503 | manager.checkArtifact(session, check);
504 | assertEquals(false, check.isRequired());
505 | }
506 |
507 | @Test
508 | public void testCheckArtifactErrorFromRepoCachingDisabled() throws Exception {
509 | artifact.getFile().delete();
510 |
511 | UpdateCheck check = newArtifactCheck();
512 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_DAILY);
513 | check.setException(new ArtifactTransferException(artifact, repository, "some error"));
514 | manager.touchArtifact(session, check);
515 | resetSessionData(session);
516 |
517 | // ! file.exists && ! updateRequired && previousError -> depends on transfer error caching
518 | check = newArtifactCheck();
519 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(false, false));
520 | manager.checkArtifact(session, check);
521 | assertEquals(true, check.isRequired());
522 | assertNull(check.getException());
523 | }
524 |
525 | @Test
526 | public void testCheckArtifactAtMostOnceDuringSessionEvenIfUpdatePolicyAlways() throws Exception {
527 | UpdateCheck check = newArtifactCheck();
528 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS);
529 |
530 | // first check
531 | manager.checkArtifact(session, check);
532 | assertEquals(true, check.isRequired());
533 |
534 | manager.touchArtifact(session, check);
535 |
536 | // second check in same session
537 | manager.checkArtifact(session, check);
538 | assertEquals(false, check.isRequired());
539 | }
540 |
541 | @Test
542 | public void testCheckArtifactWhenLocallyMissingEvenIfUpdatePolicyIsNever() throws Exception {
543 | UpdateCheck check = newArtifactCheck();
544 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_NEVER);
545 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(true, false));
546 |
547 | check.getFile().delete();
548 | assertEquals(check.getFile().getAbsolutePath(), false, check.getFile().exists());
549 |
550 | manager.checkArtifact(session, check);
551 | assertEquals(true, check.isRequired());
552 | }
553 |
554 | @Test
555 | public void testCheckArtifactWhenLocallyPresentButInvalidEvenIfUpdatePolicyIsNever() throws Exception {
556 | UpdateCheck check = newArtifactCheck();
557 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_NEVER);
558 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(true, false));
559 |
560 | manager.touchArtifact(session, check);
561 | resetSessionData(session);
562 |
563 | check.setFileValid(false);
564 |
565 | manager.checkArtifact(session, check);
566 | assertEquals(true, check.isRequired());
567 | }
568 |
569 | @Test
570 | public void testCheckArtifactWhenLocallyDeletedEvenIfTimestampUpToDate() throws Exception {
571 | UpdateCheck check = newArtifactCheck();
572 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(true, false));
573 |
574 | manager.touchArtifact(session, check);
575 | resetSessionData(session);
576 |
577 | check.getFile().delete();
578 | assertEquals(check.getFile().getAbsolutePath(), false, check.getFile().exists());
579 |
580 | manager.checkArtifact(session, check);
581 | assertEquals(true, check.isRequired());
582 | }
583 |
584 | @Test
585 | public void testCheckArtifactNotWhenUpdatePolicyIsNeverAndTimestampIsUnavailable() throws Exception {
586 | UpdateCheck check = newArtifactCheck();
587 | check.setPolicy(RepositoryPolicy.UPDATE_POLICY_NEVER);
588 | session.setResolutionErrorPolicy(new SimpleResolutionErrorPolicy(true, false));
589 |
590 | manager.checkArtifact(session, check);
591 | assertEquals(false, check.isRequired());
592 | }
593 |
594 | }
595 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/aether/localrepo/TestFileUtils.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2010, 2013 Sonatype, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * http://www.eclipse.org/legal/epl-v10.html
7 | *
8 | * Contributors:
9 | * Sonatype, Inc. - initial API and implementation
10 | *******************************************************************************/
11 | package io.takari.aether.localrepo;
12 |
13 | import java.io.BufferedOutputStream;
14 | import java.io.Closeable;
15 | import java.io.File;
16 | import java.io.FileInputStream;
17 | import java.io.FileOutputStream;
18 | import java.io.IOException;
19 | import java.io.OutputStream;
20 | import java.io.RandomAccessFile;
21 | import java.util.ArrayList;
22 | import java.util.Collection;
23 | import java.util.Properties;
24 | import java.util.UUID;
25 |
26 | import org.junit.Assert;
27 |
28 | public class TestFileUtils {
29 |
30 | private static final File TMP = new File(System.getProperty("java.io.tmpdir"), "aether-" + UUID.randomUUID().toString().substring(0, 8));
31 |
32 | static {
33 | Runtime.getRuntime().addShutdownHook(new Thread() {
34 | @Override
35 | public void run() {
36 | try {
37 | delete(TMP);
38 | } catch (IOException e) {
39 | e.printStackTrace();
40 | }
41 | }
42 | });
43 | }
44 |
45 | private TestFileUtils() {
46 | // hide constructor
47 | }
48 |
49 | public static void deleteTempFiles() throws IOException {
50 | delete(TMP);
51 | }
52 |
53 | public static File createTempFile(String contents) throws IOException {
54 | return createTempFile(contents.getBytes("UTF-8"), 1);
55 | }
56 |
57 | public static File createTempFile(byte[] pattern, int repeat) throws IOException {
58 | mkdirs(TMP);
59 | File tmpFile = File.createTempFile("tmpfile-", ".data", TMP);
60 | write(pattern, repeat, tmpFile);
61 |
62 | return tmpFile;
63 | }
64 |
65 | public static void write(String content, File file) throws IOException {
66 | write(content.getBytes("UTF-8"), 1, file);
67 | }
68 |
69 | public static void write(byte[] pattern, int repeat, File file) throws IOException {
70 | file.deleteOnExit();
71 | file.getParentFile().mkdirs();
72 | OutputStream out = null;
73 | try {
74 | out = new BufferedOutputStream(new FileOutputStream(file));
75 | for (int i = 0; i < repeat; i++) {
76 | out.write(pattern);
77 | }
78 | } finally {
79 | close(out);
80 | }
81 | }
82 |
83 | public static long copy(File source, File target) throws IOException {
84 | long total = 0;
85 |
86 | FileInputStream fis = null;
87 | OutputStream fos = null;
88 | try {
89 | fis = new FileInputStream(source);
90 |
91 | mkdirs(target.getParentFile());
92 |
93 | fos = new BufferedOutputStream(new FileOutputStream(target));
94 |
95 | for (byte[] buffer = new byte[1024 * 32];;) {
96 | int bytes = fis.read(buffer);
97 | if (bytes < 0) {
98 | break;
99 | }
100 |
101 | fos.write(buffer, 0, bytes);
102 |
103 | total += bytes;
104 | }
105 | } finally {
106 | close(fis);
107 | close(fos);
108 | }
109 |
110 | return total;
111 | }
112 |
113 | private static void close(Closeable c) throws IOException {
114 | if (c != null) {
115 | try {
116 | c.close();
117 | } catch (IOException e) {
118 | // ignore
119 | }
120 | }
121 | }
122 |
123 | public static void delete(File file) throws IOException {
124 | if (file == null) {
125 | return;
126 | }
127 |
128 | Collection undeletables = new ArrayList();
129 |
130 | delete(file, undeletables);
131 |
132 | if (!undeletables.isEmpty()) {
133 | throw new IOException("Failed to delete " + undeletables);
134 | }
135 | }
136 |
137 | private static void delete(File file, Collection undeletables) {
138 | String[] children = file.list();
139 | if (children != null) {
140 | for (String child : children) {
141 | delete(new File(file, child), undeletables);
142 | }
143 | }
144 |
145 | if (!del(file)) {
146 | undeletables.add(file.getAbsoluteFile());
147 | }
148 | }
149 |
150 | private static boolean del(File file) {
151 | for (int i = 0; i < 10; i++) {
152 | if (file.delete() || !file.exists()) {
153 | return true;
154 | }
155 | }
156 | return false;
157 | }
158 |
159 | public static byte[] getContent(File file) throws IOException {
160 | RandomAccessFile in = null;
161 | try {
162 | in = new RandomAccessFile(file, "r");
163 | byte[] actual = new byte[(int) in.length()];
164 | in.readFully(actual);
165 | return actual;
166 | } finally {
167 | close(in);
168 | }
169 | }
170 |
171 | public static void assertContent(byte[] expected, File file) throws IOException {
172 | Assert.assertArrayEquals(expected, getContent(file));
173 | }
174 |
175 | public static void assertContent(String expected, File file) throws IOException {
176 | byte[] content = getContent(file);
177 | String msg = new String(content, "UTF-8");
178 | if (msg.length() > 10) {
179 | msg = msg.substring(0, 10) + "...";
180 | }
181 | Assert.assertArrayEquals("content was '" + msg + "'\n", expected.getBytes("UTF-8"), content);
182 | }
183 |
184 | public static boolean mkdirs(File directory) {
185 | if (directory == null) {
186 | return false;
187 | }
188 |
189 | if (directory.exists()) {
190 | return false;
191 | }
192 | if (directory.mkdir()) {
193 | return true;
194 | }
195 |
196 | File canonDir = null;
197 | try {
198 | canonDir = directory.getCanonicalFile();
199 | } catch (IOException e) {
200 | return false;
201 | }
202 |
203 | File parentDir = canonDir.getParentFile();
204 | return (parentDir != null && (mkdirs(parentDir) || parentDir.exists()) && canonDir.mkdir());
205 | }
206 |
207 | public static File createTempDir() throws IOException {
208 | return createTempDir("");
209 | }
210 |
211 | public static File createTempDir(String suffix) throws IOException {
212 | mkdirs(TMP);
213 |
214 | File tmpFile = File.createTempFile("tmpdir-", suffix, TMP);
215 |
216 | delete(tmpFile);
217 | mkdirs(tmpFile);
218 |
219 | return tmpFile;
220 | }
221 |
222 | public static void read(Properties props, File file) throws IOException {
223 | FileInputStream fis = null;
224 | try {
225 | fis = new FileInputStream(file);
226 | props.load(fis);
227 | } finally {
228 | close(fis);
229 | }
230 | }
231 |
232 | public static void write(Properties props, File file) throws IOException {
233 | file.getParentFile().mkdirs();
234 |
235 | FileOutputStream fos = null;
236 | try {
237 | fos = new FileOutputStream(file);
238 | props.store(fos, "aether-test");
239 | } finally {
240 | close(fos);
241 | }
242 | }
243 |
244 | }
245 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/aether/localrepo/TrackingFileManagerTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2010, 2011 Sonatype, Inc.
3 | * All rights reserved. This program and the accompanying materials
4 | * are made available under the terms of the Eclipse Public License v1.0
5 | * which accompanies this distribution, and is available at
6 | * http://www.eclipse.org/legal/epl-v10.html
7 | *
8 | * Contributors:
9 | * Sonatype, Inc. - initial API and implementation
10 | *******************************************************************************/
11 | package io.takari.aether.localrepo;
12 |
13 | import static org.junit.Assert.assertEquals;
14 | import static org.junit.Assert.assertNotNull;
15 | import static org.junit.Assert.assertNull;
16 | import static org.junit.Assert.assertTrue;
17 |
18 | import java.io.File;
19 | import java.util.ArrayList;
20 | import java.util.Collections;
21 | import java.util.HashMap;
22 | import java.util.List;
23 | import java.util.Map;
24 | import java.util.Properties;
25 |
26 | import org.eclipse.aether.internal.test.util.TestFileUtils;
27 | import org.junit.Test;
28 |
29 | public class TrackingFileManagerTest {
30 |
31 | @Test
32 | public void testRead() throws Exception {
33 | TrackingFileManager tfm = new TrackingFileManager();
34 |
35 | File propFile = TestFileUtils.createTempFile("#COMMENT\nkey1=value1\nkey2 : value2");
36 | Properties props = tfm.read(propFile);
37 |
38 | assertNotNull(props);
39 | assertEquals(String.valueOf(props), 2, props.size());
40 | assertEquals("value1", props.get("key1"));
41 | assertEquals("value2", props.get("key2"));
42 |
43 | assertTrue("Leaked file: " + propFile, propFile.delete());
44 |
45 | props = tfm.read(propFile);
46 | assertNull(String.valueOf(props), props);
47 | }
48 |
49 | @Test
50 | public void testReadNoFileLeak() throws Exception {
51 | TrackingFileManager tfm = new TrackingFileManager();
52 |
53 | for (int i = 0; i < 1000; i++) {
54 | File propFile = TestFileUtils.createTempFile("#COMMENT\nkey1=value1\nkey2 : value2");
55 | assertNotNull(tfm.read(propFile));
56 | assertTrue("Leaked file: " + propFile, propFile.delete());
57 | }
58 | }
59 |
60 | @Test
61 | public void testUpdate() throws Exception {
62 | TrackingFileManager tfm = new TrackingFileManager();
63 |
64 | // NOTE: The excessive repetitions are to check the update properly truncates the file
65 | File propFile = TestFileUtils.createTempFile("key1=value1\nkey2 : value2\n".getBytes("UTF-8"), 1000);
66 |
67 | Map updates = new HashMap();
68 | updates.put("key1", "v");
69 | updates.put("key2", null);
70 |
71 | tfm.update(propFile, updates);
72 |
73 | Properties props = tfm.read(propFile);
74 |
75 | assertNotNull(props);
76 | assertEquals(String.valueOf(props), 1, props.size());
77 | assertEquals("v", props.get("key1"));
78 | assertNull(String.valueOf(props.get("key2")), props.get("key2"));
79 | }
80 |
81 | @Test
82 | public void testUpdateNoFileLeak() throws Exception {
83 | TrackingFileManager tfm = new TrackingFileManager();
84 |
85 | Map updates = new HashMap();
86 | updates.put("k", "v");
87 |
88 | for (int i = 0; i < 1000; i++) {
89 | File propFile = TestFileUtils.createTempFile("#COMMENT\nkey1=value1\nkey2 : value2");
90 | assertNotNull(tfm.update(propFile, updates));
91 | assertTrue("Leaked file: " + propFile, propFile.delete());
92 | }
93 | }
94 |
95 | @Test
96 | public void testLockingOnCanonicalPath() throws Exception {
97 | final TrackingFileManager tfm = new TrackingFileManager();
98 |
99 | final File propFile = TestFileUtils.createTempFile("#COMMENT\nkey1=value1\nkey2 : value2");
100 |
101 | final List errors = Collections.synchronizedList(new ArrayList());
102 |
103 | Thread[] threads = new Thread[4];
104 | for (int i = 0; i < threads.length; i++) {
105 | String path = propFile.getParent();
106 | for (int j = 0; j < i; j++) {
107 | path += "/.";
108 | }
109 | path += "/" + propFile.getName();
110 | final File file = new File(path);
111 |
112 | threads[i] = new Thread() {
113 | public void run() {
114 | try {
115 | for (int i = 0; i < 1000; i++) {
116 | assertNotNull(tfm.read(file));
117 | }
118 | } catch (Throwable e) {
119 | errors.add(e);
120 | }
121 | }
122 | };
123 | }
124 |
125 | for (int i = 0; i < threads.length; i++) {
126 | threads[i].start();
127 | }
128 |
129 | for (int i = 0; i < threads.length; i++) {
130 | threads[i].join();
131 | }
132 |
133 | assertEquals(Collections.emptyList(), errors);
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/aether/localrepo/its/TakariLocalRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package io.takari.aether.localrepo.its;
2 |
3 | import static org.junit.Assert.assertFalse;
4 | import io.takari.maven.testing.TestProperties;
5 | import io.takari.maven.testing.TestResources;
6 | import io.takari.maven.testing.executor.MavenExecution;
7 | import io.takari.maven.testing.executor.MavenExecutionResult;
8 | import io.takari.maven.testing.executor.MavenRuntime;
9 | import io.takari.maven.testing.executor.MavenRuntime.MavenRuntimeBuilder;
10 | import io.takari.maven.testing.executor.MavenVersions;
11 | import io.takari.maven.testing.executor.junit.MavenJUnitTestRunner;
12 |
13 | import java.io.File;
14 |
15 | import org.codehaus.plexus.util.FileUtils;
16 | import org.junit.Rule;
17 | import org.junit.Test;
18 | import org.junit.runner.RunWith;
19 |
20 | @RunWith(MavenJUnitTestRunner.class)
21 | @MavenVersions({"3.3.1", "3.3.3"})
22 | public class TakariLocalRepositoryTest {
23 |
24 | @Rule
25 | public final TestResources resources = new TestResources();
26 | public final TestProperties proprties = new TestProperties();
27 | public final MavenRuntime verifier;
28 | private String basedir;
29 |
30 | public TakariLocalRepositoryTest(MavenRuntimeBuilder runtimeBuilder) throws Exception {
31 | this.verifier = runtimeBuilder.withExtension(new File("target/classes").getCanonicalFile()) //
32 | .build();
33 | }
34 |
35 | @Test
36 | public void validateRetryOnDowloadErrorFlagIsFunctional() throws Exception {
37 | File localRepository = new File(getBasedir(), "target/local-repo");
38 | FileUtils.deleteDirectory(localRepository);
39 | File basedir = resources.getBasedir("basic-it");
40 | MavenExecution execution = verifier.forProject(basedir) //
41 | .withCliOptions(String.format("-Dmaven.repo.local=%s", localRepository.getAbsolutePath())) //
42 | .withCliOptions(String.format("-Dmaven.retryOnDownloadError=true"));
43 | MavenExecutionResult result = execution.execute("compile");
44 |
45 | result.assertLogText("Could not resolve dependencies for project io.takari.aether.localrepo.its:update-check:jar:0.1.0");
46 |
47 | File updateCheckFile = new File(localRepository, "io/takari/aether/localrepo/its/non-existent/1.0/non-existent-1.0.jar.lastUpdated");
48 | assertFalse(updateCheckFile.exists());
49 | }
50 |
51 | public final String getBasedir() {
52 | if (null == basedir) {
53 | basedir = System.getProperty("basedir", new File("").getAbsolutePath());
54 | }
55 | return basedir;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/filemanager/DefaultFileManagerTest.java:
--------------------------------------------------------------------------------
1 | package io.takari.filemanager;
2 |
3 | /*******************************************************************************
4 | * Copyright (c) 2010-2013 Sonatype, Inc.
5 | * All rights reserved. This program and the accompanying materials
6 | * are made available under the terms of the Eclipse Public License v1.0
7 | * which accompanies this distribution, and is available at
8 | * http://www.eclipse.org/legal/epl-v10.html
9 | *******************************************************************************/
10 |
11 | import static org.junit.Assert.*;
12 | import io.takari.filemanager.Lock;
13 | import io.takari.filemanager.internal.DefaultFileManager;
14 |
15 | import java.io.File;
16 | import java.io.IOException;
17 | import java.nio.channels.FileChannel;
18 | import java.util.concurrent.atomic.AtomicReference;
19 |
20 | import org.junit.Before;
21 | import org.junit.Test;
22 |
23 | import edu.umd.cs.mtc.MultithreadedTestCase;
24 | import edu.umd.cs.mtc.TestFramework;
25 |
26 | @SuppressWarnings("unused")
27 | public class DefaultFileManagerTest {
28 |
29 | private DefaultFileManager manager;
30 |
31 | private Process process;
32 |
33 | private DefaultFileManager newManager() {
34 | return new DefaultFileManager();
35 | }
36 |
37 | @Before
38 | public void setup() throws IOException {
39 | manager = newManager();
40 | }
41 |
42 | @Test
43 | public void testExternalLockTryReadLock() throws InterruptedException, IOException {
44 | int wait = 1500;
45 |
46 | File file = TestFileUtils.createTempFile("");
47 |
48 | Lock lock = manager.readLock(file);
49 |
50 | ExternalProcessFileLock ext = new ExternalProcessFileLock(file);
51 | process = ext.lockFile(wait);
52 |
53 | long start = ext.awaitLock();
54 |
55 | lock.lock();
56 |
57 | long end = System.currentTimeMillis();
58 |
59 | lock.unlock();
60 |
61 | String message = "expected " + wait + "ms wait, real delta: " + (end - start);
62 |
63 | assertTrue(message, end - start > wait);
64 | }
65 |
66 | @Test
67 | public void testExternalLockTryWriteLock() throws InterruptedException, IOException {
68 | int wait = 1500;
69 |
70 | File file = TestFileUtils.createTempFile("");
71 |
72 | ExternalProcessFileLock ext = new ExternalProcessFileLock(file);
73 | process = ext.lockFile(wait);
74 |
75 | Lock lock = manager.writeLock(file);
76 |
77 | long start = ext.awaitLock();
78 |
79 | lock.lock();
80 |
81 | long end = System.currentTimeMillis();
82 |
83 | lock.unlock();
84 |
85 | String message = "expected " + wait + "ms wait, real delta: " + (end - start);
86 | assertTrue(message, end - start > wait);
87 | }
88 |
89 | @Test
90 | public void testUpgradeSharedToExclusiveLock() throws Throwable {
91 | final File file = TestFileUtils.createTempFile("");
92 |
93 | TestFramework.runOnce(new MultithreadedTestCase() {
94 | public void thread1() throws IOException {
95 | Lock lock = manager.readLock(file);
96 | lock.lock();
97 | assertTrue("read lock is not shared", lock.isShared());
98 | waitForTick(2);
99 | lock.unlock();
100 | }
101 |
102 | public void thread2() throws IOException {
103 | waitForTick(1);
104 | Lock lock = manager.writeLock(file);
105 | lock.lock();
106 | assertTick(2);
107 | assertTrue("read lock did not upgrade to exclusive", !lock.isShared());
108 | lock.unlock();
109 | }
110 | });
111 | }
112 |
113 | @Test
114 | public void testCanonicalFileLock() throws Exception {
115 | File file1 = TestFileUtils.createTempFile("testCanonicalFileLock");
116 | File file2 = new File(file1.getParent() + File.separator + ".", file1.getName());
117 |
118 | Lock lock1 = manager.readLock(file1);
119 | Lock lock2 = manager.readLock(file2);
120 |
121 | lock1.lock();
122 | lock2.lock();
123 |
124 | FileChannel channel1 = lock1.getRandomAccessFile().getChannel();
125 | FileChannel channel2 = lock2.getRandomAccessFile().getChannel();
126 |
127 | assertNotSame(channel1, channel2);
128 | assertSame(lock1.getLock(), lock2.getLock());
129 |
130 | lock1.unlock();
131 | assertNull(lock1.getRandomAccessFile());
132 | assertFalse(channel1.isOpen());
133 |
134 | assertTrue(lock2.getLock().isValid());
135 | assertNotNull(lock2.getRandomAccessFile());
136 | assertTrue(channel2.isOpen());
137 |
138 | lock2.unlock();
139 | assertNull(lock2.getRandomAccessFile());
140 | assertFalse(channel2.isOpen());
141 | }
142 |
143 | @Test
144 | public void testSafeUnlockOfNonAcquiredLock() throws IOException {
145 | File file = TestFileUtils.createTempFile("");
146 |
147 | Lock lock = manager.readLock(file);
148 | lock.unlock();
149 | }
150 |
151 | @Test
152 | public void testMultipleLocksSameThread() throws Throwable {
153 | final File a = TestFileUtils.createTempFile("a");
154 | final File b = TestFileUtils.createTempFile("b");
155 |
156 | TestFramework.runOnce(new MultithreadedTestCase() {
157 | private Lock r1;
158 |
159 | private Lock r2;
160 |
161 | private Lock w1;
162 |
163 | private Lock w2;
164 |
165 | public void thread1() throws IOException {
166 | r1 = manager.readLock(a);
167 | r2 = manager.readLock(a);
168 | w1 = manager.writeLock(b);
169 | w2 = manager.writeLock(b);
170 | try {
171 |
172 | r1.lock();
173 | r2.lock();
174 | w1.lock();
175 | w2.lock();
176 |
177 | assertSame(r1.getLock(), r2.getLock());
178 | assertEquals(true, r1.getLock().isValid());
179 | assertEquals(true, r2.getLock().isValid());
180 |
181 | assertSame(w1.getLock(), w2.getLock());
182 | assertEquals(true, w1.getLock().isValid());
183 | assertEquals(true, w2.getLock().isValid());
184 |
185 | r1.unlock();
186 | assertEquals(true, r2.getLock().isValid());
187 | r2.unlock();
188 | w1.unlock();
189 | assertEquals(true, w2.getLock().isValid());
190 | w2.unlock();
191 | } finally {
192 | if (w1 != null) {
193 | w1.unlock();
194 | }
195 | if (w2 != null) {
196 | w2.unlock();
197 | }
198 | if (r1 != null) {
199 | r1.unlock();
200 | }
201 | if (r2 != null) {
202 | r2.unlock();
203 | }
204 | }
205 | }
206 |
207 | });
208 | }
209 |
210 | @Test
211 | public void testSameThreadMultipleLocksReadRead() throws Exception {
212 | File file = TestFileUtils.createTempFile("");
213 |
214 | Lock lock1 = manager.readLock(file);
215 | Lock lock2 = manager.readLock(file);
216 |
217 | lock1.lock();
218 | try {
219 | lock2.lock();
220 | lock2.unlock();
221 | } finally {
222 | lock1.unlock();
223 | }
224 | }
225 |
226 | @Test
227 | public void testSameThreadMultipleLocksWriteWrite() throws Exception {
228 | File file = TestFileUtils.createTempFile("");
229 |
230 | Lock lock1 = manager.writeLock(file);
231 | Lock lock2 = manager.writeLock(file);
232 |
233 | lock1.lock();
234 | try {
235 | lock2.lock();
236 | lock2.unlock();
237 | } finally {
238 | lock1.unlock();
239 | }
240 | }
241 |
242 | @Test
243 | public void testSameThreadMultipleLocksWriteRead() throws Exception {
244 | File file = TestFileUtils.createTempFile("");
245 |
246 | Lock lock1 = manager.writeLock(file);
247 | Lock lock2 = manager.readLock(file);
248 |
249 | lock1.lock();
250 | try {
251 | lock2.lock();
252 | lock2.unlock();
253 | } finally {
254 | lock1.unlock();
255 | }
256 | }
257 |
258 | @Test
259 | public void testSameThreadMultipleLocksReadWrite() throws Exception {
260 | File file = TestFileUtils.createTempFile("");
261 |
262 | Lock lock1 = manager.readLock(file);
263 | Lock lock2 = manager.writeLock(file);
264 |
265 | lock1.lock();
266 | try {
267 | try {
268 | lock2.lock();
269 | try {
270 | lock2.unlock();
271 | } catch (IOException e) {
272 | // ignored
273 | }
274 | } catch (IllegalStateException e) {
275 | assertTrue(true);
276 | }
277 | } finally {
278 | lock1.unlock();
279 | }
280 | }
281 |
282 | @Test
283 | public void testReentrantLock() throws Exception {
284 | File file = TestFileUtils.createTempFile("");
285 |
286 | Lock lock = manager.readLock(file);
287 | lock.lock();
288 | assertTrue(lock.isShared());
289 | lock.lock();
290 | assertTrue(lock.isShared());
291 | lock.unlock();
292 | assertTrue(lock.isShared());
293 | assertNotNull(lock.getRandomAccessFile());
294 | lock.unlock();
295 | assertNull(lock.getRandomAccessFile());
296 | }
297 |
298 | @Test
299 | public void testAcquiredLockDoesNotPreventLockedFileToBeDeleted() throws Exception {
300 | File file = TestFileUtils.createTempFile("");
301 |
302 | Lock lock = manager.writeLock(file);
303 | lock.lock();
304 | try {
305 | assertTrue(file.exists());
306 | assertTrue(file.delete());
307 | assertFalse(file.exists());
308 | } finally {
309 | lock.unlock();
310 | }
311 | }
312 |
313 | @Test
314 | public void testWaitingForAlreadyLockedFileToBeReleasedMustOnlyBlockCurrentThread() throws Exception {
315 | final File file1 = TestFileUtils.createTempFile("file1");
316 | final File file2 = TestFileUtils.createTempFile("file2");
317 |
318 | // external process locks files in opposite order than our process, i.e. file2 first
319 | ExternalProcessFileLocks external = new ExternalProcessFileLocks(file2, file1);
320 |
321 | final AtomicReference exception = new AtomicReference();
322 |
323 | // this thread will block when attempting to lock file2 which will already be locked by the external process
324 | Thread thread = new Thread() {
325 | @Override
326 | public void run() {
327 | Lock lock2 = manager.writeLock(file2);
328 | try {
329 | lock2.lock();
330 | lock2.unlock();
331 | } catch (IOException e) {
332 | e.printStackTrace();
333 | exception.set(e);
334 | }
335 | }
336 | };
337 |
338 | Lock lock1 = manager.writeLock(file1);
339 | lock1.lock();
340 | try {
341 | external.lockFiles();
342 | external.awaitLock1();
343 |
344 | thread.start();
345 |
346 | // wait a little to allow the thread to block
347 | long start = System.currentTimeMillis();
348 | while (System.currentTimeMillis() - start < 1000) {
349 | try {
350 | Thread.sleep(200);
351 | } catch (Exception e) {
352 | // irrelevant
353 | }
354 | }
355 |
356 | // this must not block or the inter-process deadlock is perfect
357 | lock1.unlock();
358 | } finally {
359 | lock1.unlock();
360 | }
361 | assertNull("inner thread got IOException: " + String.valueOf(exception.get()), exception.get());
362 | }
363 |
364 | @Test
365 | public void testMultipleManagerInstancesShareTheSameLockTable() throws Exception {
366 | File file = TestFileUtils.createTempFile("");
367 |
368 | DefaultFileManager manager2 = newManager();
369 |
370 | Lock lock1 = manager.readLock(file);
371 | Lock lock2 = manager2.readLock(file);
372 |
373 | lock1.lock();
374 | try {
375 | lock2.lock();
376 | try {
377 | assertSame(lock1.getLock(), lock2.getLock());
378 | } finally {
379 | lock2.unlock();
380 | }
381 | } finally {
382 | lock1.unlock();
383 | }
384 | }
385 |
386 | @Test
387 | public void testCopiedFilesHaveTheSameLastModifiedTime() throws Exception {
388 | File source = TestFileUtils.createTempFile("bytes");
389 | source.setLastModified(source.lastModified() - 86400000); // -1 day
390 | File target = new File(TestFileUtils.createTempDir(), "target.txt");
391 | DefaultFileManager manager = newManager();
392 | manager.copy(source, target);
393 | assertEquals("We expect the length of the source and target to be equal.", source.length(), target.length());
394 | assertEquals("We expect the last modified time of the source and target to be equal.", source.lastModified(), target.lastModified());
395 | }
396 | }
397 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/filemanager/ExternalProcessFileLock.java:
--------------------------------------------------------------------------------
1 | package io.takari.filemanager;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.io.RandomAccessFile;
6 | import java.nio.channels.FileLock;
7 |
8 | /**
9 | * @author Benjamin Hanzelmann
10 | */
11 | public class ExternalProcessFileLock {
12 |
13 | private final File file;
14 |
15 | public static void main(String[] args) throws Exception {
16 | String path = args[0];
17 | String time = args[1];
18 |
19 | File file = new File(path + ".aetherlock");
20 |
21 | file.getParentFile().mkdirs();
22 |
23 | int millis = Integer.valueOf(time);
24 |
25 | RandomAccessFile raf = new RandomAccessFile(file, "rw");
26 | FileLock lock = raf.getChannel().lock();
27 |
28 | File touchFile = getTouchFile(path);
29 | touchFile.createNewFile();
30 |
31 | for (long start = System.currentTimeMillis(); System.currentTimeMillis() - start < 5 * 1000 && touchFile.exists();) {
32 | try {
33 | Thread.sleep(10);
34 | } catch (InterruptedException e) {
35 | // ignored
36 | }
37 | }
38 |
39 | long start = System.currentTimeMillis();
40 | while (System.currentTimeMillis() - start < millis) {
41 | Thread.sleep(millis / 10 + 1);
42 | }
43 |
44 | lock.release();
45 | raf.close();
46 | }
47 |
48 | public ExternalProcessFileLock(File file) {
49 | this.file = file;
50 | }
51 |
52 | private static File getTouchFile(String path) {
53 | return new File(path + ".touch");
54 | }
55 |
56 | public Process lockFile(int wait) throws InterruptedException, IOException {
57 | ForkJvm jvm = new ForkJvm();
58 | jvm.addClassPathEntry(getClass());
59 | jvm.setParameters(file.getAbsolutePath(), String.valueOf(wait));
60 | Process p = jvm.run(getClass().getName());
61 | p.getOutputStream().close();
62 | return p;
63 | }
64 |
65 | public long awaitLock() {
66 | File touchFile = getTouchFile(file.getAbsolutePath());
67 |
68 | for (long start = System.currentTimeMillis(); System.currentTimeMillis() - start < 10 * 1000;) {
69 | if (touchFile.exists()) {
70 | long now = System.currentTimeMillis();
71 | touchFile.delete();
72 | return now;
73 | }
74 | try {
75 | Thread.sleep(10);
76 | } catch (InterruptedException e) {
77 | // ignored
78 | }
79 | }
80 |
81 | throw new IllegalStateException("External lock on " + file + " wasn't aquired in time");
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/filemanager/ExternalProcessFileLocks.java:
--------------------------------------------------------------------------------
1 | package io.takari.filemanager;
2 |
3 | /*******************************************************************************
4 | * Copyright (c) 2010-2013 Sonatype, Inc.
5 | * All rights reserved. This program and the accompanying materials
6 | * are made available under the terms of the Eclipse Public License v1.0
7 | * which accompanies this distribution, and is available at
8 | * http://www.eclipse.org/legal/epl-v10.html
9 | *******************************************************************************/
10 |
11 | import java.io.File;
12 | import java.io.IOException;
13 | import java.io.RandomAccessFile;
14 | import java.nio.channels.FileLock;
15 |
16 | /**
17 | * Locks two files at once in a forked JVM.
18 | *
19 | * @author Benjamin Bentmann
20 | */
21 | public class ExternalProcessFileLocks {
22 |
23 | private final File file1;
24 |
25 | private final File file2;
26 |
27 | public static void main(String[] args) throws Exception {
28 | String path1 = args[0];
29 | String path2 = args[1];
30 |
31 | File file1 = new File(path1 + ".aetherlock");
32 | File file2 = new File(path2 + ".aetherlock");
33 |
34 | file1.getParentFile().mkdirs();
35 | file2.getParentFile().mkdirs();
36 |
37 | // lock first file
38 | RandomAccessFile raf1 = new RandomAccessFile(file1, "rw");
39 | FileLock lock1 = raf1.getChannel().lock();
40 |
41 | // signal acquisition of first lock to parent process
42 | File touchFile = getTouchFile(path1);
43 | touchFile.createNewFile();
44 |
45 | // lock second file
46 | RandomAccessFile raf2 = new RandomAccessFile(file2, "rw");
47 | FileLock lock2 = raf2.getChannel().lock();
48 |
49 | lock1.release();
50 | raf1.close();
51 |
52 | lock2.release();
53 | raf2.close();
54 | }
55 |
56 | public ExternalProcessFileLocks(File file1, File file2) {
57 | this.file1 = file1;
58 | this.file2 = file2;
59 | }
60 |
61 | private static File getTouchFile(String path) {
62 | return new File(path + ".touch");
63 | }
64 |
65 | public Process lockFiles() throws InterruptedException, IOException {
66 | ForkJvm jvm = new ForkJvm();
67 | jvm.addClassPathEntry(getClass());
68 | jvm.setParameters(file1.getAbsolutePath(), file2.getAbsolutePath());
69 | Process p = jvm.run(getClass().getName());
70 | p.getOutputStream().close();
71 | return p;
72 | }
73 |
74 | public long awaitLock1() {
75 | File touchFile = getTouchFile(file1.getAbsolutePath());
76 |
77 | for (long start = System.currentTimeMillis(); System.currentTimeMillis() - start < 10 * 1000;) {
78 | if (touchFile.exists()) {
79 | long now = System.currentTimeMillis();
80 | touchFile.delete();
81 | return now;
82 | }
83 | try {
84 | Thread.sleep(10);
85 | } catch (InterruptedException e) {
86 | // ignored
87 | }
88 | }
89 |
90 | throw new IllegalStateException("External lock on " + file1 + " wasn't aquired in time");
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/filemanager/ForkJvm.java:
--------------------------------------------------------------------------------
1 | package io.takari.filemanager;
2 |
3 | /*******************************************************************************
4 | * Copyright (c) 2010-2013 Sonatype, Inc.
5 | * All rights reserved. This program and the accompanying materials
6 | * are made available under the terms of the Eclipse Public License v1.0
7 | * which accompanies this distribution, and is available at
8 | * http://www.eclipse.org/legal/epl-v10.html
9 | *******************************************************************************/
10 |
11 | import java.io.BufferedReader;
12 | import java.io.File;
13 | import java.io.IOException;
14 | import java.io.InputStreamReader;
15 | import java.io.UnsupportedEncodingException;
16 | import java.net.URL;
17 | import java.net.URLDecoder;
18 | import java.util.ArrayList;
19 | import java.util.Arrays;
20 | import java.util.LinkedList;
21 | import java.util.List;
22 |
23 | /**
24 | * @author Benjamin Hanzelmann
25 | */
26 | public class ForkJvm {
27 |
28 | private List classPathEntries = new ArrayList(4);
29 |
30 | private File workingDirectory = new File(".");
31 |
32 | private List parameters = new LinkedList();
33 |
34 | public void addParameter(String parameter) {
35 | parameters.add(parameter);
36 | }
37 |
38 | /**
39 | * Adds the source JAR of the specified class/interface to the class path of the forked JVM.
40 | *
41 | * @param type The class/interface to add, may be null
.
42 | */
43 | public void addClassPathEntry(Class> type) {
44 | addClassPathEntry(getClassSource(type));
45 | }
46 |
47 | /**
48 | * Adds the specified path to the class path of the forked JVM.
49 | *
50 | * @param path The path to add, may be null
.
51 | */
52 | public void addClassPathEntry(String path) {
53 | if (path != null) {
54 | this.classPathEntries.add(path);
55 | }
56 | }
57 |
58 | /**
59 | * Adds the specified path to the class path of the forked JVM.
60 | *
61 | * @param path The path to add, may be null
.
62 | */
63 | public void addClassPathEntry(File path) {
64 | if (path != null) {
65 | this.classPathEntries.add(path.getAbsolutePath());
66 | }
67 | }
68 |
69 | /**
70 | * Gets the JAR file or directory that contains the specified class.
71 | *
72 | * @param type The class/interface to find, may be null
.
73 | * @return The absolute path to the class source location or null
if unknown.
74 | */
75 | private static File getClassSource(Class> type) {
76 | if (type != null) {
77 | String classResource = type.getName().replace('.', '/') + ".class";
78 | return getResourceSource(classResource, type.getClassLoader());
79 | }
80 | return null;
81 | }
82 |
83 | /**
84 | * Gets the JAR file or directory that contains the specified resource.
85 | *
86 | * @param resource The absolute name of the resource to find, may be null
.
87 | * @param loader The class loader to use for searching the resource, may be null
.
88 | * @return The absolute path to the resource location or null
if unknown.
89 | */
90 | private static File getResourceSource(String resource, ClassLoader loader) {
91 | if (resource != null) {
92 | URL url;
93 | if (loader != null) {
94 | url = loader.getResource(resource);
95 | } else {
96 | url = ClassLoader.getSystemResource(resource);
97 | }
98 | return getResourceRoot(url, resource);
99 | }
100 | return null;
101 | }
102 |
103 | private static File getResourceRoot(URL url, String resource) {
104 | String str = url.getPath();
105 | str = str.replace(resource, "");
106 | try {
107 | str = URLDecoder.decode(str, "UTF-8");
108 | } catch (UnsupportedEncodingException e) {
109 | throw new IllegalStateException("JVM broken", e);
110 | }
111 | return new File(str);
112 | }
113 |
114 | public Process run(String mainClass) throws IOException, InterruptedException {
115 | List cmd = new LinkedList();
116 | cmd.add(getDefaultExecutable());
117 |
118 | cmd.add("-cp");
119 | StringBuilder classpath = new StringBuilder();
120 | for (int i = 0; i < classPathEntries.size(); i++) {
121 | if (i != 0) {
122 | classpath.append(File.pathSeparator);
123 | }
124 | classpath.append(classPathEntries.get(i));
125 | }
126 | cmd.add(classpath.toString());
127 |
128 | cmd.add(mainClass);
129 |
130 | cmd.addAll(parameters);
131 |
132 | ProcessBuilder builder = new ProcessBuilder(cmd);
133 | builder.directory(workingDirectory);
134 | builder.redirectErrorStream(true);
135 | Process process = builder.start();
136 |
137 | return process;
138 |
139 | }
140 |
141 | /**
142 | * Gets the absolute path to the JVM executable.
143 | *
144 | * @return The absolute path to the JVM executable.
145 | */
146 | private static String getDefaultExecutable() {
147 | return System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
148 | }
149 |
150 | public void setWorkingDirectory(File workingDirectory) {
151 | this.workingDirectory = workingDirectory;
152 | }
153 |
154 | public void setParameters(String... parameters) {
155 | this.parameters = Arrays.asList(parameters);
156 | }
157 |
158 | public static void flush(Process p) throws IOException {
159 | BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
160 | String line;
161 | while ((line = r.readLine()) != null) {
162 | System.out.println(line);
163 | }
164 | r.close();
165 | }
166 |
167 | }
168 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/filemanager/MultipleThreadsLockManagerTest.java:
--------------------------------------------------------------------------------
1 | package io.takari.filemanager;
2 |
3 | /*******************************************************************************
4 | * Copyright (c) 2010-2013 Sonatype, Inc.
5 | * All rights reserved. This program and the accompanying materials
6 | * are made available under the terms of the Eclipse Public License v1.0
7 | * which accompanies this distribution, and is available at
8 | * http://www.eclipse.org/legal/epl-v10.html
9 | *******************************************************************************/
10 |
11 | import io.takari.filemanager.Lock;
12 | import io.takari.filemanager.internal.DefaultFileManager;
13 |
14 | import java.io.File;
15 | import java.io.IOException;
16 |
17 | import org.junit.After;
18 | import org.junit.Before;
19 | import org.junit.Test;
20 |
21 | import edu.umd.cs.mtc.MultithreadedTestCase;
22 | import edu.umd.cs.mtc.TestFramework;
23 |
24 | @SuppressWarnings("unused")
25 | public class MultipleThreadsLockManagerTest {
26 | private DefaultFileManager manager;
27 |
28 | private File dir;
29 |
30 | @Before
31 | public void setup() throws IOException {
32 | manager = new DefaultFileManager();
33 | dir = TestFileUtils.createTempDir(getClass().getSimpleName());
34 | }
35 |
36 | @After
37 | public void tearDown() throws Exception {
38 | if (dir != null) {
39 | TestFileUtils.delete(dir);
40 | }
41 | manager = null;
42 | }
43 |
44 | @Test
45 | public void testLockCanonicalFile() throws Throwable {
46 | final File a = new File(dir, "a/b");
47 | final File b = new File(dir, "a/./b");
48 |
49 | TestFramework.runOnce(new MultithreadedTestCase() {
50 | public void thread1() throws IOException {
51 | Lock lock = manager.writeLock(a);
52 | lock.lock();
53 | waitForTick(3);
54 | lock.unlock();
55 | }
56 |
57 | public void thread2() throws IOException {
58 | waitForTick(1);
59 | Lock lock = manager.writeLock(b);
60 | lock.lock();
61 | assertTick(3);
62 | lock.unlock();
63 | }
64 | });
65 | }
66 |
67 | @Test
68 | public void testWriteBlocksRead() throws Throwable {
69 | final File a = new File(dir, "a/b");
70 | final File b = new File(dir, "a/b");
71 |
72 | TestFramework.runOnce(new MultithreadedTestCase() {
73 | public void thread1() throws IOException {
74 | Lock lock = manager.writeLock(a);
75 | lock.lock();
76 | waitForTick(2);
77 | lock.unlock();
78 | }
79 |
80 | public void thread2() throws IOException {
81 | waitForTick(1);
82 | Lock lock = manager.readLock(b);
83 | lock.lock();
84 | assertTick(2);
85 | lock.unlock();
86 | }
87 | });
88 | }
89 |
90 | @Test
91 | public void testReadDoesNotBlockRead() throws Throwable {
92 | final File a = new File(dir, "a/b");
93 | final File b = new File(dir, "a/b");
94 |
95 | TestFramework.runOnce(new MultithreadedTestCase() {
96 | public void thread1() throws IOException {
97 | Lock lock = manager.readLock(a);
98 | lock.lock();
99 | waitForTick(2);
100 | lock.unlock();
101 | }
102 |
103 | public void thread2() throws IOException {
104 | waitForTick(1);
105 | Lock lock = manager.readLock(b);
106 | lock.lock();
107 | assertTick(1);
108 | lock.unlock();
109 | }
110 | });
111 | }
112 |
113 | @Test
114 | public void testReadBlocksWrite() throws Throwable {
115 | final File a = new File(dir, "a/b");
116 | final File b = new File(dir, "a/b");
117 |
118 | TestFramework.runOnce(new MultithreadedTestCase() {
119 | public void thread1() throws IOException {
120 | Lock lock = manager.readLock(a);
121 | lock.lock();
122 | waitForTick(2);
123 | lock.unlock();
124 | }
125 |
126 | public void thread2() throws IOException {
127 | waitForTick(1);
128 | Lock lock = manager.writeLock(b);
129 | lock.lock();
130 | assertTick(2);
131 | lock.unlock();
132 | }
133 | });
134 | }
135 |
136 | @Test
137 | public void testWriteBlocksWrite() throws Throwable {
138 | final File a = new File(dir, "a/b");
139 | final File b = new File(dir, "a/b");
140 |
141 | TestFramework.runOnce(new MultithreadedTestCase() {
142 | public void thread1() throws IOException {
143 | Lock lock = manager.writeLock(a);
144 | lock.lock();
145 | waitForTick(2);
146 | lock.unlock();
147 | }
148 |
149 | public void thread2() throws IOException {
150 | waitForTick(1);
151 | Lock lock = manager.writeLock(b);
152 | lock.lock();
153 | assertTick(2);
154 | lock.unlock();
155 | }
156 | });
157 | }
158 |
159 | @Test
160 | public void testNoPrematureLocking() throws Throwable {
161 | final File a = new File(dir, "a/b");
162 |
163 | TestFramework.runOnce(new MultithreadedTestCase() {
164 | public void thread1() throws IOException {
165 | Lock lock = manager.readLock(a);
166 | waitForTick(2);
167 | }
168 |
169 | public void thread2() throws IOException {
170 | waitForTick(1);
171 | Lock lock = manager.writeLock(a);
172 | lock.lock();
173 | assertTick(1);
174 | lock.unlock();
175 | }
176 | });
177 | }
178 |
179 | }
180 |
--------------------------------------------------------------------------------
/src/test/java/io/takari/filemanager/TestFileUtils.java:
--------------------------------------------------------------------------------
1 | package io.takari.filemanager;
2 |
3 | /*******************************************************************************
4 | * Copyright (c) 2010, 2013 Sonatype, Inc.
5 | * All rights reserved. This program and the accompanying materials
6 | * are made available under the terms of the Eclipse Public License v1.0
7 | * which accompanies this distribution, and is available at
8 | * http://www.eclipse.org/legal/epl-v10.html
9 | *
10 | * Contributors:
11 | * Sonatype, Inc. - initial API and implementation
12 | *******************************************************************************/
13 |
14 | import java.io.BufferedOutputStream;
15 | import java.io.Closeable;
16 | import java.io.File;
17 | import java.io.FileInputStream;
18 | import java.io.FileOutputStream;
19 | import java.io.IOException;
20 | import java.io.OutputStream;
21 | import java.io.RandomAccessFile;
22 | import java.util.ArrayList;
23 | import java.util.Collection;
24 | import java.util.Properties;
25 | import java.util.UUID;
26 |
27 | import org.junit.Assert;
28 |
29 | public class TestFileUtils {
30 |
31 | private static final File TMP = new File(System.getProperty("java.io.tmpdir"), "aether-" + UUID.randomUUID().toString().substring(0, 8));
32 |
33 | static {
34 | Runtime.getRuntime().addShutdownHook(new Thread() {
35 | @Override
36 | public void run() {
37 | try {
38 | delete(TMP);
39 | } catch (IOException e) {
40 | e.printStackTrace();
41 | }
42 | }
43 | });
44 | }
45 |
46 | private TestFileUtils() {
47 | // hide constructor
48 | }
49 |
50 | public static void deleteTempFiles() throws IOException {
51 | delete(TMP);
52 | }
53 |
54 | public static File createTempFile(String contents) throws IOException {
55 | return createTempFile(contents.getBytes("UTF-8"), 1);
56 | }
57 |
58 | public static File createTempFile(byte[] pattern, int repeat) throws IOException {
59 | mkdirs(TMP);
60 | File tmpFile = File.createTempFile("tmpfile-", ".data", TMP);
61 | write(pattern, repeat, tmpFile);
62 |
63 | return tmpFile;
64 | }
65 |
66 | public static void write(String content, File file) throws IOException {
67 | write(content.getBytes("UTF-8"), 1, file);
68 | }
69 |
70 | public static void write(byte[] pattern, int repeat, File file) throws IOException {
71 | file.deleteOnExit();
72 | file.getParentFile().mkdirs();
73 | OutputStream out = null;
74 | try {
75 | out = new BufferedOutputStream(new FileOutputStream(file));
76 | for (int i = 0; i < repeat; i++) {
77 | out.write(pattern);
78 | }
79 | } finally {
80 | close(out);
81 | }
82 | }
83 |
84 | public static long copy(File source, File target) throws IOException {
85 | long total = 0;
86 |
87 | FileInputStream fis = null;
88 | OutputStream fos = null;
89 | try {
90 | fis = new FileInputStream(source);
91 |
92 | mkdirs(target.getParentFile());
93 |
94 | fos = new BufferedOutputStream(new FileOutputStream(target));
95 |
96 | for (byte[] buffer = new byte[1024 * 32];;) {
97 | int bytes = fis.read(buffer);
98 | if (bytes < 0) {
99 | break;
100 | }
101 |
102 | fos.write(buffer, 0, bytes);
103 |
104 | total += bytes;
105 | }
106 | } finally {
107 | close(fis);
108 | close(fos);
109 | }
110 |
111 | return total;
112 | }
113 |
114 | private static void close(Closeable c) throws IOException {
115 | if (c != null) {
116 | try {
117 | c.close();
118 | } catch (IOException e) {
119 | // ignore
120 | }
121 | }
122 | }
123 |
124 | public static void delete(File file) throws IOException {
125 | if (file == null) {
126 | return;
127 | }
128 |
129 | Collection undeletables = new ArrayList();
130 |
131 | delete(file, undeletables);
132 |
133 | if (!undeletables.isEmpty()) {
134 | throw new IOException("Failed to delete " + undeletables);
135 | }
136 | }
137 |
138 | private static void delete(File file, Collection undeletables) {
139 | String[] children = file.list();
140 | if (children != null) {
141 | for (String child : children) {
142 | delete(new File(file, child), undeletables);
143 | }
144 | }
145 |
146 | if (!del(file)) {
147 | undeletables.add(file.getAbsoluteFile());
148 | }
149 | }
150 |
151 | private static boolean del(File file) {
152 | for (int i = 0; i < 10; i++) {
153 | if (file.delete() || !file.exists()) {
154 | return true;
155 | }
156 | }
157 | return false;
158 | }
159 |
160 | public static byte[] getContent(File file) throws IOException {
161 | RandomAccessFile in = null;
162 | try {
163 | in = new RandomAccessFile(file, "r");
164 | byte[] actual = new byte[(int) in.length()];
165 | in.readFully(actual);
166 | return actual;
167 | } finally {
168 | close(in);
169 | }
170 | }
171 |
172 | public static void assertContent(byte[] expected, File file) throws IOException {
173 | Assert.assertArrayEquals(expected, getContent(file));
174 | }
175 |
176 | public static void assertContent(String expected, File file) throws IOException {
177 | byte[] content = getContent(file);
178 | String msg = new String(content, "UTF-8");
179 | if (msg.length() > 10) {
180 | msg = msg.substring(0, 10) + "...";
181 | }
182 | Assert.assertArrayEquals("content was '" + msg + "'\n", expected.getBytes("UTF-8"), content);
183 | }
184 |
185 | public static boolean mkdirs(File directory) {
186 | if (directory == null) {
187 | return false;
188 | }
189 |
190 | if (directory.exists()) {
191 | return false;
192 | }
193 | if (directory.mkdir()) {
194 | return true;
195 | }
196 |
197 | File canonDir = null;
198 | try {
199 | canonDir = directory.getCanonicalFile();
200 | } catch (IOException e) {
201 | return false;
202 | }
203 |
204 | File parentDir = canonDir.getParentFile();
205 | return (parentDir != null && (mkdirs(parentDir) || parentDir.exists()) && canonDir.mkdir());
206 | }
207 |
208 | public static File createTempDir() throws IOException {
209 | return createTempDir("");
210 | }
211 |
212 | public static File createTempDir(String suffix) throws IOException {
213 | mkdirs(TMP);
214 |
215 | File tmpFile = File.createTempFile("tmpdir-", suffix, TMP);
216 |
217 | delete(tmpFile);
218 | mkdirs(tmpFile);
219 |
220 | return tmpFile;
221 | }
222 |
223 | public static void read(Properties props, File file) throws IOException {
224 | FileInputStream fis = null;
225 | try {
226 | fis = new FileInputStream(file);
227 | props.load(fis);
228 | } finally {
229 | close(fis);
230 | }
231 | }
232 |
233 | public static void write(Properties props, File file) throws IOException {
234 | file.getParentFile().mkdirs();
235 |
236 | FileOutputStream fos = null;
237 | try {
238 | fos = new FileOutputStream(file);
239 | props.store(fos, "aether-test");
240 | } finally {
241 | close(fos);
242 | }
243 | }
244 |
245 | }
246 |
--------------------------------------------------------------------------------
/src/test/projects/basic-it/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | io.takari.aether.localrepo.its
4 | update-check
5 | 0.1.0
6 |
7 |
8 |
9 | io.takari.aether.localrepo.its
10 | non-existent
11 | 1.0
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------