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