You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2023/01/01 22:54:17 UTC

[couchdb] branch import-nouveau-reorg created (now 32a5000af)

This is an automated email from the ASF dual-hosted git repository.

rnewson pushed a change to branch import-nouveau-reorg
in repository https://gitbox.apache.org/repos/asf/couchdb.git


      at 32a5000af simplify the index management

This branch includes the following new commits:

     new 9a7d3301d remove SuppressWarnings junk
     new affc8358f move server into module
     new 92a20a723 isolate Lucene behind interface
     new fb11df781 reorder for readability
     new 32a5000af simplify the index management

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[couchdb] 05/05: simplify the index management

Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rnewson pushed a commit to branch import-nouveau-reorg
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 32a5000afabd4d1138baf9b941ee7203e03fbd43
Author: Robert Newson <rn...@apache.org>
AuthorDate: Sun Jan 1 21:26:22 2023 +0000

    simplify the index management
---
 .../apache/couchdb/nouveau/NouveauApplication.java |   4 -
 .../core/FileAlreadyExistsExceptionMapper.java     |  35 ------
 .../nouveau/core/FileNotFoundExceptionMapper.java  |  35 ------
 .../org/apache/couchdb/nouveau/core/Index.java     | 119 ++++--------------
 .../apache/couchdb/nouveau/core/IndexManager.java  | 139 ++++++++-------------
 .../apache/couchdb/nouveau/core/Lucene9Index.java  |  16 +--
 .../nouveau/health/IndexManagerHealthCheck.java    |  18 +--
 .../couchdb/nouveau/resources/IndexResource.java   |  23 +---
 .../couchdb/nouveau/resources/SearchResource.java  |   7 +-
 .../apache/couchdb/nouveau/IntegrationTest.java    |   6 +-
 10 files changed, 95 insertions(+), 307 deletions(-)

diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
index 215483d84..7a64e89bd 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
@@ -13,8 +13,6 @@
 
 package org.apache.couchdb.nouveau;
 
-import org.apache.couchdb.nouveau.core.FileAlreadyExistsExceptionMapper;
-import org.apache.couchdb.nouveau.core.FileNotFoundExceptionMapper;
 import org.apache.couchdb.nouveau.core.IndexManager;
 import org.apache.couchdb.nouveau.core.Lucene9AnalyzerFactory;
 import org.apache.couchdb.nouveau.core.Lucene9IndexFactory;
@@ -73,8 +71,6 @@ public class NouveauApplication extends Application<NouveauApplicationConfigurat
         indexManager.setIndexFactory(indexFactory);
         environment.lifecycle().manage(indexManager);
 
-        environment.jersey().register(new FileNotFoundExceptionMapper());
-        environment.jersey().register(new FileAlreadyExistsExceptionMapper());
         environment.jersey().register(new UpdatesOutOfOrderExceptionMapper());
 
         final AnalyzeResource analyzeResource = new AnalyzeResource(analyzerFactory);
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileAlreadyExistsExceptionMapper.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileAlreadyExistsExceptionMapper.java
deleted file mode 100644
index e9ef3ed0c..000000000
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileAlreadyExistsExceptionMapper.java
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// Licensed 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.
-
-package org.apache.couchdb.nouveau.core;
-
-import java.nio.file.FileAlreadyExistsException;
-
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.ext.ExceptionMapper;
-
-import io.dropwizard.jersey.errors.ErrorMessage;
-
-public class FileAlreadyExistsExceptionMapper implements ExceptionMapper<FileAlreadyExistsException> {
-
-    @Override
-    public Response toResponse(final FileAlreadyExistsException exception) {
-        return Response.status(Status.EXPECTATION_FAILED)
-            .type(MediaType.APPLICATION_JSON_TYPE)
-            .entity(new ErrorMessage(Status.EXPECTATION_FAILED.getStatusCode(), "Index already exists"))
-            .build();
-    }
-
-}
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileNotFoundExceptionMapper.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileNotFoundExceptionMapper.java
deleted file mode 100644
index 84e692df7..000000000
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileNotFoundExceptionMapper.java
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// Licensed 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.
-
-package org.apache.couchdb.nouveau.core;
-
-import java.io.FileNotFoundException;
-
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.ext.ExceptionMapper;
-
-import io.dropwizard.jersey.errors.ErrorMessage;
-
-public class FileNotFoundExceptionMapper implements ExceptionMapper<FileNotFoundException> {
-
-    @Override
-    public Response toResponse(final FileNotFoundException exception) {
-        return Response.status(Status.NOT_FOUND)
-            .type(MediaType.APPLICATION_JSON_TYPE)
-            .entity(new ErrorMessage(Status.NOT_FOUND.getStatusCode(), "Index does not exist"))
-            .build();
-    }
-
-}
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Index.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Index.java
index 739768913..880b2c983 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Index.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Index.java
@@ -15,7 +15,6 @@ package org.apache.couchdb.nouveau.core;
 
 import java.io.Closeable;
 import java.io.IOException;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import org.apache.couchdb.nouveau.api.DocumentDeleteRequest;
 import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
