View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.index.reader;
20  
21  import java.io.Closeable;
22  import java.io.IOException;
23  import java.text.ParseException;
24  import java.util.Date;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.Properties;
28  import java.util.UUID;
29  import java.util.concurrent.atomic.AtomicBoolean;
30  
31  import org.apache.maven.index.reader.WritableResourceHandler.WritableResource;
32  
33  import static java.util.Objects.requireNonNull;
34  import static org.apache.maven.index.reader.Utils.loadProperties;
35  import static org.apache.maven.index.reader.Utils.storeProperties;
36  
37  /**
38   * Maven Index writer that writes chunk and maintains published property file. Instances of this class MUST BE handled
39   * as resources (have them closed once done with them), it is user responsibility to close them, ideally in
40   * try-with-resource block.
41   * <p/>
42   * <strong>Currently no incremental update is supported, as the deletion states should be maintained by
43   * caller</strong>. Hence, this writer will always produce the "main" chunk only.
44   *
45   * @since 5.1.2
46   */
47  public class IndexWriter implements Closeable {
48      private static final int INDEX_V1 = 1;
49  
50      private final AtomicBoolean closed;
51  
52      private final WritableResourceHandler local;
53  
54      private final Properties localIndexProperties;
55  
56      private final boolean incremental;
57  
58      private final String nextChunkCounter;
59  
60      private final String nextChunkName;
61  
62      public IndexWriter(final WritableResourceHandler local, final String indexId, final boolean incrementalSupported)
63              throws IOException {
64          requireNonNull(local, "local resource handler null");
65          requireNonNull(indexId, "indexId null");
66          this.closed = new AtomicBoolean(false);
67          this.local = local;
68          Properties indexProperties = loadProperties(local.locate(Utils.INDEX_FILE_PREFIX + ".properties"));
69          if (incrementalSupported && indexProperties != null) {
70              this.localIndexProperties = indexProperties;
71              // existing index, this is incremental publish, and we will add new chunk
72              String localIndexId = localIndexProperties.getProperty("nexus.index.id");
73              if (localIndexId == null || !localIndexId.equals(indexId)) {
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          } else {
81              // non-existing index, create published index from scratch
82              this.localIndexProperties = new Properties();
83              this.localIndexProperties.setProperty("nexus.index.id", indexId);
84              this.localIndexProperties.setProperty(
85                      "nexus.index.chain-id", UUID.randomUUID().toString());
86              this.incremental = false;
87              this.nextChunkCounter = null;
88              this.nextChunkName = Utils.INDEX_FILE_PREFIX + ".gz";
89          }
90      }
91  
92      /**
93       * Returns the index context ID that published index has set.
94       */
95      public String getIndexId() {
96          return localIndexProperties.getProperty("nexus.index.id");
97      }
98  
99      /**
100      * Returns the {@link Date} when index was last published or {@code null} if this is first publishing. In other
101      * words,returns {@code null} when {@link #isIncremental()} returns {@code false}. After this writer is closed, the
102      * return value is updated to "now" (in {@link #close() method}.
103      */
104     public Date getPublishedTimestamp() {
105         try {
106             String timestamp = localIndexProperties.getProperty("nexus.index.timestamp");
107             if (timestamp != null) {
108                 return Utils.INDEX_DATE_FORMAT.parse(timestamp);
109             }
110             return null;
111         } catch (ParseException e) {
112             throw new RuntimeException("Corrupt date", e);
113         }
114     }
115 
116     /**
117      * Returns {@code true} if incremental publish is about to happen.
118      */
119     public boolean isIncremental() {
120         return incremental;
121     }
122 
123     /**
124      * Returns the chain id of published index. If {@link #isIncremental()} is {@code false}, this is the newly
125      * generated chain ID.
126      */
127     public String getChainId() {
128         return localIndexProperties.getProperty("nexus.index.chain-id");
129     }
130 
131     /**
132      * Returns the next chunk name about to be published.
133      */
134     public String getNextChunkName() {
135         return nextChunkName;
136     }
137 
138     /**
139      * Writes out the record iterator and returns the written record count.
140      */
141     public int writeChunk(final Iterator<Map<String, String>> iterator) throws IOException {
142         int written;
143 
144         try (WritableResource writableResource = local.locate(nextChunkName)) {
145             try (ChunkWriter chunkWriter =
146                     new ChunkWriter(nextChunkName, writableResource.write(), INDEX_V1, new Date())) {
147                 written = chunkWriter.writeChunk(iterator);
148             }
149             if (incremental) {
150                 // TODO: update main gz file
151             }
152             return written;
153         }
154     }
155 
156     /**
157      * Closes the underlying {@link ResourceHandler} and synchronizes published index properties, so remote clients
158      * becomes able to consume newly published index. If sync is not desired (ie. due to aborted publish), then this
159      * method should NOT be invoked, but rather the {@link ResourceHandler} that caller provided in constructor of
160      * this class should be closed manually.
161      */
162     @Override
163     public void close() throws IOException {
164         if (closed.compareAndSet(false, true)) {
165             try {
166                 if (incremental) {
167                     localIndexProperties.setProperty("nexus.index.last-incremental", nextChunkCounter);
168                 }
169                 localIndexProperties.setProperty("nexus.index.timestamp", Utils.INDEX_DATE_FORMAT.format(new Date()));
170                 storeProperties(local.locate(Utils.INDEX_FILE_PREFIX + ".properties"), localIndexProperties);
171             } finally {
172                 local.close();
173             }
174         }
175     }
176 
177     /**
178      * Calculates the chunk names that needs to be fetched.
179      */
180     private String calculateNextChunkCounter() {
181         String lastChunkCounter = localIndexProperties.getProperty("nexus.index.last-incremental");
182         if (lastChunkCounter != null) {
183             return String.valueOf(Integer.parseInt(lastChunkCounter) + 1);
184         } else {
185             return "1";
186         }
187     }
188 }