Update Lucene from 3.0.3 to the current version 8.2.0

Signed-off-by: Andrew DeMaria <lostonamountain@gmail.com>
This commit is contained in:
tesshucom
2019-09-12 16:33:44 -06:00
committed by Andrew DeMaria
parent 8e279a2d2a
commit 5c3c558923
18 changed files with 1994 additions and 1615 deletions
@@ -1,138 +1,138 @@
package org.airsonic.player.service.search;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import org.airsonic.player.TestCaseUtils;
import org.airsonic.player.dao.DaoHelper;
import org.airsonic.player.dao.MusicFolderDao;
import org.airsonic.player.service.MediaScannerService;
import org.airsonic.player.service.SettingsService;
import org.airsonic.player.util.HomeRule;
import org.airsonic.player.util.MusicFolderTestData;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
@ContextConfiguration(locations = {
"/applicationContext-service.xml",
"/applicationContext-cache.xml",
"/applicationContext-testdb.xml"})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@Component
/**
* Abstract class for scanning MusicFolder.
*/
public abstract class AbstractAirsonicHomeTest implements AirsonicHomeTest {
@ClassRule
public static final SpringClassRule classRule = new SpringClassRule() {
HomeRule homeRule = new HomeRule();
@Override
public Statement apply(Statement base, Description description) {
Statement spring = super.apply(base, description);
return homeRule.apply(spring, description);
}
};
/*
* Currently, Maven is executing test classes in series,
* so this class can hold the state.
* When executing in parallel, subclasses should override this.
*/
private static AtomicBoolean dataBasePopulated = new AtomicBoolean();
// Above.
private static AtomicBoolean dataBaseReady = new AtomicBoolean();
protected final static Function<String, String> resolveBaseMediaPath = (childPath) -> {
return MusicFolderTestData.resolveBaseMediaPath().concat(childPath);
};
@Autowired
protected DaoHelper daoHelper;
@Autowired
protected MediaScannerService mediaScannerService;
@Autowired
protected MusicFolderDao musicFolderDao;
@Autowired
protected SettingsService settingsService;
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Override
public AtomicBoolean dataBasePopulated() {
return dataBasePopulated;
}
@Override
public AtomicBoolean dataBaseReady() {
return dataBaseReady;
}
@Override
public final void populateDatabaseOnlyOnce() {
if (!dataBasePopulated().get()) {
dataBasePopulated().set(true);
getMusicFolders().forEach(musicFolderDao::createMusicFolder);
settingsService.clearMusicFolderCache();
try {
// Await time to avoid scan failure.
for (int i = 0; i < 10; i++) {
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
TestCaseUtils.execScan(mediaScannerService);
System.out.println("--- Report of records count per table ---");
Map<String, Integer> records = TestCaseUtils.recordsInAllTables(daoHelper);
records.keySet().stream().filter(s ->
s.equals("MEDIA_FILE")
| s.equals("ARTIST")
| s.equals("MUSIC_FOLDER")
| s.equals("ALBUM"))
.forEach(tableName ->
System.out.println("\t" + tableName + " : " + records.get(tableName).toString()));
System.out.println("--- *********************** ---");
try {
// Await for Lucene to finish writing(asynchronous).
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
dataBaseReady().set(true);
} else {
while (!dataBaseReady().get()) {
try {
// The subsequent test method waits while reading DB data.
for (int i = 0; i < 10; i++) {
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package org.airsonic.player.service.search;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import org.airsonic.player.TestCaseUtils;
import org.airsonic.player.dao.DaoHelper;
import org.airsonic.player.dao.MusicFolderDao;
import org.airsonic.player.service.MediaScannerService;
import org.airsonic.player.service.SettingsService;
import org.airsonic.player.util.HomeRule;
import org.airsonic.player.util.MusicFolderTestData;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
@ContextConfiguration(locations = {
"/applicationContext-service.xml",
"/applicationContext-cache.xml",
"/applicationContext-testdb.xml"})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@Component
/**
* Abstract class for scanning MusicFolder.
*/
public abstract class AbstractAirsonicHomeTest implements AirsonicHomeTest {
@ClassRule
public static final SpringClassRule classRule = new SpringClassRule() {
HomeRule homeRule = new HomeRule();
@Override
public Statement apply(Statement base, Description description) {
Statement spring = super.apply(base, description);
return homeRule.apply(spring, description);
}
};
/*
* Currently, Maven is executing test classes in series,
* so this class can hold the state.
* When executing in parallel, subclasses should override this.
*/
private static AtomicBoolean dataBasePopulated = new AtomicBoolean();
// Above.
private static AtomicBoolean dataBaseReady = new AtomicBoolean();
protected final static Function<String, String> resolveBaseMediaPath = (childPath) -> {
return MusicFolderTestData.resolveBaseMediaPath().concat(childPath);
};
@Autowired
protected DaoHelper daoHelper;
@Autowired
protected MediaScannerService mediaScannerService;
@Autowired
protected MusicFolderDao musicFolderDao;
@Autowired
protected SettingsService settingsService;
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Override
public AtomicBoolean dataBasePopulated() {
return dataBasePopulated;
}
@Override
public AtomicBoolean dataBaseReady() {
return dataBaseReady;
}
@Override
public final void populateDatabaseOnlyOnce() {
if (!dataBasePopulated().get()) {
dataBasePopulated().set(true);
getMusicFolders().forEach(musicFolderDao::createMusicFolder);
settingsService.clearMusicFolderCache();
try {
// Await time to avoid scan failure.
for (int i = 0; i < 10; i++) {
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
TestCaseUtils.execScan(mediaScannerService);
System.out.println("--- Report of records count per table ---");
Map<String, Integer> records = TestCaseUtils.recordsInAllTables(daoHelper);
records.keySet().stream().filter(s ->
s.equals("MEDIA_FILE")
| s.equals("ARTIST")
| s.equals("MUSIC_FOLDER")
| s.equals("ALBUM"))
.forEach(tableName ->
System.out.println("\t" + tableName + " : " + records.get(tableName).toString()));
System.out.println("--- *********************** ---");
try {
// Await for Lucene to finish writing(asynchronous).
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
dataBaseReady().set(true);
} else {
while (!dataBaseReady().get()) {
try {
// The subsequent test method waits while reading DB data.
for (int i = 0; i < 10; i++) {
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
@@ -1,45 +1,45 @@
package org.airsonic.player.service.search;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.airsonic.player.domain.MusicFolder;
import org.airsonic.player.util.MusicFolderTestData;
/**
* Test case interface for scanning MusicFolder.
*/
public interface AirsonicHomeTest {
/**
* MusicFolder used by test class.
*
* @return MusicFolder used by test class
*/
default List<MusicFolder> getMusicFolders() {
return MusicFolderTestData.getTestMusicFolders();
};
/**
* Whether the data input has been completed.
*
* @return Static AtomicBoolean indicating whether the data injection has been
* completed
*/
abstract AtomicBoolean dataBasePopulated();
/**
* Whether the data input has been completed.
*
* @return Static AtomicBoolean indicating whether the data injection has been
* completed
*/
abstract AtomicBoolean dataBaseReady();
/**
* Populate the database only once.
* It is called in the @Before granted method.
*/
void populateDatabaseOnlyOnce();
}
package org.airsonic.player.service.search;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.airsonic.player.domain.MusicFolder;
import org.airsonic.player.util.MusicFolderTestData;
/**
* Test case interface for scanning MusicFolder.
*/
public interface AirsonicHomeTest {
/**
* MusicFolder used by test class.
*
* @return MusicFolder used by test class
*/
default List<MusicFolder> getMusicFolders() {
return MusicFolderTestData.getTestMusicFolders();
};
/**
* Whether the data input has been completed.
*
* @return Static AtomicBoolean indicating whether the data injection has been
* completed
*/
abstract AtomicBoolean dataBasePopulated();
/**
* Whether the data input has been completed.
*
* @return Static AtomicBoolean indicating whether the data injection has been
* completed
*/
abstract AtomicBoolean dataBaseReady();
/**
* Populate the database only once.
* It is called in the @Before granted method.
*/
void populateDatabaseOnlyOnce();
}
@@ -11,7 +11,7 @@ import java.util.Arrays;
import java.util.List;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.TermAttribute;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.junit.Test;
import org.slf4j.LoggerFactory;
@@ -85,9 +85,12 @@ public class AnalyzerFactoryTestCase {
public void testPunctuation1() {
String query = "B︴C";
String expected1 = "b";
String expected2 = "c";
String expected = "b︴c";
/*
* XXX 3.x -> 8.x :
* The definition of punctuation has changed.
*/
Arrays.stream(IndexType.values()).flatMap(i -> Arrays.stream(i.getFields())).forEach(n -> {
List<String> terms = toTermString(n, query);
switch (n) {
@@ -100,9 +103,8 @@ public class AnalyzerFactoryTestCase {
case FieldNames.FOLDER:
case FieldNames.GENRE:
case FieldNames.MEDIA_TYPE:
assertEquals("tokenized : " + n, 2, terms.size());
assertEquals("tokenized : " + n, expected1, terms.get(0));
assertEquals("tokenized : " + n, expected2, terms.get(1));
assertEquals("tokenized : " + n, 1, terms.size());
assertEquals("tokenized : " + n, expected, terms.get(0));
break;
/*
@@ -112,9 +114,8 @@ public class AnalyzerFactoryTestCase {
case FieldNames.ARTIST:
case FieldNames.ALBUM:
case FieldNames.TITLE:
assertEquals("tokenized : " + n, 2, terms.size());
assertEquals("tokenized : " + n, expected1, terms.get(0));
assertEquals("tokenized : " + n, expected2, terms.get(1));
assertEquals("tokenized : " + n, 1, terms.size());
assertEquals("tokenized : " + n, expected, terms.get(0));
break;
/*
* ID, FOLDER_ID, YEAR
@@ -256,12 +257,17 @@ public class AnalyzerFactoryTestCase {
*/
String queryFullWidth = "THIS IS FULL-WIDTH SENTENCES.";
terms = toTermString(queryFullWidth);
assertEquals(5, terms.size());
assertEquals("this", terms.get(0));// removal target is ignored
assertEquals("is", terms.get(1));
assertEquals("full", terms.get(2));
assertEquals("width", terms.get(3));
assertEquals("sentences", terms.get(4));
/*
* XXX 3.x -> 8.x :
*
* This is not a change due to the library but an intentional change.
* The filter order has been changed properly
* as it is probably not a deliberate specification.
*/
assertEquals(3, terms.size());
assertEquals("full", terms.get(0));
assertEquals("width", terms.get(1));
assertEquals("sentences", terms.get(2));
}
@@ -428,6 +434,11 @@ public class AnalyzerFactoryTestCase {
query = "_ID3_ARTIST_ Céline Frisch: Café Zimmermann";
terms = toTermString(query);
assertEquals(5, terms.size());
/*
* XXX 3.x -> 8.x : _id3_artist_ in UAX#29.
* Since the effect is large, trim with Filter.
*/
assertEquals("id3_artist", terms.get(0));
assertEquals("celine", terms.get(1));
assertEquals("frisch", terms.get(2));
@@ -438,6 +449,11 @@ public class AnalyzerFactoryTestCase {
query = "_ID3_ARTIST_ Sarah Walker/Nash Ensemble";
terms = toTermString(query);
assertEquals(5, terms.size());
/*
* XXX 3.x -> 8.x : _id3_artist_ in UAX#29.
* Since the effect is large, trim with Filter.
*/
assertEquals("id3_artist", terms.get(0));
assertEquals("sarah", terms.get(1));
assertEquals("walker", terms.get(2));
@@ -464,14 +480,18 @@ public class AnalyzerFactoryTestCase {
assertEquals(asList("abc", "def"), toTermString("~ABC~DEF~"));
assertEquals(asList("abc", "def"), toTermString("*ABC*DEF*"));
assertEquals(asList("abc", "def"), toTermString("?ABC?DEF?"));
assertEquals(asList("abc", "def"), toTermString(":ABC:DEF:"));
assertEquals(asList("abc:def"), toTermString(":ABC:DEF:")); // XXX 3.x -> 8.x : abc def -> abc:def
assertEquals(asList("abc", "def"), toTermString("-ABC-DEF-"));
assertEquals(asList("abc", "def"), toTermString("/ABC/DEF/"));
assertEquals(asList("abc", "def"), toTermString("_ABC_DEF_"));
/*
* XXX 3.x -> 8.x : _abc_def_ in UAX#29.
* Since the effect is large, trim with Filter.
*/
assertEquals(asList("abc_def"), toTermString("_ABC_DEF_")); // XXX 3.x -> 8.x : abc def -> abc_def
assertEquals(asList("abc", "def"), toTermString(",ABC,DEF,"));
assertEquals(asList("abc.def"), toTermString(".ABC.DEF."));
assertEquals(asList("abc&def"), toTermString("&ABC&DEF&"));
assertEquals(asList("abc@def"), toTermString("@ABC@DEF@"));
assertEquals(asList("abc", "def"), toTermString("&ABC&DEF&")); // XXX 3.x -> 8.x : abc&def -> abc def
assertEquals(asList("abc", "def"), toTermString("@ABC@DEF@")); // XXX 3.x -> 8.x : abc@def -> abc def
assertEquals(asList("abc'def"), toTermString("'ABC'DEF'"));
// trim and delimiter and number
@@ -491,11 +511,15 @@ public class AnalyzerFactoryTestCase {
assertEquals(asList("abc1", "def"), toTermString("*ABC1*DEF*"));
assertEquals(asList("abc1", "def"), toTermString("?ABC1?DEF?"));
assertEquals(asList("abc1", "def"), toTermString(":ABC1:DEF:"));
assertEquals(asList("abc1,def"), toTermString(",ABC1,DEF,"));
assertEquals(asList("abc1-def"), toTermString("-ABC1-DEF-"));
assertEquals(asList("abc1/def"), toTermString("/ABC1/DEF/"));
assertEquals(asList("abc1", "def"), toTermString(",ABC1,DEF,")); // XXX 3.x -> 8.x : abc1,def -> abc1 def
assertEquals(asList("abc1", "def"), toTermString("-ABC1-DEF-")); // XXX 3.x -> 8.x : abc1-def -> abc1 def
assertEquals(asList("abc1", "def"), toTermString("/ABC1/DEF/")); // XXX 3.x -> 8.x : abc1/def -> abc1 def
/*
* XXX 3.x -> 8.x : _abc1_def_ in UAX#29.
* Since the effect is large, trim with Filter.
*/
assertEquals(asList("abc1_def"), toTermString("_ABC1_DEF_"));
assertEquals(asList("abc1.def"), toTermString(".ABC1.DEF."));
assertEquals(asList("abc1", "def"), toTermString(".ABC1.DEF.")); // XXX 3.x -> 8.x : abc1.def -> abc1 def
assertEquals(asList("abc1", "def"), toTermString("&ABC1&DEF&"));
assertEquals(asList("abc1", "def"), toTermString("@ABC1@DEF@"));
assertEquals(asList("abc1", "def"), toTermString("'ABC1'DEF'"));
@@ -517,14 +541,17 @@ public class AnalyzerFactoryTestCase {
assertEquals("airsonic", terms.get(0));
assertEquals("analysis", terms.get(1));
/*
* XXX 3.x -> 8.x :
* we ve -> we've
*/
query = "Weve been here before.";
terms = toTermString(query);
assertEquals(5, terms.size());
assertEquals("we", terms.get(0));
assertEquals("ve", terms.get(1));
assertEquals("been", terms.get(2));
assertEquals("here", terms.get(3));
assertEquals("before", terms.get(4));
assertEquals(4, terms.size());
assertEquals("we've", terms.get(0));
assertEquals("been", terms.get(1));
assertEquals("here", terms.get(2));
assertEquals("before", terms.get(3));
query = "LʼHomme";
terms = toTermString(query);
@@ -591,6 +618,21 @@ public class AnalyzerFactoryTestCase {
assertEquals("glasses", terms.get(5));
}
@Test
public void testGenre() {
/*
* Confirming no conversion to singular.
*/
String query = "{}";
List<String> terms = toQueryTermString(FieldNames.GENRE, query);
assertEquals(1, terms.size());
assertEquals("{ }", terms.get(0));
}
private List<String> toTermString(String str) {
return toTermString(null, str);
}
@@ -602,7 +644,7 @@ public class AnalyzerFactoryTestCase {
new StringReader(str));
stream.reset();
while (stream.incrementToken()) {
result.add(stream.getAttribute(TermAttribute.class).toString()
result.add(stream.getAttribute(CharTermAttribute.class).toString()
.replaceAll("^term\\=", ""));
}
stream.close();
@@ -627,7 +669,7 @@ public class AnalyzerFactoryTestCase {
new StringReader(str));
stream.reset();
while (stream.incrementToken()) {
result.add(stream.getAttribute(TermAttribute.class).toString()
result.add(stream.getAttribute(CharTermAttribute.class).toString()
.replaceAll("^term\\=", ""));
}
stream.close();
@@ -8,9 +8,7 @@ import org.airsonic.player.domain.RandomSearchCriteria;
import org.airsonic.player.domain.SearchCriteria;
import org.airsonic.player.util.HomeRule;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.NumericUtils;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@@ -77,8 +75,34 @@ public class QueryFactoryTestCase {
private static final List<MusicFolder> SINGLE_FOLDERS = Arrays.asList(MUSIC_FOLDER1);
private static final List<MusicFolder> MULTI_FOLDERS = Arrays.asList(MUSIC_FOLDER1, MUSIC_FOLDER2);
/*
* XXX 3.x -> 8.x :
* It does not change the basic functional requirements for the query.
* However, some minor improvements are included.
*
* - Use 'Or' instead of 'SpanOr'.
* This is suitable for 8.x document definition and query grammar.
* A more rigorous comparison.
*
* - Removed comparison of input value and path from condition of search.
* It causes a false search that the user can not imagine.
* Originally unnecessary.
*
* - mediaType and genre changed to raw string key comparison.
* Currently, these are "key" strings, both in the requirements and in the implementation.
* The legacy "normalize" is dirty code that compensates for the incomplete analytics implementation
* and is not necessary as long as proper key comparison can be done.
*
* => Treating these strictly as keys enables DB reference.
* For example, can support multi-genre by creating a new genre field that implements another Tokenizer.
*
* - The method for comparing ranges of numbers has changed.
* This is suitable for 8.x.
*/
@Test
public void testSearchArtist() throws ParseException, IOException {
public void testSearchArtist() throws IOException {
SearchCriteria criteria = new SearchCriteria();
criteria.setOffset(10);
criteria.setCount(Integer.MAX_VALUE);
@@ -86,16 +110,16 @@ public class QueryFactoryTestCase {
Query query = queryFactory.search(criteria, SINGLE_FOLDERS, IndexType.ARTIST);
assertEquals("SearchArtist",
"+((artist:abc* folder:abc*) (artist:def* folder:def*)) +spanOr([folder:" + PATH1 + "])",
"+((artist:abc*) (artist:def*)) +(folder:" + PATH1 + ")",
query.toString());
query = queryFactory.search(criteria, MULTI_FOLDERS, IndexType.ARTIST);
assertEquals("SearchArtist", "+((artist:abc* folder:abc*) (artist:def* folder:def*)) +spanOr([folder:" + PATH1
+ ", folder:" + PATH2 + "])", query.toString());
assertEquals("SearchArtist", "+((artist:abc*) (artist:def*)) +(folder:" + PATH1
+ " folder:" + PATH2 + ")", query.toString());
}
@Test
public void testSearchAlbum() throws ParseException, IOException {
public void testSearchAlbum() throws IOException {
SearchCriteria criteria = new SearchCriteria();
criteria.setOffset(10);
criteria.setCount(Integer.MAX_VALUE);
@@ -103,19 +127,19 @@ public class QueryFactoryTestCase {
Query query = queryFactory.search(criteria, SINGLE_FOLDERS, IndexType.ALBUM);
assertEquals("SearchAlbum",
"+((album:abc* artist:abc* folder:abc*) (album:def* artist:def* folder:def*)) +spanOr([folder:" + PATH1
+ "])",
"+((album:abc* artist:abc*) (album:def* artist:def*)) +(folder:" + PATH1
+ ")",
query.toString());
query = queryFactory.search(criteria, MULTI_FOLDERS, IndexType.ALBUM);
assertEquals("SearchAlbum",
"+((album:abc* artist:abc* folder:abc*) (album:def* artist:def* folder:def*)) +spanOr([folder:" + PATH1
+ ", folder:" + PATH2 + "])",
"+((album:abc* artist:abc*) (album:def* artist:def*)) +(folder:" + PATH1
+ " folder:" + PATH2 + ")",
query.toString());
}
@Test
public void testSearchSong() throws ParseException, IOException {
public void testSearchSong() throws IOException {
SearchCriteria criteria = new SearchCriteria();
criteria.setOffset(10);
criteria.setCount(Integer.MAX_VALUE);
@@ -123,34 +147,34 @@ public class QueryFactoryTestCase {
Query query = queryFactory.search(criteria, SINGLE_FOLDERS, IndexType.SONG);
assertEquals("SearchSong",
"+((title:abc* artist:abc*) (title:def* artist:def*)) +spanOr([folder:" + PATH1 + "])",
"+((title:abc* artist:abc*) (title:def* artist:def*)) +(folder:" + PATH1 + ")",
query.toString());
query = queryFactory.search(criteria, MULTI_FOLDERS, IndexType.SONG);
assertEquals("SearchSong", "+((title:abc* artist:abc*) (title:def* artist:def*)) +spanOr([folder:" + PATH1
+ ", folder:" + PATH2 + "])", query.toString());
assertEquals("SearchSong", "+((title:abc* artist:abc*) (title:def* artist:def*)) +(folder:" + PATH1
+ " folder:" + PATH2 + ")", query.toString());
}
@Test
public void testSearchArtistId3() throws ParseException, IOException {
public void testSearchArtistId3() throws IOException {
SearchCriteria criteria = new SearchCriteria();
criteria.setOffset(10);
criteria.setCount(Integer.MAX_VALUE);
criteria.setQuery(QUERY_ENG_ONLY);
Query query = queryFactory.search(criteria, SINGLE_FOLDERS, IndexType.ARTIST_ID3);
assertEquals("SearchSong", "+((artist:abc*) (artist:def*)) +spanOr([folderId:"
+ NumericUtils.intToPrefixCoded(FID1) + "])", query.toString());
assertEquals("SearchSong", "+((artist:abc*) (artist:def*)) +(folderId:"
+ FID1 + ")", query.toString());
query = queryFactory.search(criteria, MULTI_FOLDERS, IndexType.ARTIST_ID3);
assertEquals("SearchSong",
"+((artist:abc*) (artist:def*)) +spanOr([folderId:" + NumericUtils.intToPrefixCoded(FID1)
+ ", folderId:" + NumericUtils.intToPrefixCoded(FID2) + "])",
"+((artist:abc*) (artist:def*)) +(folderId:" + FID1
+ " folderId:" + FID2 + ")",
query.toString());
}
@Test
public void testSearchAlbumId3() throws ParseException, IOException {
public void testSearchAlbumId3() throws IOException {
SearchCriteria criteria = new SearchCriteria();
criteria.setOffset(10);
criteria.setCount(Integer.MAX_VALUE);
@@ -158,74 +182,74 @@ public class QueryFactoryTestCase {
Query query = queryFactory.search(criteria, SINGLE_FOLDERS, IndexType.ALBUM_ID3);
assertEquals(
"SearchAlbumId3", "+((album:abc* artist:abc* folderId:abc*) (album:def* artist:def* folderId:def*)) "
+ "+spanOr([folderId:" + NumericUtils.intToPrefixCoded(FID1) + "])",
"SearchAlbumId3", "+((album:abc* artist:abc*) (album:def* artist:def*)) "
+ "+(folderId:" + FID1 + ")",
query.toString());
query = queryFactory.search(criteria, MULTI_FOLDERS, IndexType.ALBUM_ID3);
assertEquals("SearchAlbumId3",
"+((album:abc* artist:abc* folderId:abc*) (album:def* artist:def* folderId:def*)) +spanOr([folderId:"
+ NumericUtils.intToPrefixCoded(FID1) + ", folderId:"
+ NumericUtils.intToPrefixCoded(FID2) + "])",
"+((album:abc* artist:abc*) (album:def* artist:def*)) +(folderId:"
+ FID1 + " folderId:"
+ FID2 + ")",
query.toString());
}
@Test
public void testSearchByNameArtist() throws ParseException {
public void testSearchByNameArtist() throws IOException {
Query query = queryFactory.searchByName(FieldNames.ARTIST, QUERY_ENG_ONLY);
assertEquals("SearchByNameArtist", "artist:abc artist:def*", query.toString());
}
@Test
public void testSearchByNameAlbum() throws ParseException {
public void testSearchByNameAlbum() throws IOException {
Query query = queryFactory.searchByName(FieldNames.ALBUM, QUERY_ENG_ONLY);
assertEquals("SearchByNameAlbum", "album:abc album:def*", query.toString());
}
@Test
public void testSearchByNameTitle() throws ParseException {
public void testSearchByNameTitle() throws IOException {
Query query = queryFactory.searchByName(FieldNames.TITLE, QUERY_ENG_ONLY);
assertEquals("SearchByNameTitle", "title:abc title:def*", query.toString());
}
@Test
public void testGetRandomSongs() {
public void testGetRandomSongs() throws IOException {
RandomSearchCriteria criteria = new RandomSearchCriteria(50, "Classic Rock",
Integer.valueOf(1900), Integer.valueOf(2000), SINGLE_FOLDERS);
Query query = queryFactory.getRandomSongs(criteria);
assertEquals(ToStringBuilder.reflectionToString(criteria),
"+mediaType:music +genre:classicrock +year:[1900 TO 2000] +spanOr([folder:" + PATH1 + "])",
"+mediaType:MUSIC +genre:Classic Rock +year:[1900 TO 2000] +(folder:" + PATH1 + ")",
query.toString());
criteria = new RandomSearchCriteria(50, "Classic Rock", Integer.valueOf(1900),
Integer.valueOf(2000), MULTI_FOLDERS);
query = queryFactory.getRandomSongs(criteria);
assertEquals(ToStringBuilder.reflectionToString(criteria),
"+mediaType:music +genre:classicrock +year:[1900 TO 2000] +spanOr([folder:" + PATH1 + ", folder:" + PATH2
+ "])",
"+mediaType:MUSIC +genre:Classic Rock +year:[1900 TO 2000] +(folder:" + PATH1 + " folder:" + PATH2
+ ")",
query.toString());
criteria = new RandomSearchCriteria(50, "Classic Rock", null, null, MULTI_FOLDERS);
query = queryFactory.getRandomSongs(criteria);
assertEquals(ToStringBuilder.reflectionToString(criteria),
"+mediaType:music +genre:classicrock +spanOr([folder:" + PATH1 + ", folder:" + PATH2 + "])",
"+mediaType:MUSIC +genre:Classic Rock +(folder:" + PATH1 + " folder:" + PATH2 + ")",
query.toString());
criteria = new RandomSearchCriteria(50, "Classic Rock", Integer.valueOf(1900), null,
MULTI_FOLDERS);
query = queryFactory.getRandomSongs(criteria);
assertEquals(ToStringBuilder.reflectionToString(criteria),
"+mediaType:music +genre:classicrock +year:[1900 TO *] +spanOr([folder:" + PATH1 + ", folder:" + PATH2
+ "])",
"+mediaType:MUSIC +genre:Classic Rock +year:[1900 TO 2147483647] +(folder:" + PATH1 + " folder:" + PATH2
+ ")",
query.toString());
criteria = new RandomSearchCriteria(50, "Classic Rock", null, Integer.valueOf(2000),
MULTI_FOLDERS);
query = queryFactory.getRandomSongs(criteria);
assertEquals(ToStringBuilder.reflectionToString(criteria),
"+mediaType:music +genre:classicrock +year:[* TO 2000] +spanOr([folder:" + PATH1 + ", folder:" + PATH2
+ "])",
"+mediaType:MUSIC +genre:Classic Rock +year:[-2147483648 TO 2000] +(folder:" + PATH1 + " folder:" + PATH2
+ ")",
query.toString());
}
@@ -233,23 +257,23 @@ public class QueryFactoryTestCase {
public void testGetRandomAlbums() {
Query query = queryFactory.getRandomAlbums(SINGLE_FOLDERS);
assertEquals(ToStringBuilder.reflectionToString(SINGLE_FOLDERS),
"spanOr([folder:" + PATH1 + "])", query.toString());
"(folder:" + PATH1 + ")", query.toString());
query = queryFactory.getRandomAlbums(MULTI_FOLDERS);
assertEquals(ToStringBuilder.reflectionToString(MULTI_FOLDERS),
"spanOr([folder:" + PATH1 + ", folder:" + PATH2 + "])", query.toString());
"(folder:" + PATH1 + " folder:" + PATH2 + ")", query.toString());
}
@Test
public void testGetRandomAlbumsId3() {
Query query = queryFactory.getRandomAlbumsId3(SINGLE_FOLDERS);
assertEquals(ToStringBuilder.reflectionToString(SINGLE_FOLDERS),
"spanOr([folderId:" + NumericUtils.intToPrefixCoded(FID1) + "])", query.toString());
"(folderId:" + FID1 + ")", query.toString());
query = queryFactory.getRandomAlbumsId3(MULTI_FOLDERS);
assertEquals(ToStringBuilder.reflectionToString(MULTI_FOLDERS),
"spanOr([folderId:" + NumericUtils.intToPrefixCoded(FID1) + ", folderId:"
+ NumericUtils.intToPrefixCoded(FID2) + "])",
"(folderId:" + FID1 + " folderId:"
+ FID2 + ")",
query.toString());
}
@@ -52,103 +52,182 @@ public class SearchServiceSpecialGenreTestCase extends AbstractAirsonicHomeTest
*
* Legacy can not search all these genres.
* (Strictly speaking, the genre field is not created at index creation.)
*
* // XXX 3.x -> 8.x : Do the process more strictly.
*
* - Values that can be cross-referenced with DB are stored in the index.
* - Search is also possible with user's readable value (file tag value).
* - However, there is an exception in parentheses.
*/
@Test
public void testQueryEscapeRequires() {
List<MusicFolder> folders = getMusicFolders();
Function<String, RandomSearchCriteria> simpleStringCriteria = s ->
new RandomSearchCriteria(Integer.MAX_VALUE, // count
s, // genre,
null, // fromYear
null, // toYear
folders // musicFolders
);
new RandomSearchCriteria(Integer.MAX_VALUE, // count
s, // genre,
null, // fromYear
null, // toYear
getMusicFolders() // musicFolders
);
List<MediaFile> songs = searchService.getRandomSongs(simpleStringCriteria.apply("+"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("+", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 1", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("-"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("-", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 2", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("&&"));
Assert.assertEquals(0, songs.size());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("+"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("&&", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 3", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("||"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("||", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 4", songs.get(0).getTitle());
/*
* // XXX 3.x -> 8.x : Brackets ()
*
* Lucene can handle these.
* However, brackets are specially parsed before the index creation process.
*
* This string is never stored in the index.
* This is the only exception.
*/
songs = searchService.getRandomSongs(simpleStringCriteria.apply(" ("));// space & bracket
Assert.assertEquals(0, songs.size());
songs = searchService.getRandomSongs(simpleStringCriteria.apply(")"));
Assert.assertEquals(0, songs.size());
/*
* // XXX 3.x -> 8.x : Brackets {}[]
*
* Lucene can handle these.
* However, brackets are specially parsed before the index creation process.
*
* This can be done with a filter that performs the reverse process
* on the input values when searching.
* As a result, the values stored in the file can be retrieved by search.
*
* @see AnalyzerFactory
*
* >>>>>
*/
songs = searchService.getRandomSongs(simpleStringCriteria.apply("{}"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
/*
* This is the result of the tag parser and domain value.
* It is different from the tag value in file.
*/
Assert.assertEquals("{ }", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 7", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("{ }"));
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("Query Escape Requires 7", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("[]"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
/*
* This is the result of the tag parser and domain value.
* It is different from the tag value in file.
*/
Assert.assertEquals("[ ]", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 8", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("[ ]"));
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("Query Escape Requires 8", songs.get(0).getTitle());
// <<<<<
songs = searchService.getRandomSongs(simpleStringCriteria.apply("^"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("^", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 9", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("\""));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("\"", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 10", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("~"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("~", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 11", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("*"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("*", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 12", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("?"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("?", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 13", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply(":"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals(":", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 14", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("\\"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("\\", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 15", songs.get(0).getTitle());
songs = searchService.getRandomSongs(simpleStringCriteria.apply("/"));
Assert.assertEquals(0, songs.size());
Assert.assertEquals(1, songs.size());// XXX 3.x -> 8.x : Searchable
Assert.assertEquals("/", songs.get(0).getGenre());
Assert.assertEquals("Query Escape Requires 16", songs.get(0).getTitle());
}
/*
* Jaudiotagger applies special treatment to bracket (FILE17).
*
* XXX 3.x -> 8.x : Specification of genre search became more natural.
*/
@Test
public void testBrackets() {
List<MusicFolder> folders = getMusicFolders();
RandomSearchCriteria criteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count
"-(GENRE)-", // genre,
Function<String, RandomSearchCriteria> simpleStringCriteria = s ->
new RandomSearchCriteria(Integer.MAX_VALUE, // count
s, // genre,
null, // fromYear
null, // toYear
folders // musicFolders
getMusicFolders() // musicFolders
);
List<MediaFile> songs = searchService.getRandomSongs(criteria);
Assert.assertEquals(0, songs.size());
// -(GENRE)- is registered as genre of FILE17.
criteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count
" genre", // genre,
null, // fromYear
null, // toYear
folders // musicFolders
);
songs = searchService.getRandomSongs(criteria);
/*
* Search by genre string registered in file.
*
* The value stored in the index is different from legacy.
* Domain value is kept as it is.
*/
List<MediaFile> songs = searchService.getRandomSongs(simpleStringCriteria.apply("-(GENRE)-"));
Assert.assertEquals(1, songs.size());
Assert.assertEquals("Consistency with Tag Parser 1", songs.get(0).getTitle());
Assert.assertEquals("-GENRE -", songs.get(0).getGenre());
Assert.assertEquals("Consistency with Tag Parser 1", songs.get(0).getTitle());
/*
* Search by Domain value.
*/
songs = searchService.getRandomSongs(simpleStringCriteria.apply("-GENRE -"));
Assert.assertEquals(1, songs.size());
Assert.assertEquals("-GENRE -", songs.get(0).getGenre());
Assert.assertEquals("Consistency with Tag Parser 1", songs.get(0).getTitle());
/*
* Legacy genre search
*/
songs = searchService.getRandomSongs(simpleStringCriteria.apply(" genre"));
// Strong unique parsing rules have been removed.
Assert.assertEquals(0, songs.size());
}
@@ -188,29 +267,28 @@ public class SearchServiceSpecialGenreTestCase extends AbstractAirsonicHomeTest
@Test
public void testOthers() {
List<MusicFolder> folders = getMusicFolders();
RandomSearchCriteria criteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count
"{'“『【【】】[︴○◎@ $〒→+]FULL-WIDTHCæsar's", // genre,
Function<String, RandomSearchCriteria> simpleStringCriteria = s ->
new RandomSearchCriteria(Integer.MAX_VALUE, // count
s, // genre,
null, // fromYear
null, // toYear
folders // musicFolders
getMusicFolders() // musicFolders
);
List<MediaFile> songs = searchService.getRandomSongs(criteria);
Assert.assertEquals(0, songs.size());
criteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count
"widthcaesar", // genre,
null, // fromYear
null, // toYear
folders // musicFolders
);
songs = searchService.getRandomSongs(criteria);
// XXX 3.x -> 8.x : Do the process more strictly.
List<MediaFile> songs = searchService
.getRandomSongs(simpleStringCriteria.apply("{'“『【【】】[︴○◎@ $〒→+]FULL-WIDTHCæsar's"));
Assert.assertEquals(1, songs.size());
Assert.assertEquals(1, songs.size());
Assert.assertEquals("Other special strings 1", songs.get(0).getTitle());
Assert.assertEquals("{'“『【【】】[︴○◎@ $〒→+]FULL-WIDTHCæsar's", songs.get(0).getGenre());
/*
* Legacy kept "widthcaesar" using their own rules.
* The previous rule has been discarded.
*/
songs = searchService.getRandomSongs(simpleStringCriteria.apply("widthcaesar"));
Assert.assertEquals(0, songs.size());
}
}
@@ -56,11 +56,23 @@ public class SearchServiceStartWithStopwardsTestCase extends AbstractAirsonicHom
criteria.setQuery("will");
SearchResult result = searchService.search(criteria, folders, IndexType.ARTIST_ID3);
Assert.assertEquals("Williams hit by \"will\" ", 1, result.getTotalHits());
// XXX 3.x -> 8.x : The filter is properly applied to the input(Stopward)
Assert.assertEquals("Williams hit by \"will\" ", 0, result.getTotalHits());
criteria.setQuery("the");
result = searchService.search(criteria, folders, IndexType.SONG);
Assert.assertEquals("Theater hit by \"the\" ", 1, result.getTotalHits());
// XXX 3.x -> 8.x : The filter is properly applied to the input(Stopward)
Assert.assertEquals("Theater hit by \"the\" ", 0, result.getTotalHits());
criteria.setQuery("willi");
result = searchService.search(criteria, folders, IndexType.ARTIST_ID3);
// XXX 3.x -> 8.x : Normal forward matching
Assert.assertEquals("Williams hit by \"Williams\" ", 1, result.getTotalHits());
criteria.setQuery("thea");
result = searchService.search(criteria, folders, IndexType.SONG);
// XXX 3.x -> 8.x : Normal forward matching
Assert.assertEquals("Theater hit by \"thea\" ", 1, result.getTotalHits());
}
@@ -164,7 +164,12 @@ public class SearchServiceTestCase extends AbstractAirsonicHomeTest {
artistId3Result.getItems().size());
ParamSearchResult<Artist> artistResult = searchService.searchByName(query, 0,
Integer.MAX_VALUE, allMusicFolders, Artist.class);
Assert.assertEquals("(21) Specify '" + query + "' as the name, and get an artist.", 0,
/*
* // XXX 3.x -> 8.x :
* Hit 'Nash*' as the slash becomes a delimiter.
*/
Assert.assertEquals("(21) Specify '" + query + "' as the name, and get an artist.", 1,
artistResult.getItems().size());
// *** testGetRandomSongs() ***