MINDEXER-100: forward port 5.x changes to master Squashed from https://github.com/apache/maven-indexer/pull/14
Project: http://git-wip-us.apache.org/repos/asf/maven-indexer/repo Commit: http://git-wip-us.apache.org/repos/asf/maven-indexer/commit/6cb2fcbe Tree: http://git-wip-us.apache.org/repos/asf/maven-indexer/tree/6cb2fcbe Diff: http://git-wip-us.apache.org/repos/asf/maven-indexer/diff/6cb2fcbe Branch: refs/heads/master Commit: 6cb2fcbe47883cd0decac0295493bef6577d7eb4 Parents: a7aeb9f Author: Tamas Cservenak <ta...@cservenak.net> Authored: Fri Mar 31 14:31:35 2017 +0200 Committer: Tamas Cservenak <ta...@cservenak.net> Committed: Fri Mar 31 14:31:35 2017 +0200 ---------------------------------------------------------------------- .../maven/index/context/NexusIndexWriter.java | 29 +- .../maven/index/updater/IndexDataReader.java | 15 +- .../apache/maven/index/updater/WagonHelper.java | 4 + .../org.apache.karaf.features.command-2.2.2.pom | 120 +++++- indexer-reader/README.md | 8 + indexer-reader/header.txt | 17 + indexer-reader/pom.xml | 66 ++++ .../apache/maven/index/reader/ChunkReader.java | 259 +++++++++++++ .../apache/maven/index/reader/ChunkWriter.java | 177 +++++++++ .../apache/maven/index/reader/IndexReader.java | 272 +++++++++++++ .../apache/maven/index/reader/IndexWriter.java | 196 ++++++++++ .../org/apache/maven/index/reader/Record.java | 384 +++++++++++++++++++ .../maven/index/reader/RecordCompactor.java | 207 ++++++++++ .../maven/index/reader/RecordExpander.java | 229 +++++++++++ .../maven/index/reader/ResourceHandler.java | 51 +++ .../org/apache/maven/index/reader/Utils.java | 170 ++++++++ .../index/reader/WritableResourceHandler.java | 58 +++ .../org/apache/maven/index/reader/packageinfo | 1 + .../index/reader/CachingResourceHandler.java | 129 +++++++ .../maven/index/reader/ChunkReaderTest.java | 95 +++++ .../index/reader/DirectoryResourceHandler.java | 87 +++++ .../maven/index/reader/HttpResourceHandler.java | 75 ++++ .../maven/index/reader/IndexReaderTest.java | 209 ++++++++++ .../maven/index/reader/IndexWriterTest.java | 93 +++++ .../apache/maven/index/reader/TestSupport.java | 184 +++++++++ .../apache/maven/index/reader/TestUtils.java | 108 ++++++ .../maven/index/reader/TransformTest.java | 98 +++++ .../simple/nexus-maven-repository-index.gz | Bin 0 -> 319 bytes .../nexus-maven-repository-index.properties | 6 + pom.xml | 33 +- 30 files changed, 3351 insertions(+), 29 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-core/src/main/java/org/apache/maven/index/context/NexusIndexWriter.java ---------------------------------------------------------------------- diff --git a/indexer-core/src/main/java/org/apache/maven/index/context/NexusIndexWriter.java b/indexer-core/src/main/java/org/apache/maven/index/context/NexusIndexWriter.java index 038eaa8..130ee88 100644 --- a/indexer-core/src/main/java/org/apache/maven/index/context/NexusIndexWriter.java +++ b/indexer-core/src/main/java/org/apache/maven/index/context/NexusIndexWriter.java @@ -25,6 +25,7 @@ import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.IndexWriterConfig.OpenMode; import org.apache.lucene.index.SerialMergeScheduler; import org.apache.lucene.store.Directory; import org.apache.lucene.store.LockObtainFailedException; @@ -38,16 +39,27 @@ import org.apache.lucene.util.Version; public class NexusIndexWriter extends IndexWriter { + public interface IndexWriterConfigFactory { + IndexWriterConfig create(Analyzer analyzer); + } + + public static IndexWriterConfigFactory CONFIG_FACTORY = new IndexWriterConfigFactory() { + public IndexWriterConfig create(final Analyzer analyzer) { + IndexWriterConfig config = new IndexWriterConfig( Version.LUCENE_46, analyzer ); + config.setRAMBufferSizeMB( 2.0 ); // old default + config.setMergeScheduler( new SerialMergeScheduler() ); // merging serially + config.setWriteLockTimeout(IndexWriterConfig.WRITE_LOCK_TIMEOUT); + return config; + } + }; + @Deprecated public NexusIndexWriter( final Directory directory, final Analyzer analyzer, boolean create ) throws CorruptIndexException, LockObtainFailedException, IOException { - //super( directory, analyzer, create, MaxFieldLength.LIMITED ); - this(directory, new IndexWriterConfig(Version.LUCENE_46, analyzer)); - - // setSimilarity( new NexusSimilarity() ); + this(directory, CONFIG_FACTORY.create(analyzer).setOpenMode(create ? OpenMode.CREATE : OpenMode.APPEND)); } - + public NexusIndexWriter( final Directory directory, final IndexWriterConfig config ) throws CorruptIndexException, LockObtainFailedException, IOException { @@ -58,11 +70,6 @@ public class NexusIndexWriter public static IndexWriterConfig defaultConfig() { - final IndexWriterConfig config = new IndexWriterConfig( Version.LUCENE_46, new NexusAnalyzer() ); - // default open mode is CreateOrAppend which suits us - config.setRAMBufferSizeMB( 2.0 ); // old default - config.setMergeScheduler( new SerialMergeScheduler() ); // merging serially - config.setWriteLockTimeout(IndexWriterConfig.WRITE_LOCK_TIMEOUT); - return config; + return CONFIG_FACTORY.create(new NexusAnalyzer()); } } http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-core/src/main/java/org/apache/maven/index/updater/IndexDataReader.java ---------------------------------------------------------------------- diff --git a/indexer-core/src/main/java/org/apache/maven/index/updater/IndexDataReader.java b/indexer-core/src/main/java/org/apache/maven/index/updater/IndexDataReader.java index f76200c..f55fbf9 100644 --- a/indexer-core/src/main/java/org/apache/maven/index/updater/IndexDataReader.java +++ b/indexer-core/src/main/java/org/apache/maven/index/updater/IndexDataReader.java @@ -9,7 +9,7 @@ package org.apache.maven.index.updater; * "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 + * 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 @@ -41,18 +41,17 @@ import org.apache.maven.index.context.IndexingContext; /** * An index data reader used to parse transfer index format. - * + * * @author Eugene Kuleshov */ public class IndexDataReader { private final DataInputStream dis; - public IndexDataReader( InputStream is ) + public IndexDataReader( final InputStream is ) throws IOException { BufferedInputStream bis = new BufferedInputStream( is, 1024 * 8 ); - // MINDEXER-13 // LightweightHttpWagon may have performed automatic decompression // Handle it transparently @@ -61,7 +60,7 @@ public class IndexDataReader if ( bis.read() == 0x1f && bis.read() == 0x8b ) // GZIPInputStream.GZIP_MAGIC { bis.reset(); - data = new GZIPInputStream( bis, 2 * 1024 ); + data = new GZIPInputStream( bis, 1024 * 8 ); } else { @@ -97,7 +96,7 @@ public class IndexDataReader } w.commit(); - + w.forceMerge(1); w.commit(); @@ -317,7 +316,7 @@ public class IndexDataReader /** * Reads index content by using a visitor. <br> * The visitor is called for each read documents after it has been populated with Lucene fields. - * + * * @param visitor an index data visitor * @param context indexing context * @return statistics about read data @@ -361,7 +360,7 @@ public class IndexDataReader /** * Called on each read document. The document is already populated with fields. - * + * * @param document read document */ void visitDocument( Document document ); http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-core/src/main/java/org/apache/maven/index/updater/WagonHelper.java ---------------------------------------------------------------------- diff --git a/indexer-core/src/main/java/org/apache/maven/index/updater/WagonHelper.java b/indexer-core/src/main/java/org/apache/maven/index/updater/WagonHelper.java index 0e450c9..40ea1d9 100644 --- a/indexer-core/src/main/java/org/apache/maven/index/updater/WagonHelper.java +++ b/indexer-core/src/main/java/org/apache/maven/index/updater/WagonHelper.java @@ -198,6 +198,7 @@ public class WagonHelper throws IOException, FileNotFoundException { final File target = File.createTempFile( name, "" ); + target.deleteOnExit(); retrieve( name, target ); return new FileInputStream( target ) { @@ -220,6 +221,7 @@ public class WagonHelper } catch ( AuthorizationException e ) { + targetFile.delete(); String msg = "Authorization exception retrieving " + name; logError( msg, e ); IOException ioException = new IOException( msg ); @@ -228,6 +230,7 @@ public class WagonHelper } catch ( ResourceDoesNotExistException e ) { + targetFile.delete(); String msg = "Resource " + name + " does not exist"; logError( msg, e ); FileNotFoundException fileNotFoundException = new FileNotFoundException( msg ); @@ -236,6 +239,7 @@ public class WagonHelper } catch ( WagonException e ) { + targetFile.delete(); String msg = "Transfer for " + name + " failed"; logError( msg, e ); IOException ioException = new IOException( msg + "; " + e.getMessage() ); http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-core/src/test/repo-with-osgi/org/apache/karaf/features/org.apache.karaf.features.command/2.2.2/org.apache.karaf.features.command-2.2.2.pom ---------------------------------------------------------------------- diff --git a/indexer-core/src/test/repo-with-osgi/org/apache/karaf/features/org.apache.karaf.features.command/2.2.2/org.apache.karaf.features.command-2.2.2.pom b/indexer-core/src/test/repo-with-osgi/org/apache/karaf/features/org.apache.karaf.features.command/2.2.2/org.apache.karaf.features.command-2.2.2.pom index 67e206f..062383c 100644 --- a/indexer-core/src/test/repo-with-osgi/org/apache/karaf/features/org.apache.karaf.features.command/2.2.2/org.apache.karaf.features.command-2.2.2.pom +++ b/indexer-core/src/test/repo-with-osgi/org/apache/karaf/features/org.apache.karaf.features.command/2.2.2/org.apache.karaf.features.command-2.2.2.pom @@ -1,5 +1,115 @@ -#Generated by org.apache.felix.bundleplugin -#Thu Jun 30 15:25:37 NDT 2011 -version=2.2.2 -groupId=org.apache.karaf.features -artifactId=org.apache.karaf.features.command +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <!-- + + + 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. + + --> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.karaf.features</groupId> + <artifactId>features</artifactId> + <version>2.2.2</version> + </parent> + <artifactId>org.apache.karaf.features.command</artifactId> + <packaging>bundle</packaging> + <name>Apache Karaf :: Features :: Command</name> + <description> + This bundle provides the Karaf shell commands to manipulate features. + </description> + <properties> + <appendedResourcesDirectory>${basedir}/../../etc/appended-resources</appendedResourcesDirectory> + </properties> + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.karaf.features</groupId> + <artifactId>org.apache.karaf.features.core</artifactId> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.bundlerepository</artifactId> + </dependency> + <dependency> + <groupId>org.apache.karaf.shell</groupId> + <artifactId>org.apache.karaf.shell.console</artifactId> + </dependency> + <dependency> + <groupId>org.apache.karaf.shell</groupId> + <artifactId>org.apache.karaf.shell.obr</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.osgi</groupId> + <artifactId>spring-osgi-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.servicemix.bundles</groupId> + <artifactId>org.apache.servicemix.bundles.junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-jdk14</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <resources> + <resource> + <directory>${project.basedir}/src/main/resources</directory> + <includes> + <include>**/*</include> + </includes> + </resource> + <resource> + <directory>${project.basedir}/src/main/resources</directory> + <filtering>true</filtering> + <includes> + <include>**/*.info</include> + </includes> + </resource> + </resources> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <configuration> + <instructions> + <Import-Package> + !${project.artifactId}*, javax.management, javax.management.loading, org.apache.felix.service.command, org.apache.felix.gogo.commands, org.apache.karaf.shell.console, * + </Import-Package> + <Private-Package>!*</Private-Package> + </instructions> + </configuration> + </plugin> + </plugins> + </build> +</project> http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-reader/README.md ---------------------------------------------------------------------- diff --git a/indexer-reader/README.md b/indexer-reader/README.md new file mode 100644 index 0000000..95aa1b8 --- /dev/null +++ b/indexer-reader/README.md @@ -0,0 +1,8 @@ +Indexer Reader Notes +================== + +Indexer Reader is a minimal dep-less library that is able to read published (remote) +index with incremental update support, making this library user able to integrate +published Maven Indexes into any engine without depending on maven-indexer-core +and it's transitive dependencies. + http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-reader/header.txt ---------------------------------------------------------------------- diff --git a/indexer-reader/header.txt b/indexer-reader/header.txt new file mode 100644 index 0000000..1a2ef73 --- /dev/null +++ b/indexer-reader/header.txt @@ -0,0 +1,17 @@ +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. + http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-reader/pom.xml ---------------------------------------------------------------------- diff --git a/indexer-reader/pom.xml b/indexer-reader/pom.xml new file mode 100644 index 0000000..3f91847 --- /dev/null +++ b/indexer-reader/pom.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.maven.indexer</groupId> + <artifactId>maven-indexer</artifactId> + <version>6.0-SNAPSHOT</version> + </parent> + + <artifactId>indexer-reader</artifactId> + <packaging>bundle</packaging> + + <name>Maven :: Indexer Reader</name> + <description> + Indexer Reader is a minimal dep-less library that is able to read published (remote) index with incremental update + support, making user able to integrate published Maven Indexes into any engine without depending on + maven-indexer-core and it's transitive dependencies. + </description> + + <dependencies> + <!-- Test --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>18.0</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <version>3.0.0</version> + <extensions>true</extensions> + </plugin> + </plugins> + </build> + +</project> http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-reader/src/main/java/org/apache/maven/index/reader/ChunkReader.java ---------------------------------------------------------------------- diff --git a/indexer-reader/src/main/java/org/apache/maven/index/reader/ChunkReader.java b/indexer-reader/src/main/java/org/apache/maven/index/reader/ChunkReader.java new file mode 100644 index 0000000..a6c75ac --- /dev/null +++ b/indexer-reader/src/main/java/org/apache/maven/index/reader/ChunkReader.java @@ -0,0 +1,259 @@ +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.DataInput; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UTFDataFormatException; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.zip.GZIPInputStream; + +/** + * Maven 2 Index published binary chunk reader, it reads raw Maven Indexer records from the transport binary format. + * + * @since 5.1.2 + */ +public class ChunkReader + implements Closeable, Iterable<Map<String, String>> +{ + private final String chunkName; + + private final DataInputStream dataInputStream; + + private final int version; + + private final Date timestamp; + + public ChunkReader(final String chunkName, final InputStream inputStream) throws IOException + { + this.chunkName = chunkName.trim(); + this.dataInputStream = new DataInputStream(new GZIPInputStream(inputStream, 2 * 1024)); + this.version = ((int) dataInputStream.readByte()) & 0xff; + this.timestamp = new Date(dataInputStream.readLong()); + } + + /** + * Returns the chunk name. + */ + public String getName() { + return chunkName; + } + + /** + * Returns index version. All releases so far always returned {@code 1}. + */ + public int getVersion() { + return version; + } + + /** + * Returns the index timestamp of last update of the index. + */ + public Date getTimestamp() { + return timestamp; + } + + /** + * Returns the {@link Record} iterator. + */ + public Iterator<Map<String, String>> iterator() { + try { + return new IndexIterator(dataInputStream); + } + catch (IOException e) { + throw new RuntimeException("error", e); + } + } + + /** + * Closes this reader and it's underlying input. + */ + public void close() throws IOException { + dataInputStream.close(); + } + + /** + * Low memory footprint index iterator that incrementally parses the underlying stream. + */ + private static class IndexIterator + implements Iterator<Map<String, String>> + { + private final DataInputStream dataInputStream; + + private Map<String, String> nextRecord; + + public IndexIterator(final DataInputStream dataInputStream) throws IOException { + this.dataInputStream = dataInputStream; + this.nextRecord = nextRecord(); + } + + public boolean hasNext() { + return nextRecord != null; + } + + public Map<String, String> next() { + if (nextRecord == null) { + throw new NoSuchElementException("chunk depleted"); + } + Map<String, String> result = nextRecord; + nextRecord = nextRecord(); + return result; + } + + public void remove() { + throw new UnsupportedOperationException("remove"); + } + + private Map<String, String> nextRecord() { + try { + return readRecord(dataInputStream); + } + catch (IOException e) { + throw new RuntimeException("read error", e); + } + } + } + + /** + * Reads and returns next record from the underlying stream, or {@code null} if no more records. + */ + private static Map<String, String> readRecord(final DataInput dataInput) + throws IOException + { + int fieldCount; + try { + fieldCount = dataInput.readInt(); + } + catch (EOFException ex) { + return null; // no more documents + } + + Map<String, String> recordMap = new HashMap<String, String>(); + for (int i = 0; i < fieldCount; i++) { + readField(recordMap, dataInput); + } + return recordMap; + } + + private static void readField(final Map<String, String> record, final DataInput dataInput) + throws IOException + { + dataInput.readByte(); // flags: neglect them + String name = dataInput.readUTF(); + String value = readUTF(dataInput); + record.put(name, value); + } + + private static String readUTF(final DataInput dataInput) + throws IOException + { + int utflen = dataInput.readInt(); + + byte[] bytearr; + char[] chararr; + + try { + bytearr = new byte[utflen]; + chararr = new char[utflen]; + } + catch (OutOfMemoryError e) { + IOException ioex = new IOException("Index data content is corrupt"); + ioex.initCause(e); + throw ioex; + } + + int c, char2, char3; + int count = 0; + int chararr_count = 0; + + dataInput.readFully(bytearr, 0, utflen); + + while (count < utflen) { + c = bytearr[count] & 0xff; + if (c > 127) { + break; + } + count++; + chararr[chararr_count++] = (char) c; + } + + while (count < utflen) { + c = bytearr[count] & 0xff; + switch (c >> 4) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + /* 0xxxxxxx */ + count++; + chararr[chararr_count++] = (char) c; + break; + + case 12: + case 13: + /* 110x xxxx 10xx xxxx */ + count += 2; + if (count > utflen) { + throw new UTFDataFormatException("malformed input: partial character at end"); + } + char2 = bytearr[count - 1]; + if ((char2 & 0xC0) != 0x80) { + throw new UTFDataFormatException("malformed input around byte " + count); + } + chararr[chararr_count++] = (char) (((c & 0x1F) << 6) | (char2 & 0x3F)); + break; + + case 14: + /* 1110 xxxx 10xx xxxx 10xx xxxx */ + count += 3; + if (count > utflen) { + throw new UTFDataFormatException("malformed input: partial character at end"); + } + char2 = bytearr[count - 2]; + char3 = bytearr[count - 1]; + if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) { + throw new UTFDataFormatException("malformed input around byte " + (count - 1)); + } + chararr[chararr_count++] = + (char) (((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | (char3 & 0x3F)); + break; + + default: + /* 10xx xxxx, 1111 xxxx */ + throw new UTFDataFormatException("malformed input around byte " + count); + } + } + + // The number of chars produced may be less than utflen + return new String(chararr, 0, chararr_count); + } +} http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-reader/src/main/java/org/apache/maven/index/reader/ChunkWriter.java ---------------------------------------------------------------------- diff --git a/indexer-reader/src/main/java/org/apache/maven/index/reader/ChunkWriter.java b/indexer-reader/src/main/java/org/apache/maven/index/reader/ChunkWriter.java new file mode 100644 index 0000000..24e4701 --- /dev/null +++ b/indexer-reader/src/main/java/org/apache/maven/index/reader/ChunkWriter.java @@ -0,0 +1,177 @@ +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.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; +import java.util.Iterator; +import java.util.Map; +import java.util.zip.GZIPOutputStream; + +/** + * Maven 2 Index published binary chunk writer, it writes raw Maven Indexer records to the transport binary format. + * + * @since 5.1.2 + */ +public class ChunkWriter + implements Closeable +{ + private static final int F_INDEXED = 1; + + private static final int F_TOKENIZED = 2; + + private static final int F_STORED = 4; + + private final String chunkName; + + private final DataOutputStream dataOutputStream; + + private final int version; + + private final Date timestamp; + + public ChunkWriter(final String chunkName, final OutputStream outputStream, final int version, final Date timestamp) + throws IOException + { + this.chunkName = chunkName.trim(); + this.dataOutputStream = new DataOutputStream(new GZIPOutputStream(outputStream, 2 * 1024)); + this.version = version; + this.timestamp = timestamp; + + dataOutputStream.writeByte(version); + dataOutputStream.writeLong(timestamp == null ? -1 : timestamp.getTime()); + } + + /** + * Returns the chunk name. + */ + public String getName() { + return chunkName; + } + + /** + * Returns index version. All releases so far always returned {@code 1}. + */ + public int getVersion() { + return version; + } + + /** + * Returns the index timestamp of last update of the index. + */ + public Date getTimestamp() { + return timestamp; + } + + /** + * Writes out the record iterator and returns the written record count. + */ + public int writeChunk(final Iterator<Map<String, String>> iterator) throws IOException { + int written = 0; + while (iterator.hasNext()) { + writeRecord(iterator.next(), dataOutputStream); + written++; + } + return written; + } + + /** + * Closes this reader and it's underlying input. + */ + public void close() throws IOException { + dataOutputStream.close(); + } + + private static void writeRecord(final Map<String, String> record, final DataOutput dataOutput) + throws IOException + { + dataOutput.writeInt(record.size()); + for (Map.Entry<String, String> entry : record.entrySet()) { + writeField(entry.getKey(), entry.getValue(), dataOutput); + } + } + + private static void writeField(final String fieldName, final String fieldValue, final DataOutput dataOutput) + throws IOException + { + boolean isIndexed = !(fieldName.equals("i") || fieldName.equals("m")); + boolean isTokenized = !(fieldName.equals("i") + || fieldName.equals("m") + || fieldName.equals("1") + || fieldName.equals("px")); + int flags = (isIndexed ? F_INDEXED : 0) + (isTokenized ? F_TOKENIZED : 0) + F_STORED; + dataOutput.writeByte(flags); + dataOutput.writeUTF(fieldName); + writeUTF(fieldValue, dataOutput); + } + + private static void writeUTF(final String str, final DataOutput dataOutput) + throws IOException + { + int strlen = str.length(); + int utflen = 0; + int c; + // use charAt instead of copying String to char array + for (int i = 0; i < strlen; i++) { + c = str.charAt(i); + if ((c >= 0x0001) && (c <= 0x007F)) { + utflen++; + } + else if (c > 0x07FF) { + utflen += 3; + } + else { + utflen += 2; + } + } + dataOutput.writeInt(utflen); + byte[] bytearr = new byte[utflen]; + int count = 0; + int i = 0; + for (; i < strlen; i++) { + c = str.charAt(i); + if (!((c >= 0x0001) && (c <= 0x007F))) { + break; + } + bytearr[count++] = (byte) c; + } + for (; i < strlen; i++) { + c = str.charAt(i); + if ((c >= 0x0001) && (c <= 0x007F)) { + bytearr[count++] = (byte) c; + + } + else if (c > 0x07FF) { + bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F)); + bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F)); + bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); + } + else { + bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F)); + bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); + } + } + dataOutput.write(bytearr, 0, utflen); + } +} http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-reader/src/main/java/org/apache/maven/index/reader/IndexReader.java ---------------------------------------------------------------------- diff --git a/indexer-reader/src/main/java/org/apache/maven/index/reader/IndexReader.java b/indexer-reader/src/main/java/org/apache/maven/index/reader/IndexReader.java new file mode 100644 index 0000000..85c5409 --- /dev/null +++ b/indexer-reader/src/main/java/org/apache/maven/index/reader/IndexReader.java @@ -0,0 +1,272 @@ +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<String>(); + 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"); + } + } +} http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-reader/src/main/java/org/apache/maven/index/reader/IndexWriter.java ---------------------------------------------------------------------- diff --git a/indexer-reader/src/main/java/org/apache/maven/index/reader/IndexWriter.java b/indexer-reader/src/main/java/org/apache/maven/index/reader/IndexWriter.java new file mode 100644 index 0000000..68d4abb --- /dev/null +++ b/indexer-reader/src/main/java/org/apache/maven/index/reader/IndexWriter.java @@ -0,0 +1,196 @@ +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.Date; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.UUID; + +import org.apache.maven.index.reader.WritableResourceHandler.WritableResource; + +import static org.apache.maven.index.reader.Utils.loadProperties; +import static org.apache.maven.index.reader.Utils.storeProperties; + +/** + * Maven 2 Index writer that writes chunk and maintains published property file. + * <p/> + * <strong>Currently no incremental update is supported, as the deleteion states should be maintained by + * caller</strong>. Hence, this writer will always produce the "main" chunk only. + * + * @since 5.1.2 + */ +public class IndexWriter + implements Closeable +{ + private static final int INDEX_V1 = 1; + + private final WritableResourceHandler local; + + private final Properties localIndexProperties; + + private final boolean incremental; + + private final String nextChunkCounter; + + private final String nextChunkName; + + public IndexWriter(final WritableResourceHandler local, final String indexId, final boolean incrementalSupported) + throws IOException + { + if (local == null) { + throw new NullPointerException("local resource handler null"); + } + if (indexId == null) { + throw new NullPointerException("indexId null"); + } + this.local = local; + Properties indexProperties = loadProperties(local.locate(Utils.INDEX_FILE_PREFIX + ".properties")); + if (incrementalSupported && indexProperties != null) { + this.localIndexProperties = indexProperties; + // existing index, this is incremental publish, and we will add new chunk + String localIndexId = localIndexProperties.getProperty("nexus.index.id"); + if (localIndexId == null || !localIndexId.equals(indexId)) { + throw new IllegalArgumentException( + "index already exists and indexId mismatch or unreadable: " + localIndexId + ", " + + indexId); + } + this.incremental = true; + this.nextChunkCounter = calculateNextChunkCounter(); + this.nextChunkName = Utils.INDEX_FILE_PREFIX + "." + nextChunkCounter + ".gz"; + } + else { + // non-existing index, create published index from scratch + this.localIndexProperties = new Properties(); + this.localIndexProperties.setProperty("nexus.index.id", indexId); + this.localIndexProperties.setProperty("nexus.index.chain-id", UUID.randomUUID().toString()); + this.incremental = false; + this.nextChunkCounter = null; + this.nextChunkName = Utils.INDEX_FILE_PREFIX + ".gz"; + } + } + + /** + * Returns the index context ID that published index has set. + */ + public String getIndexId() { + return localIndexProperties.getProperty("nexus.index.id"); + } + + /** + * Returns the {@link Date} when index was last published or {@code null} if this is first publishing. In other + * words,returns {@code null} when {@link #isIncremental()} returns {@code false}. After this writer is closed, the + * return value is updated to "now" (in {@link #close() method}. + */ + public Date getPublishedTimestamp() { + try { + String timestamp = localIndexProperties.getProperty("nexus.index.timestamp"); + if (timestamp != null) { + return Utils.INDEX_DATE_FORMAT.parse(timestamp); + } + return null; + } + catch (ParseException e) { + throw new RuntimeException("Corrupt date", e); + } + } + + /** + * Returns {@code true} if incremental publish is about to happen. + */ + public boolean isIncremental() { + return incremental; + } + + /** + * Returns the chain id of published index. If {@link #isIncremental()} is {@code false}, this is the newly generated + * chain ID. + */ + public String getChainId() { + return localIndexProperties.getProperty("nexus.index.chain-id"); + } + + /** + * Returns the next chunk name about to be published. + */ + public String getNextChunkName() { + return nextChunkName; + } + + /** + * Writes out the record iterator and returns the written record count. + */ + public int writeChunk(final Iterator<Map<String, String>> iterator) throws IOException { + int written; + WritableResource writableResource = local.locate(nextChunkName); + try { + final ChunkWriter chunkWriter = new ChunkWriter(nextChunkName, writableResource.write(), INDEX_V1, new Date()); + try { + written = chunkWriter.writeChunk(iterator); + } + finally { + chunkWriter.close(); + } + if (incremental) { + // TODO: update main gz file + } + return written; + } + finally { + writableResource.close(); + } + } + + /** + * Closes the underlying {@link ResourceHandler} and synchronizes published index properties, so remote clients + * becomes able to consume newly published index. If sync is not desired (ie. due to aborted publish), then this + * method should NOT be invoked, but rather the {@link ResourceHandler} that caller provided in constructor of + * this class should be closed manually. + */ + public void close() throws IOException { + try { + if (incremental) { + localIndexProperties.setProperty("nexus.index.last-incremental", nextChunkCounter); + } + localIndexProperties.setProperty("nexus.index.timestamp", Utils.INDEX_DATE_FORMAT.format(new Date())); + storeProperties(local.locate(Utils.INDEX_FILE_PREFIX + ".properties"), localIndexProperties); + } + finally { + local.close(); + } + } + + /** + * Calculates the chunk names that needs to be fetched. + */ + private String calculateNextChunkCounter() { + String lastChunkCounter = localIndexProperties.getProperty("nexus.index.last-incremental"); + if (lastChunkCounter != null) { + return String.valueOf(Integer.parseInt(lastChunkCounter) + 1); + } + else { + return "1"; + } + } +} http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-reader/src/main/java/org/apache/maven/index/reader/Record.java ---------------------------------------------------------------------- diff --git a/indexer-reader/src/main/java/org/apache/maven/index/reader/Record.java b/indexer-reader/src/main/java/org/apache/maven/index/reader/Record.java new file mode 100644 index 0000000..1746845 --- /dev/null +++ b/indexer-reader/src/main/java/org/apache/maven/index/reader/Record.java @@ -0,0 +1,384 @@ +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.util.Map; + +/** + * Maven 2 Index record. + * + * @since 5.1.2 + */ +public final class Record +{ + public static final class EntryKey<T> + { + private final String name; + + private final Class<T> proto; + + public EntryKey(final String name, final Class<T> proto) { + if (name == null) { + throw new NullPointerException("name is null"); + } + if (proto == null) { + throw new NullPointerException("proto is null"); + } + this.name = name; + this.proto = proto; + } + + public T coerce(final Object object) { + return (T) proto.cast(object); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (!(o instanceof EntryKey)) { + return false; + } + EntryKey entryKey = (EntryKey) o; + return name.equals(entryKey.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public String toString() { + return "Key{" + + "name='" + name + '\'' + + ", type=" + proto.getSimpleName() + + '}'; + } + } + + /** + * Key of repository ID entry, that contains {@link String}. + */ + public static final EntryKey<String> REPOSITORY_ID = new EntryKey<String>("repositoryId", String.class); + + /** + * Key of all groups list entry, that contains {@link java.util.List<String>}. + */ + public static final EntryKey<String[]> ALL_GROUPS = new EntryKey<String[]>("allGroups", String[].class); + + /** + * Key of root groups list entry, that contains {@link java.util.List<String>}. + */ + public static final EntryKey<String[]> ROOT_GROUPS = new EntryKey<String[]>("rootGroups", String[].class); + + /** + * Key of index record modification (added to index or removed from index) timestamp entry, that contains {@link + * Long}. + */ + public static final EntryKey<Long> REC_MODIFIED = new EntryKey<Long>("recordModified", Long.class); + + /** + * Key of artifact groupId entry, that contains {@link String}. + */ + public static final EntryKey<String> GROUP_ID = new EntryKey<String>("groupId", String.class); + + /** + * Key of artifact artifactId entry, that contains {@link String}. + */ + public static final EntryKey<String> ARTIFACT_ID = new EntryKey<String>("artifactId", String.class); + + /** + * Key of artifact version entry, that contains {@link String}. + */ + public static final EntryKey<String> VERSION = new EntryKey<String>("version", String.class); + + /** + * Key of artifact classifier entry, that contains {@link String}. + */ + public static final EntryKey<String> CLASSIFIER = new EntryKey<String>("classifier", String.class); + + /** + * Key of artifact packaging entry, that contains {@link String}. + */ + public static final EntryKey<String> PACKAGING = new EntryKey<String>("packaging", String.class); + + /** + * Key of artifact file extension, that contains {@link String}. + */ + public static final EntryKey<String> FILE_EXTENSION = new EntryKey<String>("fileExtension", String.class); + + /** + * Key of artifact file last modified timestamp, that contains {@link Long}. + */ + public static final EntryKey<Long> FILE_MODIFIED = new EntryKey<Long>("fileModified", Long.class); + + /** + * Key of artifact file size in bytes, that contains {@link Long}. + */ + public static final EntryKey<Long> FILE_SIZE = new EntryKey<Long>("fileSize", Long.class); + + /** + * Key of artifact Sources presence flag, that contains {@link Boolean}. + */ + public static final EntryKey<Boolean> HAS_SOURCES = new EntryKey<Boolean>("hasSources", Boolean.class); + + /** + * Key of artifact Javadoc presence flag, that contains {@link Boolean}. + */ + public static final EntryKey<Boolean> HAS_JAVADOC = new EntryKey<Boolean>("hasJavadoc", Boolean.class); + + /** + * Key of artifact signature presence flag, that contains {@link Boolean}. + */ + public static final EntryKey<Boolean> HAS_SIGNATURE = new EntryKey<Boolean>("hasSignature", Boolean.class); + + /** + * Key of artifact name (as set in POM), that contains {@link String}. + */ + public static final EntryKey<String> NAME = new EntryKey<String>("name", String.class); + + /** + * Key of artifact description (as set in POM), that contains {@link String}. + */ + public static final EntryKey<String> DESCRIPTION = new EntryKey<String>("description", String.class); + + /** + * Key of artifact SHA1 digest, that contains {@link String}. + */ + public static final EntryKey<String> SHA1 = new EntryKey<String>("sha1", String.class); + + /** + * Key of artifact contained class names, that contains {@link java.util.List<String>}. Extracted by {@code + * JarFileContentsIndexCreator}. + */ + public static final EntryKey<String[]> CLASSNAMES = new EntryKey<String[]>("classNames", String[].class); + + /** + * Key of plugin artifact prefix, that contains {@link String}. Extracted by {@code + * MavenPluginArtifactInfoIndexCreator}. + */ + public static final EntryKey<String> PLUGIN_PREFIX = new EntryKey<String>("pluginPrefix", String.class); + + /** + * Key of plugin artifact goals, that contains {@link java.util.List<String>}. Extracted by {@code + * MavenPluginArtifactInfoIndexCreator}. + */ + public static final EntryKey<String[]> PLUGIN_GOALS = new EntryKey<String[]>("pluginGoals", String[].class); + + /** + * Key of OSGi "Bundle-SymbolicName" manifest entry, that contains {@link String}. Extracted by {@code + * OsgiArtifactIndexCreator}. + */ + public static final EntryKey<String> OSGI_BUNDLE_SYMBOLIC_NAME = new EntryKey<String>("Bundle-SymbolicName", + String.class); + + /** + * Key of OSGi "Bundle-Version" manifest entry, that contains {@link String}. Extracted by {@code + * OsgiArtifactIndexCreator}. + */ + public static final EntryKey<String> OSGI_BUNDLE_VERSION = new EntryKey<String>("Bundle-Version", String.class); + + /** + * Key of OSGi "Export-Package" manifest entry, that contains {@link String}. Extracted by {@code + * OsgiArtifactIndexCreator}. + */ + public static final EntryKey<String> OSGI_EXPORT_PACKAGE = new EntryKey<String>("Export-Package", String.class); + + /** + * Key of OSGi "Export-Service" manifest entry, that contains {@link String}. Extracted by {@code + * OsgiArtifactIndexCreator}. + */ + public static final EntryKey<String> OSGI_EXPORT_SERVICE = new EntryKey<String>("Export-Service", String.class); + + /** + * Key of OSGi "Bundle-Description" manifest entry, that contains {@link String}. Extracted by {@code + * OsgiArtifactIndexCreator}. + */ + public static final EntryKey<String> OSGI_BUNDLE_DESCRIPTION = new EntryKey<String>("Bundle-Description", + String.class); + + /** + * Key of OSGi "Bundle-Name" manifest entry, that contains {@link String}. Extracted by {@code + * OsgiArtifactIndexCreator}. + */ + public static final EntryKey<String> OSGI_BUNDLE_NAME = new EntryKey<String>("Bundle-Name", String.class); + + /** + * Key of OSGi "Bundle-License" manifest entry, that contains {@link String}. Extracted by {@code + * OsgiArtifactIndexCreator}. + */ + public static final EntryKey<String> OSGI_BUNDLE_LICENSE = new EntryKey<String>("Bundle-License", String.class); + + /** + * Key of OSGi "Bundle-DocURL" manifest entry, that contains {@link String}. Extracted by {@code + * OsgiArtifactIndexCreator}. + */ + public static final EntryKey<String> OSGI_EXPORT_DOCURL = new EntryKey<String>("Bundle-DocURL", String.class); + + /** + * Key of OSGi "Import-Package" manifest entry, that contains {@link String}. Extracted by {@code + * OsgiArtifactIndexCreator}. + */ + public static final EntryKey<String> OSGI_IMPORT_PACKAGE = new EntryKey<String>("Import-Package", String.class); + + /** + * Key of OSGi "Require-Bundle" manifest entry, that contains {@link String}. Extracted by {@code + * OsgiArtifactIndexCreator}. + */ + public static final EntryKey<String> OSGI_REQUIRE_BUNDLE = new EntryKey<String>("Require-Bundle", String.class); + + /** + * Types of returned records returned from index. + */ + public enum Type + { + /** + * Descriptor record. Can be safely ignored. + * Contains following entries: + * <ul> + * <li>{@link #REPOSITORY_ID}</li> + * </ul> + */ + DESCRIPTOR, + + /** + * Artifact ADD record. Records of this type should be added to your indexing system. + * Contains following entries: + * <ul> + * <li>{@link #REC_MODIFIED} (when record was added/modified on index)</li> + * <li>{@link #GROUP_ID}</li> + * <li>{@link #ARTIFACT_ID}</li> + * <li>{@link #VERSION}</li> + * <li>{@link #CLASSIFIER} (optional)</li> + * <li>{@link #FILE_EXTENSION}</li> + * <li>{@link #FILE_MODIFIED}</li> + * <li>{@link #FILE_SIZE}</li> + * <li>{@link #PACKAGING}</li> + * <li>{@link #HAS_SOURCES}</li> + * <li>{@link #HAS_JAVADOC}</li> + * <li>{@link #HAS_SIGNATURE}</li> + * <li>{@link #NAME}</li> + * <li>{@link #DESCRIPTION}</li> + * <li>{@link #SHA1}</li> + * <li>{@link #CLASSNAMES} (optional)</li> + * <li>{@link #PLUGIN_PREFIX} (optional, for maven-plugins only)</li> + * <li>{@link #PLUGIN_GOALS} (optional, for maven-plugins only)</li> + * </ul> + */ + ARTIFACT_ADD, + + /** + * Artifact REMOVE record. In case of incremental updates, signals that this artifact was removed. Records of this + * type should be removed from your indexing system. + * Contains following entries: + * <ul> + * <li>{@link #REC_MODIFIED} (when record was deleted from index)</li> + * <li>{@link #GROUP_ID}</li> + * <li>{@link #ARTIFACT_ID}</li> + * <li>{@link #VERSION}</li> + * <li>{@link #CLASSIFIER} (optional)</li> + * <li>{@link #FILE_EXTENSION} (if {@link #CLASSIFIER} present)</li> + * <li>{@link #PACKAGING} (optional)</li> + * </ul> + */ + ARTIFACT_REMOVE, + + /** + * Special record, containing all the Maven "groupId"s that are enlisted on the index. Can be safely ignored. + * Contains following entries: + * <ul> + * <li>{@link #ALL_GROUPS}</li> + * </ul> + */ + ALL_GROUPS, + + /** + * Special record, containing all the root groups of Maven "groupId"s that are enlisted on the index. Can be safely + * ignored. + * Contains following entries: + * <ul> + * <li>{@link #ROOT_GROUPS}</li> + * </ul> + */ + ROOT_GROUPS + } + + private final Type type; + + private final Map<EntryKey, Object> expanded; + + public Record(final Type type, final Map<EntryKey, Object> expanded) { + this.type = type; + this.expanded = expanded; + } + + /** + * Returns the {@link Type} of this record. Usually users would be interested in {@link Type#ARTIFACT_ADD} and {@link + * Type#ARTIFACT_REMOVE} types only to maintain their own index. Still, indexer offers extra records too, see {@link + * Type} for all existing types. + */ + public Type getType() { + return type; + } + + /** + * Returns the expanded (processed and expanded synthetic fields) record as {@link Map} ready for consumption. + */ + public Map<EntryKey, Object> getExpanded() { + return expanded; + } + + /** + * Returns {@code true} if this record contains given {@link EntryKey}. + */ + boolean containsKey(final EntryKey<?> entryKey) { return expanded.containsKey(entryKey); } + + /** + * Type safe handy method to get value from expanded map. + */ + public <T> T get(final EntryKey<T> entryKey) { + return entryKey.coerce(expanded.get(entryKey)); + } + + /** + * Type safe handy method to put value to expanded map. Accepts {@code null} values, that removes the mapping. + */ + public <T> T put(final EntryKey<T> entryKey, final T value) { + if (value == null) { + return entryKey.coerce(expanded.remove(entryKey)); + } + else { + if (!entryKey.proto.isAssignableFrom(value.getClass())) { + throw new IllegalArgumentException("Key " + entryKey + " does not accepts value " + value); + } + return entryKey.coerce(expanded.put(entryKey, value)); + } + } + + @Override + public String toString() { + return "Record{" + + "type=" + type + + ", expanded=" + expanded + + '}'; + } +} http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-reader/src/main/java/org/apache/maven/index/reader/RecordCompactor.java ---------------------------------------------------------------------- diff --git a/indexer-reader/src/main/java/org/apache/maven/index/reader/RecordCompactor.java b/indexer-reader/src/main/java/org/apache/maven/index/reader/RecordCompactor.java new file mode 100644 index 0000000..76fc1e5 --- /dev/null +++ b/indexer-reader/src/main/java/org/apache/maven/index/reader/RecordCompactor.java @@ -0,0 +1,207 @@ +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.util.HashMap; +import java.util.Map; + +import org.apache.maven.index.reader.Record.Type; + +import static org.apache.maven.index.reader.Utils.FIELD_SEPARATOR; +import static org.apache.maven.index.reader.Utils.INFO; +import static org.apache.maven.index.reader.Utils.UINFO; +import static org.apache.maven.index.reader.Utils.nvl; + +/** + * Maven 2 Index record transformer, that transforms {@link Record}s into "native" Maven Indexer records. + * + * @since 5.1.2 + */ +public class RecordCompactor +{ + /** + * Compacts {@link Record} into low level MI record with all the encoded fields as physically present in MI binary + * chunk. + */ + public Map<String, String> apply(final Record record) { + if (Type.DESCRIPTOR == record.getType()) { + return compactDescriptor(record); + } + else if (Type.ALL_GROUPS == record.getType()) { + return compactAllGroups(record); + } + else if (Type.ROOT_GROUPS == record.getType()) { + return compactRootGroups(record); + } + else if (Type.ARTIFACT_REMOVE == record.getType()) { + return compactDeletedArtifact(record); + } + else if (Type.ARTIFACT_ADD == record.getType()) { + return compactAddedArtifact(record); + } + else { + throw new IllegalArgumentException("Unknown record: " + record); + } + } + + private static Map<String, String> compactDescriptor(final Record record) { + final Map<String, String> result = new HashMap<String, String>(); + result.put("DESCRIPTOR", "NexusIndex"); + result.put("IDXINFO", "1.0|" + record.get(Record.REPOSITORY_ID)); + return result; + } + + private static Map<String, String> compactAllGroups(final Record record) { + final Map<String, String> result = new HashMap<String, String>(); + result.put("allGroups", "allGroups"); + putIfNotNullAsStringArray(record.get(Record.ALL_GROUPS), result, "allGroupsList"); + return result; + } + + private static Map<String, String> compactRootGroups(final Record record) { + final Map<String, String> result = new HashMap<String, String>(); + result.put("rootGroups", "allGroups"); + putIfNotNullAsStringArray(record.get(Record.ROOT_GROUPS), result, "rootGroupsList"); + return result; + } + + private static Map<String, String> compactDeletedArtifact(final Record record) { + final Map<String, String> result = new HashMap<String, String>(); + putIfNotNullTS(record.get(Record.REC_MODIFIED), result, "m"); + result.put("del", compactUinfo(record)); + return result; + } + + /** + * Expands the "encoded" Maven Indexer record by splitting the synthetic fields and applying expanded field naming. + */ + private static Map<String, String> compactAddedArtifact(final Record record) { + final Map<String, String> result = new HashMap<String, String>(); + + // Minimal + result.put(UINFO, compactUinfo(record)); + + StringBuilder info = new StringBuilder(); + info.append(nvl(record.get(Record.PACKAGING))); + info.append(FIELD_SEPARATOR); + info.append(record.get(Record.FILE_MODIFIED)); + info.append(FIELD_SEPARATOR); + info.append(record.get(Record.FILE_SIZE)); + info.append(FIELD_SEPARATOR); + info.append(record.get(Record.HAS_SOURCES) ? "1" : "0"); + info.append(FIELD_SEPARATOR); + info.append(record.get(Record.HAS_JAVADOC) ? "1" : "0"); + info.append(FIELD_SEPARATOR); + info.append(record.get(Record.HAS_SIGNATURE) ? "1" : "0"); + info.append(FIELD_SEPARATOR); + info.append(nvl(record.get(Record.FILE_EXTENSION))); + result.put(INFO, info.toString()); + + putIfNotNullTS(record.get(Record.REC_MODIFIED), result, "m"); + putIfNotNull(record.get(Record.NAME), result, "n"); + putIfNotNull(record.get(Record.DESCRIPTION), result, "d"); + putIfNotNull(record.get(Record.SHA1), result, "1"); + + // Jar file contents (optional) + putIfNotNullAsStringArray(record.get(Record.CLASSNAMES), result, "classnames"); + + // Maven Plugin (optional) + putIfNotNull(record.get(Record.PLUGIN_PREFIX), result, "px"); + putIfNotNullAsStringArray(record.get(Record.PLUGIN_GOALS), result, "gx"); + + // OSGi (optional) + putIfNotNull(record.get(Record.OSGI_BUNDLE_SYMBOLIC_NAME), result, "Bundle-SymbolicName"); + putIfNotNull(record.get(Record.OSGI_BUNDLE_VERSION), result, "Bundle-Version"); + putIfNotNull(record.get(Record.OSGI_EXPORT_PACKAGE), result, "Export-Package"); + putIfNotNull(record.get(Record.OSGI_EXPORT_SERVICE), result, "Export-Service"); + putIfNotNull(record.get(Record.OSGI_BUNDLE_DESCRIPTION), result, "Bundle-Description"); + putIfNotNull(record.get(Record.OSGI_BUNDLE_NAME), result, "Bundle-Name"); + putIfNotNull(record.get(Record.OSGI_BUNDLE_LICENSE), result, "Bundle-License"); + putIfNotNull(record.get(Record.OSGI_EXPORT_DOCURL), result, "Bundle-DocURL"); + putIfNotNull(record.get(Record.OSGI_IMPORT_PACKAGE), result, "Import-Package"); + putIfNotNull(record.get(Record.OSGI_REQUIRE_BUNDLE), result, "Require-Bundle"); + + return result; + } + + /** + * Creates UINFO synthetic field. + */ + private static String compactUinfo(final Record record) { + final String classifier = record.get(Record.CLASSIFIER); + StringBuilder sb = new StringBuilder(); + sb.append(record.get(Record.GROUP_ID)) + .append(FIELD_SEPARATOR) + .append(record.get(Record.ARTIFACT_ID)) + .append(FIELD_SEPARATOR) + .append(record.get(Record.VERSION)) + .append(FIELD_SEPARATOR) + .append(nvl(classifier)); + if (classifier != null) { + sb.append(FIELD_SEPARATOR).append(record.get(Record.FILE_EXTENSION)); + } + return sb.toString(); + } + + /** + * Helper to put a value from source map into target map, if not null. + */ + private static void putIfNotNull( + final String source, + final Map<String, String> target, + final String targetName) + { + if (source != null) { + target.put(targetName, source); + } + } + + /** + * Helper to put a {@link Long} value from source map into target map, if not null. + */ + private static void putIfNotNullTS( + final Long source, + final Map<String, String> target, + final String targetName) + { + if (source != null) { + target.put(targetName, String.valueOf(source)); + } + } + + /** + * Helper to put a array value from source map into target map joined with {@link Utils#FIELD_SEPARATOR}, if not + * null. + */ + private static void putIfNotNullAsStringArray( + final String[] source, + final Map<String, String> target, + final String targetName) + { + if (source != null && source.length > 0) { + StringBuilder sb = new StringBuilder(); + sb.append(source[0]); + for (int i = 1; i < source.length; i++) { + sb.append(FIELD_SEPARATOR).append(source[i]); + } + target.put(targetName, sb.toString()); + } + } +} http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-reader/src/main/java/org/apache/maven/index/reader/RecordExpander.java ---------------------------------------------------------------------- diff --git a/indexer-reader/src/main/java/org/apache/maven/index/reader/RecordExpander.java b/indexer-reader/src/main/java/org/apache/maven/index/reader/RecordExpander.java new file mode 100644 index 0000000..6d7f4be --- /dev/null +++ b/indexer-reader/src/main/java/org/apache/maven/index/reader/RecordExpander.java @@ -0,0 +1,229 @@ +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.util.HashMap; +import java.util.Map; + +import org.apache.maven.index.reader.Record.EntryKey; +import org.apache.maven.index.reader.Record.Type; + +import static org.apache.maven.index.reader.Utils.FIELD_SEPARATOR; +import static org.apache.maven.index.reader.Utils.FS_PATTERN; +import static org.apache.maven.index.reader.Utils.INFO; +import static org.apache.maven.index.reader.Utils.NOT_AVAILABLE; +import static org.apache.maven.index.reader.Utils.UINFO; +import static org.apache.maven.index.reader.Utils.renvl; + +/** + * Maven 2 Index record transformer, that transforms "native" Maven Indexer records into {@link Record}s. + * + * @since 5.1.2 + */ +public class RecordExpander +{ + /** + * Expands MI low level record into {@link Record}. + */ + public Record apply(final Map<String, String> recordMap) { + if (recordMap.containsKey("DESCRIPTOR")) { + return expandDescriptor(recordMap); + } + else if (recordMap.containsKey("allGroups")) { + return expandAllGroups(recordMap); + } + else if (recordMap.containsKey("rootGroups")) { + return expandRootGroups(recordMap); + } + else if (recordMap.containsKey("del")) { + return expandDeletedArtifact(recordMap); + } + else { + // Fix up UINFO field wrt MINDEXER-41 + final String uinfo = recordMap.get(UINFO); + final String info = recordMap.get(INFO); + if (uinfo != null && !(info == null || info.trim().length() == 0)) { + final String[] splitInfo = FS_PATTERN.split(info); + if (splitInfo.length > 6) { + final String extension = splitInfo[6]; + if (uinfo.endsWith(FIELD_SEPARATOR + NOT_AVAILABLE)) { + recordMap.put(UINFO, uinfo + FIELD_SEPARATOR + extension); + } + } + } + return expandAddedArtifact(recordMap); + } + } + + private static Record expandDescriptor(final Map<String, String> raw) { + final Record result = new Record(Type.DESCRIPTOR, new HashMap<EntryKey, Object>()); + String[] r = FS_PATTERN.split(raw.get("IDXINFO")); + result.put(Record.REPOSITORY_ID, r[1]); + return result; + } + + private static Record expandAllGroups(final Map<String, String> raw) { + final Record result = new Record(Type.ALL_GROUPS, new HashMap<EntryKey, Object>()); + putIfNotNullAsStringArray(raw, "allGroupsList", result, Record.ALL_GROUPS); + return result; + } + + private static Record expandRootGroups(final Map<String, String> raw) { + final Record result = new Record(Type.ROOT_GROUPS, new HashMap<EntryKey, Object>()); + putIfNotNullAsStringArray(raw, "rootGroupsList", result, Record.ROOT_GROUPS); + return result; + } + + private static Record expandDeletedArtifact(final Map<String, String> raw) { + final Record result = new Record(Type.ARTIFACT_REMOVE, new HashMap<EntryKey, Object>()); + putIfNotNullTS(raw, "m", result, Record.REC_MODIFIED); + if (raw.containsKey("del")) { + expandUinfo(raw.get("del"), result); + } + return result; + } + + /** + * Expands the "encoded" Maven Indexer record by splitting the synthetic fields and applying expanded field naming. + */ + private static Record expandAddedArtifact(final Map<String, String> raw) { + final Record result = new Record(Type.ARTIFACT_ADD, new HashMap<EntryKey, Object>()); + + // Minimal + expandUinfo(raw.get(UINFO), result); + final String info = raw.get(INFO); + if (info != null) { + String[] r = FS_PATTERN.split(info); + result.put(Record.PACKAGING, renvl(r[0])); + result.put(Record.FILE_MODIFIED, Long.valueOf(r[1])); + result.put(Record.FILE_SIZE, Long.valueOf(r[2])); + result.put(Record.HAS_SOURCES, "1".equals(r[3]) ? Boolean.TRUE : Boolean.FALSE); + result.put(Record.HAS_JAVADOC, "1".equals(r[4]) ? Boolean.TRUE : Boolean.FALSE); + result.put(Record.HAS_SIGNATURE, "1".equals(r[5]) ? Boolean.TRUE : Boolean.FALSE); + if (r.length > 6) { + result.put(Record.FILE_EXTENSION, r[6]); + } + else { + final String packaging = Record.PACKAGING.coerce(result.get(Record.PACKAGING)); + if (result.containsKey(Record.CLASSIFIER) + || "pom".equals(packaging) + || "war".equals(packaging) + || "ear".equals(packaging)) { + result.put(Record.FILE_EXTENSION, packaging); + } + else { + result.put(Record.FILE_EXTENSION, "jar"); // best guess + } + } + } + putIfNotNullTS(raw, "m", result, Record.REC_MODIFIED); + putIfNotNull(raw, "n", result, Record.NAME); + putIfNotNull(raw, "d", result, Record.DESCRIPTION); + putIfNotNull(raw, "1", result, Record.SHA1); + + // Jar file contents (optional) + putIfNotNullAsStringArray(raw, "classnames", result, Record.CLASSNAMES); + + // Maven Plugin (optional) + putIfNotNull(raw, "px", result, Record.PLUGIN_PREFIX); + putIfNotNullAsStringArray(raw, "gx", result, Record.PLUGIN_GOALS); + + // OSGi (optional) + putIfNotNull(raw, "Bundle-SymbolicName", result, Record.OSGI_BUNDLE_SYMBOLIC_NAME); + putIfNotNull(raw, "Bundle-Version", result, Record.OSGI_BUNDLE_VERSION); + putIfNotNull(raw, "Export-Package", result, Record.OSGI_EXPORT_PACKAGE); + putIfNotNull(raw, "Export-Service", result, Record.OSGI_EXPORT_SERVICE); + putIfNotNull(raw, "Bundle-Description", result, Record.OSGI_BUNDLE_DESCRIPTION); + putIfNotNull(raw, "Bundle-Name", result, Record.OSGI_BUNDLE_NAME); + putIfNotNull(raw, "Bundle-License", result, Record.OSGI_BUNDLE_LICENSE); + putIfNotNull(raw, "Bundle-DocURL", result, Record.OSGI_EXPORT_DOCURL); + putIfNotNull(raw, "Import-Package", result, Record.OSGI_IMPORT_PACKAGE); + putIfNotNull(raw, "Require-Bundle", result, Record.OSGI_REQUIRE_BUNDLE); + + return result; + } + + /** + * Expands UINFO synthetic field. Handles {@code null} String inputs. + */ + private static void expandUinfo(final String uinfo, final Record result) { + if (uinfo != null) { + String[] r = FS_PATTERN.split(uinfo); + result.put(Record.GROUP_ID, r[0]); + result.put(Record.ARTIFACT_ID, r[1]); + result.put(Record.VERSION, r[2]); + String classifier = renvl(r[3]); + if (classifier != null) { + result.put(Record.CLASSIFIER, classifier); + if (r.length > 4) { + result.put(Record.FILE_EXTENSION, r[4]); + } + } + else if (r.length > 4) { + result.put(Record.PACKAGING, r[4]); + } + } + } + + /** + * Helper to put a value from source map into target map, if not null. + */ + private static void putIfNotNull( + final Map<String, String> source, + final String sourceName, + final Record target, + final EntryKey targetName) + { + String value = source.get(sourceName); + if (value != null && value.trim().length() != 0) { + target.put(targetName, value); + } + } + + /** + * Helper to put a {@link Long} value from source map into target map, if not null. + */ + private static void putIfNotNullTS( + final Map<String, String> source, + final String sourceName, + final Record target, + final EntryKey targetName) + { + String value = source.get(sourceName); + if (value != null && value.trim().length() != 0) { + target.put(targetName, Long.valueOf(value)); + } + } + + /** + * Helper to put a collection value from source map into target map as {@link java.util.List}, if not null. + */ + private static void putIfNotNullAsStringArray( + final Map<String, String> source, + final String sourceName, + final Record target, + final EntryKey targetName) + { + String value = source.get(sourceName); + if (value != null && value.trim().length() != 0) { + target.put(targetName, FS_PATTERN.split(value)); + } + } +} http://git-wip-us.apache.org/repos/asf/maven-indexer/blob/6cb2fcbe/indexer-reader/src/main/java/org/apache/maven/index/reader/ResourceHandler.java ---------------------------------------------------------------------- diff --git a/indexer-reader/src/main/java/org/apache/maven/index/reader/ResourceHandler.java b/indexer-reader/src/main/java/org/apache/maven/index/reader/ResourceHandler.java new file mode 100644 index 0000000..244a994 --- /dev/null +++ b/indexer-reader/src/main/java/org/apache/maven/index/reader/ResourceHandler.java @@ -0,0 +1,51 @@ +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.io.InputStream; + +/** + * Maven 2 Index resource abstraction, that should be handled as a resource (is {@link Closeable}. That means, that + * implementations could perform any extra activity as FS locking or so (if uses FS as backing store). Is used by single + * thread only. + * + * @since 5.1.2 + */ +public interface ResourceHandler + extends Closeable +{ + interface Resource + { + /** + * Returns the {@link InputStream} stream of the resource, if exists, {@code null} otherwise. The stream should + * be closed by caller, otherwise resource leaks might be introduced. + */ + InputStream read() throws IOException; + } + + /** + * Returns the {@link Resource} with {@code name}, non {@code null}. + * + * @param name Resource name, guaranteed to be non-{@code null} and is FS and URL safe string. + */ + Resource locate(String name) throws IOException; +}