View Javadoc
1   package org.eclipse.aether.internal.impl;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   * 
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   * 
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  
25  import java.io.ByteArrayInputStream;
26  import java.io.ByteArrayOutputStream;
27  import java.io.Closeable;
28  import java.io.File;
29  import java.io.FileInputStream;
30  import java.io.IOException;
31  import java.io.RandomAccessFile;
32  import java.nio.channels.FileChannel;
33  import java.nio.channels.FileLock;
34  import java.nio.channels.OverlappingFileLockException;
35  import java.util.Map;
36  import java.util.Properties;
37  
38  /**
39   * Manages potentially concurrent accesses to a properties file.
40   */
41  class TrackingFileManager
42  {
43  
44      private static final Logger LOGGER = LoggerFactory.getLogger( TrackingFileManager.class );
45  
46      public Properties read( File file )
47      {
48          synchronized ( getLock( file ) )
49          {
50              FileLock lock = null;
51              FileInputStream stream = null;
52              try
53              {
54                  if ( !file.exists() )
55                  {
56                      return null;
57                  }
58  
59                  stream = new FileInputStream( file );
60  
61                  lock = lock( stream.getChannel(), Math.max( 1, file.length() ), true );
62  
63                  Properties props = new Properties();
64                  props.load( stream );
65  
66                  return props;
67              }
68              catch ( IOException e )
69              {
70                  LOGGER.warn( "Failed to read tracking file {}", file, e );
71              }
72              finally
73              {
74                  release( lock, file );
75                  close( stream, file );
76              }
77          }
78  
79          return null;
80      }
81  
82      public Properties update( File file, Map<String, String> updates )
83      {
84          Properties props = new Properties();
85  
86          synchronized ( getLock( file ) )
87          {
88              File directory = file.getParentFile();
89              if ( !directory.mkdirs() && !directory.exists() )
90              {
91                  LOGGER.warn( "Failed to create parent directories for tracking file {}", file );
92                  return props;
93              }
94  
95              RandomAccessFile raf = null;
96              FileLock lock = null;
97              try
98              {
99                  raf = new RandomAccessFile( file, "rw" );
100                 lock = lock( raf.getChannel(), Math.max( 1, raf.length() ), false );
101 
102                 if ( file.canRead() )
103                 {
104                     byte[] buffer = new byte[(int) raf.length()];
105 
106                     raf.readFully( buffer );
107 
108                     ByteArrayInputStream stream = new ByteArrayInputStream( buffer );
109 
110                     props.load( stream );
111                 }
112 
113                 for ( Map.Entry<String, String> update : updates.entrySet() )
114                 {
115                     if ( update.getValue() == null )
116                     {
117                         props.remove( update.getKey() );
118                     }
119                     else
120                     {
121                         props.setProperty( update.getKey(), update.getValue() );
122                     }
123                 }
124 
125                 ByteArrayOutputStream stream = new ByteArrayOutputStream( 1024 * 2 );
126 
127                 LOGGER.debug( "Writing tracking file {}", file );
128                 props.store( stream, "NOTE: This is a Maven Resolver internal implementation file"
129                     + ", its format can be changed without prior notice." );
130 
131                 raf.seek( 0 );
132                 raf.write( stream.toByteArray() );
133                 raf.setLength( raf.getFilePointer() );
134             }
135             catch ( IOException e )
136             {
137                 LOGGER.warn( "Failed to write tracking file {}", file, e );
138             }
139             finally
140             {
141                 release( lock, file );
142                 close( raf, file );
143             }
144         }
145 
146         return props;
147     }
148 
149     private void release( FileLock lock, File file )
150     {
151         if ( lock != null )
152         {
153             try
154             {
155                 lock.release();
156             }
157             catch ( IOException e )
158             {
159                 LOGGER.warn( "Error releasing lock for tracking file {}", file, e );
160             }
161         }
162     }
163 
164     private void close( Closeable closeable, File file )
165     {
166         if ( closeable != null )
167         {
168             try
169             {
170                 closeable.close();
171             }
172             catch ( IOException e )
173             {
174                 LOGGER.warn( "Error closing tracking file {}", file, e );
175             }
176         }
177     }
178 
179     private Object getLock( File file )
180     {
181         /*
182          * NOTE: Locks held by one JVM must not overlap and using the canonical path is our best bet, still another
183          * piece of code might have locked the same file (unlikely though) or the canonical path fails to capture file
184          * identity sufficiently as is the case with Java 1.6 and symlinks on Windows.
185          */
186         try
187         {
188             return file.getCanonicalPath().intern();
189         }
190         catch ( IOException e )
191         {
192             LOGGER.warn( "Failed to canonicalize path {}: {}", file, e.getMessage() );
193             return file.getAbsolutePath().intern();
194         }
195     }
196 
197     private FileLock lock( FileChannel channel, long size, boolean shared )
198         throws IOException
199     {
200         FileLock lock = null;
201 
202         for ( int attempts = 8; attempts >= 0; attempts-- )
203         {
204             try
205             {
206                 lock = channel.lock( 0, size, shared );
207                 break;
208             }
209             catch ( OverlappingFileLockException e )
210             {
211                 if ( attempts <= 0 )
212                 {
213                     throw (IOException) new IOException().initCause( e );
214                 }
215                 try
216                 {
217                     Thread.sleep( 50L );
218                 }
219                 catch ( InterruptedException e1 )
220                 {
221                     Thread.currentThread().interrupt();
222                 }
223             }
224         }
225 
226         if ( lock == null )
227         {
228             throw new IOException( "Could not lock file" );
229         }
230 
231         return lock;
232     }
233 
234 }