/* This file is part of Airsonic. Airsonic is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Airsonic is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Airsonic. If not, see . Copyright 2016 (C) Airsonic Authors Based upon Subsonic, Copyright 2009 (C) Sindre Mehus */ package org.airsonic.player.service.search; import org.airsonic.player.domain.Album; import org.airsonic.player.domain.Artist; import org.airsonic.player.domain.MediaFile; import org.airsonic.player.domain.MusicFolder; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field.Store; import org.apache.lucene.document.FieldType; import org.apache.lucene.document.IntPoint; import org.apache.lucene.document.SortedDocValuesField; import org.apache.lucene.document.StoredField; import org.apache.lucene.document.TextField; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.Term; import org.apache.lucene.util.BytesRef; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.stereotype.Component; import java.util.function.BiConsumer; import static org.springframework.util.ObjectUtils.isEmpty; /** * A factory that generates the documents to be stored in the index. */ @Component public class DocumentFactory { private static final FieldType TYPE_ID; private static final FieldType TYPE_ID_NO_STORE; private static final FieldType TYPE_KEY; static { TYPE_ID = new FieldType(); TYPE_ID.setIndexOptions(IndexOptions.DOCS); TYPE_ID.setTokenized(false); TYPE_ID.setOmitNorms(true); TYPE_ID.setStored(true); TYPE_ID.freeze(); TYPE_ID_NO_STORE = new FieldType(); TYPE_ID_NO_STORE.setIndexOptions(IndexOptions.DOCS); TYPE_ID_NO_STORE.setTokenized(false); TYPE_ID_NO_STORE.setOmitNorms(true); TYPE_ID_NO_STORE.setStored(false); TYPE_ID_NO_STORE.freeze(); TYPE_KEY = new FieldType(); TYPE_KEY.setIndexOptions(IndexOptions.DOCS); TYPE_KEY.setTokenized(false); TYPE_KEY.setOmitNorms(true); TYPE_KEY.setStored(false); TYPE_KEY.freeze(); } @FunctionalInterface private interface Consumer { void accept(T t, U u, V v); } private BiConsumer<@NonNull Document, @NonNull Integer> fieldId = (doc, value) -> { doc.add(new StoredField(FieldNames.ID, Integer.toString(value), TYPE_ID)); }; private BiConsumer<@NonNull Document, @NonNull Integer> fieldFolderId = (doc, value) -> { doc.add(new StoredField(FieldNames.FOLDER_ID, Integer.toString(value), TYPE_ID_NO_STORE)); }; private Consumer<@NonNull Document, @NonNull String, @NonNull String> fieldKey = (doc, field, value) -> { doc.add(new StoredField(field, value, TYPE_KEY)); }; private BiConsumer<@NonNull Document, @NonNull String> fieldMediatype = (doc, value) -> fieldKey.accept(doc, FieldNames.MEDIA_TYPE, value); private BiConsumer<@NonNull Document, @NonNull String> fieldFolderPath = (doc, value) -> fieldKey.accept(doc, FieldNames.FOLDER, value); private BiConsumer<@NonNull Document, @Nullable String> fieldGenre = (doc, value) -> { if (isEmpty(value)) { return; } fieldKey.accept(doc, FieldNames.GENRE, value); }; private Consumer<@NonNull Document, @NonNull String, @Nullable Integer> fieldYear = (doc, fieldName, value) -> { if (isEmpty(value)) { return; } doc.add(new IntPoint(fieldName, value)); }; private Consumer<@NonNull Document, @NonNull String, @Nullable String> fieldWords = (doc, fieldName, value) -> { if (isEmpty(value)) { return; } doc.add(new TextField(fieldName, value, Store.NO)); doc.add(new SortedDocValuesField(fieldName, new BytesRef(value))); }; public final Term createPrimarykey(Integer id) { return new Term(FieldNames.ID, Integer.toString(id)); } public final Term createPrimarykey(Album album) { return createPrimarykey(album.getId()); } public final Term createPrimarykey(Artist artist) { return createPrimarykey(artist.getId()); } public final Term createPrimarykey(MediaFile mediaFile) { return createPrimarykey(mediaFile.getId()); } /** * Create a document. * * @param mediaFile target of document * @return document * @since legacy */ public Document createAlbumDocument(MediaFile mediaFile) { Document doc = new Document(); fieldId.accept(doc, mediaFile.getId()); fieldWords.accept(doc, FieldNames.ARTIST, mediaFile.getArtist()); fieldWords.accept(doc, FieldNames.ALBUM, mediaFile.getAlbumName()); fieldFolderPath.accept(doc, mediaFile.getFolder()); return doc; } /** * Create a document. * * @param mediaFile target of document * @return document * @since legacy */ public Document createArtistDocument(MediaFile mediaFile) { Document doc = new Document(); fieldId.accept(doc, mediaFile.getId()); fieldWords.accept(doc, FieldNames.ARTIST, mediaFile.getArtist()); fieldFolderPath.accept(doc, mediaFile.getFolder()); return doc; } /** * Create a document. * * @param album target of document * @return document * @since legacy */ public Document createAlbumId3Document(Album album) { Document doc = new Document(); fieldId.accept(doc, album.getId()); fieldWords.accept(doc, FieldNames.ARTIST, album.getArtist()); fieldWords.accept(doc, FieldNames.ALBUM, album.getName()); fieldFolderId.accept(doc, album.getFolderId()); return doc; } /** * Create a document. * * @param artist target of document * @param musicFolder target folder exists * @return document * @since legacy */ /* * XXX 3.x -> 8.x : * Only null check specification of createArtistId3Document is different from legacy. * (The reason is only to simplify the function.) * * Since the field of domain object Album is nonnull, * null check was not performed. * * In implementation ARTIST and ALBUM became nullable, * but null is not input at this point in data flow. */ public Document createArtistId3Document(Artist artist, MusicFolder musicFolder) { Document doc = new Document(); fieldId.accept(doc, artist.getId()); fieldWords.accept(doc, FieldNames.ARTIST, artist.getName()); fieldFolderId.accept(doc, musicFolder.getId()); return doc; } /** * Create a document. * * @param mediaFile target of document * @return document * @since legacy */ public Document createSongDocument(MediaFile mediaFile) { Document doc = new Document(); fieldId.accept(doc, mediaFile.getId()); fieldMediatype.accept(doc, mediaFile.getMediaType().name()); fieldWords.accept(doc, FieldNames.TITLE, mediaFile.getTitle()); fieldWords.accept(doc, FieldNames.ARTIST, mediaFile.getArtist()); fieldGenre.accept(doc, mediaFile.getGenre()); fieldYear.accept(doc, FieldNames.YEAR, mediaFile.getYear()); fieldFolderPath.accept(doc, mediaFile.getFolder()); return doc; } }