@@ -25,152 +24,78 @@ import org.apache.couchdb.nouveau.api.SearchResults;
 
 public abstract class Index implements Closeable {
 
-    /*
-     * The close lock is to ensure there are no readers/searchers when
-     * we want to close the index.
-     */
-    private ReentrantReadWriteLock closeLock = new ReentrantReadWriteLock();
-
-    /*
-     * The update lock ensures serial updates to the index.
-     */
-    private ReentrantReadWriteLock updateLock = new ReentrantReadWriteLock();
-
     private long updateSeq;
 
     private boolean deleteOnClose = false;
 
-    private boolean closed = false;
-
     protected Index(final long updateSeq) {
         this.updateSeq = updateSeq;
     }
 
     public final IndexInfo info() throws IOException {
-        final long updateSeq = getUpdateSeq();
-        closeLock.readLock().lock();
-        try {
-            final int numDocs = doNumDocs();
-            return new IndexInfo(updateSeq, numDocs);
-        } finally {
-            closeLock.readLock().unlock();
-        }
+        final int numDocs = doNumDocs();
+        return new IndexInfo(updateSeq, numDocs);
     }
 
     protected abstract int doNumDocs() throws IOException;
 
-    public final void update(final String docId, final DocumentUpdateRequest request) throws IOException {
-        updateLock.writeLock().lock();
-        try {
-            assertUpdateSeqIsLower(request.getSeq());
-            closeLock.readLock().lock();
-            try {
-                doUpdate(docId, request);
-            } finally {
-                closeLock.readLock().unlock();
-            }
-            incrementUpdateSeq(request.getSeq());
-        } finally {
-            updateLock.writeLock().unlock();
-        }
+    public final synchronized void update(final String docId, final DocumentUpdateRequest request) throws IOException {
+        assertUpdateSeqIsLower(request.getSeq());
+        doUpdate(docId, request);
+        incrementUpdateSeq(request.getSeq());
     }
 
     protected abstract void doUpdate(final String docId, final DocumentUpdateRequest request) throws IOException;
 
-    public final void delete(final String docId, final DocumentDeleteRequest request) throws IOException {
-        updateLock.writeLock().lock();
-        try {
-            assertUpdateSeqIsLower(request.getSeq());
-            closeLock.readLock().lock();
-            try {
-                doDelete(docId, request);
-            } finally {
-                closeLock.readLock().unlock();
-            }
-            incrementUpdateSeq(request.getSeq());
-        } finally {
-            updateLock.writeLock().unlock();
-        }
+    public final synchronized void delete(final String docId, final DocumentDeleteRequest request) throws IOException {
+        assertUpdateSeqIsLower(request.getSeq());
+        doDelete(docId, request);
+        incrementUpdateSeq(request.getSeq());
     }
 
     protected abstract void doDelete(final String docId, final DocumentDeleteRequest request) throws IOException;
 
     public final SearchResults search(final SearchRequest request) throws IOException, QueryParserException {
-        closeLock.readLock().lock();
-        try {
-            return doSearch(request);
-        } finally {
-            closeLock.readLock().unlock();
-        }
+        return doSearch(request);
     }
 
     protected abstract SearchResults doSearch(final SearchRequest request) throws IOException, QueryParserException;
 
     public final boolean commit() throws IOException {
-        final long updateSeq = getUpdateSeq();
-        closeLock.readLock().lock();
-        try {
-            return doCommit(updateSeq);
-        } finally {
-            closeLock.readLock().unlock();
+        final long updateSeq;
+        synchronized (this) {
+            updateSeq = this.updateSeq;
         }
+        return doCommit(updateSeq);
     }
 
     protected abstract boolean doCommit(final long updateSeq) throws IOException;
 
     public final void close() throws IOException {
-        closeLock.writeLock().lock();
-        try {
-            doClose(deleteOnClose);
-            closed = true;
-        } finally {
-            closeLock.writeLock().unlock();
-        }
+        doClose();
     }
 
-    protected abstract void doClose(final boolean deleteOnClose) throws IOException;
+    protected abstract void doClose() throws IOException;
 
-    public final void lock() {
-        closeLock.readLock().lock();
-    }
-
-    public final void unlock() {
-        closeLock.readLock().unlock();
-    }
+    public abstract boolean isOpen();
 
-    public final boolean isClosed() {
-        return closed;
+    public boolean isDeleteOnClose() {
+        return deleteOnClose;
     }
 
-    public final void setDeleteOnClose(final boolean deleteOnClose) {
-        closeLock.writeLock().lock();
-        try {
-            this.deleteOnClose = true;
-        } finally {
-            closeLock.writeLock().unlock();
-        }
+    public void setDeleteOnClose(final boolean deleteOnClose) {
+        this.deleteOnClose = deleteOnClose;
     }
 
     protected final void assertUpdateSeqIsLower(final long updateSeq) throws UpdatesOutOfOrderException {
-        assert updateLock.isWriteLockedByCurrentThread();
         if (!(updateSeq > this.updateSeq)) {
             throw new UpdatesOutOfOrderException();
         }
     }
 
     protected final void incrementUpdateSeq(final long updateSeq) throws IOException {
-        assert updateLock.isWriteLockedByCurrentThread();
         assertUpdateSeqIsLower(updateSeq);
         this.updateSeq = updateSeq;
     }
 
-    private long getUpdateSeq() {
-        updateLock.readLock().lock();
-        try {
-            return this.updateSeq;
-        } finally {
-            updateLock.readLock().unlock();
-        }
-    }
-
 }
\ No newline at end of file
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java
index a5bb2970b..696097daa 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java
@@ -18,7 +18,6 @@ import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.time.Duration;
-import java.util.concurrent.CompletionException;
 import java.util.stream.Stream;
 
 import javax.validation.constraints.Min;
@@ -28,6 +27,7 @@ import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Response.Status;
 
 import org.apache.couchdb.nouveau.api.IndexDefinition;
+import org.apache.lucene.util.IOUtils;
 import org.checkerframework.checker.nullness.qual.NonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.slf4j.Logger;
@@ -46,49 +46,46 @@ import com.github.benmanes.caffeine.cache.Scheduler;
 
 import io.dropwizard.lifecycle.Managed;
 
-public class IndexManager implements Managed {
+public final class IndexManager implements Managed {
 
-    private static final int RETRY_LIMIT = 500;
-    private static final int RETRY_SLEEP_MS = 5;
     private static final Logger LOGGER = LoggerFactory.getLogger(IndexManager.class);
 
     private class IndexLoader implements CacheLoader<String, Index> {
 
         @Override
         public @Nullable Index load(@NonNull String name) throws Exception {
-            return openExistingIndex(name);
+            final Path path = indexPath(name);
+            final IndexDefinition indexDefinition = objectMapper.readValue(indexDefinitionPath(name).toFile(),
+                    IndexDefinition.class);
+            return indexFactory.open(path, indexDefinition);
         }
 
         @Override
         public @Nullable Index reload(@NonNull String name, @NonNull Index index) throws Exception {
-            try {
-                if (index.commit()) {
-                    LOGGER.info("{} committed.", index);
-                }
-            } catch (final IOException e) {
-                LOGGER.error(index + " threw exception when committing.", e);
-                index.close();
-                return openExistingIndex(name);
+            if (index.commit()) {
+                LOGGER.info("{} committed.", name);
             }
             return index;
         }
 
     }
 
-    private static class IndexCloser implements RemovalListener<String, Index> {
+    private class IndexRemovalListener implements RemovalListener<String, Index> {
 
         public void onRemoval(String name, Index index, RemovalCause cause) {
             try {
-                index.close();
-            } catch (IOException e) {
+                if (index.isOpen()) {
+                    index.close();
+                    if (index.isDeleteOnClose()) {
+                        IOUtils.rm(indexRootPath(name));
+                    }
+                }
+            } catch (final IOException e) {
                 LOGGER.error(index + " threw exception when closing", e);
             }
         }
     }
 
-    private static final IndexCloser INDEX_CLOSER = new IndexCloser();
-
-
     @Min(1)
     private int maxIndexesOpen;
 
@@ -114,33 +111,34 @@ public class IndexManager implements Managed {
     private LoadingCache<String, Index> cache;
 
     public Index acquire(final String name) throws IOException {
-        for (int i = 0; i < RETRY_LIMIT; i++) {
-            final Index result = getFromCache(name);
-
-            // Check if we're in the middle of closing.
-            result.lock();
-            if (!result.isClosed()) {
-                return result;
-            }
-            result.unlock();
-
-            // Retry after a short sleep.
-            try {
-                Thread.sleep(RETRY_SLEEP_MS);
-            } catch (InterruptedException e) {
-                Thread.interrupted();
-                break;
-            }
+        if (!exists(name)) {
+            throw new WebApplicationException("Index does not exist", Status.NOT_FOUND);
         }
-        throw new IOException("Failed to acquire " + name);
+        return cache.get(name);
     }
 
-    public void release(final Index index) throws IOException {
-        index.unlock();
+    public void invalidate(final String name) {
+        cache.invalidate(name);
     }
 
     public void create(final String name, IndexDefinition indexDefinition) throws IOException {
-        createNewIndex(name, indexDefinition);
+        if (exists(name)) {
+            throw new WebApplicationException("Index already exists", Status.EXPECTATION_FAILED);
+        }
+        // Validate index definiton
+        analyzerFactory.fromDefinition(indexDefinition);
+
+        // Persist definition
+        final Path path = indexDefinitionPath(name);
+        if (Files.exists(path)) {
+            throw new FileAlreadyExistsException(name + " already exists");
+        }
+        Files.createDirectories(path.getParent());
+        objectMapper.writeValue(path.toFile(), indexDefinition);
+    }
+
+    public boolean exists(final String name) {
+        return Files.exists(indexDefinitionPath(name));
     }
 
     public void deleteAll(final String path) throws IOException {
@@ -149,7 +147,7 @@ public class IndexManager implements Managed {
             return;
         }
         Stream<Path> stream = Files.find(rootPath, 100,
-            (p, attr) -> attr.isDirectory() && isIndex(p));
+                (p, attr) -> attr.isDirectory() && isIndex(p));
         try {
             stream.forEach((p) -> {
                 try {
@@ -164,12 +162,12 @@ public class IndexManager implements Managed {
     }
 
     private void deleteIndex(final String name) throws IOException {
-        final Index index = acquire(name);
-        try {
+        final Index index = cache.getIfPresent(name);
+        if (index == null) {
+            IOUtils.rm(indexRootPath(name));
+        } else {
             index.setDeleteOnClose(true);
             cache.invalidate(name);
-        } finally {
-            release(index);
         }
     }
 
@@ -227,17 +225,18 @@ public class IndexManager implements Managed {
 
     @Override
     public void start() throws IOException {
+        final IndexRemovalListener indexRemovalListener = new IndexRemovalListener();
         cache = Caffeine.newBuilder()
-            .recordStats(() -> new MetricsStatsCounter(metricRegistry, "IndexManager"))
-            .initialCapacity(maxIndexesOpen)
-            .maximumSize(maxIndexesOpen)
-            .expireAfterAccess(Duration.ofSeconds(idleSeconds))
-            .expireAfterWrite(Duration.ofSeconds(idleSeconds))
-            .refreshAfterWrite(Duration.ofSeconds(commitIntervalSeconds))
-            .scheduler(Scheduler.systemScheduler())
-            .removalListener(INDEX_CLOSER)
-            .evictionListener(INDEX_CLOSER)
-            .build(new IndexLoader());
+                .recordStats(() -> new MetricsStatsCounter(metricRegistry, "IndexManager"))
+                .initialCapacity(maxIndexesOpen)
+                .maximumSize(maxIndexesOpen)
+                .expireAfterAccess(Duration.ofSeconds(idleSeconds))
+                .expireAfterWrite(Duration.ofSeconds(idleSeconds))
+                .refreshAfterWrite(Duration.ofSeconds(commitIntervalSeconds))
+                .scheduler(Scheduler.systemScheduler())
+                .evictionListener(indexRemovalListener)
+                .removalListener(indexRemovalListener)
+                .build(new IndexLoader());
     }
 
     @Override
@@ -245,36 +244,6 @@ public class IndexManager implements Managed {
         cache.invalidateAll();
     }
 
-    private Index getFromCache(final String name) throws IOException {
-        try {
-            return cache.get(name);
-        } catch (CompletionException e) {
-            if (e.getCause() instanceof IOException) {
-                throw (IOException) e.getCause();
-            }
-            throw e;
-        }
-    }
-
-    private void createNewIndex(final String name, final IndexDefinition indexDefinition) throws IOException {
-        // Validate index definiton
-        analyzerFactory.fromDefinition(indexDefinition);
-
-        // Persist definition
-        final Path path = indexDefinitionPath(name);
-        if (Files.exists(path)) {
-            throw new FileAlreadyExistsException(name + " already exists");
-        }
-        Files.createDirectories(path.getParent());
-        objectMapper.writeValue(path.toFile(), indexDefinition);
-    }
-
-    private Index openExistingIndex(final String name) throws IOException {
-        final Path path = indexPath(name);
-        final IndexDefinition indexDefinition = objectMapper.readValue(indexDefinitionPath(name).toFile(), IndexDefinition.class);
-        return indexFactory.open(path, indexDefinition);
-    }
-
     private boolean isIndex(final Path path) {
         return path.resolve("index_definition.json").toFile().exists();
     }
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java
index fe86092d4..0c89afef2 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java
@@ -81,7 +81,6 @@ class Lucene9Index extends Index {
             throws IOException {
         final Directory dir = new DirectIODirectory(FSDirectory.open(path));
         final IndexWriterConfig config = new IndexWriterConfig(analyzer);
-        config.setCommitOnClose(true);
         config.setUseCompoundFile(false);
         final IndexWriter writer = new IndexWriter(dir, config);
         final long updateSeq = getUpdateSeq(writer);
@@ -122,18 +121,15 @@ class Lucene9Index extends Index {
     }
 
     @Override
-    public void doClose(final boolean deleteOnClose) throws IOException {
-        if (deleteOnClose) {
-            // No need to commit in this case.
-            writer.rollback();
-            final Directory dir = writer.getDirectory();
-            for (final String name : dir.listAll()) {
-                dir.deleteFile(name);
-            }
-        }
+    public void doClose() throws IOException {
         writer.close();
     }
 
+    @Override
+    public boolean isOpen() {
+        return writer.isOpen();
+    }
+
     @Override
     public SearchResults doSearch(final SearchRequest request) throws IOException, QueryParserException {
         final Query query = newQueryParser().parse(request);
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java
index e76d8da9c..315111534 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java
@@ -44,18 +44,12 @@ public class IndexManagerHealthCheck extends HealthCheck {
 
         indexManager.create(name, new IndexDefinition(LUCENE_9, "standard", null));
         final Index index = indexManager.acquire(name);
-        try {
-            final DocumentUpdateRequest request = new DocumentUpdateRequest(1, null, Collections.emptyList());
-            index.update("foo", request);
-            index.commit();
-            index.setDeleteOnClose(true);
-            index.close();
-            return Result.healthy();
-        } catch (final IOException e) {
-            return Result.unhealthy(e);
-        } finally {
-            indexManager.release(index);
-        }
+        final DocumentUpdateRequest request = new DocumentUpdateRequest(1, null, Collections.emptyList());
+        index.update("foo", request);
+        index.commit();
+        index.setDeleteOnClose(true);
+        indexManager.invalidate(name);
+        return Result.healthy();
     }
 
 }
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java
index ca7ee15c0..6cc2c1132 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java
@@ -24,7 +24,9 @@ import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response.Status;
 
 import org.apache.couchdb.nouveau.api.DocumentDeleteRequest;
 import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
@@ -49,12 +51,7 @@ public class IndexResource {
     @GET
     @SuppressWarnings("resource")
     public IndexInfo indexInfo(@PathParam("name") String name) throws IOException {
-        final Index index = indexManager.acquire(name);
-        try {
-            return index.info();
-        } finally {
-            indexManager.release(index);
-        }
+        return indexManager.acquire(name).info();
     }
 
     @DELETE
@@ -71,24 +68,14 @@ public class IndexResource {
     @Timed
     @Path("/doc/{docId}")
     public void deleteDoc(@PathParam("name") String name, @PathParam("docId") String docId, @NotNull @Valid final DocumentDeleteRequest request) throws IOException {
-        final Index index = indexManager.acquire(name);
-        try {
-            index.delete(docId, request);
-        } finally {
-            indexManager.release(index);
-        }
+        indexManager.acquire(name).delete(docId, request);
     }
 
     @PUT
     @Timed
     @Path("/doc/{docId}")
     public void updateDoc(@PathParam("name") String name, @PathParam("docId") String docId, @NotNull @Valid final DocumentUpdateRequest request) throws IOException {
-        final Index index = indexManager.acquire(name);
-        try {
-            index.update(docId, request);
-        } finally {
-            indexManager.release(index);
-        }
+        indexManager.acquire(name).update(docId, request);
     }
 
 }
\ No newline at end of file
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
index 03f6c0c20..f93335921 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
@@ -48,12 +48,7 @@ public class SearchResource {
     @Path("/search")
     public SearchResults searchIndex(@PathParam("name") String name, @NotNull @Valid SearchRequest searchRequest)
             throws IOException, QueryParserException {
-        final Index index = indexManager.acquire(name);
-        try {
-            return index.search(searchRequest);
-        } finally {
-            indexManager.release(index);
-        }
+        return indexManager.acquire(name).search(searchRequest);
     }
 
 }
diff --git a/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java
index 66f774514..72e3dcb9a 100644
--- a/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java
+++ b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java
@@ -54,7 +54,7 @@ public class IntegrationTest {
     );
 
     @Test
-    public void indexTest() {
+    public void indexTest() throws Exception{
         final String url = "http://localhost:" + APP.getLocalPort();
         final String indexName = "foo";
         final IndexDefinition indexDefinition = new IndexDefinition(LUCENE_9, "standard", null);
@@ -118,10 +118,6 @@ public class IntegrationTest {
                 .request()
                 .get();
 
-        //healthCheckResponse.bufferEntity();
-        //InputStream in = (InputStream) healthCheckResponse.getEntity();
-        //System.err.printf("health check response: %s\n", new String(in.readAllBytes()));
-
         assertThat(healthCheckResponse)
                 .extracting(Response::getStatus)
                 .isEqualTo(Response.Status.OK.getStatusCode());


[couchdb] 04/05: reorder for readability

Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rnewson pushed a commit to branch import-nouveau-reorg
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit fb11df7810a5650d6dac017cdb8ec4fad652e033
Author: Robert Newson <rn...@apache.org>
AuthorDate: Sun Jan 1 20:04:12 2023 +0000

    reorder for readability
---
 .../apache/couchdb/nouveau/core/Lucene9Index.java  | 38 +++++++++++-----------
 1 file changed, 19 insertions(+), 19 deletions(-)

diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java
index d62beff9b..fe86092d4 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java
@@ -115,6 +115,25 @@ class Lucene9Index extends Index {
         writer.deleteDocuments(query);
     }
 
+    @Override
+    public boolean doCommit(final long updateSeq) throws IOException {
+        writer.setLiveCommitData(Collections.singletonMap("update_seq", Long.toString(updateSeq)).entrySet());
+        return writer.commit() != -1;
+    }
+
+    @Override
+    public void doClose(final boolean deleteOnClose) throws IOException {
+        if (deleteOnClose) {
+            // No need to commit in this case.
+            writer.rollback();
+            final Directory dir = writer.getDirectory();
+            for (final String name : dir.listAll()) {
+                dir.deleteFile(name);
+            }
+        }
+        writer.close();
+    }
+
     @Override
     public SearchResults doSearch(final SearchRequest request) throws IOException, QueryParserException {
         final Query query = newQueryParser().parse(request);
@@ -273,25 +292,6 @@ class Lucene9Index extends Index {
         return new SortField(m.group(2), type, reverse);
     }
 
-    @Override
-    public boolean doCommit(final long updateSeq) throws IOException {
-        writer.setLiveCommitData(Collections.singletonMap("update_seq", Long.toString(updateSeq)).entrySet());
-        return writer.commit() != -1;
-    }
-
-    @Override
-    public void doClose(final boolean deleteOnClose) throws IOException {
-        if (deleteOnClose) {
-            // No need to commit in this case.
-            writer.rollback();
-            final Directory dir = writer.getDirectory();
-            for (final String name : dir.listAll()) {
-                dir.deleteFile(name);
-            }
-        }
-        writer.close();
-    }
-
     private static Document toDocument(final String docId, final DocumentUpdateRequest request) throws IOException {
         final Document result = new Document();
 


[couchdb] 03/05: isolate Lucene behind interface

Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rnewson pushed a commit to branch import-nouveau-reorg
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 92a20a7238310d098e1f48da4ab71c2c7f7baf59
Author: Robert Newson <rn...@apache.org>
AuthorDate: Thu Dec 29 23:20:27 2022 +0000

    isolate Lucene behind interface
---
 .tool-versions                                     |   4 +
 Makefile                                           |   4 +-
 .../apache/couchdb/nouveau/NouveauApplication.java |  28 ++-
 .../couchdb/nouveau/core/DocumentFactory.java      |  50 -----
 .../org/apache/couchdb/nouveau/core/Index.java     | 176 ++++++++++++++++++
 ...allelSearcherFactory.java => IndexFactory.java} |  23 +--
 .../apache/couchdb/nouveau/core/IndexManager.java  | 206 ++-------------------
 ...zerFactory.java => Lucene9AnalyzerFactory.java} |   5 +-
 .../SearchResource.java => core/Lucene9Index.java} | 194 ++++++++++++++-----
 .../couchdb/nouveau/core/Lucene9IndexFactory.java  |  43 +++++
 ...ry.java => Lucene9ParallelSearcherFactory.java} |   2 +-
 .../nouveau/health/IndexManagerHealthCheck.java    |  27 +--
 .../couchdb/nouveau/resources/AnalyzeResource.java |   6 +-
 .../couchdb/nouveau/resources/IndexResource.java   |  28 +--
 .../couchdb/nouveau/resources/SearchResource.java  | 196 +-------------------
 .../apache/couchdb/nouveau/IntegrationTest.java    |  10 +-
 .../couchdb/nouveau/api/SearchRequestTest.java     |   4 +-
 .../couchdb/nouveau/core/IndexManagerTest.java     |   2 +-
 ...ryTest.java => Lucene9AnalyzerFactoryTest.java} |   4 +-
 19 files changed, 443 insertions(+), 569 deletions(-)

diff --git a/.tool-versions b/.tool-versions
new file mode 100644
index 000000000..ad8836716
--- /dev/null
+++ b/.tool-versions
@@ -0,0 +1,4 @@
+java zulu-11.60.19
+maven 3.8.6
+elixir 1.13.4-otp-23
+erlang 23.3.4.14
diff --git a/Makefile b/Makefile
index 19c1af066..5c77b2868 100644
--- a/Makefile
+++ b/Makefile
@@ -516,7 +516,7 @@ derived:
 
 .PHONY: nouveau
 nouveau:
-	@cd java/nouveau && mvn
+	@cd java/nouveau && mvn test
 
 .PHONY: nouveau-clean
 nouveau-clean:
@@ -524,4 +524,4 @@ nouveau-clean:
 
 .PHONY: nouveau-start
 nouveau-start: nouveau
-	@cd java/nouveau && mvn exec:exec -Dexec.executable="java"
+	@cd java/nouveau/server && mvn exec:exec -Dexec.executable="java"
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
index a97f9e1a3..215483d84 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
@@ -13,14 +13,12 @@
 
 package org.apache.couchdb.nouveau;
 
-import java.util.concurrent.ExecutorService;
-
-import org.apache.couchdb.nouveau.core.AnalyzerFactory;
-import org.apache.couchdb.nouveau.core.DocumentFactory;
 import org.apache.couchdb.nouveau.core.FileAlreadyExistsExceptionMapper;
 import org.apache.couchdb.nouveau.core.FileNotFoundExceptionMapper;
 import org.apache.couchdb.nouveau.core.IndexManager;
-import org.apache.couchdb.nouveau.core.ParallelSearcherFactory;
+import org.apache.couchdb.nouveau.core.Lucene9AnalyzerFactory;
+import org.apache.couchdb.nouveau.core.Lucene9IndexFactory;
+import org.apache.couchdb.nouveau.core.Lucene9ParallelSearcherFactory;
 import org.apache.couchdb.nouveau.core.UpdatesOutOfOrderExceptionMapper;
 import org.apache.couchdb.nouveau.core.ser.LuceneModule;
 import org.apache.couchdb.nouveau.health.AnalyzeHealthCheck;
@@ -28,10 +26,10 @@ import org.apache.couchdb.nouveau.health.IndexManagerHealthCheck;
 import org.apache.couchdb.nouveau.resources.AnalyzeResource;
 import org.apache.couchdb.nouveau.resources.IndexResource;
 import org.apache.couchdb.nouveau.resources.SearchResource;
-import com.fasterxml.jackson.databind.ObjectMapper;
 
 import com.codahale.metrics.MetricRegistry;
 import com.codahale.metrics.jersey2.InstrumentedResourceMethodApplicationListener;
+import com.fasterxml.jackson.databind.ObjectMapper;
 
 import io.dropwizard.Application;
 import io.dropwizard.setup.Environment;
@@ -52,14 +50,14 @@ public class NouveauApplication extends Application<NouveauApplicationConfigurat
         final MetricRegistry metricsRegistry = new MetricRegistry();
         environment.jersey().register(new InstrumentedResourceMethodApplicationListener(metricsRegistry));
 
-        final DocumentFactory documentFactory = new DocumentFactory();
-        final AnalyzerFactory analyzerFactory = new AnalyzerFactory();
+        final Lucene9AnalyzerFactory analyzerFactory = new Lucene9AnalyzerFactory();
 
-        final ExecutorService searchExecutor =
-            environment.lifecycle().executorService("nouveau-search-%d").build();
+        final Lucene9ParallelSearcherFactory searcherFactory = new Lucene9ParallelSearcherFactory();
+        searcherFactory.setExecutor(environment.lifecycle().executorService("nouveau-search-thread-%d").build());
 
-        final ParallelSearcherFactory searcherFactory = new ParallelSearcherFactory();
-        searcherFactory.setExecutor(searchExecutor);
+        final Lucene9IndexFactory indexFactory = new Lucene9IndexFactory();
+        indexFactory.setAnalyzerFactory(analyzerFactory);
+        indexFactory.setSearcherFactory(searcherFactory);
 
         final ObjectMapper objectMapper = environment.getObjectMapper();
         objectMapper.registerModule(new LuceneModule());
@@ -70,9 +68,9 @@ public class NouveauApplication extends Application<NouveauApplicationConfigurat
         indexManager.setMaxIndexesOpen(configuration.getMaxIndexesOpen());
         indexManager.setCommitIntervalSeconds(configuration.getCommitIntervalSeconds());
         indexManager.setIdleSeconds(configuration.getIdleSeconds());
-        indexManager.setAnalyzerFactory(analyzerFactory);
         indexManager.setObjectMapper(objectMapper);
-        indexManager.setSearcherFactory(searcherFactory);
+        indexManager.setAnalyzerFactory(analyzerFactory);
+        indexManager.setIndexFactory(indexFactory);
         environment.lifecycle().manage(indexManager);
 
         environment.jersey().register(new FileNotFoundExceptionMapper());
@@ -81,7 +79,7 @@ public class NouveauApplication extends Application<NouveauApplicationConfigurat
 
         final AnalyzeResource analyzeResource = new AnalyzeResource(analyzerFactory);
         environment.jersey().register(analyzeResource);
-        environment.jersey().register(new IndexResource(indexManager, documentFactory));
+        environment.jersey().register(new IndexResource(indexManager));
         environment.jersey().register(new SearchResource(indexManager));
 
         // health checks
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/DocumentFactory.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/DocumentFactory.java
deleted file mode 100644
index 904a215c0..000000000
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/DocumentFactory.java
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-// Licensed 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.
-
-package org.apache.couchdb.nouveau.core;
-
-import java.io.IOException;
-
-import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
-
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field.Store;
-import org.apache.lucene.index.IndexableField;
-import org.apache.lucene.util.BytesRef;
-
-public class DocumentFactory {
-
-    public Document build(final String docId, final DocumentUpdateRequest request) throws IOException {
-        final Document result = new Document();
-
-        // id
-        result.add(new org.apache.lucene.document.StringField("_id", docId, Store.YES));
-        result.add(new org.apache.lucene.document.SortedDocValuesField("_id", new BytesRef(docId)));
-
-        // partition (optional)
-        if (request.hasPartition()) {
-            result.add(new org.apache.lucene.document.StringField("_partition", request.getPartition(), Store.NO));
-        }
-
-        for (IndexableField field : request.getFields()) {
-            // Underscore-prefix is reserved.
-            if (field.name().startsWith("_")) {
-                continue;
-            }
-            result.add(field);
-        }
-
-        return result;
-    }
-
-}
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Index.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Index.java
new file mode 100644
index 000000000..739768913
--- /dev/null
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Index.java
@@ -0,0 +1,176 @@
+//
+// Licensed 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.
+
+package org.apache.couchdb.nouveau.core;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.couchdb.nouveau.api.DocumentDeleteRequest;
+import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
+import org.apache.couchdb.nouveau.api.IndexInfo;
+import org.apache.couchdb.nouveau.api.SearchRequest;
+import org.apache.couchdb.nouveau.api.SearchResults;
+
+public abstract class Index implements Closeable {
+
+    /*
+     * The close lock is to ensure there are no readers/searchers when
+     * we want to close the index.
+     */
+    private ReentrantReadWriteLock closeLock = new ReentrantReadWriteLock();
+
+    /*
+     * The update lock ensures serial updates to the index.
+     */
+    private ReentrantReadWriteLock updateLock = new ReentrantReadWriteLock();
+
+    private long updateSeq;
+
+    private boolean deleteOnClose = false;
+
+    private boolean closed = false;
+
+    protected Index(final long updateSeq) {
+        this.updateSeq = updateSeq;
+    }
+
+    public final IndexInfo info() throws IOException {
+        final long updateSeq = getUpdateSeq();
+        closeLock.readLock().lock();
+        try {
+            final int numDocs = doNumDocs();
+            return new IndexInfo(updateSeq, numDocs);
+        } finally {
+            closeLock.readLock().unlock();
+        }
+    }
+
+    protected abstract int doNumDocs() throws IOException;
+
+    public final void update(final String docId, final DocumentUpdateRequest request) throws IOException {
+        updateLock.writeLock().lock();
+        try {
+            assertUpdateSeqIsLower(request.getSeq());
+            closeLock.readLock().lock();
+            try {
+                doUpdate(docId, request);
+            } finally {
+                closeLock.readLock().unlock();
+            }
+            incrementUpdateSeq(request.getSeq());
+        } finally {
+            updateLock.writeLock().unlock();
+        }
+    }
+
+    protected abstract void doUpdate(final String docId, final DocumentUpdateRequest request) throws IOException;
+
+    public final void delete(final String docId, final DocumentDeleteRequest request) throws IOException {
+        updateLock.writeLock().lock();
+        try {
+            assertUpdateSeqIsLower(request.getSeq());
+            closeLock.readLock().lock();
+            try {
+                doDelete(docId, request);
+            } finally {
+                closeLock.readLock().unlock();
+            }
+            incrementUpdateSeq(request.getSeq());
+        } finally {
+            updateLock.writeLock().unlock();
+        }
+    }
+
+    protected abstract void doDelete(final String docId, final DocumentDeleteRequest request) throws IOException;
+
+    public final SearchResults search(final SearchRequest request) throws IOException, QueryParserException {
+        closeLock.readLock().lock();
+        try {
+            return doSearch(request);
+        } finally {
+            closeLock.readLock().unlock();
+        }
+    }
+
+    protected abstract SearchResults doSearch(final SearchRequest request) throws IOException, QueryParserException;
+
+    public final boolean commit() throws IOException {
+        final long updateSeq = getUpdateSeq();
+        closeLock.readLock().lock();
+        try {
+            return doCommit(updateSeq);
+        } finally {
+            closeLock.readLock().unlock();
+        }
+    }
+
+    protected abstract boolean doCommit(final long updateSeq) throws IOException;
+
+    public final void close() throws IOException {
+        closeLock.writeLock().lock();
+        try {
+            doClose(deleteOnClose);
+            closed = true;
+        } finally {
+            closeLock.writeLock().unlock();
+        }
+    }
+
+    protected abstract void doClose(final boolean deleteOnClose) throws IOException;
+
+    public final void lock() {
+        closeLock.readLock().lock();
+    }
+
+    public final void unlock() {
+        closeLock.readLock().unlock();
+    }
+
+    public final boolean isClosed() {
+        return closed;
+    }
+
+    public final void setDeleteOnClose(final boolean deleteOnClose) {
+        closeLock.writeLock().lock();
+        try {
+            this.deleteOnClose = true;
+        } finally {
+            closeLock.writeLock().unlock();
+        }
+    }
+
+    protected final void assertUpdateSeqIsLower(final long updateSeq) throws UpdatesOutOfOrderException {
+        assert updateLock.isWriteLockedByCurrentThread();
+        if (!(updateSeq > this.updateSeq)) {
+            throw new UpdatesOutOfOrderException();
+        }
+    }
+
+    protected final void incrementUpdateSeq(final long updateSeq) throws IOException {
+        assert updateLock.isWriteLockedByCurrentThread();
+        assertUpdateSeqIsLower(updateSeq);
+        this.updateSeq = updateSeq;
+    }
+
+    private long getUpdateSeq() {
+        updateLock.readLock().lock();
+        try {
+            return this.updateSeq;
+        } finally {
+            updateLock.readLock().unlock();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ParallelSearcherFactory.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexFactory.java
similarity index 50%
copy from java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ParallelSearcherFactory.java
copy to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexFactory.java
index bd31801fd..8d728c080 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ParallelSearcherFactory.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexFactory.java
@@ -14,27 +14,12 @@
 package org.apache.couchdb.nouveau.core;
 
 import java.io.IOException;
-import java.util.concurrent.Executor;
+import java.nio.file.Path;
 
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.SearcherFactory;
+import org.apache.couchdb.nouveau.api.IndexDefinition;
 
-public class ParallelSearcherFactory extends SearcherFactory {
+public interface IndexFactory {
 
-    private Executor executor;
-
-    public Executor getExecutor() {
-        return executor;
-    }
-
-    public void setExecutor(Executor executor) {
-        this.executor = executor;
-    }
-
-    @Override
-    public IndexSearcher newSearcher(final IndexReader reader, final IndexReader previousReader) throws IOException {
-        return new IndexSearcher(reader, executor);
-    }
+    Index open(final Path path, final IndexDefinition indexDefinition) throws IOException;
 
 }
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java
index dd0f1f2e0..a5bb2970b 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java
@@ -18,13 +18,7 @@ import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.time.Duration;
-import java.util.Collections;
-import java.util.Map;
 import java.util.concurrent.CompletionException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.stream.Stream;
 
 import javax.validation.constraints.Min;
@@ -34,21 +28,13 @@ import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Response.Status;
 
 import org.apache.couchdb.nouveau.api.IndexDefinition;
-import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.IndexWriterConfig;
-import org.apache.lucene.misc.store.DirectIODirectory;
-import org.apache.lucene.search.SearcherFactory;
-import org.apache.lucene.search.SearcherManager;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.store.FSDirectory;
-import org.apache.lucene.store.LockObtainFailedException;
-import org.apache.lucene.util.IOUtils;
 import org.checkerframework.checker.nullness.qual.NonNull;
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.caffeine.MetricsStatsCounter;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.github.benmanes.caffeine.cache.CacheLoader;
@@ -58,9 +44,6 @@ import com.github.benmanes.caffeine.cache.RemovalCause;
 import com.github.benmanes.caffeine.cache.RemovalListener;
 import com.github.benmanes.caffeine.cache.Scheduler;
 
-import com.codahale.metrics.MetricRegistry;
-import com.codahale.metrics.caffeine.MetricsStatsCounter;
-
 import io.dropwizard.lifecycle.Managed;
 
 public class IndexManager implements Managed {
@@ -69,124 +52,6 @@ public class IndexManager implements Managed {
     private static final int RETRY_SLEEP_MS = 5;
     private static final Logger LOGGER = LoggerFactory.getLogger(IndexManager.class);
 
-    public class Index {
-        private static final String DEFAULT_FIELD = "default";
-        private final String name;
-        private IndexWriter writer;
-        private SearcherManager searcherManager;
-        private Analyzer analyzer;
-        private final AtomicBoolean deleteOnClose = new AtomicBoolean();
-        private final AtomicLong updateSeq = new AtomicLong();
-
-        // The write lock is to ensure there are no readers/searchers when
-        // we want to close the index.
-        private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
-        private Lock rl = rwl.readLock();
-        private Lock wl = rwl.writeLock();
-
-        private Index(
-                      String name,
-                      IndexWriter writer,
-                      SearcherManager searcherManager,
-                      Analyzer analyzer,
-                      long updateSeq) {
-            this.name = name;
-            this.writer = writer;
-            this.searcherManager = searcherManager;
-            this.analyzer = analyzer;
-            this.updateSeq.set(updateSeq);
-        }
-
-        public String getName() {
-            return name;
-        }
-
-        public IndexWriter getWriter() {
-            return writer;
-        }
-
-        public SearcherManager getSearcherManager() {
-            return searcherManager;
-        }
-
-        public QueryParser getQueryParser() {
-            return new NouveauQueryParser(DEFAULT_FIELD, analyzer);
-        }
-
-        public boolean commit() throws IOException {
-            rl.lock();
-            try {
-                writer.setLiveCommitData(generateCommitData().entrySet());
-                return writer.commit() != -1;
-            } finally {
-                rl.unlock();
-            }
-        }
-
-        public long getUpdateSeq() throws IOException {
-            return updateSeq.get();
-        }
-
-        public void incrementUpdateSeq(final long updateSeq) throws IOException {
-            final long newSeq = this.updateSeq.accumulateAndGet(updateSeq, (a, b) -> Math.max(a, b));
-            if (newSeq != updateSeq) {
-                throw new UpdatesOutOfOrderException();
-            }
-        }
-
-        public void close() throws IOException {
-            wl.lock();
-            try {
-                if (writer == null) {
-                    // Already closed.
-                    return;
-                }
-
-                // Close searcher manager
-                if (searcherManager != null) {
-                    try {
-                        searcherManager.close();
-                    } catch (IOException e) {
-                        LOGGER.info(this + " threw exception when closing searcherManager.", e);
-                    } finally {
-                        searcherManager = null;
-                    }
-                }
-
-                if (deleteOnClose.get()) {
-                    try {
-                        // No need to commit in this case.
-                        writer.rollback();
-                    } catch (IOException e) {
-                        LOGGER.info(this + " threw exception when rolling back writer.", e);
-                    } finally {
-                        writer = null;
-                    }
-                    IOUtils.rm(indexRootPath(name));
-                } else {
-                    try {
-                        writer.setLiveCommitData(generateCommitData().entrySet());
-                        writer.close();
-                        LOGGER.info("{} closed.", this);
-                    } finally {
-                        writer = null;
-                    }
-                }
-            } finally {
-                wl.unlock();
-            }
-        }
-
-        private Map<String, String> generateCommitData() {
-            return Collections.singletonMap("update_seq", Long.toString(updateSeq.get()));
-        }
-
-        @Override
-        public String toString() {
-            return "Index [name=" + name + "]";
-        }
-    }
-
     private class IndexLoader implements CacheLoader<String, Index> {
 
         @Override
@@ -237,12 +102,12 @@ public class IndexManager implements Managed {
     private Path rootDir;
 
     @NotNull
-    private AnalyzerFactory analyzerFactory;
+    private Lucene9AnalyzerFactory analyzerFactory;
 
     @NotNull
     private ObjectMapper objectMapper;
 
-    private SearcherFactory searcherFactory;
+    private IndexFactory indexFactory;
 
     private MetricRegistry metricRegistry;
 
@@ -253,11 +118,11 @@ public class IndexManager implements Managed {
             final Index result = getFromCache(name);
 
             // Check if we're in the middle of closing.
-            result.rl.lock();
-            if (result.writer != null) {
+            result.lock();
+            if (!result.isClosed()) {
                 return result;
             }
-            result.rl.unlock();
+            result.unlock();
 
             // Retry after a short sleep.
             try {
@@ -271,7 +136,7 @@ public class IndexManager implements Managed {
     }
 
     public void release(final Index index) throws IOException {
-        index.rl.unlock();
+        index.unlock();
     }
 
     public void create(final String name, IndexDefinition indexDefinition) throws IOException {
@@ -301,7 +166,7 @@ public class IndexManager implements Managed {
     private void deleteIndex(final String name) throws IOException {
         final Index index = acquire(name);
         try {
-            index.deleteOnClose.set(true);
+            index.setDeleteOnClose(true);
             cache.invalidate(name);
         } finally {
             release(index);
@@ -344,7 +209,7 @@ public class IndexManager implements Managed {
         this.rootDir = rootDir;
     }
 
-    public void setAnalyzerFactory(final AnalyzerFactory analyzerFactory) {
+    public void setAnalyzerFactory(final Lucene9AnalyzerFactory analyzerFactory) {
         this.analyzerFactory = analyzerFactory;
     }
 
@@ -352,8 +217,8 @@ public class IndexManager implements Managed {
         this.objectMapper = objectMapper;
     }
 
-    public void setSearcherFactory(final SearcherFactory searcherFactory) {
-        this.searcherFactory = searcherFactory;
+    public void setIndexFactory(final IndexFactory indexFactory) {
+        this.indexFactory = indexFactory;
     }
 
     public void setMetricRegistry(final MetricRegistry metricRegistry) {
@@ -405,48 +270,9 @@ public class IndexManager implements Managed {
     }
 
     private Index openExistingIndex(final String name) throws IOException {
-        final IndexDefinition indexDefinition = objectMapper.readValue(indexDefinitionPath(name).toFile(), IndexDefinition.class);
-        final Analyzer analyzer =  analyzerFactory.fromDefinition(indexDefinition);
         final Path path = indexPath(name);
-        final Directory dir = directory(path);
-        final IndexWriter writer = newWriter(dir, analyzer);
-        final SearcherManager searcherManager = new SearcherManager(writer, searcherFactory);
-        final long updateSeq = getUpdateSeq(writer);
-        return new Index(name, writer, searcherManager, analyzer, updateSeq);
-    }
-
-    private long getUpdateSeq(final IndexWriter writer) throws IOException {
-        final Iterable<Map.Entry<String, String>> commitData = writer.getLiveCommitData();
-        if (commitData == null) {
-            return 0L;
-        }
-        for (Map.Entry<String, String> entry : commitData) {
-            if (entry.getKey().equals("update_seq")) {
-                return Long.parseLong(entry.getValue());
-            }
-        }
-        return 0L;
-    }
-
-    private IndexWriter newWriter(final Directory dir, final Analyzer analyzer) throws IOException {
-        LockObtainFailedException exceptionThrown = null;
-        for (int i = 0; i < RETRY_LIMIT; i++) {
-            try {
-                final IndexWriterConfig config = new IndexWriterConfig(analyzer);
-                config.setCommitOnClose(true);
-                config.setUseCompoundFile(false);
-                return new IndexWriter(dir, config);
-            } catch (LockObtainFailedException e) {
-                exceptionThrown = e;
-                try {
-                    Thread.sleep(RETRY_SLEEP_MS);
-                } catch (InterruptedException e1) {
-                    Thread.interrupted();
-                    break;
-                }
-            }
-        }
-        throw exceptionThrown;
+        final IndexDefinition indexDefinition = objectMapper.readValue(indexDefinitionPath(name).toFile(), IndexDefinition.class);
+        return indexFactory.open(path, indexDefinition);
     }
 
     private boolean isIndex(final Path path) {
@@ -470,8 +296,4 @@ public class IndexManager implements Managed {
                 Status.BAD_REQUEST);
     }
 
-    private Directory directory(final Path path) throws IOException {
-        return new DirectIODirectory(FSDirectory.open(path));
-    }
-
 }
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/AnalyzerFactory.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9AnalyzerFactory.java
similarity index 98%
rename from java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/AnalyzerFactory.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9AnalyzerFactory.java
index 0ad6c0311..5544af267 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/AnalyzerFactory.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9AnalyzerFactory.java
@@ -62,7 +62,10 @@ import org.apache.lucene.analysis.sv.SwedishAnalyzer;
 import org.apache.lucene.analysis.th.ThaiAnalyzer;
 import org.apache.lucene.analysis.tr.TurkishAnalyzer;
 
-public class AnalyzerFactory {
+public final class Lucene9AnalyzerFactory {
+
+    public Lucene9AnalyzerFactory() {
+    }
 
     public Analyzer fromDefinition(final IndexDefinition indexDefinition) {
         final Analyzer defaultAnalyzer = newAnalyzer(indexDefinition.getDefaultAnalyzer());
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java
similarity index 57%
copy from java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
copy to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java
index dd78e861c..d62beff9b 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9Index.java
@@ -11,10 +11,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.couchdb.nouveau.resources;
+package org.apache.couchdb.nouveau.core;
 
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -22,26 +24,17 @@ import java.util.Map.Entry;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import javax.validation.Valid;
-import javax.validation.constraints.NotNull;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
 import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response.Status;
 
+import org.apache.couchdb.nouveau.api.DocumentDeleteRequest;
+import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
 import org.apache.couchdb.nouveau.api.SearchHit;
 import org.apache.couchdb.nouveau.api.SearchRequest;
 import org.apache.couchdb.nouveau.api.SearchResults;
-import org.apache.couchdb.nouveau.core.IndexManager;
-import org.apache.couchdb.nouveau.core.IndexManager.Index;
-import org.apache.couchdb.nouveau.core.QueryParserException;
-import com.codahale.metrics.annotation.Timed;
-
+import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field.Store;
 import org.apache.lucene.facet.FacetResult;
 import org.apache.lucene.facet.Facets;
 import org.apache.lucene.facet.FacetsCollector;
@@ -51,66 +44,100 @@ import org.apache.lucene.facet.StringDocValuesReaderState;
 import org.apache.lucene.facet.StringValueFacetCounts;
 import org.apache.lucene.facet.range.DoubleRange;
 import org.apache.lucene.facet.range.DoubleRangeFacetCounts;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.misc.store.DirectIODirectory;
 import org.apache.lucene.search.CollectorManager;
 import org.apache.lucene.search.FieldDoc;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.MultiCollectorManager;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.SearcherFactory;
 import org.apache.lucene.search.SearcherManager;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.search.TopFieldCollector;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.util.BytesRef;
 
-@Path("/index/{name}")
-@Consumes(MediaType.APPLICATION_JSON)
-@Produces(MediaType.APPLICATION_JSON)
-public class SearchResource {
+class Lucene9Index extends Index {
 
     private static final DoubleRange[] EMPTY_DOUBLE_RANGE_ARRAY = new DoubleRange[0];
     private static final Sort DEFAULT_SORT = new Sort(SortField.FIELD_SCORE,
             new SortField("_id", SortField.Type.STRING));
     private static final Pattern SORT_FIELD_RE = Pattern.compile("^([-+])?([\\.\\w]+)(?:<(\\w+)>)?$");
-    private final IndexManager indexManager;
 
-    public SearchResource(final IndexManager indexManager) {
-        this.indexManager = indexManager;
+    private final Analyzer analyzer;
+    private final IndexWriter writer;
+    private final SearcherManager searcherManager;
+
+    static Index open(final Path path, final Analyzer analyzer, final SearcherFactory searcherFactory)
+            throws IOException {
+        final Directory dir = new DirectIODirectory(FSDirectory.open(path));
+        final IndexWriterConfig config = new IndexWriterConfig(analyzer);
+        config.setCommitOnClose(true);
+        config.setUseCompoundFile(false);
+        final IndexWriter writer = new IndexWriter(dir, config);
+        final long updateSeq = getUpdateSeq(writer);
+        final SearcherManager searcherManager = new SearcherManager(writer, searcherFactory);
+        return new Lucene9Index(analyzer, writer, updateSeq, searcherManager);
     }
 
-    @POST
-    @Timed
-    @Path("/search")
-    public SearchResults searchIndex(@PathParam("name") String name, @NotNull @Valid SearchRequest searchRequest)
-            throws IOException, QueryParserException {
-        final Index index = indexManager.acquire(name);
-        try {
-            final Query query = index.getQueryParser().parse(searchRequest);
+    private Lucene9Index(final Analyzer analyzer, final IndexWriter writer, final long updateSeq,
+            final SearcherManager searcherManager) {
+        super(updateSeq);
+        this.analyzer = analyzer;
+        this.writer = writer;
+        this.searcherManager = searcherManager;
+    }
 
-            // Construct CollectorManagers.
-            final MultiCollectorManager cm;
-            final CollectorManager<?, ? extends TopDocs> hits = hitCollector(searchRequest);
+    @Override
+    public int doNumDocs() throws IOException {
+        return writer.getDocStats().numDocs;
+    }
 
-            final SearcherManager searcherManager = index.getSearcherManager();
-            searcherManager.maybeRefreshBlocking();
+    @Override
+    public void doUpdate(final String docId, final DocumentUpdateRequest request) throws IOException {
+        final Term docIdTerm = docIdTerm(docId);
+        final Document doc = toDocument(docId, request);
+        writer.updateDocument(docIdTerm, doc);
+    }
 
-            final IndexSearcher searcher = searcherManager.acquire();
-            try {
-                if (searchRequest.hasCounts() || searchRequest.hasRanges()) {
-                    cm = new MultiCollectorManager(hits, new FacetsCollectorManager());
-                } else {
-                    cm = new MultiCollectorManager(hits);
-                }
-                final Object[] reduces = searcher.search(query, cm);
-                return toSearchResults(searchRequest, searcher, reduces);
-            } catch (IllegalStateException e) {
-                throw new WebApplicationException(e.getMessage(), e, Status.BAD_REQUEST);
-            } finally {
-                searcherManager.release(searcher);
+    @Override
+    public void doDelete(final String docId, final DocumentDeleteRequest request) throws IOException {
+        final Query query = docIdQuery(docId);
+        writer.deleteDocuments(query);
+    }
+
+    @Override
+    public SearchResults doSearch(final SearchRequest request) throws IOException, QueryParserException {
+        final Query query = newQueryParser().parse(request);
+
+        // Construct CollectorManagers.
+        final MultiCollectorManager cm;
+        final CollectorManager<?, ? extends TopDocs> hits = hitCollector(request);
+
+        searcherManager.maybeRefreshBlocking();
+
+        final IndexSearcher searcher = searcherManager.acquire();
+        try {
+            if (request.hasCounts() || request.hasRanges()) {
+                cm = new MultiCollectorManager(hits, new FacetsCollectorManager());
+            } else {
+                cm = new MultiCollectorManager(hits);
             }
+            final Object[] reduces = searcher.search(query, cm);
+            return toSearchResults(request, searcher, reduces);
+        } catch (IllegalStateException e) {
+            throw new WebApplicationException(e.getMessage(), e, Status.BAD_REQUEST);
         } finally {
-            indexManager.release(index);
+            searcherManager.release(searcher);
         }
     }
 
@@ -214,12 +241,12 @@ public class SearchResource {
         final List<String> sort = new ArrayList<String>(searchRequest.getSort());
         final String last = sort.get(sort.size() - 1);
         // Append _id field if not already present.
-        switch(last) {
+        switch (last) {
             case "-_id<string>":
             case "_id<string>":
                 break;
             default:
-            sort.add("_id<string>");
+                sort.add("_id<string>");
         }
         return convertSort(sort);
     }
@@ -246,4 +273,71 @@ public class SearchResource {
         return new SortField(m.group(2), type, reverse);
     }
 
+    @Override
+    public boolean doCommit(final long updateSeq) throws IOException {
+        writer.setLiveCommitData(Collections.singletonMap("update_seq", Long.toString(updateSeq)).entrySet());
+        return writer.commit() != -1;
+    }
+
+    @Override
+    public void doClose(final boolean deleteOnClose) throws IOException {
+        if (deleteOnClose) {
+            // No need to commit in this case.
+            writer.rollback();
+            final Directory dir = writer.getDirectory();
+            for (final String name : dir.listAll()) {
+                dir.deleteFile(name);
+            }
+        }
+        writer.close();
+    }
+
+    private static Document toDocument(final String docId, final DocumentUpdateRequest request) throws IOException {
+        final Document result = new Document();
+
+        // id
+        result.add(new org.apache.lucene.document.StringField("_id", docId, Store.YES));
+        result.add(new org.apache.lucene.document.SortedDocValuesField("_id", new BytesRef(docId)));
+
+        // partition (optional)
+        if (request.hasPartition()) {
+            result.add(new org.apache.lucene.document.StringField("_partition", request.getPartition(), Store.NO));
+        }
+
+        for (IndexableField field : request.getFields()) {
+            // Underscore-prefix is reserved.
+            if (field.name().startsWith("_")) {
+                continue;
+            }
+            result.add(field);
+        }
+
+        return result;
+    }
+
+    private static Query docIdQuery(final String docId) {
+        return new TermQuery(docIdTerm(docId));
+    }
+
+    private static Term docIdTerm(final String docId) {
+        return new Term("_id", docId);
+    }
+
+    private static long getUpdateSeq(final IndexWriter writer) throws IOException {
+        final Iterable<Map.Entry<String, String>> commitData = writer.getLiveCommitData();
+        if (commitData == null) {
+            return 0L;
+        }
+        for (Map.Entry<String, String> entry : commitData) {
+            if (entry.getKey().equals("update_seq")) {
+                return Long.parseLong(entry.getValue());
+            }
+        }
+        return 0L;
+    }
+
+    public QueryParser newQueryParser() {
+        return new NouveauQueryParser("default", analyzer);
+    }
+
 }
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9IndexFactory.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9IndexFactory.java
new file mode 100644
index 000000000..016f427c1
--- /dev/null
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9IndexFactory.java
@@ -0,0 +1,43 @@
+//
+// Licensed 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.
+
+package org.apache.couchdb.nouveau.core;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.apache.couchdb.nouveau.api.IndexDefinition;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.search.SearcherFactory;
+
+public class Lucene9IndexFactory implements IndexFactory {
+
+    private Lucene9AnalyzerFactory analyzerFactory;
+
+    private SearcherFactory searcherFactory;
+
+    public void setAnalyzerFactory(final Lucene9AnalyzerFactory analyzerFactory) {
+        this.analyzerFactory = analyzerFactory;
+    }
+
+    public void setSearcherFactory(final SearcherFactory searcherFactory) {
+        this.searcherFactory = searcherFactory;
+    }
+
+    @Override
+    public Index open(Path path, final IndexDefinition indexDefinition) throws IOException {
+        final Analyzer analyzer = analyzerFactory.fromDefinition(indexDefinition);
+        return Lucene9Index.open(path, analyzer, searcherFactory);
+    }
+
+}
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ParallelSearcherFactory.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9ParallelSearcherFactory.java
similarity index 94%
rename from java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ParallelSearcherFactory.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9ParallelSearcherFactory.java
index bd31801fd..7021f2997 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ParallelSearcherFactory.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/Lucene9ParallelSearcherFactory.java
@@ -20,7 +20,7 @@ import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.SearcherFactory;
 
-public class ParallelSearcherFactory extends SearcherFactory {
+public class Lucene9ParallelSearcherFactory extends SearcherFactory {
 
     private Executor executor;
 
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java
index b2653db38..e76d8da9c 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java
@@ -13,16 +13,17 @@
 
 package org.apache.couchdb.nouveau.health;
 
+import static org.apache.couchdb.nouveau.api.LuceneVersion.LUCENE_9;
+
 import java.io.IOException;
+import java.util.Collections;
 
+import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
 import org.apache.couchdb.nouveau.api.IndexDefinition;
-import static org.apache.couchdb.nouveau.api.LuceneVersion.*;
+import org.apache.couchdb.nouveau.core.Index;
 import org.apache.couchdb.nouveau.core.IndexManager;
-import org.apache.couchdb.nouveau.core.IndexManager.Index;
-import com.codahale.metrics.health.HealthCheck;
 
-import org.apache.lucene.document.Document;
-import org.apache.lucene.index.IndexWriter;
+import com.codahale.metrics.health.HealthCheck;
 
 public class IndexManagerHealthCheck extends HealthCheck {
 
@@ -44,14 +45,14 @@ public class IndexManagerHealthCheck extends HealthCheck {
         indexManager.create(name, new IndexDefinition(LUCENE_9, "standard", null));
         final Index index = indexManager.acquire(name);
         try {
-            final IndexWriter writer = index.getWriter();
-            try {
-                writer.addDocument(new Document());
-                writer.commit();
-                return Result.healthy();
-            } finally {
-                indexManager.deleteAll(name);
-            }
+            final DocumentUpdateRequest request = new DocumentUpdateRequest(1, null, Collections.emptyList());
+            index.update("foo", request);
+            index.commit();
+            index.setDeleteOnClose(true);
+            index.close();
+            return Result.healthy();
+        } catch (final IOException e) {
+            return Result.unhealthy(e);
         } finally {
             indexManager.release(index);
         }
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/AnalyzeResource.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/AnalyzeResource.java
index 60e8c8ca3..c495cf5fa 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/AnalyzeResource.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/AnalyzeResource.java
@@ -29,7 +29,7 @@ import javax.ws.rs.core.Response.Status;
 
 import org.apache.couchdb.nouveau.api.AnalyzeRequest;
 import org.apache.couchdb.nouveau.api.AnalyzeResponse;
-import org.apache.couchdb.nouveau.core.AnalyzerFactory;
+import org.apache.couchdb.nouveau.core.Lucene9AnalyzerFactory;
 import com.codahale.metrics.annotation.Timed;
 
 import org.apache.lucene.analysis.Analyzer;
@@ -41,9 +41,9 @@ import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 @Produces(MediaType.APPLICATION_JSON)
 public class AnalyzeResource {
 
-    private final AnalyzerFactory analyzerFactory;
+    private final Lucene9AnalyzerFactory analyzerFactory;
 
-    public AnalyzeResource(AnalyzerFactory analyzerFactory) {
+    public AnalyzeResource(Lucene9AnalyzerFactory analyzerFactory) {
         this.analyzerFactory = analyzerFactory;
     }
 
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java
index cd10226db..ca7ee15c0 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java
@@ -30,15 +30,10 @@ import org.apache.couchdb.nouveau.api.DocumentDeleteRequest;
 import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
 import org.apache.couchdb.nouveau.api.IndexDefinition;
 import org.apache.couchdb.nouveau.api.IndexInfo;
-import org.apache.couchdb.nouveau.core.DocumentFactory;
+import org.apache.couchdb.nouveau.core.Index;
 import org.apache.couchdb.nouveau.core.IndexManager;
-import org.apache.couchdb.nouveau.core.IndexManager.Index;
-import com.codahale.metrics.annotation.Timed;
 
-import org.apache.lucene.document.Document;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.search.TermQuery;
+import com.codahale.metrics.annotation.Timed;
 
 @Path("/index/{name}")
 @Consumes(MediaType.APPLICATION_JSON)
@@ -46,26 +41,20 @@ import org.apache.lucene.search.TermQuery;
 public class IndexResource {
 
     private final IndexManager indexManager;
-    private final DocumentFactory documentFactory;
 
-    public IndexResource(final IndexManager indexManager, final DocumentFactory documentFactory) {
+    public IndexResource(final IndexManager indexManager) {
         this.indexManager = indexManager;
-        this.documentFactory = documentFactory;
     }
 
     @GET
     @SuppressWarnings("resource")
     public IndexInfo indexInfo(@PathParam("name") String name) throws IOException {
-        final long updateSeq;
-        final int numDocs;
         final Index index = indexManager.acquire(name);
         try {
-            updateSeq = index.getUpdateSeq();
-            numDocs = index.getWriter().getDocStats().numDocs;
+            return index.info();
         } finally {
             indexManager.release(index);
         }
-        return new IndexInfo(updateSeq, numDocs);
     }
 
     @DELETE
@@ -84,9 +73,7 @@ public class IndexResource {
     public void deleteDoc(@PathParam("name") String name, @PathParam("docId") String docId, @NotNull @Valid final DocumentDeleteRequest request) throws IOException {
         final Index index = indexManager.acquire(name);
         try {
-            final IndexWriter writer = index.getWriter();
-            writer.deleteDocuments(new TermQuery(new Term("_id", docId)));
-            index.incrementUpdateSeq(request.getSeq());
+            index.delete(docId, request);
         } finally {
             indexManager.release(index);
         }
@@ -98,10 +85,7 @@ public class IndexResource {
     public void updateDoc(@PathParam("name") String name, @PathParam("docId") String docId, @NotNull @Valid final DocumentUpdateRequest request) throws IOException {
         final Index index = indexManager.acquire(name);
         try {
-            final IndexWriter writer = index.getWriter();
-            final Document doc = documentFactory.build(docId, request);
-            writer.updateDocument(new Term("_id", docId), doc);
-            index.incrementUpdateSeq(request.getSeq());
+            index.update(docId, request);
         } finally {
             indexManager.release(index);
         }
diff --git a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
index dd78e861c..03f6c0c20 100644
--- a/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
+++ b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
@@ -14,13 +14,6 @@
 package org.apache.couchdb.nouveau.resources;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import javax.validation.Valid;
 import javax.validation.constraints.NotNull;
@@ -29,50 +22,21 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
-import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response.Status;
 
-import org.apache.couchdb.nouveau.api.SearchHit;
 import org.apache.couchdb.nouveau.api.SearchRequest;
 import org.apache.couchdb.nouveau.api.SearchResults;
+import org.apache.couchdb.nouveau.core.Index;
 import org.apache.couchdb.nouveau.core.IndexManager;
-import org.apache.couchdb.nouveau.core.IndexManager.Index;
 import org.apache.couchdb.nouveau.core.QueryParserException;
-import com.codahale.metrics.annotation.Timed;
 
-import org.apache.lucene.document.Document;
-import org.apache.lucene.facet.FacetResult;
-import org.apache.lucene.facet.Facets;
-import org.apache.lucene.facet.FacetsCollector;
-import org.apache.lucene.facet.FacetsCollectorManager;
-import org.apache.lucene.facet.LabelAndValue;
-import org.apache.lucene.facet.StringDocValuesReaderState;
-import org.apache.lucene.facet.StringValueFacetCounts;
-import org.apache.lucene.facet.range.DoubleRange;
-import org.apache.lucene.facet.range.DoubleRangeFacetCounts;
-import org.apache.lucene.index.IndexableField;
-import org.apache.lucene.search.CollectorManager;
-import org.apache.lucene.search.FieldDoc;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.MultiCollectorManager;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.ScoreDoc;
-import org.apache.lucene.search.SearcherManager;
-import org.apache.lucene.search.Sort;
-import org.apache.lucene.search.SortField;
-import org.apache.lucene.search.TopDocs;
-import org.apache.lucene.search.TopFieldCollector;
+import com.codahale.metrics.annotation.Timed;
 
 @Path("/index/{name}")
 @Consumes(MediaType.APPLICATION_JSON)
 @Produces(MediaType.APPLICATION_JSON)
 public class SearchResource {
 
-    private static final DoubleRange[] EMPTY_DOUBLE_RANGE_ARRAY = new DoubleRange[0];
-    private static final Sort DEFAULT_SORT = new Sort(SortField.FIELD_SCORE,
-            new SortField("_id", SortField.Type.STRING));
-    private static final Pattern SORT_FIELD_RE = Pattern.compile("^([-+])?([\\.\\w]+)(?:<(\\w+)>)?$");
     private final IndexManager indexManager;
 
     public SearchResource(final IndexManager indexManager) {
@@ -86,164 +50,10 @@ public class SearchResource {
             throws IOException, QueryParserException {
         final Index index = indexManager.acquire(name);
         try {
-            final Query query = index.getQueryParser().parse(searchRequest);
-
-            // Construct CollectorManagers.
-            final MultiCollectorManager cm;
-            final CollectorManager<?, ? extends TopDocs> hits = hitCollector(searchRequest);
-
-            final SearcherManager searcherManager = index.getSearcherManager();
-            searcherManager.maybeRefreshBlocking();
-
-            final IndexSearcher searcher = searcherManager.acquire();
-            try {
-                if (searchRequest.hasCounts() || searchRequest.hasRanges()) {
-                    cm = new MultiCollectorManager(hits, new FacetsCollectorManager());
-                } else {
-                    cm = new MultiCollectorManager(hits);
-                }
-                final Object[] reduces = searcher.search(query, cm);
-                return toSearchResults(searchRequest, searcher, reduces);
-            } catch (IllegalStateException e) {
-                throw new WebApplicationException(e.getMessage(), e, Status.BAD_REQUEST);
-            } finally {
-                searcherManager.release(searcher);
-            }
+            return index.search(searchRequest);
         } finally {
             indexManager.release(index);
         }
     }
 
-    private CollectorManager<?, ? extends TopDocs> hitCollector(final SearchRequest searchRequest) {
-        final Sort sort = toSort(searchRequest);
-
-        final FieldDoc after = searchRequest.getAfter();
-        if (after != null) {
-            if (getLastSortField(sort).getReverse()) {
-                after.doc = 0;
-            } else {
-                after.doc = Integer.MAX_VALUE;
-            }
-        }
-
-        return TopFieldCollector.createSharedManager(
-                sort,
-                searchRequest.getLimit(),
-                after,
-                1000);
-    }
-
-    private SortField getLastSortField(final Sort sort) {
-        final SortField[] sortFields = sort.getSort();
-        return sortFields[sortFields.length - 1];
-    }
-
-    private SearchResults toSearchResults(final SearchRequest searchRequest, final IndexSearcher searcher,
-            final Object[] reduces) throws IOException {
-        final SearchResults result = new SearchResults();
-        collectHits(searcher, (TopDocs) reduces[0], result);
-        if (reduces.length == 2) {
-            collectFacets(searchRequest, searcher, (FacetsCollector) reduces[1], result);
-        }
-        return result;
-    }
-
-    private void collectHits(final IndexSearcher searcher, final TopDocs topDocs, final SearchResults searchResults)
-            throws IOException {
-        final List<SearchHit> hits = new ArrayList<SearchHit>(topDocs.scoreDocs.length);
-
-        for (final ScoreDoc scoreDoc : topDocs.scoreDocs) {
-            final Document doc = searcher.doc(scoreDoc.doc);
-
-            final List<IndexableField> fields = new ArrayList<IndexableField>(doc.getFields());
-            for (IndexableField field : doc.getFields()) {
-                if (field.name().equals("_id")) {
-                    fields.remove(field);
-                }
-            }
-
-            hits.add(new SearchHit(doc.get("_id"), (FieldDoc) scoreDoc, fields));
-        }
-
-        searchResults.setTotalHits(topDocs.totalHits);
-        searchResults.setHits(hits);
-    }
-
-    private void collectFacets(final SearchRequest searchRequest, final IndexSearcher searcher,
-            final FacetsCollector fc, final SearchResults searchResults) throws IOException {
-        if (searchRequest.hasCounts()) {
-            final Map<String, Map<String, Number>> countsMap = new HashMap<String, Map<String, Number>>(
-                    searchRequest.getCounts().size());
-            for (final String field : searchRequest.getCounts()) {
-                final StringDocValuesReaderState state = new StringDocValuesReaderState(searcher.getIndexReader(),
-                        field);
-                final StringValueFacetCounts counts = new StringValueFacetCounts(state, fc);
-                countsMap.put(field, collectFacets(counts, searchRequest.getTopN(), field));
-            }
-            searchResults.setCounts(countsMap);
-        }
-
-        if (searchRequest.hasRanges()) {
-            final Map<String, Map<String, Number>> rangesMap = new HashMap<String, Map<String, Number>>(
-                    searchRequest.getRanges().size());
-            for (final Entry<String, List<DoubleRange>> entry : searchRequest.getRanges().entrySet()) {
-                final DoubleRangeFacetCounts counts = new DoubleRangeFacetCounts(entry.getKey(), fc,
-                        entry.getValue().toArray(EMPTY_DOUBLE_RANGE_ARRAY));
-                rangesMap.put(entry.getKey(), collectFacets(counts, searchRequest.getTopN(), entry.getKey()));
-            }
-            searchResults.setRanges(rangesMap);
-        }
-    }
-
-    private Map<String, Number> collectFacets(final Facets facets, final int topN, final String dim)
-            throws IOException {
-        final FacetResult topChildren = facets.getTopChildren(topN, dim);
-        final Map<String, Number> result = new HashMap<String, Number>(topChildren.childCount);
-        for (final LabelAndValue lv : topChildren.labelValues) {
-            result.put(lv.label, lv.value);
-        }
-        return result;
-    }
-
-    // Ensure _id is final sort field so we can paginate.
-    private Sort toSort(final SearchRequest searchRequest) {
-        if (!searchRequest.hasSort()) {
-            return DEFAULT_SORT;
-        }
-
-        final List<String> sort = new ArrayList<String>(searchRequest.getSort());
-        final String last = sort.get(sort.size() - 1);
-        // Append _id field if not already present.
-        switch(last) {
-            case "-_id<string>":
-            case "_id<string>":
-                break;
-            default:
-            sort.add("_id<string>");
-        }
-        return convertSort(sort);
-    }
-
-    private Sort convertSort(final List<String> sort) {
-        final SortField[] fields = new SortField[sort.size()];
-        for (int i = 0; i < sort.size(); i++) {
-            fields[i] = convertSortField(sort.get(i));
-        }
-        return new Sort(fields);
-    }
-
-    private SortField convertSortField(final String sortString) {
-        final Matcher m = SORT_FIELD_RE.matcher(sortString);
-        if (!m.matches()) {
-            throw new WebApplicationException(
-                    sortString + " is not a valid sort parameter", Status.BAD_REQUEST);
-        }
-        final boolean reverse = "-".equals(m.group(1));
-        SortField.Type type = SortField.Type.DOUBLE;
-        if ("string".equals(m.group(3))) {
-            type = SortField.Type.STRING;
-        }
-        return new SortField(m.group(2), type, reverse);
-    }
-
 }
diff --git a/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java
index 8b2d7029a..66f774514 100644
--- a/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java
+++ b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java
@@ -13,8 +13,10 @@
 
 package org.apache.couchdb.nouveau;
 
+import static org.apache.couchdb.nouveau.api.LuceneVersion.LUCENE_9;
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 
@@ -24,10 +26,8 @@ import javax.ws.rs.core.Response;
 
 import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
 import org.apache.couchdb.nouveau.api.IndexDefinition;
-import static org.apache.couchdb.nouveau.api.LuceneVersion.*;
 import org.apache.couchdb.nouveau.api.SearchRequest;
 import org.apache.couchdb.nouveau.api.SearchResults;
-
 import org.apache.lucene.document.DoubleDocValuesField;
 import org.apache.lucene.document.DoublePoint;
 import org.apache.lucene.document.SortedSetDocValuesField;
@@ -112,12 +112,16 @@ public class IntegrationTest {
     }
 
     @Test
-    public void healthCheckShouldSucceed() {
+    public void healthCheckShouldSucceed() throws IOException {
         final Response healthCheckResponse =
                 APP.client().target("http://localhost:" + APP.getAdminPort() + "/healthcheck")
                 .request()
                 .get();
 
+        //healthCheckResponse.bufferEntity();
+        //InputStream in = (InputStream) healthCheckResponse.getEntity();
+        //System.err.printf("health check response: %s\n", new String(in.readAllBytes()));
+
         assertThat(healthCheckResponse)
                 .extracting(Response::getStatus)
                 .isEqualTo(Response.Status.OK.getStatusCode());
diff --git a/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/api/SearchRequestTest.java b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/api/SearchRequestTest.java
index 9544b6f6e..7bc886af2 100644
--- a/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/api/SearchRequestTest.java
+++ b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/api/SearchRequestTest.java
@@ -7,12 +7,12 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.couchdb.nouveau.core.ser.LuceneModule;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
 import org.apache.lucene.facet.range.DoubleRange;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+
 public class SearchRequestTest {
 
     private static ObjectMapper mapper;
diff --git a/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/IndexManagerTest.java b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/IndexManagerTest.java
index 64081d5c0..4487b69e3 100644
--- a/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/IndexManagerTest.java
+++ b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/IndexManagerTest.java
@@ -42,7 +42,7 @@ public class IndexManagerTest {
     public void setup() throws Exception {
         manager = new IndexManager();
         manager.setMetricRegistry(new MetricRegistry());
-        manager.setAnalyzerFactory(new AnalyzerFactory());
+        manager.setAnalyzerFactory(new Lucene9AnalyzerFactory());
         manager.setCommitIntervalSeconds(5);
         manager.setObjectMapper(new ObjectMapper());
         manager.setRootDir(tempDir);
diff --git a/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/AnalyzerFactoryTest.java b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/Lucene9AnalyzerFactoryTest.java
similarity index 98%
rename from java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/AnalyzerFactoryTest.java
rename to java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/Lucene9AnalyzerFactoryTest.java
index 43bdb4e14..0adf18d45 100644
--- a/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/AnalyzerFactoryTest.java
+++ b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/Lucene9AnalyzerFactoryTest.java
@@ -56,7 +56,7 @@ import org.apache.lucene.analysis.th.ThaiAnalyzer;
 import org.apache.lucene.analysis.tr.TurkishAnalyzer;
 import org.junit.jupiter.api.Test;
 
-public class AnalyzerFactoryTest {
+public class Lucene9AnalyzerFactoryTest {
 
     @Test
     public void testkeyword() throws Exception {
@@ -249,7 +249,7 @@ public class AnalyzerFactoryTest {
     }
 
     private void assertAnalyzer(final String name, final Class<? extends Analyzer> clazz) throws Exception {
-        final AnalyzerFactory factory = new AnalyzerFactory();
+        final Lucene9AnalyzerFactory factory = new Lucene9AnalyzerFactory();
         assertThat(factory.newAnalyzer(name)).isInstanceOf(clazz);
     }
 


[couchdb] 01/05: remove SuppressWarnings junk

Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rnewson pushed a commit to branch import-nouveau-reorg
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 9a7d3301d61e70012ea0fb3c5e801b4a5026feaa
Author: Robert Newson <rn...@apache.org>
AuthorDate: Wed Dec 28 19:46:14 2022 +0000

    remove SuppressWarnings junk
---
 .../src/main/java/org/apache/couchdb/nouveau/api/AnalyzeRequest.java     | 1 -
 .../src/main/java/org/apache/couchdb/nouveau/api/AnalyzeResponse.java    | 1 -
 .../main/java/org/apache/couchdb/nouveau/api/DocumentDeleteRequest.java  | 1 -
 .../main/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequest.java  | 1 -
 .../src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java    | 1 -
 .../src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java      | 1 -
 6 files changed, 6 deletions(-)

diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeRequest.java b/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeRequest.java
index 41f7ffdfe..58f1e941e 100644
--- a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeRequest.java
+++ b/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeRequest.java
@@ -32,7 +32,6 @@ public class AnalyzeRequest {
     @NotEmpty
     private String text;
 
-    @SuppressWarnings("unused")
     public AnalyzeRequest() {
         // Jackson deserialization
     }
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeResponse.java b/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeResponse.java
index 6cc006c7a..32d1f61c2 100644
--- a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeResponse.java
+++ b/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeResponse.java
@@ -28,7 +28,6 @@ public class AnalyzeResponse {
     @NotNull
     private List<@NotEmpty String> tokens;
 
-    @SuppressWarnings("unused")
     public AnalyzeResponse() {
         // Jackson deserialization
     }
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/DocumentDeleteRequest.java b/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/DocumentDeleteRequest.java
index d8c8332d4..f7a4c572b 100644
--- a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/DocumentDeleteRequest.java
+++ b/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/DocumentDeleteRequest.java
@@ -25,7 +25,6 @@ public class DocumentDeleteRequest {
     @Min(1)
     private long seq;
 
-    @SuppressWarnings("unused")
     public DocumentDeleteRequest() {
         // Jackson deserialization
     }
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequest.java b/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequest.java
index d312b9d7f..3bec1c5e2 100644
--- a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequest.java
+++ b/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequest.java
@@ -36,7 +36,6 @@ public class DocumentUpdateRequest {
     @NotEmpty
     private Collection<@NotNull IndexableField> fields;
 
-    @SuppressWarnings("unused")
     public DocumentUpdateRequest() {
         // Jackson deserialization
     }
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java b/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java
index d876d3b82..61b8f08ab 100644
--- a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java
+++ b/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java
@@ -33,7 +33,6 @@ public class IndexDefinition {
 
     private Map<@NotEmpty String, @NotEmpty String> fieldAnalyzers;
 
-    @SuppressWarnings("unused")
     public IndexDefinition() {
         // Jackson deserialization
     }
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java b/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java
index 2de199ce2..3af396efd 100644
--- a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java
+++ b/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java
@@ -52,7 +52,6 @@ public class SearchRequest {
     @Max(100)
     private int topN = 10;
 
-    @SuppressWarnings("unused")
     public SearchRequest() {
         // Jackson deserialization
     }


[couchdb] 02/05: move server into module

Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rnewson pushed a commit to branch import-nouveau-reorg
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit affc8358fc45ed4c51f317d3849a84e548fa5e12
Author: Robert Newson <rn...@apache.org>
AuthorDate: Wed Dec 28 20:23:50 2022 +0000

    move server into module
---
 java/nouveau/pom.xml                               | 268 +++++----------------
 java/nouveau/{ => server}/nouveau.yaml             |   0
 java/nouveau/{ => server}/pom.xml                  |  12 +-
 .../apache/couchdb/nouveau/NouveauApplication.java |   0
 .../nouveau/NouveauApplicationConfiguration.java   |   0
 .../apache/couchdb/nouveau/api/AnalyzeRequest.java |   0
 .../couchdb/nouveau/api/AnalyzeResponse.java       |   0
 .../couchdb/nouveau/api/DocumentDeleteRequest.java |   0
 .../couchdb/nouveau/api/DocumentUpdateRequest.java |   0
 .../couchdb/nouveau/api/IndexDefinition.java       |   0
 .../org/apache/couchdb/nouveau/api/IndexInfo.java  |   0
 .../apache/couchdb/nouveau/api/LuceneVersion.java  |   0
 .../org/apache/couchdb/nouveau/api/SearchHit.java  |   0
 .../apache/couchdb/nouveau/api/SearchRequest.java  |   0
 .../apache/couchdb/nouveau/api/SearchResults.java  |   0
 .../couchdb/nouveau/core/AnalyzerFactory.java      |   0
 .../couchdb/nouveau/core/DocumentFactory.java      |   0
 .../core/FileAlreadyExistsExceptionMapper.java     |   0
 .../nouveau/core/FileNotFoundExceptionMapper.java  |   0
 .../apache/couchdb/nouveau/core/IndexManager.java  |   0
 .../couchdb/nouveau/core/NouveauQueryParser.java   |   0
 .../nouveau/core/NumericRangeQueryProcessor.java   |   0
 .../nouveau/core/ParallelSearcherFactory.java      |   0
 .../apache/couchdb/nouveau/core/QueryParser.java   |   0
 .../couchdb/nouveau/core/QueryParserException.java |   0
 .../nouveau/core/UpdatesOutOfOrderException.java   |   0
 .../core/UpdatesOutOfOrderExceptionMapper.java     |   0
 .../nouveau/core/ser/BytesRefDeserializer.java     |   0
 .../nouveau/core/ser/BytesRefSerializer.java       |   0
 .../nouveau/core/ser/DoubleRangeDeserializer.java  |   0
 .../nouveau/core/ser/DoubleRangeSerializer.java    |   0
 .../nouveau/core/ser/FieldDocDeserializer.java     |   0
 .../nouveau/core/ser/FieldDocSerializer.java       |   0
 .../core/ser/IndexableFieldDeserializer.java       |   0
 .../nouveau/core/ser/IndexableFieldSerializer.java |   0
 .../couchdb/nouveau/core/ser/LuceneModule.java     |   0
 .../couchdb/nouveau/core/ser/SupportedType.java    |   0
 .../nouveau/core/ser/TotalHitsDeserializer.java    |   0
 .../couchdb/nouveau/health/AnalyzeHealthCheck.java |   0
 .../nouveau/health/IndexManagerHealthCheck.java    |   0
 .../couchdb/nouveau/resources/AnalyzeResource.java |   0
 .../couchdb/nouveau/resources/IndexResource.java   |   0
 .../couchdb/nouveau/resources/SearchResource.java  |   0
 .../{ => server}/src/main/resources/banner.txt     |   0
 .../apache/couchdb/nouveau/IntegrationTest.java    |   0
 .../nouveau/api/DocumentUpdateRequestTest.java     |   0
 .../couchdb/nouveau/api/SearchRequestTest.java     |   0
 .../couchdb/nouveau/core/AnalyzerFactoryTest.java  |   0
 .../couchdb/nouveau/core/IndexManagerTest.java     |   0
 .../couchdb/nouveau/core/ser/LuceneModuleTest.java |   0
 .../resources/fixtures/DocumentUpdateRequest.json  |   0
 .../src/test/resources/fixtures/SearchRequest.json |   0
 .../src/test/resources/test-nouveau.yaml           |   0
 53 files changed, 63 insertions(+), 217 deletions(-)

diff --git a/java/nouveau/pom.xml b/java/nouveau/pom.xml
index 8c4dc038c..4eb8cd23e 100644
--- a/java/nouveau/pom.xml
+++ b/java/nouveau/pom.xml
@@ -1,228 +1,68 @@
-<!--
- Licensed 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/maven-v4_0_0.xsd">
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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>
-  <groupId>org.apache.couchdb</groupId>
-  <artifactId>nouveau</artifactId>
-  <version>0.2.0-SNAPSHOT</version>
-  <name>${project.artifactId}</name>
-  <description>Full-text indexing for CouchDB</description>
-  <inceptionYear>2022</inceptionYear>
-
+  <groupId>org.apache.couchdb.nouveau</groupId>
+  <artifactId>parent</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     <maven.compiler.source>11</maven.compiler.source>
     <maven.compiler.target>11</maven.compiler.target>
     <argLine>-Duser.language=en -Duser.region=US -Duser.timezone=UTC</argLine>
-    <dropwizard.version>2.1.4</dropwizard.version>
-    <lucene.version>9.4.2</lucene.version>
-    <slf4j.version>1.7.32</slf4j.version>
-    <junit5.version>5.8.2</junit5.version>
   </properties>
 
-  <dependencyManagement>
-	<dependencies>
-	  <dependency>
-		<groupId>org.junit</groupId>
-		<artifactId>junit-bom</artifactId>
-		<version>${junit5.version}</version>
-		<type>pom</type>
-		<scope>import</scope>
-	  </dependency>
-      <dependency>
-        <groupId>io.dropwizard</groupId>
-        <artifactId>dropwizard-dependencies</artifactId>
-        <version>${dropwizard.version}</version>
-        <type>pom</type>
-        <scope>import</scope>
-      </dependency>
-	</dependencies>
-  </dependencyManagement>
-
-  <dependencies>
-    <!-- Lucene -->
-    <dependency>
-      <groupId>org.apache.lucene</groupId>
-      <artifactId>lucene-core</artifactId>
-      <version>${lucene.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.lucene</groupId>
-      <artifactId>lucene-grouping</artifactId>
-      <version>${lucene.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.lucene</groupId>
-      <artifactId>lucene-queryparser</artifactId>
-      <version>${lucene.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.lucene</groupId>
-      <artifactId>lucene-analysis-common</artifactId>
-      <version>${lucene.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.lucene</groupId>
-      <artifactId>lucene-analysis-stempel</artifactId>
-      <version>${lucene.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.lucene</groupId>
-      <artifactId>lucene-analysis-smartcn</artifactId>
-      <version>${lucene.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.lucene</groupId>
-      <artifactId>lucene-analysis-kuromoji</artifactId>
-      <version>${lucene.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.lucene</groupId>
-      <artifactId>lucene-facet</artifactId>
-      <version>${lucene.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.lucene</groupId>
-      <artifactId>lucene-spatial-extras</artifactId>
-      <version>${lucene.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.lucene</groupId>
-      <artifactId>lucene-misc</artifactId>
-      <version>${lucene.version}</version>
-    </dependency>
-
-    <!-- Dropwizard -->
-    <dependency>
-      <groupId>io.dropwizard</groupId>
-      <artifactId>dropwizard-core</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>io.dropwizard</groupId>
-      <artifactId>dropwizard-http2</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>com.fasterxml.jackson.module</groupId>
-      <artifactId>jackson-module-afterburner</artifactId>
-    </dependency>
-
-    <!-- Dropwizard metrics -->
-    <dependency>
-        <groupId>io.dropwizard.metrics</groupId>
-        <artifactId>metrics-core</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>io.dropwizard.metrics</groupId>
-      <artifactId>metrics-caffeine</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>io.dropwizard.metrics</groupId>
-      <artifactId>metrics-jersey2</artifactId>
-    </dependency>
-
-    <!-- Test -->
-    <dependency>
-      <groupId>io.dropwizard</groupId>
-      <artifactId>dropwizard-testing</artifactId>
-      <scope>test</scope>
-      <exclusions>
-        <exclusion>
-          <groupId>junit</groupId>
-          <artifactId>junit</artifactId>
-        </exclusion>
-      </exclusions>
-    </dependency>
-    <dependency>
-	  <groupId>org.junit.jupiter</groupId>
-	  <artifactId>junit-jupiter</artifactId>
-      <version>${junit5.version}</version>
-	  <scope>test</scope>
-	</dependency>
-    <dependency>
-      <groupId>org.junit.jupiter</groupId>
-      <artifactId>junit-jupiter-engine</artifactId>
-      <version>${junit5.version}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.assertj</groupId>
-      <artifactId>assertj-core</artifactId>
-      <version>3.22.0</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-
-  <scm>
-    <developerConnection>scm:git:git@github.com:rnewson/nouveau.git</developerConnection>
-    <tag>HEAD</tag>
-  </scm>
+  <modules>
+    <module>server</module>
+  </modules>
 
   <build>
-    <defaultGoal>package</defaultGoal>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-release-plugin</artifactId>
-        <version>3.0.0-M5</version>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-jar-plugin</artifactId>
-        <version>2.4</version>
-        <configuration>
-          <archive>
-            <manifest>
-              <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
-            </manifest>
-          </archive>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>exec-maven-plugin</artifactId>
-        <version>3.1.0</version>
-        <executions>
-          <execution>
-            <goals>
-              <goal>exec</goal>
-            </goals>
-          </execution>
-        </executions>
-        <configuration>
-          <executable>java</executable>
-          <arguments>
-            <argument>-classpath</argument>
-            <classpath/>
-            <argument>org.apache.couchdb.nouveau.NouveauApplication</argument>
-            <argument>server</argument>
-            <argument>nouveau.yaml</argument>
-         </arguments>
-        </configuration>
-      </plugin>
-      <plugin>
-		<artifactId>maven-surefire-plugin</artifactId>
-		<version>2.22.2</version>
-	  </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-javadoc-plugin</artifactId>
-        <version>3.4.0</version>
-        <configuration>
-        </configuration>
-      </plugin>
-    </plugins>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>3.8.0</version>
+          <configuration>
+            <source>11</source>
+            <target>11</target>
+          </configuration>
+        </plugin>
+        <plugin>
+          <artifactId>maven-clean-plugin</artifactId>
+          <version>3.1.0</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-resources-plugin</artifactId>
+          <version>3.0.2</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-surefire-plugin</artifactId>
+          <version>2.22.1</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-jar-plugin</artifactId>
+          <version>3.0.2</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-install-plugin</artifactId>
+          <version>2.5.2</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-deploy-plugin</artifactId>
+          <version>2.8.2</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-site-plugin</artifactId>
+          <version>3.7.1</version>
+        </plugin>
+        <plugin>
+          <artifactId>maven-project-info-reports-plugin</artifactId>
+          <version>3.0.0</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
   </build>
 
 </project>
diff --git a/java/nouveau/nouveau.yaml b/java/nouveau/server/nouveau.yaml
similarity index 100%
rename from java/nouveau/nouveau.yaml
rename to java/nouveau/server/nouveau.yaml
diff --git a/java/nouveau/pom.xml b/java/nouveau/server/pom.xml
similarity index 96%
copy from java/nouveau/pom.xml
copy to java/nouveau/server/pom.xml
index 8c4dc038c..8d8d30932 100644
--- a/java/nouveau/pom.xml
+++ b/java/nouveau/server/pom.xml
@@ -13,9 +13,15 @@
 -->
 <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/maven-v4_0_0.xsd">
   <modelVersion>4.0.0</modelVersion>
-  <groupId>org.apache.couchdb</groupId>
-  <artifactId>nouveau</artifactId>
-  <version>0.2.0-SNAPSHOT</version>
+  <parent>
+    <groupId>org.apache.couchdb.nouveau</groupId>
+    <artifactId>parent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+
+  <groupId>org.apache.couchdb.nouveau</groupId>
+  <artifactId>server</artifactId>
+  <version>1.0-SNAPSHOT</version>
   <name>${project.artifactId}</name>
   <description>Full-text indexing for CouchDB</description>
   <inceptionYear>2022</inceptionYear>
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplication.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/NouveauApplicationConfiguration.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplicationConfiguration.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/NouveauApplicationConfiguration.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/NouveauApplicationConfiguration.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeRequest.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeRequest.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeRequest.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeRequest.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeResponse.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeResponse.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeResponse.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/AnalyzeResponse.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/DocumentDeleteRequest.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/DocumentDeleteRequest.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/DocumentDeleteRequest.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/DocumentDeleteRequest.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequest.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequest.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequest.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequest.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/IndexDefinition.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexInfo.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/IndexInfo.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/IndexInfo.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/IndexInfo.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/LuceneVersion.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/LuceneVersion.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/LuceneVersion.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/LuceneVersion.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchHit.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/SearchHit.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchHit.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/SearchHit.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/SearchRequest.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchResults.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/SearchResults.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/api/SearchResults.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/api/SearchResults.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/AnalyzerFactory.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/AnalyzerFactory.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/AnalyzerFactory.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/AnalyzerFactory.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/DocumentFactory.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/DocumentFactory.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/DocumentFactory.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/DocumentFactory.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/FileAlreadyExistsExceptionMapper.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileAlreadyExistsExceptionMapper.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/FileAlreadyExistsExceptionMapper.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileAlreadyExistsExceptionMapper.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/FileNotFoundExceptionMapper.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileNotFoundExceptionMapper.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/FileNotFoundExceptionMapper.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/FileNotFoundExceptionMapper.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/IndexManager.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/NouveauQueryParser.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/NouveauQueryParser.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/NouveauQueryParser.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/NouveauQueryParser.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/NumericRangeQueryProcessor.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/NumericRangeQueryProcessor.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/NumericRangeQueryProcessor.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/NumericRangeQueryProcessor.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ParallelSearcherFactory.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ParallelSearcherFactory.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ParallelSearcherFactory.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ParallelSearcherFactory.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/QueryParser.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/QueryParser.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/QueryParser.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/QueryParser.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/QueryParserException.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/QueryParserException.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/QueryParserException.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/QueryParserException.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderException.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderException.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderException.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderException.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderExceptionMapper.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderExceptionMapper.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderExceptionMapper.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/UpdatesOutOfOrderExceptionMapper.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/BytesRefDeserializer.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/BytesRefDeserializer.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/BytesRefDeserializer.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/BytesRefDeserializer.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/BytesRefSerializer.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/BytesRefSerializer.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/BytesRefSerializer.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/BytesRefSerializer.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/DoubleRangeDeserializer.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/DoubleRangeDeserializer.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/DoubleRangeDeserializer.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/DoubleRangeDeserializer.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/DoubleRangeSerializer.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/DoubleRangeSerializer.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/DoubleRangeSerializer.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/DoubleRangeSerializer.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/FieldDocDeserializer.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/FieldDocDeserializer.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/FieldDocDeserializer.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/FieldDocDeserializer.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/FieldDocSerializer.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/FieldDocSerializer.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/FieldDocSerializer.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/FieldDocSerializer.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/IndexableFieldDeserializer.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/IndexableFieldDeserializer.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/IndexableFieldDeserializer.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/IndexableFieldDeserializer.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/IndexableFieldSerializer.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/IndexableFieldSerializer.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/IndexableFieldSerializer.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/IndexableFieldSerializer.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/LuceneModule.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/LuceneModule.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/LuceneModule.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/LuceneModule.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/SupportedType.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/SupportedType.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/SupportedType.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/SupportedType.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/TotalHitsDeserializer.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/TotalHitsDeserializer.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/core/ser/TotalHitsDeserializer.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/core/ser/TotalHitsDeserializer.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/health/AnalyzeHealthCheck.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/AnalyzeHealthCheck.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/health/AnalyzeHealthCheck.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/AnalyzeHealthCheck.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/health/IndexManagerHealthCheck.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/resources/AnalyzeResource.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/AnalyzeResource.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/resources/AnalyzeResource.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/AnalyzeResource.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/IndexResource.java
diff --git a/java/nouveau/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java b/java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
similarity index 100%
rename from java/nouveau/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
rename to java/nouveau/server/src/main/java/org/apache/couchdb/nouveau/resources/SearchResource.java
diff --git a/java/nouveau/src/main/resources/banner.txt b/java/nouveau/server/src/main/resources/banner.txt
similarity index 100%
rename from java/nouveau/src/main/resources/banner.txt
rename to java/nouveau/server/src/main/resources/banner.txt
diff --git a/java/nouveau/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java
similarity index 100%
rename from java/nouveau/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java
rename to java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/IntegrationTest.java
diff --git a/java/nouveau/src/test/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequestTest.java b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequestTest.java
similarity index 100%
rename from java/nouveau/src/test/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequestTest.java
rename to java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/api/DocumentUpdateRequestTest.java
diff --git a/java/nouveau/src/test/java/org/apache/couchdb/nouveau/api/SearchRequestTest.java b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/api/SearchRequestTest.java
similarity index 100%
rename from java/nouveau/src/test/java/org/apache/couchdb/nouveau/api/SearchRequestTest.java
rename to java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/api/SearchRequestTest.java
diff --git a/java/nouveau/src/test/java/org/apache/couchdb/nouveau/core/AnalyzerFactoryTest.java b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/AnalyzerFactoryTest.java
similarity index 100%
rename from java/nouveau/src/test/java/org/apache/couchdb/nouveau/core/AnalyzerFactoryTest.java
rename to java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/AnalyzerFactoryTest.java
diff --git a/java/nouveau/src/test/java/org/apache/couchdb/nouveau/core/IndexManagerTest.java b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/IndexManagerTest.java
similarity index 100%
rename from java/nouveau/src/test/java/org/apache/couchdb/nouveau/core/IndexManagerTest.java
rename to java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/IndexManagerTest.java
diff --git a/java/nouveau/src/test/java/org/apache/couchdb/nouveau/core/ser/LuceneModuleTest.java b/java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/ser/LuceneModuleTest.java
similarity index 100%
rename from java/nouveau/src/test/java/org/apache/couchdb/nouveau/core/ser/LuceneModuleTest.java
rename to java/nouveau/server/src/test/java/org/apache/couchdb/nouveau/core/ser/LuceneModuleTest.java
diff --git a/java/nouveau/src/test/resources/fixtures/DocumentUpdateRequest.json b/java/nouveau/server/src/test/resources/fixtures/DocumentUpdateRequest.json
similarity index 100%
rename from java/nouveau/src/test/resources/fixtures/DocumentUpdateRequest.json
rename to java/nouveau/server/src/test/resources/fixtures/DocumentUpdateRequest.json
diff --git a/java/nouveau/src/test/resources/fixtures/SearchRequest.json b/java/nouveau/server/src/test/resources/fixtures/SearchRequest.json
similarity index 100%
rename from java/nouveau/src/test/resources/fixtures/SearchRequest.json
rename to java/nouveau/server/src/test/resources/fixtures/SearchRequest.json
diff --git a/java/nouveau/src/test/resources/test-nouveau.yaml b/java/nouveau/server/src/test/resources/test-nouveau.yaml
similarity index 100%
rename from java/nouveau/src/test/resources/test-nouveau.yaml
rename to java/nouveau/server/src/test/resources/test-nouveau.yaml