blob: 1f7586462937f335a24d41fa9c745f5ee679dbf8 [file] [log] [blame]
package org.apache.maven.index.reader;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.Closeable;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import org.apache.maven.index.reader.ResourceHandler.Resource;
import static org.apache.maven.index.reader.Utils.loadProperties;
import static org.apache.maven.index.reader.Utils.storeProperties;
/**
* Maven 2 Index reader that handles incremental updates if possible and provides one or more {@link ChunkReader}s, to
* read all the required records.
*
* @since 5.1.2
*/
public class IndexReader
implements Iterable<ChunkReader>, Closeable
{
private final WritableResourceHandler local;
private final ResourceHandler remote;
private final Properties localIndexProperties;
private final Properties remoteIndexProperties;
private final String indexId;
private final Date publishedTimestamp;
private final boolean incremental;
private final List<String> chunkNames;
public IndexReader(final WritableResourceHandler local, final ResourceHandler remote) throws IOException {
if (remote == null) {
throw new NullPointerException("remote resource handler null");
}
this.local = local;
this.remote = remote;
remoteIndexProperties = loadProperties(remote.locate(Utils.INDEX_FILE_PREFIX + ".properties"));
if (remoteIndexProperties == null) {
throw new IllegalArgumentException("Non-existent remote index");
}
try {
if (local != null) {
Properties localProperties = loadProperties(local.locate(Utils.INDEX_FILE_PREFIX + ".properties"));
if (localProperties != null) {
this.localIndexProperties = localProperties;
String remoteIndexId = remoteIndexProperties.getProperty("nexus.index.id");
String localIndexId = localIndexProperties.getProperty("nexus.index.id");
if (remoteIndexId == null || localIndexId == null || !remoteIndexId.equals(localIndexId)) {
throw new IllegalArgumentException(
"local and remote index IDs does not match or is null: " + localIndexId + ", " +
remoteIndexId);
}
this.indexId = localIndexId;
this.incremental = canRetrieveAllChunks();
}
else {
localIndexProperties = null;
this.indexId = remoteIndexProperties.getProperty("nexus.index.id");
this.incremental = false;
}
}
else {
localIndexProperties = null;
this.indexId = remoteIndexProperties.getProperty("nexus.index.id");
this.incremental = false;
}
this.publishedTimestamp = Utils.INDEX_DATE_FORMAT.parse(remoteIndexProperties.getProperty("nexus.index.timestamp"));
this.chunkNames = calculateChunkNames();
}
catch (ParseException e) {
IOException ex = new IOException("Index properties corrupted");
ex.initCause(e);
throw ex;
}
}
/**
* Returns the index context ID that published index has set. Usually it is equal to "repository ID" used in {@link
* Record.Type#DESCRIPTOR} but does not have to be.
*/
public String getIndexId() {
return indexId;
}
/**
* Returns the {@link Date} when remote index was last published.
*/
public Date getPublishedTimestamp() {
return publishedTimestamp;
}
/**
* Returns {@code true} if incremental update is about to happen. If incremental update, the {@link #iterator()} will
* return only the diff from the last update.
*/
public boolean isIncremental() {
return incremental;
}
/**
* Returns unmodifiable list of actual chunks that needs to be pulled from remote {@link ResourceHandler}. Those are
* incremental chunks or the big main file, depending on result of {@link #isIncremental()}. Empty list means local
* index is up to date, and {@link #iterator()} will return empty iterator.
*/
public List<String> getChunkNames() {
return chunkNames;
}
/**
* Closes the underlying {@link ResourceHandler}s. In case of incremental update use, it also assumes that user
* consumed all the iterator and integrated it, hence, it will update the {@link WritableResourceHandler} contents to
* prepare it for future incremental update. If this is not desired (ie. due to aborted update), then this method
* should NOT be invoked, but rather the {@link ResourceHandler}s that caller provided in constructor of
* this class should be closed manually.
*/
public void close() throws IOException {
remote.close();
if (local != null) {
try {
syncLocalWithRemote();
}
finally {
local.close();
}
}
}
/**
* Returns an {@link Iterator} of {@link ChunkReader}s, that if read in sequence, provide all the (incremental)
* updates from the index. It is caller responsibility to either consume fully this iterator, or to close current
* {@link ChunkReader} if aborting.
*/
public Iterator<ChunkReader> iterator() {
return new ChunkReaderIterator(remote, chunkNames.iterator());
}
/**
* Stores the remote index properties into local index properties, preparing local {@link WritableResourceHandler}
* for future incremental updates.
*/
private void syncLocalWithRemote() throws IOException {
storeProperties(local.locate(Utils.INDEX_FILE_PREFIX + ".properties"), remoteIndexProperties);
}
/**
* Calculates the chunk names that needs to be fetched.
*/
private List<String> calculateChunkNames() {
if (incremental) {
ArrayList<String> chunkNames = new ArrayList<>();
int maxCounter = Integer.parseInt(remoteIndexProperties.getProperty("nexus.index.last-incremental"));
int currentCounter = Integer.parseInt(localIndexProperties.getProperty("nexus.index.last-incremental"));
currentCounter++;
while (currentCounter <= maxCounter) {
chunkNames.add(Utils.INDEX_FILE_PREFIX + "." + currentCounter++ + ".gz");
}
return Collections.unmodifiableList(chunkNames);
}
else {
return Collections.singletonList(Utils.INDEX_FILE_PREFIX + ".gz");
}
}
/**
* Verifies incremental update is possible, as all the diff chunks we need are still enlisted in remote properties.
*/
private boolean canRetrieveAllChunks()
{
String localChainId = localIndexProperties.getProperty("nexus.index.chain-id");
String remoteChainId = remoteIndexProperties.getProperty("nexus.index.chain-id");
// If no chain id, or not the same, do full update
if (localChainId == null || remoteChainId == null || !localChainId.equals(remoteChainId)) {
return false;
}
try {
int localLastIncremental = Integer.parseInt(localIndexProperties.getProperty("nexus.index.last-incremental"));
String currentLocalCounter = String.valueOf(localLastIncremental);
String nextLocalCounter = String.valueOf(localLastIncremental + 1);
// check remote props for existence of current or next chunk after local
for (Object key : remoteIndexProperties.keySet()) {
String sKey = (String) key;
if (sKey.startsWith("nexus.index.incremental-")) {
String value = remoteIndexProperties.getProperty(sKey);
if (currentLocalCounter.equals(value) || nextLocalCounter.equals(value)) {
return true;
}
}
}
}
catch (NumberFormatException e) {
// fall through
}
return false;
}
/**
* Internal iterator implementation that lazily opens and closes the returned {@link ChunkReader}s as this iterator
* is being consumed.
*/
private static class ChunkReaderIterator
implements Iterator<ChunkReader>
{
private final ResourceHandler resourceHandler;
private final Iterator<String> chunkNamesIterator;
private Resource currentResource;
private ChunkReader currentChunkReader;
private ChunkReaderIterator(final ResourceHandler resourceHandler, final Iterator<String> chunkNamesIterator) {
this.resourceHandler = resourceHandler;
this.chunkNamesIterator = chunkNamesIterator;
}
public boolean hasNext() {
return chunkNamesIterator.hasNext();
}
public ChunkReader next() {
String chunkName = chunkNamesIterator.next();
try {
if (currentChunkReader != null) {
currentChunkReader.close();
}
currentResource = resourceHandler.locate(chunkName);
currentChunkReader = new ChunkReader(chunkName, currentResource.read());
return currentChunkReader;
}
catch (IOException e) {
throw new RuntimeException("IO problem while switching chunk readers", e);
}
}
public void remove() {
throw new UnsupportedOperationException("remove");
}
}
}