View Javadoc
1   package org.apache.maven.index.reader;
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.apache.maven.index.reader.WritableResourceHandler.WritableResource;
23  
24  import java.io.Closeable;
25  import java.io.IOException;
26  import java.text.ParseException;
27  import java.util.Date;
28  import java.util.Iterator;
29  import java.util.Map;
30  import java.util.Properties;
31  import java.util.UUID;
32  
33  import static org.apache.maven.index.reader.Utils.loadProperties;
34  import static org.apache.maven.index.reader.Utils.storeProperties;
35  
36  /**
37   * Maven 2 Index writer that writes chunk and maintains published property file.
38   * <p/>
39   * <strong>Currently no incremental update is supported, as the deleteion states should be maintained by
40   * caller</strong>. Hence, this writer will always produce the "main" chunk only.
41   *
42   * @since 5.1.2
43   */
44  public class IndexWriter
45      implements Closeable
46  {
47      private static final int INDEX_V1 = 1;
48  
49      private final WritableResourceHandler local;
50  
51      private final Properties localIndexProperties;
52  
53      private final boolean incremental;
54  
55      private final String nextChunkCounter;
56  
57      private final String nextChunkName;
58  
59      public IndexWriter( final WritableResourceHandler local, final String indexId, final boolean incrementalSupported )
60          throws IOException
61      {
62          if ( local == null )
63          {
64              throw new NullPointerException( "local resource handler null" );
65          }
66          if ( indexId == null )
67          {
68              throw new NullPointerException( "indexId null" );
69          }
70          this.local = local;
71          Properties indexProperties = loadProperties( local.locate( Utils.INDEX_FILE_PREFIX + ".properties" ) );
72          if ( incrementalSupported && indexProperties != null )
73          {
74              this.localIndexProperties = indexProperties;
75              // existing index, this is incremental publish, and we will add new chunk
76              String localIndexId = localIndexProperties.getProperty( "nexus.index.id" );
77              if ( localIndexId == null || !localIndexId.equals( indexId ) )
78              {
79                  throw new IllegalArgumentException(
80                      "index already exists and indexId mismatch or unreadable: " + localIndexId + ", " + indexId );
81              }
82              this.incremental = true;
83              this.nextChunkCounter = calculateNextChunkCounter();
84              this.nextChunkName = Utils.INDEX_FILE_PREFIX + "." + nextChunkCounter + ".gz";
85          }
86          else
87          {
88              // non-existing index, create published index from scratch
89              this.localIndexProperties = new Properties();
90              this.localIndexProperties.setProperty( "nexus.index.id", indexId );
91              this.localIndexProperties.setProperty( "nexus.index.chain-id", UUID.randomUUID().toString() );
92              this.incremental = false;
93              this.nextChunkCounter = null;
94              this.nextChunkName = Utils.INDEX_FILE_PREFIX + ".gz";
95          }
96      }
97  
98      /**
99       * Returns the index context ID that published index has set.
100      */
101     public String getIndexId()
102     {
103         return localIndexProperties.getProperty( "nexus.index.id" );
104     }
105 
106     /**
107      * Returns the {@link Date} when index was last published or {@code null} if this is first publishing. In other
108      * words,returns {@code null} when {@link #isIncremental()} returns {@code false}. After this writer is closed, the
109      * return value is updated to "now" (in {@link #close() method}.
110      */
111     public Date getPublishedTimestamp()
112     {
113         try
114         {
115             String timestamp = localIndexProperties.getProperty( "nexus.index.timestamp" );
116             if ( timestamp != null )
117             {
118                 return Utils.INDEX_DATE_FORMAT.parse( timestamp );
119             }
120             return null;
121         }
122         catch ( ParseException e )
123         {
124             throw new RuntimeException( "Corrupt date", e );
125         }
126     }
127 
128     /**
129      * Returns {@code true} if incremental publish is about to happen.
130      */
131     public boolean isIncremental()
132     {
133         return incremental;
134     }
135 
136     /**
137      * Returns the chain id of published index. If {@link #isIncremental()} is {@code false}, this is the newly
138      * generated chain ID.
139      */
140     public String getChainId()
141     {
142         return localIndexProperties.getProperty( "nexus.index.chain-id" );
143     }
144 
145     /**
146      * Returns the next chunk name about to be published.
147      */
148     public String getNextChunkName()
149     {
150         return nextChunkName;
151     }
152 
153     /**
154      * Writes out the record iterator and returns the written record count.
155      */
156     public int writeChunk( final Iterator<Map<String, String>> iterator )
157         throws IOException
158     {
159         int written;
160 
161         try ( WritableResource writableResource = local.locate( nextChunkName ) )
162         {
163             final ChunkWriter chunkWriter =
164                 new ChunkWriter( nextChunkName, writableResource.write(), INDEX_V1, new Date() );
165             try
166             {
167                 written = chunkWriter.writeChunk( iterator );
168             }
169             finally
170             {
171                 chunkWriter.close();
172             }
173             if ( incremental )
174             {
175                 // TODO: update main gz file
176             }
177             return written;
178         }
179     }
180 
181     /**
182      * Closes the underlying {@link ResourceHandler} and synchronizes published index properties, so remote clients
183      * becomes able to consume newly published index. If sync is not desired (ie. due to aborted publish), then this
184      * method should NOT be invoked, but rather the {@link ResourceHandler} that caller provided in constructor of
185      * this class should be closed manually.
186      */
187     public void close()
188         throws IOException
189     {
190         try
191         {
192             if ( incremental )
193             {
194                 localIndexProperties.setProperty( "nexus.index.last-incremental", nextChunkCounter );
195             }
196             localIndexProperties.setProperty( "nexus.index.timestamp", Utils.INDEX_DATE_FORMAT.format( new Date() ) );
197             storeProperties( local.locate( Utils.INDEX_FILE_PREFIX + ".properties" ), localIndexProperties );
198         }
199         finally
200         {
201             local.close();
202         }
203     }
204 
205     /**
206      * Calculates the chunk names that needs to be fetched.
207      */
208     private String calculateNextChunkCounter()
209     {
210         String lastChunkCounter = localIndexProperties.getProperty( "nexus.index.last-incremental" );
211         if ( lastChunkCounter != null )
212         {
213             return String.valueOf( Integer.parseInt( lastChunkCounter ) + 1 );
214         }
215         else
216         {
217             return "1";
218         }
219     }
220 }