From 1271d5d1a17c121672c7b3f9bf449a540e443516 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Sat, 14 Nov 2026 13:49:45 -0700 Subject: [PATCH 1/3] Require Java 21 and upgrade to Lucene 10.6.0 and Derby 10.17.1.0 Roller 7 now requires Java 21. The compiler release in the root and Playwright poms, the CI matrix (JDK 21 and 25, dropping 17), the other CI jobs, CodeQL, the Docker base images, the README, the install guide and CHANGES.md all move from 17 to 21. There was no code that existed only to support older Java, so none is removed. Java 21 allows two upgrades that need it: - Lucene 9.12.3 to 10.6.0 for every Lucene artifact. Lucene 10 removed IndexSearcher.doc(int), so search results read stored fields through IndexSearcher.storedFields(), fetched on the request thread that reads them. TotalHits is now a record, so the count comes from value() and relation(). The tests use MatchAllDocsQuery.INSTANCE in place of its deprecated constructor. The exact total hit count and the search.results.max cap behave as before, and their tests pass unchanged apart from the accessor calls. - Derby 10.16.1.1 to 10.17.1.0, which fixes CVE-2022-46337. Its artifacts and their dependencies are the same as in 10.16, so the test, jetty:run and db-utils setup needs no change. Derby is not in the WAR. Lucene 10 reads Roller 6.1's Lucene 9 index through lucene-backward-codecs, but not Roller 6.0's Lucene 8 index. Roller already deleted an index it could not open and rebuilt it in the background, but until the rebuild wrote one there was no index, so a search in that window failed. Roller now creates an empty index first, as it does for a missing index or one with removed fields, and logs a one-line warning without a stack trace when the format is too old or too new. IndexUpgradeTest starts Roller's index manager on a Lucene 8.3.0 index with Roller 6.0's fields and on a Lucene 9.12.3 index with Roller 6.1.7's fields. The first is replaced and rebuilt, and the second is kept, searched and written to. The zipped fixtures are excluded from test resource filtering. CHANGES.md and the install guide's upgrade section describe what happens to an existing index. --- .github/workflows/codeql-analysis.yml | 4 +- .github/workflows/main.yml | 16 +- CHANGES.md | 9 +- Dockerfile | 4 +- README.md | 2 +- app/pom.xml | 7 +- .../search/lucene/LuceneIndexManager.java | 17 +- .../search/lucene/SearchOperation.java | 2 +- .../search/lucene/IndexUpgradeTest.java | 193 ++++++++++++++++++ .../lucene/SearchFieldsQueryParserTest.java | 2 +- .../search/lucene/SearchHitPagingTest.java | 5 +- .../search/lucene/SearchResultLimitTest.java | 4 +- .../search-index/roller-6.0-lucene-8.3.0.zip | Bin 0 -> 2207 bytes .../roller-6.1.7-lucene-9.12.3.zip | Bin 0 -> 2209 bytes docs/roller-install-guide.adoc | 14 +- it-playwright/pom.xml | 2 +- pom.xml | 4 +- 17 files changed, 257 insertions(+), 28 deletions(-) create mode 100644 app/src/test/java/org/apache/roller/weblogger/business/search/lucene/IndexUpgradeTest.java create mode 100644 app/src/test/resources/search-index/roller-6.0-lucene-8.3.0.zip create mode 100644 app/src/test/resources/search-index/roller-6.1.7-lucene-9.12.3.zip diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 07264ef246..d38f2c4de6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -45,10 +45,10 @@ jobs: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v6 with: - java-version: 17 + java-version: 21 distribution: 'zulu' problem-matcher: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6eb0ad6c21..39165fbacb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: false matrix: - java: [ '17', '21', '25' ] + java: [ '21', '25' ] steps: - name: Set up JDK ${{ matrix.java }} @@ -63,7 +63,7 @@ jobs: # only on integration and only once in this matrix - name: Upload Dev Build on Integration - if: ${{ (matrix.java == '17') && (github.event_name == 'push') }} + if: ${{ (matrix.java == '21') && (github.event_name == 'push') }} uses: actions/upload-artifact@v7 with: name: dev-build @@ -79,10 +79,10 @@ jobs: timeout-minutes: 10 steps: - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v6 with: - java-version: '17' + java-version: '21' distribution: 'zulu' - name: Checkout Project @@ -105,10 +105,10 @@ jobs: timeout-minutes: 30 steps: - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v6 with: - java-version: '17' + java-version: '21' distribution: 'zulu' - name: Checkout Project @@ -167,10 +167,10 @@ jobs: auth: [ 'oidc', 'db-oidc' ] steps: - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v6 with: - java-version: '17' + java-version: '21' distribution: 'zulu' - name: Checkout Project diff --git a/CHANGES.md b/CHANGES.md index 631a2498db..1f748e1797 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -114,9 +114,16 @@ error and continues startup. - **Roller now runs on Jakarta EE** - ([ROL-2183](https://issues.apache.org/jira/browse/ROL-2183)). It needs Java 17 + ([ROL-2183](https://issues.apache.org/jira/browse/ROL-2183)). It needs Java 21 or later and a Servlet 6.1 container such as Tomcat 11. Tomcat 9 and other `javax.servlet` containers can no longer run Roller. +- **The search index is rebuilt when upgrading from Roller 6.1.6 or + earlier.** Roller 7 uses Lucene 10, which reads the index Roller 6.1.7 + writes. An older index either holds fields Roller no longer indexes or, + like the Lucene 8 index of Roller 6.0, can't be read at all. Roller + replaces it at first startup and rebuilds it in the background, so no + action is needed. Search results are complete again once the log shows + "Completed rebuilding index". - **OAuth 1.0a is removed** ([ROL-2183](https://issues.apache.org/jira/browse/ROL-2183)). AtomPub accepts only BASIC authentication, and the OAuth credentials page and consumer keys are gone. If `webservices.atomPubAuth` is still set to `oauth`, diff --git a/Dockerfile b/Dockerfile index 09e0a5fe2a..51b68bff8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ # STAGE 1 - BUILD ------------------------------------------------ -FROM maven:3-eclipse-temurin-17 AS builder +FROM maven:3.9.16-eclipse-temurin-21 AS builder COPY . /project WORKDIR /project @@ -30,7 +30,7 @@ RUN mvn -Duser.home=/builder/home -DskipTests=true -B clean install # STAGE 2 - PACKAGE ------------------------------------------------ -FROM tomcat:11.0-jdk17-temurin +FROM tomcat:11.0.29-jdk21-temurin # Remove existing Tomcat webapps diff --git a/README.md b/README.md index 4f258c5406..f7ad178472 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ If you want to run Roller in production, then you should down load the latest of ## Quick start: Running via Maven You probably should not run Roller in production using this technique, but it's a relatively easy way to try Roller for yourself. -Assuming you've got a UNIX shell, Java, Maven and Git: +Assuming you've got a UNIX shell, Java 21 or later, Maven and Git: Get the code: diff --git a/app/pom.xml b/app/pom.xml index 43c8b51ef8..1aa9560067 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -48,7 +48,7 @@ limitations under the License. 5.0.2 7.0.0 2.26.1 - 9.12.3 + 10.6.0 3.5.1 3.6.0 1.0b3 @@ -744,6 +744,11 @@ limitations under the License. true + + + zip + diff --git a/app/src/main/java/org/apache/roller/weblogger/business/search/lucene/LuceneIndexManager.java b/app/src/main/java/org/apache/roller/weblogger/business/search/lucene/LuceneIndexManager.java index fc7132a65a..0accc1e058 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/search/lucene/LuceneIndexManager.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/search/lucene/LuceneIndexManager.java @@ -40,9 +40,12 @@ import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.FieldInfos; +import org.apache.lucene.index.IndexFormatTooNewException; +import org.apache.lucene.index.IndexFormatTooOldException; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.StoredFields; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopFieldDocs; import org.apache.lucene.store.Directory; @@ -162,9 +165,16 @@ public void initialize() throws InitializationException { } } } catch (IOException | IllegalArgumentException ex) { // IAE for incompatible codecs - logger.warn("Failed to open search index, scheduling rebuild.", ex); + if (ex instanceof IndexFormatTooOldException || ex instanceof IndexFormatTooNewException) { + // for example the Lucene 8 index of Roller 6.0 + logger.warn("Search index uses an unsupported format, rebuilding it: " + ex.getMessage()); + } else { + logger.warn("Failed to open search index, scheduling rebuild.", ex); + } inconsistentAtStartup = true; deleteIndex(); + // until the rebuild is done, searches find nothing rather than fail + createIndex(getIndexDirectory()); } } else { logger.debug("Creating index"); @@ -474,11 +484,14 @@ static SearchResultList convertHitsToEntryList( List visible = new ArrayList<>(); Timestamp now = new Timestamp(new Date().getTime()); long wanted = (long) (pageNum + 1) * entryCount + 1; + // a StoredFields may only be used on the thread that got it, and + // the search ran on another one + StoredFields storedFields = search.getSearcher().storedFields(); for (ScoreDoc hit : hits) { if (visible.size() >= wanted) { break; } - Document doc = search.getSearcher().doc(hit.doc); + Document doc = storedFields.document(hit.doc); // maybe null if search result returned inactive user // or entry's user is not the requested user WeblogEntry entry = weblogMgr.getWeblogEntry(doc.getField(FieldConstants.ID).stringValue()); diff --git a/app/src/main/java/org/apache/roller/weblogger/business/search/lucene/SearchOperation.java b/app/src/main/java/org/apache/roller/weblogger/business/search/lucene/SearchOperation.java index 8a651ec26d..145686ceda 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/search/lucene/SearchOperation.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/search/lucene/SearchOperation.java @@ -212,7 +212,7 @@ public int getResultsCount() { if (searchresults == null) { return -1; } - return (int) searchresults.totalHits.value; + return (int) searchresults.totalHits.value(); } /** diff --git a/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/IndexUpgradeTest.java b/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/IndexUpgradeTest.java new file mode 100644 index 0000000000..9141535c83 --- /dev/null +++ b/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/IndexUpgradeTest.java @@ -0,0 +1,193 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://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.roller.weblogger.business.search.lucene; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.Timestamp; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.StringField; +import org.apache.lucene.document.TextField; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexFormatTooOldException; +import org.apache.lucene.index.SegmentInfos; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.util.Version; +import org.apache.roller.weblogger.business.WeblogEntryManager; +import org.apache.roller.weblogger.business.Weblogger; +import org.apache.roller.weblogger.business.runnable.ThreadManager; +import org.apache.roller.weblogger.config.WebloggerConfig; +import org.apache.roller.weblogger.pojos.Weblog; +import org.apache.roller.weblogger.pojos.WeblogEntry; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Starting on a search index that an earlier Roller release left behind. + * Each fixture holds two entries, written with the Lucene version and the + * index fields of the release it is named after. + */ +class IndexUpgradeTest { + + @TempDir + Path indexDir; + + @Test + void indexFromRoller60IsReplacedAndRebuilt() throws Exception { + // Roller 6.0 shipped Lucene 8.3, which this Lucene cannot read + unzip("roller-6.0-lucene-8.3.0.zip"); + try (Directory dir = FSDirectory.open(indexDir)) { + assertThrows(IndexFormatTooOldException.class, () -> DirectoryReader.open(dir)); + } + + ThreadManager threads = mock(ThreadManager.class); + WeblogEntryManager entries = mock(WeblogEntryManager.class); + LuceneIndexManager manager = manager(threads, entries); + try { + manager.initialize(); + + assertTrue(manager.isInconsistentAtStartup()); + ArgumentCaptor rebuild = ArgumentCaptor.forClass(Runnable.class); + verify(threads).executeInBackground(rebuild.capture()); + assertInstanceOf(RebuildWebsiteIndexOperation.class, rebuild.getValue()); + try (Directory dir = FSDirectory.open(indexDir)) { + assertEquals(Version.LATEST.major, + SegmentInfos.readLatestCommit(dir).getIndexCreatedVersionMajor()); + } + // until the rebuild fills it, searches find nothing rather than fail + assertEquals(0, search(manager)); + + List published = List.of( + entry("entry-1", "Roller rebuilds its search index after an upgrade."), + entry("entry-2", "Another Roller entry about search.")); + when(entries.getWeblogEntries(any())).thenReturn(published); + rebuild.getValue().run(); + assertEquals(2, search(manager)); + } finally { + manager.shutdown(); + } + } + + @Test + void indexFromRoller617IsKept() throws Exception { + // Roller 6.1 shipped Lucene 9, which this Lucene reads through + // lucene-backward-codecs, and 6.1.7 indexes the fields this one does + unzip("roller-6.1.7-lucene-9.12.3.zip"); + try (Directory dir = FSDirectory.open(indexDir)) { + assertEquals(9, SegmentInfos.readLatestCommit(dir).getIndexCreatedVersionMajor()); + } + + ThreadManager threads = mock(ThreadManager.class); + LuceneIndexManager manager = manager(threads, mock(WeblogEntryManager.class)); + try { + manager.initialize(); + + assertFalse(manager.isInconsistentAtStartup()); + verify(threads, never()).executeInBackground(any()); + assertEquals(2, search(manager)); + + // new entries go into the same index + new WriteToIndexOperation(manager) { + @Override + protected void doRun() { + Document doc = new Document(); + doc.add(new StringField(FieldConstants.ID, "new-entry", Field.Store.YES)); + doc.add(new StringField(FieldConstants.WEBSITE_HANDLE, "upgrade", Field.Store.YES)); + doc.add(new TextField(FieldConstants.CONTENT, "Written by Roller 7", Field.Store.NO)); + try { + beginWriting().addDocument(doc); + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + endWriting(); + } + } + }.run(); + assertEquals(3, search(manager)); + } finally { + manager.shutdown(); + } + } + + private void unzip(String fixture) throws IOException { + try (InputStream in = getClass().getResourceAsStream("/search-index/" + fixture); + ZipInputStream zip = new ZipInputStream(in)) { + for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) { + Files.copy(zip, indexDir.resolve(Path.of(entry.getName()).getFileName())); + } + } + } + + private LuceneIndexManager manager(ThreadManager threads, WeblogEntryManager entries) { + Weblogger roller = mock(Weblogger.class); + when(roller.getThreadManager()).thenReturn(threads); + when(roller.getWeblogEntryManager()).thenReturn(entries); + WebloggerConfig.init(); + try (MockedStatic config = mockStatic(WebloggerConfig.class, CALLS_REAL_METHODS)) { + config.when(() -> WebloggerConfig.getProperty("search.index.dir")) + .thenReturn(indexDir.toString()); + return new LuceneIndexManager(roller); + } + } + + private static WeblogEntry entry(String id, String text) { + Weblog weblog = mock(Weblog.class); + when(weblog.getHandle()).thenReturn("upgrade"); + Timestamp time = new Timestamp(System.currentTimeMillis()); + WeblogEntry entry = mock(WeblogEntry.class); + when(entry.getId()).thenReturn(id); + when(entry.getWebsite()).thenReturn(weblog); + when(entry.getTitle()).thenReturn(id); + when(entry.getLocale()).thenReturn("en_US"); + when(entry.getText()).thenReturn(text); + when(entry.getUpdateTime()).thenReturn(time); + when(entry.getPubTime()).thenReturn(time); + return entry; + } + + /** Number of entries matching "roller", or -1 if the search failed. */ + private static int search(LuceneIndexManager manager) { + SearchOperation search = new SearchOperation(manager); + search.setTerm("roller"); + search.run(); + return search.getResultsCount(); + } +} diff --git a/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/SearchFieldsQueryParserTest.java b/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/SearchFieldsQueryParserTest.java index 047921cb5f..c12eee08f4 100644 --- a/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/SearchFieldsQueryParserTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/SearchFieldsQueryParserTest.java @@ -47,7 +47,7 @@ void plainAndSearchFieldQueriesParse() throws Exception { @Test void matchAllParses() throws Exception { - assertEquals(new MatchAllDocsQuery(), parser().parse("*:*")); + assertEquals(MatchAllDocsQuery.INSTANCE, parser().parse("*:*")); } @Test diff --git a/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/SearchHitPagingTest.java b/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/SearchHitPagingTest.java index 1c3cf3851d..a7edc423c0 100644 --- a/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/SearchHitPagingTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/SearchHitPagingTest.java @@ -21,6 +21,7 @@ import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; +import org.apache.lucene.index.StoredFields; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.ScoreDoc; import org.apache.roller.weblogger.business.URLStrategy; @@ -119,13 +120,15 @@ private static WeblogEntryManager entries(int draftIndex) throws Exception { private static SearchResultList convert(WeblogEntryManager entries, int page, int size) throws Exception { IndexSearcher searcher = mock(IndexSearcher.class); + StoredFields storedFields = mock(StoredFields.class); + when(searcher.storedFields()).thenReturn(storedFields); ScoreDoc[] hits = new ScoreDoc[HITS]; for (int i = 0; i < HITS; i++) { hits[i] = new ScoreDoc(i, 1f); Document doc = new Document(); doc.add(new StringField(FieldConstants.ID, "id" + i, Field.Store.YES)); doc.add(new StringField(FieldConstants.WEBSITE_HANDLE, "team", Field.Store.YES)); - when(searcher.doc(i)).thenReturn(doc); + when(storedFields.document(i)).thenReturn(doc); } SearchOperation search = mock(SearchOperation.class); when(search.getSearcher()).thenReturn(searcher); diff --git a/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/SearchResultLimitTest.java b/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/SearchResultLimitTest.java index 699238fb53..cb51fdb6f5 100644 --- a/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/SearchResultLimitTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/business/search/lucene/SearchResultLimitTest.java @@ -67,7 +67,7 @@ void countsEveryMatchPastFiveHundredAndPastOneThousand() throws Exception { SearchOperation search = search(reader); search.run(); assertEquals(MATCHES, search.getResultsCount()); - assertEquals(TotalHits.Relation.EQUAL_TO, search.getResults().totalHits.relation); + assertEquals(TotalHits.Relation.EQUAL_TO, search.getResults().totalHits.relation()); assertEquals(MATCHES, search.getResults().scoreDocs.length); } } @@ -105,7 +105,7 @@ void pagingStopsAtTheLimitButTheTotalDoesNot() throws Exception { // an exact count, not the lower bound Lucene gives by default // when there are more than 1,000 matches and fewer are kept assertEquals(MATCHES, search.getResultsCount()); - assertEquals(TotalHits.Relation.EQUAL_TO, search.getResults().totalHits.relation); + assertEquals(TotalHits.Relation.EQUAL_TO, search.getResults().totalHits.relation()); assertEquals(max, search.getResults().scoreDocs.length); // the page before the limit has a next page diff --git a/app/src/test/resources/search-index/roller-6.0-lucene-8.3.0.zip b/app/src/test/resources/search-index/roller-6.0-lucene-8.3.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..05277f49859550408fa20bfe518baaa7cb812968 GIT binary patch literal 2207 zcmZ{ld05if8plDw&|EWbMW*4B*Clr|)QLoGR4mE0%-t{~cO6qI6?20YH^Klzaw&7k zurbqSR45xYaml4|(o$1z;+mL-xm?tHpKjh~{&>%M&+j~+=RNQFo#*>SqeMZw0RR95 zAQ=+s=J_b)37^>~3^Jok@&E2W3v9a-N4ij5b)jz}V zuRwIbXDNC&QWe`I6(waGIS{qI1QCbNVps`8)K~JM2jj&adsos#GE>*Fi9l&-tV297 zAWfWX1O%Xes2=2JnoujOdJzZ!hzVyOecgaS%tmG18E0A^Efgf$ zjmxi{m?H=#PXfKLXLOHM@>xUXyqyj{PXDCN17^~q4n9azLH3BYhKoLks}4pB&Oo@I zfNxwxh94hJ!QsJ%O1!|u_>v)vv8mMARHRA1j7sOg-*hm1_H1P@Y)wf%OBgC zBNV=Fp@9H^C8_^uE~?t?Bv#Q?F=3Y5)*i1u82!ZVA}!iVE;%N*#XqEcRP<&Ru1B5d zp?4_fZcRVCpNctVFA{G}PLh*Ru?AVncn8CFu$Uuk`a!G=^0v}Q*GS7#TG`r%w)(&$ zRIW<&g$qm5-#XU6ttK=KKJ>ZCUV~R6PvYI;IHjgK9X)|A?l$4SKeaFuG`-6)FRVo5 zuL-D^W(qVrkF{67rhMd$m-4eKp7$_VTE2uOx?1gGHgdjjl>E=8(O4C#{nCO`%fbQf z;=jbiG7cp8etJ3AY?sVR1Sj_|uUaX;M{*}oQRj1Pjx9g)bM^N6Rnw~|W{}#Yt!tqm zE#6X|TEPF}72O;wy7TJ1R&a1{Bd9@T%-XpU%>pv4ZRMLN8jYgRE6OMyX{p6T zc(}Whz04l+6EH>xaX-)fPcN6nqTu~&e3S^A89>VA&^0?Hp?j}z;*Ge- z`4bWzI$D+YH%lQz0!F!GL>u9Su$jFTo%6TRd;n*!k_-l&ETA4j{q?~ zE>an0(~0?`G*qjx#jSQ8J=}0Ow&BX^L?^5b_+0vEa(AwOdU_{ZC*9CN&T|-&GFkIR z2dUh_hhLYoHiuqE6*a9azeLCw^8J(|+10_NT;A?Xvof<8aO#H=HM!3bu?(rDu%lD_ z92N>;kl*k2W`tqJqUb+=48a-bK+KaS9(thS#T^Z8+s2UhwFxYSGC>ZJr4)#s5 zCTGq;kk1#$W9^FZwF)oxwpyHy(u&FB=IgwSo}!;P;5 z9+1lI>pC|9&fm*dF7%dBK*h=2zG+woiQfBYfoqyqcKGo^+SjwZQjh1Bft_nLaS^6$ zcTSC@Hgd;-MH^h0> zF~9bgXYIIIm#3r2+p)}3Rd+wfH&IsCI8BIpI)A+iRBTc3m5*naFMIwht$3dH30jt( zYoGeMXEEfcdpk$JB$UO|i;zx@!QFDN;9Wdp$)e$KV&8@v*6TkqtU{bCoRgBj&3Z-t z@|KA-_533-+w~f05KEG@y+rUY3RROPgPHd{%J)+>pzlfd%uPyLV66vL2WFIF3Rg0; zL%z&_Wwn^^iy3bxJNee`uI)<1_^}$Tb^c3;dx(yTAr4upz2b1#uSWi30zFY20RX$MKGIWfjU! z`Edf|;(q8P&0}sXaS)OdP~BGY`W@Z@}FVj>!rw`XpC-fSinUz3Mir^`uG0(U$Tg>k$rdL?c8?f{LaM- z(=8v}&TKcN@60*jPu^srwlmwG!FOh#(6EI}RL~ak_sxGt+;;qU72ple7rrhqV5jf^ J@xomI;NK8Zsx<%r literal 0 HcmV?d00001 diff --git a/app/src/test/resources/search-index/roller-6.1.7-lucene-9.12.3.zip b/app/src/test/resources/search-index/roller-6.1.7-lucene-9.12.3.zip new file mode 100644 index 0000000000000000000000000000000000000000..ddc40b401590b9580d91d474206f019e176a37c6 GIT binary patch literal 2209 zcmZ{lc{o)2AIFbbkR^?VvXx49np~wUmua^QQ@ECiu{0QyF$|ZGt&0pXV;MWsy>f|^ zWi0he)@)-dg;I9omVGDb4&C45m*;o?_@47Q=RB|Hd_L!Up7#rF2!_A`0N@1#+=w=H zAEVkq_z(>e424DZq5{ge&5)_CzAglnmipEOT#1IE2-5YlVDWgY z-jw~S-^!#Yfc~M2i&y&aU7WYSRmDY!UcKYC4ak{iypMj&PE zh|sgzK2S4~>UG3p<(@lYwN|m&#iKlC=wMj>*GkEPeIELkCXv+q8g80PL!q<=4y<+I zT;?94o9#5znAdr4_xkqG84Zk}XRW}lq3zce+9M$V5Fq$JLz9`mbh!6lfGS#VR zStEQ79PNAUTN!uzL?}8Fpcc*+2LoplY;a; zp3RX~Foi*86CG!|nS_!|l!nvPy%=eIz;9^tfYG;R{hsoVSYCM>#vsrW;h6r*&+zwR zr|DiHwEEBAru?IZ1l7mJH>@Xom6+vVx$K%!ckg+HJyBEvL8Ku_ck@GR;|N|O!1C+i zs^4zJ#Xkv!vEU;2-=0X;o_lS<^J~k|!ogKcjzkx$AO72DTuf^?F+FeMg!$r7js0yS z=b%5AhC6NkkZ+s2^=gy4F@NNp)?7vZ5tBq7AzjDibtfHJ7tqk@E^Xo6gBVYOl9BT*GI)!pS-2D`7#qap8lt-jC<+bQ| z>9tK>!tsd)l{|Has&n4?G10DJe)gnCY6W>+>jvn{IVi&iN{CZ#gN=h}-lNY{1v6#4 zF%;K7gZ&wa4p=EJ_N8tSk_y5meDD2Uf`de=^<`K@_JhXpyfOb}JE^%kaA!r@07u$QKyS;roE`@Ada#rG5|#Wv2-&9fOOwW+hBHW98!xD30DrXGSG0QX&A z{-d+t+!R9R^+KJw_-ugYBt^?!h0#g_j|!aeckLD^c#{meyr)L-NHM)NuTl=q;QKUi zX#%9w0?|Ctj@A(=xMF!ImYJ-zYI=b<$n7JPoMtVN4GCKjjk~)6V$+TE7DQWSG!DvX zJ19p(I!?v4j?t_u?sekC{u5jkt3~e{&!P3o;1IVYjAY3kGb>9$TI?gLAvOzC^xrV3rbOio`a-)=R9izBt1$CSkj~(!XI=OvWte(iD6^@rRF#7(jpZi zX3H#;9cmXIzpNA^h%8men|+YdgR40NoPs{9yKy|)NA#rX1JksRh?f}Vfy%PzSt8fS zrQ`Sw-S>(Xy*STV`59wHzWtCV0Y9y=t;6Ih5jCshvhBWvRw*k)7p3G@{1B_*=BiAg zxPJKcr$Li+i#frAIEKEuYR>asf325JN@_pJ85fCqA z<;l#o_C8H*_oQM@>AZiCYq2WhbuP(4f137%)L@j<^4t>}777EA6Krn-{rh|?b5VXv z(6uF(K5y6j?r^pVG4+h9Sz4@1lpqRz->5QMuuttgGeW@$n2t50ViPRmQnKVgbXCPRrDhfis;H#M%{PBg z?337p7+ymBbE-8d^ajOHn9T1h*rtMFDJv^S8?jIVy_5yRs5n!r*PaGZKdpLJw}**U zJLNgMWIMAGu>uE9s_}^9oA$!>l?~eMOdvETS);WE$*Ra^zRemw80+R7w_h_puR!nN znf7JsuEqvMn82$r+U-fk$^Vj0v!VXyMg~pW`}H5J3ijc?RMr47tSfa z8+13bTRA(7KWAUJ8S-vs_s;Jy@*Guj7&87l^6$g%AeKBoehTQ8?KwXb6yWDH5X^ZB G0DlAZwU}Z6 literal 0 HcmV?d00001 diff --git a/docs/roller-install-guide.adoc b/docs/roller-install-guide.adoc index 7cf2c81a40..d316308d27 100644 --- a/docs/roller-install-guide.adoc +++ b/docs/roller-install-guide.adoc @@ -87,9 +87,9 @@ Java, a Java Servlet container such as Tomcat, a connection to a database such as MySQL and optionally a connect to a mail server. More specifically, here’s what you need to install and run Roller: -* *Java Development Kit*, specifically the Java 2 SE 1.6 JDK or more -recent. The computer on which you install Roller should already have the -JDK installed and configured. +* *Java Development Kit*, specifically Java 21 or later. The computer on +which you install Roller should already have the JDK installed and +configured. * *Java EE 6 Application Server*, or more specifically a Servlet container that supports at least the Servlet 2.4 API. Hereinafter, we’ll just call this your _server_. Roller has traditionally worked best on @@ -1118,3 +1118,11 @@ Roller. *NOTE*: On Tomcat, before startup you should delete the contents of the Tomcat work directory (located under the webapps folder.) + +*NOTE*: Roller 7 keeps a search index written by Roller 6.1.7. It replaces +an older index at first startup and rebuilds it in the background, with no +action needed: an index from Roller 6.1.6 or earlier holds fields Roller no +longer indexes, and Roller 7's Lucene 10 can't read the Lucene 8 index of +Roller 6.0. Search results are complete again once the log shows +"Completed rebuilding index". To force a rebuild at any time, stop Roller +and delete the directory that `search.index.dir` names. diff --git a/it-playwright/pom.xml b/it-playwright/pom.xml index 212b54f0f7..54f6912a26 100644 --- a/it-playwright/pom.xml +++ b/it-playwright/pom.xml @@ -48,7 +48,7 @@ --> - 17 + 21 UTF-8 1.63.0 6.1.3 diff --git a/pom.xml b/pom.xml index fdf43762b4..e3bbf4994d 100644 --- a/pom.xml +++ b/pom.xml @@ -40,7 +40,7 @@ limitations under the License. - 10.16.1.1 + 10.17.1.0 12.1.13 UTF-8 UTF-8 @@ -63,7 +63,7 @@ limitations under the License. maven-compiler-plugin 3.16.0 - 17 + 21 true -Xlint:all From ccc47f34828b32f44c6512f3d73b6a1abacf6cb6 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Sat, 14 Nov 2026 13:56:39 -0700 Subject: [PATCH 2/3] Update the install guide's server, database and download prerequisites The prerequisites still named Java EE 6, Servlet 2.4, Tomcat 8, Jetty 9, JPA 2.0 and the roller-release-6.0.0-standard archives. Roller 7 needs a Servlet 6.1 container; CI tests it on Jetty 12 (EE 11) and the Docker image on Tomcat 11. The binary archives are apache-roller--binary. --- docs/roller-install-guide.adoc | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/roller-install-guide.adoc b/docs/roller-install-guide.adoc index d316308d27..9bfec5d1e4 100644 --- a/docs/roller-install-guide.adoc +++ b/docs/roller-install-guide.adoc @@ -90,15 +90,13 @@ specifically, here’s what you need to install and run Roller: * *Java Development Kit*, specifically Java 21 or later. The computer on which you install Roller should already have the JDK installed and configured. -* *Java EE 6 Application Server*, or more specifically a Servlet -container that supports at least the Servlet 2.4 API. Hereinafter, we’ll -just call this your _server_. Roller has traditionally worked best on -Tomcat and Tomcat only, but Roller 6.0.0 is known to run on: -** Tomcat 8 -** Jetty 9 +* *Jakarta EE Servlet container* that supports the Servlet 6.1 API. +Hereinafter, we’ll just call this your _server_. Roller is tested on: +** Tomcat 11 +** Jetty 12 (EE 11) * *Relational database* such as MySQL or Apache Derby. Roller stores blog entries, comments, bookmarks and almost all other data in a -relational database accessed via the Java Persistence API 2.0. +relational database accessed via Jakarta Persistence. PostgresSQL, MySQL and Derby are best supported but Roller. Roller also includes database creation scripts for DB2, HSQL-DB, Microsoft SQL Server and Oracle but these have not been tested recently. @@ -108,8 +106,8 @@ for comments and other events via the JavaMail and Activation APIs. Roller WAR file, ready to deploy to your server, plus Roller license files, README and documentation. Unpack using either file based on a compression method your operating system supports: -** roller-release-6.0.0-standard.zip -** roller-release-6.0.0-standard.tar.gz +** apache-roller--binary.zip +** apache-roller--binary.tar.gz This step can also be done after deployment and determination that you would like themes other than the defaults available available, just From 47cea1e6ad3d69335fa6ba8e7b4aa2d3fb3cb9a4 Mon Sep 17 00:00:00 2001 From: Matt Raible Date: Sat, 14 Nov 2026 15:15:21 -0700 Subject: [PATCH 3/3] Say what happens to the search index when going back to Roller 6.1.x Lucene 9 can't read an index Lucene 10 has written. Roller 6.1.7 treats that as an unreadable index: it deletes it and rebuilds in the background, but creates no empty index first, so searches fail until the rebuild finishes. --- docs/roller-install-guide.adoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/roller-install-guide.adoc b/docs/roller-install-guide.adoc index 9bfec5d1e4..f8558427df 100644 --- a/docs/roller-install-guide.adoc +++ b/docs/roller-install-guide.adoc @@ -1124,3 +1124,6 @@ longer indexes, and Roller 7's Lucene 10 can't read the Lucene 8 index of Roller 6.0. Search results are complete again once the log shows "Completed rebuilding index". To force a rebuild at any time, stop Roller and delete the directory that `search.index.dir` names. +If you go back to Roller 6.1.x afterwards, it can't read the index Roller 7 +wrote. It deletes the index and rebuilds it in the background, and searches +return an error until that rebuild finishes.