org.musicbrainz.search.index.RecordingIndex.java Source code

Java tutorial

Introduction

Here is the source code for org.musicbrainz.search.index.RecordingIndex.java

Source

/*
 * MusicBrainz Search Server
 * Copyright (C) 2009  Lukas Lalinsky
    
 * This program 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 2
 * of the License, or (at your option) any later version.
    
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

package org.musicbrainz.search.index;

import com.google.common.base.Strings;
import org.apache.commons.lang.time.StopWatch;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.search.similarities.Similarity;
import org.musicbrainz.mmd2.*;
import org.musicbrainz.search.MbDocument;
import org.musicbrainz.search.analysis.RecordingSimilarity;
import org.musicbrainz.search.helper.*;

import java.io.IOException;
import java.math.BigInteger;
import java.sql.*;
import java.util.*;

public class RecordingIndex extends DatabaseIndex {

    private static final String VARIOUS_ARTISTS_GUID = "89ad4ac3-39f7-470e-963a-56509c546377";
    private static final String VARIOUS_ARTISTS_NAME = "Various Artists";

    private static final int VARIOUS_ARTIST_CREDIT_ID = 1;

    public static final String INDEX_NAME = "recording";

    private StopWatch trackClock = new StopWatch();
    private StopWatch isrcClock = new StopWatch();
    private StopWatch artistClock = new StopWatch();
    private StopWatch trackArtistClock = new StopWatch();
    private StopWatch releaseClock = new StopWatch();
    private StopWatch recordingClock = new StopWatch();
    private StopWatch buildClock = new StopWatch();
    private StopWatch storeClock = new StopWatch();

    private final static int QUANTIZED_DURATION = 2000;

    public RecordingIndex(Connection dbConnection) {
        super(dbConnection);
        trackClock.start();
        isrcClock.start();
        artistClock.start();
        trackArtistClock.start();
        releaseClock.start();
        recordingClock.start();
        buildClock.start();
        storeClock.start();
        trackClock.suspend();
        isrcClock.suspend();
        artistClock.suspend();
        releaseClock.suspend();
        recordingClock.suspend();
        trackArtistClock.suspend();
        buildClock.suspend();
        storeClock.suspend();
    }

    public RecordingIndex() {
    }

    public String getName() {
        return RecordingIndex.INDEX_NAME;
    }

    public Analyzer getAnalyzer() {
        return DatabaseIndex.getAnalyzer(RecordingIndexField.class);
    }

    @Override
    public IndexField getIdentifierField() {
        return RecordingIndexField.ID;
    }

    public int getMaxId() throws SQLException {
        Statement st = dbConnection.createStatement();
        ResultSet rs = st.executeQuery("SELECT MAX(id) FROM recording");
        rs.next();
        return rs.getInt(1);
    }

    public int getNoOfRows(int maxId) throws SQLException {
        Statement st = dbConnection.createStatement();
        ResultSet rs = st.executeQuery("SELECT count(*) FROM recording WHERE id<=" + maxId);
        rs.next();
        return rs.getInt(1);
    }

    String releases;
    String releaseArtistCredits;
    String releaseEvents;
    String releaseSecondaryTypes;

    @Override
    public Similarity getSimilarity() {
        return new RecordingSimilarity();
    }

    public void init(IndexWriter indexWriter, boolean isUpdater) throws SQLException {

        if (!isUpdater) {
            addPreparedStatement("TRACKS",
                    "SELECT id, gid, track_name, length as duration, recording, track_position, track_number, track_count, "
                            + "  release_id, medium_position, format " + " FROM tmp_track "
                            + " WHERE recording between ? AND ?");
        } else {
            addPreparedStatement("TRACKS",
                    "SELECT t.id, t.gid, t.name as track_name, t.length as duration, t.recording, t.position as track_position, t.number as track_number, m.track_count, "
                            + "  m.release as release_id, m.position as medium_position,mf.name as format "
                            + " FROM track t " + "  INNER JOIN medium m ON t.medium=m.id "
                            + "  LEFT JOIN  medium_format mf ON m.format=mf.id "
                            + " WHERE t.recording BETWEEN ? AND ?");
        }

        addPreparedStatement("TAGS", TagHelper.constructTagQuery("recording_tag", "recording"));

        addPreparedStatement("ISRCS", "SELECT recording as recordingId, isrc " + " FROM isrc "
                + " WHERE recording BETWEEN ? AND ?  " + " ORDER BY recording, id");

        addPreparedStatement("ARTISTCREDITS",
                "SELECT r.id as recordingId, " + "  a.artist_credit, " + "  a.pos, " + "  a.joinphrase, "
                        + "  a.artistId,  " + "  a.comment, " + "  a.artistName, " + "  a.artistCreditName, "
                        + "  a.artistSortName " + " FROM recording AS r "
                        + "  INNER JOIN tmp_artistcredit a ON r.artist_credit=a.artist_credit "
                        + " WHERE r.id BETWEEN ? AND ?  " + " ORDER BY r.id, a.pos");

        addPreparedStatement("ARTISTCREDITALIASES", "SELECT r.id as recordingId," + " a.artist_credit, "
                + " a.pos, " + " aa.name," + " aa.sort_name," + " aa.primary_for_locale," + " aa.locale,"
                + " aa.begin_date_year," + " aa.begin_date_month," + " aa.begin_date_day," + " aa.end_date_year,"
                + " aa.end_date_month," + " aa.end_date_day," + " att.name as type" + " FROM recording AS r "
                + "  INNER JOIN tmp_artistcredit a ON r.artist_credit=a.artist_credit "
                + "  INNER JOIN artist_alias aa ON a.id=aa.artist"
                + "  LEFT  JOIN artist_alias_type att on (aa.type=att.id)" + " WHERE r.id BETWEEN ? AND ?  "
                + " AND a.artistId!='" + ArtistIndex.VARIOUS_ARTIST_MBID + "'" + " AND a.artistId!='"
                + ArtistIndex.UNKNOWN_ARTIST_MBID + "'" + " ORDER BY r.id, a.pos, aa.name");

        addPreparedStatement("TRACKARTISTCREDITS",
                "SELECT t.id as id, " + "  a.artist_credit, " + "  a.pos, " + "  a.joinphrase, " + "  a.artistId,  "
                        + "  a.comment, " + "  a.artistName, " + "  a.artistCreditName, " + "  a.artistSortName "
                        + " FROM track AS t "
                        + "  INNER JOIN tmp_artistcredit a ON t.artist_credit=a.artist_credit "
                        + " WHERE t.recording BETWEEN ? AND ?  " + " ORDER BY t.recording, a.pos");

        addPreparedStatement("TRACKARTISTCREDITALIASES", "SELECT r.id as recordingId," + " a.artist_credit, "
                + " a.pos, " + " aa.name," + " aa.sort_name," + " aa.primary_for_locale," + " aa.locale,"
                + " aa.begin_date_year," + " aa.begin_date_month," + " aa.begin_date_day," + " aa.end_date_year,"
                + " aa.end_date_month," + " aa.end_date_day," + " att.name as type" + " FROM track AS r "
                + "  INNER JOIN tmp_artistcredit a ON r.artist_credit=a.artist_credit "
                + "  INNER JOIN artist_alias aa ON a.id=aa.artist"
                + "  LEFT  JOIN artist_alias_type att on (aa.type=att.id)" + " WHERE r.recording BETWEEN ? AND ?  "
                + " AND a.artistId!='" + ArtistIndex.VARIOUS_ARTIST_MBID + "'" + " AND a.artistId!='"
                + ArtistIndex.UNKNOWN_ARTIST_MBID + "'" + " ORDER BY r.id, a.pos, aa.name");

        releaseArtistCredits = "SELECT r.id as releaseKey, " + "  a.artist_credit, " + "  a.pos, "
                + "  a.joinphrase, " + "  a.artistId,  " + "  a.comment, " + "  a.artistName, "
                + "  a.artistCreditName, " + "  a.artistSortName " + " FROM release AS r "
                + "  INNER JOIN tmp_artistcredit a ON r.artist_credit=a.artist_credit " + " WHERE a.artistId!='"
                + ArtistIndex.VARIOUS_ARTIST_MBID + "'" + " AND r.id in ";

        releases = "SELECT " + "  id as releaseKey, gid as releaseid, name as releasename, type, "
                + "  status, tracks,artist_credit, rg_gid " + " FROM tmp_release r1 " + " WHERE r1.id in ";

        releaseEvents = " SELECT release, country, " + "   date_year, date_month, date_day, name, gid"
                + " FROM tmp_release_event r " + " WHERE r.release in ";

        releaseSecondaryTypes = "SELECT rg.name as type, r.id as releaseKey" + " FROM tmp_release r "
                + " INNER JOIN release_group_secondary_type_join  rgj " + " ON r.rg_id=rgj.release_group "
                + " INNER JOIN release_group_secondary_type rg  " + " ON rgj.secondary_type = rg.id "
                + " WHERE r.id in ";

        addPreparedStatement("RECORDINGS",
                "SELECT re.id as recordingId, re.gid as trackid, re.length as duration, re.name as trackname, re.comment, re.video "
                        + " FROM recording re " + " WHERE re.id BETWEEN ? AND ?");
    }

    public void destroy() throws SQLException {

        super.destroy();
        System.out.println(this.getName() + ":Isrcs Queries " + Utils.formatClock(isrcClock));
        System.out.println(this.getName() + ":Track Queries " + Utils.formatClock(trackClock));
        System.out.println(this.getName() + ":Artists Queries " + Utils.formatClock(artistClock));
        System.out.println(this.getName() + ":Track Artists Queries " + Utils.formatClock(trackArtistClock));
        System.out.println(this.getName() + ":Releases Queries " + Utils.formatClock(releaseClock));
        System.out.println(this.getName() + ":Recording Queries " + Utils.formatClock(recordingClock));
        System.out.println(this.getName() + ":Build Index " + Utils.formatClock(buildClock));
        System.out.println(this.getName() + ":Build Store " + Utils.formatClock(storeClock));

    }

    /**
     * Get tag information
     *
     * @param min min recording id
     * @param max max recording id
     * @return A map of matches
     * @throws SQLException
     * @throws IOException
     */
    private Map<Integer, List<Tag>> loadTags(int min, int max) throws SQLException, IOException {

        PreparedStatement st = getPreparedStatement("TAGS");
        st.setInt(1, min);
        st.setInt(2, max);
        ResultSet rs = st.executeQuery();
        Map<Integer, List<Tag>> tags = TagHelper.completeTagsFromDbResults(rs, "recording");
        rs.close();
        return tags;
    }

    /**
     * Get ISRC Information for the recordings
     *
     * @param min min recording id
     * @param max max recording id
     * @return map of matches
     * @throws SQLException
     * @throws IOException
     */
    private Map<Integer, List<String>> loadISRCs(int min, int max) throws SQLException, IOException {

        //ISRC
        isrcClock.resume();
        Map<Integer, List<String>> isrcWrapper = new HashMap<Integer, List<String>>();
        PreparedStatement st = getPreparedStatement("ISRCS");
        st.setInt(1, min);
        st.setInt(2, max);
        ResultSet rs = st.executeQuery();
        while (rs.next()) {
            int recordingId = rs.getInt("recordingId");
            List<String> list;
            if (!isrcWrapper.containsKey(recordingId)) {
                list = new LinkedList<String>();
                isrcWrapper.put(recordingId, list);
            } else {
                list = isrcWrapper.get(recordingId);
            }
            String isrc = new String(rs.getString("isrc"));
            list.add(isrc);
        }
        rs.close();
        isrcClock.suspend();
        return isrcWrapper;
    }

    /**
     * Get Recording Artist Credit
     *
     * @param min min recording id
     * @param max max recording id
     * @return A map of matches
     * @throws SQLException if sql problem
     * @throws IOException  if io exception
     */
    private Map<Integer, ArtistCreditWrapper> loadArtists(int min, int max) throws SQLException, IOException {

        //Artists
        artistClock.resume();
        PreparedStatement st = getPreparedStatement("ARTISTCREDITS");
        st.setInt(1, min);
        st.setInt(2, max);
        ResultSet rs = st.executeQuery();
        Map<Integer, ArtistCreditWrapper> artistCredits = ArtistCreditHelper.completeArtistCreditFromDbResults(rs,
                "recordingId", "artist_Credit", "artistId", "artistName", "artistSortName", "comment", "joinphrase",
                "artistCreditName");
        rs.close();
        artistClock.suspend();
        return artistCredits;
    }

    private Map<Integer, ArtistCreditWrapper> updateArtistCreditWithAliases(
            Map<Integer, ArtistCreditWrapper> artistCredits, int min, int max) throws SQLException, IOException {

        //Artist Credit Aliases
        PreparedStatement st = getPreparedStatement("ARTISTCREDITALIASES");
        st.setInt(1, min);
        st.setInt(2, max);
        ResultSet rs = st.executeQuery();
        return ArtistCreditHelper.updateArtistCreditWithAliases(artistCredits, "recordingId", rs);
    }

    private Map<Integer, ArtistCreditWrapper> updateTrackArtistCreditWithAliases(
            Map<Integer, ArtistCreditWrapper> artistCredits, int min, int max) throws SQLException, IOException {

        //Artist Credit Aliases
        PreparedStatement st = getPreparedStatement("TRACKARTISTCREDITALIASES");
        st.setInt(1, min);
        st.setInt(2, max);
        ResultSet rs = st.executeQuery();
        return ArtistCreditHelper.updateArtistCreditWithAliases(artistCredits, "recordingId", rs);
    }

    /**
     * Get Track Artist Credit
     *
     * @param min min recording id
     * @param max max recording id
     * @return A map of matches
     * @throws SQLException if sql problem
     * @throws IOException  if io exception
     */
    private Map<Integer, ArtistCreditWrapper> loadTrackArtists(int min, int max) throws SQLException, IOException {

        //Artists
        trackArtistClock.resume();
        PreparedStatement st = getPreparedStatement("TRACKARTISTCREDITS");
        st.setInt(1, min);
        st.setInt(2, max);
        ResultSet rs = st.executeQuery();
        Map<Integer, ArtistCreditWrapper> artistCredits = ArtistCreditHelper.completeArtistCreditFromDbResults(rs,
                "id", "artist_Credit", "artistId", "artistName", "artistSortName", "comment", "joinphrase",
                "artistCreditName");
        rs.close();
        trackArtistClock.suspend();
        return artistCredits;
    }

    /**
     * Get Release Artist Credit
     *
     * @param min min recording id
     * @param max max recording id
     * @return A map of matches
     * @throws SQLException if sql problem
     * @throws IOException  if io exception
     */
    private Map<Integer, ArtistCreditWrapper> loadReleaseArtists(Map<Integer, Release> releases, int min, int max)
            throws SQLException, IOException {

        //Add release artists
        PreparedStatement stmt = createReleaseArtistCreditsStatement(releases.size());
        int count = 1;
        for (Integer key : releases.keySet()) {
            stmt.setInt(count, key);
            count++;
        }

        ResultSet rs = stmt.executeQuery();
        Map<Integer, ArtistCreditWrapper> releaseArtistCredits = ArtistCreditHelper
                .completeArtistCreditFromDbResults(rs, "releaseKey", "artist_Credit", "artistId", "artistName",
                        "artistSortName", "comment", "joinphrase", "artistCreditName");
        rs.close();
        return releaseArtistCredits;
    }

    /**
     * Get track  information for recordings
     * <p/>
     * One recording can be linked to by multiple tracks
     *
     * @param min
     * @param max
     * @return A map of matches
     * @throws SQLException
     * @throws IOException
     */
    private Map<Integer, List<TrackWrapper>> loadTracks(int min, int max) throws SQLException, IOException {

        //Tracks and Release Info
        trackClock.resume();
        Map<Integer, List<TrackWrapper>> tracks = new HashMap<Integer, List<TrackWrapper>>();
        PreparedStatement st = getPreparedStatement("TRACKS");
        st.setInt(1, min);
        st.setInt(2, max);
        ResultSet rs = st.executeQuery();
        while (rs.next()) {
            int recordingId = rs.getInt("recording");
            List<TrackWrapper> list;
            if (!tracks.containsKey(recordingId)) {
                list = new LinkedList<TrackWrapper>();
                tracks.put(recordingId, list);
            } else {
                list = tracks.get(recordingId);
            }
            TrackWrapper tw = new TrackWrapper();
            tw.setTrackId(rs.getInt("id"));
            tw.setTrackGuid(rs.getString("gid"));
            tw.setReleaseId(rs.getInt("release_id"));
            tw.setTrackCount(rs.getInt("track_count"));
            tw.setTrackPosition(rs.getInt("track_position"));
            tw.setTrackName(rs.getString("track_name"));
            tw.setMediumPosition(rs.getInt("medium_position"));
            tw.setMediumFormat(rs.getString("format"));
            tw.setDuration(rs.getInt("duration"));
            tw.setTrackNumber(rs.getString("track_number"));
            list.add(tw);
        }
        rs.close();
        trackClock.suspend();
        return tracks;
    }

    /**
     * Create the release statement
     *
     * @param noOfElements
     * @return
     * @throws SQLException
     */
    private PreparedStatement createReleaseStatement(int noOfElements) throws SQLException {
        StringBuilder inClause = new StringBuilder();
        boolean firstValue = true;
        for (int i = 0; i < noOfElements; i++) {
            if (firstValue) {
                firstValue = false;
            } else {
                inClause.append(',');
            }
            inClause.append('?');
        }
        PreparedStatement stmt = dbConnection.prepareStatement(releases + "(" + inClause.toString() + ')');
        return stmt;

    }

    private PreparedStatement createReleaseEventStatement(int noOfElements) throws SQLException {
        StringBuilder inClause = new StringBuilder();
        boolean firstValue = true;
        for (int i = 0; i < noOfElements; i++) {
            if (firstValue) {
                firstValue = false;
            } else {
                inClause.append(',');
            }
            inClause.append('?');
        }
        PreparedStatement stmt = dbConnection.prepareStatement(releaseEvents + "(" + inClause.toString() + ')');
        return stmt;

    }

    /**
     * Create the release secondary types statement
     *
     * @param noOfElements
     * @return
     * @throws SQLException
     */
    private PreparedStatement createReleaseSecondaryTypesStatement(int noOfElements) throws SQLException {
        StringBuilder inClause = new StringBuilder();
        boolean firstValue = true;
        for (int i = 0; i < noOfElements; i++) {
            if (firstValue) {
                firstValue = false;
            } else {
                inClause.append(',');
            }
            inClause.append('?');
        }
        PreparedStatement stmt = dbConnection
                .prepareStatement(releaseSecondaryTypes + "(" + inClause.toString() + ')');
        return stmt;

    }

    /**
     * Create the release artist credits statement
     *
     * @param noOfElements
     * @return
     * @throws SQLException
     */
    private PreparedStatement createReleaseArtistCreditsStatement(int noOfElements) throws SQLException {
        StringBuilder inClause = new StringBuilder();
        boolean firstValue = true;
        for (int i = 0; i < noOfElements; i++) {
            if (firstValue) {
                firstValue = false;
            } else {
                inClause.append(',');
            }
            inClause.append('?');
        }
        PreparedStatement stmt = dbConnection
                .prepareStatement(releaseArtistCredits + "(" + inClause.toString() + ')');
        return stmt;

    }

    /**
     * Get release information for recordings
     *
     * @param tracks
     * @return A map of matches
     * @throws SQLException
     * @throws IOException
     */
    private Map<Integer, Release> loadReleases(Map<Integer, List<TrackWrapper>> tracks)
            throws SQLException, IOException {

        Map<Integer, Release> releases = new HashMap<Integer, Release>();
        ObjectFactory of = new ObjectFactory();

        try {
            releaseClock.resume();
        } catch (IllegalStateException e) {
            System.out.println("Warning: IllegalStateException during StopWatch.resume");
        }

        // Add all the releaseKeys to a set to prevent duplicates
        Set<Integer> releaseKeys = new HashSet<Integer>();
        for (List<TrackWrapper> recording : tracks.values()) {
            for (TrackWrapper track : recording) {
                releaseKeys.add(track.getReleaseId());
            }
        }

        if (releaseKeys.isEmpty()) {
            return releases;
        }

        PreparedStatement stmt = createReleaseStatement(releaseKeys.size());
        int count = 1;
        for (Integer key : releaseKeys) {
            stmt.setInt(count, key);
            count++;
        }

        Release release;
        ResultSet rs = stmt.executeQuery();
        while (rs.next()) {
            int releaseKey = rs.getInt("releaseKey");
            if (!releases.containsKey(releaseKey)) {
                release = of.createRelease();
                releases.put(releaseKey, release);
            } else {
                release = releases.get(releaseKey);
            }

            MediumList ml = of.createMediumList();
            ReleaseGroup rg = of.createReleaseGroup();
            release.setId(rs.getString("releaseId"));
            release.setTitle(rs.getString("releasename"));
            PrimaryType pt = new PrimaryType();
            pt.setContent(rs.getString("type"));
            rg.setPrimaryType(pt);
            rg.setId(rs.getString("rg_gid"));
            release.setReleaseGroup(rg);
            Status status = new Status();
            status.setContent(rs.getString("status"));
            release.setStatus(status);
            ml.setTrackCount(BigInteger.valueOf(rs.getInt("tracks")));
            release.setReleaseGroup(rg);
            release.setMediumList(ml);
            //Manually create various artist artists they are not retrieved from the database
            //for performance reasons
            if (rs.getInt("artist_credit") == VARIOUS_ARTIST_CREDIT_ID) {
                ArtistCredit ac = createVariousArtistsCredit();
                release.setArtistCredit(ac);
            }
        }
        rs.close();

        //Add ReleaseEvents for each Release
        stmt = createReleaseEventStatement(releaseKeys.size());
        count = 1;
        for (Integer key : releaseKeys) {
            stmt.setInt(count, key);
            count++;
        }
        rs = stmt.executeQuery();
        while (rs.next()) {
            int releaseKey = rs.getInt("release");
            release = releases.get(releaseKey);
            if (release != null) {
                if (release.getReleaseEventList() == null) {
                    release.setReleaseEventList(of.createReleaseEventList());
                }
                ReleaseEvent re = of.createReleaseEvent();
                re.setDate(Strings.emptyToNull(
                        Utils.formatDate(rs.getInt("date_year"), rs.getInt("date_month"), rs.getInt("date_day"))));
                String iso_code = rs.getString("country");
                String gid = rs.getString("gid");
                String name = rs.getString("name");
                String sort_name = name;
                if (iso_code != null) {
                    Iso31661CodeList isoList = of.createIso31661CodeList();
                    isoList.getIso31661Code().add(iso_code);
                    DefAreaElementInner area = of.createDefAreaElementInner();
                    area.setIso31661CodeList(isoList);
                    re.setArea(area);
                    area.setId(gid);
                    area.setName(name);
                    area.setSortName(sort_name);
                }
                release.getReleaseEventList().getReleaseEvent().add(re);
            }
        }
        //Add secondary types of the releasegroup that each release is part of
        stmt = createReleaseSecondaryTypesStatement(releaseKeys.size());
        count = 1;
        for (Integer key : releaseKeys) {
            stmt.setInt(count, key);
            count++;
        }
        rs = stmt.executeQuery();
        while (rs.next()) {
            int releaseKey = rs.getInt("releaseKey");
            release = releases.get(releaseKey);
            ReleaseGroup rg = release.getReleaseGroup();
            if (rg.getSecondaryTypeList() == null) {
                rg.setSecondaryTypeList(of.createSecondaryTypeList());
            }
            SecondaryType st = new SecondaryType();
            st.setContent(rs.getString("type"));
            rg.getSecondaryTypeList().getSecondaryType().add(st);
        }

        try {
            releaseClock.suspend();
        } catch (IllegalStateException e) {
            System.out.println("Warning: IllegalStateException during StopWatch.resume");
        }
        return releases;
    }

    public void indexData(IndexWriter indexWriter, int min, int max) throws SQLException, IOException {

        Map<Integer, List<Tag>> tags = loadTags(min, max);
        Map<Integer, List<String>> isrcs = loadISRCs(min, max);
        Map<Integer, ArtistCreditWrapper> artistCredits = updateArtistCreditWithAliases(loadArtists(min, max), min,
                max);
        Map<Integer, ArtistCreditWrapper> trackArtistCredits = updateTrackArtistCreditWithAliases(
                loadTrackArtists(min, max), min, max);
        Map<Integer, List<TrackWrapper>> tracks = loadTracks(min, max);
        Map<Integer, Release> releases = loadReleases(tracks);
        Map<Integer, ArtistCreditWrapper> releaseArtists = loadReleaseArtists(releases, min, max);

        PreparedStatement st = getPreparedStatement("RECORDINGS");
        st.setInt(1, min);
        st.setInt(2, max);
        recordingClock.resume();
        ResultSet rs = st.executeQuery();
        recordingClock.suspend();
        while (rs.next()) {
            indexWriter.addDocument(documentFromResultSet(rs, tags, isrcs, artistCredits, trackArtistCredits,
                    tracks, releases, releaseArtists));
        }
        rs.close();

    }

    public Document documentFromResultSet(ResultSet rs, Map<Integer, List<Tag>> tags,
            Map<Integer, List<String>> isrcs, Map<Integer, ArtistCreditWrapper> artistCredits,
            Map<Integer, ArtistCreditWrapper> trackArtistCredits, Map<Integer, List<TrackWrapper>> tracks,
            Map<Integer, Release> releases, Map<Integer, ArtistCreditWrapper> releaseArtists) throws SQLException {

        buildClock.resume();
        Set<Integer> durations = new HashSet<Integer>();
        Set<Integer> qdurs = new HashSet<Integer>();

        Set<String> trackNames = new HashSet<String>();

        int id = rs.getInt("recordingId");

        MbDocument doc = new MbDocument();
        ObjectFactory of = new ObjectFactory();
        Recording recording = of.createRecording();

        doc.addField(RecordingIndexField.ID, id);

        String guid = rs.getString("trackid");
        doc.addField(RecordingIndexField.RECORDING_ID, guid);
        recording.setId(guid);

        String recordingName = rs.getString("trackname");
        //Just add an accent version for recording name not track names
        doc.addField(RecordingIndexField.RECORDING_ACCENT, recordingName);
        recording.setTitle(recordingName);

        trackNames.add(recordingName.toLowerCase(Locale.UK));
        int recordingDuration = rs.getInt("duration");
        if (recordingDuration > 0) {
            durations.add(recordingDuration);
            recording.setLength(BigInteger.valueOf(recordingDuration));
        }

        String comment = rs.getString("comment");
        doc.addFieldOrNoValue(RecordingIndexField.COMMENT, comment);
        if (!Strings.isNullOrEmpty(comment)) {
            recording.setDisambiguation(comment);
        }

        boolean video = rs.getBoolean("video");
        if (video) {
            doc.addField(RecordingIndexField.VIDEO, Boolean.toString(video));
            recording.setVideo("true");
        }

        if (isrcs.containsKey(id)) {
            IsrcList isrcList = of.createIsrcList();
            for (String nextIsrc : isrcs.get(id)) {
                doc.addField(RecordingIndexField.ISRC, nextIsrc);
                Isrc isrc = of.createIsrc();
                isrc.setId(nextIsrc);
                isrcList.getIsrc().add(isrc);
            }
            recording.setIsrcList(isrcList);
        } else {
            doc.addFieldOrNoValue(RecordingIndexField.ISRC, null);
        }

        //Recording Artist Credit
        ArtistCreditWrapper ac = artistCredits.get(id);
        if (ac != null) {
            ArtistCreditHelper.buildIndexFieldsOnlyFromArtistCredit(doc, ac.getArtistCredit(),
                    RecordingIndexField.ARTIST, RecordingIndexField.ARTIST_NAMECREDIT,
                    RecordingIndexField.ARTIST_ID, RecordingIndexField.ARTIST_NAME);
            recording.setArtistCredit(ac.getArtistCredit());
        } else {
            System.out.println("\nNo artist credit found for recording:" + rs.getString("trackid"));
        }

        if (tracks.containsKey(id)) {

            ReleaseList releaseList = of.createReleaseList();
            recording.setReleaseList(releaseList);

            // For each track that uses recording
            for (TrackWrapper trackWrapper : tracks.get(id)) {
                //Get the release details for this track
                Release origRelease = releases.get(trackWrapper.getReleaseId());

                if (origRelease != null) {

                    //Get Artist Credit for Track
                    ArtistCreditWrapper taw = trackArtistCredits.get(trackWrapper.getTrackId());

                    //This release instance will be shared by all recordings that have a track on the release so we need
                    //to copy details so we can append track specific details
                    Release release = of.createRelease();
                    release.setId(origRelease.getId());
                    release.setTitle(origRelease.getTitle());
                    MediumList ml = of.createMediumList();
                    release.setReleaseGroup(origRelease.getReleaseGroup());
                    release.setStatus(origRelease.getStatus());
                    //This will be a va release
                    if (origRelease.getArtistCredit() != null) {
                        release.setArtistCredit(origRelease.getArtistCredit());
                    }
                    //Only add if release artist credit is different to track artist, or if that not set
                    //because same as recording artist only set if different to recording artist
                    else {
                        ArtistCreditWrapper racWrapper = releaseArtists.get(trackWrapper.getReleaseId());
                        if (racWrapper != null) {
                            if (taw != null) {
                                if (taw.getArtistCreditId() != racWrapper.getArtistCreditId()) {
                                    release.setArtistCredit(racWrapper.getArtistCredit());
                                }
                            } else if (ac != null) {
                                if (ac.getArtistCreditId() != racWrapper.getArtistCreditId()) {
                                    release.setArtistCredit(racWrapper.getArtistCredit());
                                }
                            }
                        }
                    }

                    ml.setTrackCount(origRelease.getMediumList().getTrackCount());
                    release.setMediumList(ml);
                    release.setReleaseEventList(origRelease.getReleaseEventList());
                    releaseList.getRelease().add(release);

                    ReleaseGroup rg = release.getReleaseGroup();
                    String trackGuid = trackWrapper.getTrackGuid();
                    doc.addNonEmptyField(RecordingIndexField.TRACK_ID, trackGuid);
                    String primaryType = "";
                    if (rg.getPrimaryType() != null) {
                        primaryType = rg.getPrimaryType().getContent();
                    }
                    doc.addFieldOrUnknown(RecordingIndexField.RELEASEGROUP_ID, rg.getId());
                    doc.addFieldOrUnknown(RecordingIndexField.RELEASE_PRIMARY_TYPE, primaryType);
                    if ((rg.getSecondaryTypeList() != null)
                            && (rg.getSecondaryTypeList().getSecondaryType() != null)) {
                        List<String> secondaryTypeStringList = new ArrayList<String>();
                        for (SecondaryType secondaryType : rg.getSecondaryTypeList().getSecondaryType()) {
                            String st = "";
                            if (secondaryType != null) {
                                st = secondaryType.getContent();
                            }
                            doc.addField(RecordingIndexField.RELEASE_SECONDARY_TYPE, st);
                            secondaryTypeStringList.add(st);
                        }

                        String type = ReleaseGroupHelper.calculateOldTypeFromPrimaryType(primaryType,
                                secondaryTypeStringList);
                        doc.addFieldOrNoValue(RecordingIndexField.RELEASE_TYPE, type);
                        rg.setType(type);
                    } else {
                        String pt = "";
                        if (release.getReleaseGroup().getPrimaryType() != null) {
                            pt = release.getReleaseGroup().getPrimaryType().getContent();
                        }
                        doc.addFieldOrNoValue(RecordingIndexField.RELEASE_TYPE, pt);
                        rg.setType(pt);
                    }

                    doc.addNumericField(RecordingIndexField.NUM_TRACKS, trackWrapper.getTrackCount());
                    doc.addNumericField(RecordingIndexField.TRACKNUM, trackWrapper.getTrackPosition());
                    doc.addFieldOrNoValue(RecordingIndexField.NUMBER, trackWrapper.getTrackNumber());
                    DefTrackData track = of.createDefTrackData();
                    track.setId(trackGuid);
                    track.setTitle(trackWrapper.getTrackName());
                    track.setLength(BigInteger.valueOf(trackWrapper.getDuration()));
                    track.setNumber(trackWrapper.getTrackNumber());

                    Medium medium = of.createMedium();
                    medium.setPosition(BigInteger.valueOf(trackWrapper.getMediumPosition()));
                    Format format = new Format();
                    format.setContent(trackWrapper.getMediumFormat());
                    medium.setFormat(format);

                    Medium.TrackList tl = of.createMediumTrackList();
                    tl.setCount(BigInteger.valueOf(trackWrapper.getTrackCount()));
                    tl.setOffset(BigInteger.valueOf(trackWrapper.getTrackPosition() - 1));

                    release.getMediumList().getMedium().add(medium);
                    medium.setTrackList(tl);
                    tl.getDefTrack().add(track);
                    String st = "";
                    if (release.getStatus() != null) {
                        st = release.getStatus().getContent();
                    }
                    doc.addFieldOrNoValue(RecordingIndexField.RELEASE_STATUS, st);

                    if ((release.getReleaseEventList() != null)
                            && (release.getReleaseEventList().getReleaseEvent().size() > 0)) {
                        for (ReleaseEvent re : release.getReleaseEventList().getReleaseEvent()) {
                            doc.addNonEmptyField(RecordingIndexField.RELEASE_DATE, re.getDate());
                            if (re.getArea() != null) {
                                if (re.getArea().getIso31661CodeList() != null) {
                                    doc.addNonEmptyField(RecordingIndexField.COUNTRY,
                                            re.getArea().getIso31661CodeList().getIso31661Code().get(0));
                                }
                            }
                        }
                        Collections.sort(release.getReleaseEventList().getReleaseEvent(),
                                new ReleaseEventComparator());
                        ReleaseEvent firstReleaseEvent = release.getReleaseEventList().getReleaseEvent().get(0);
                        if (!Strings.isNullOrEmpty(firstReleaseEvent.getDate())) {
                            release.setDate(firstReleaseEvent.getDate());
                        }
                        if (firstReleaseEvent.getArea() != null) {
                            if (firstReleaseEvent.getArea().getIso31661CodeList() != null) {
                                release.setCountry(
                                        firstReleaseEvent.getArea().getIso31661CodeList().getIso31661Code().get(0));
                            }
                        }

                    } else {
                        doc.addFieldOrNoValue(RecordingIndexField.RELEASE_DATE, null);
                        doc.addFieldOrNoValue(RecordingIndexField.COUNTRY, null);
                    }

                    doc.addField(RecordingIndexField.RELEASE_ID, release.getId());
                    doc.addField(RecordingIndexField.RELEASE, release.getTitle());
                    doc.addNumericField(RecordingIndexField.NUM_TRACKS_RELEASE,
                            release.getMediumList().getTrackCount().intValue());

                    trackNames.add(trackWrapper.getTrackName().toLowerCase(Locale.UK));
                    doc.addField(RecordingIndexField.POSITION, String.valueOf(trackWrapper.getMediumPosition()));
                    doc.addFieldOrNoValue(RecordingIndexField.FORMAT, trackWrapper.getMediumFormat());

                    //If different to the Artist Credit for the recording
                    if (taw != null && ((ac == null) || (taw.getArtistCreditId() != ac.getArtistCreditId()))) {
                        ArtistCreditHelper.buildIndexFieldsOnlyFromArtistCredit(doc, taw.getArtistCredit(),
                                RecordingIndexField.ARTIST, RecordingIndexField.ARTIST_NAMECREDIT,
                                RecordingIndexField.ARTIST_ID, RecordingIndexField.ARTIST_NAME);
                        track.setArtistCredit(taw.getArtistCredit());
                    }
                }
            }
        } else {
            doc.addFieldOrNoValue(RecordingIndexField.RELEASE_TYPE, "standalone");
        }

        if (tags.containsKey(id)) {
            recording.setTagList(
                    TagHelper.addTagsToDocAndConstructTagList(of, doc, tags, id, RecordingIndexField.TAG));
        }

        //If we have no recording length in the recording itself or the track length then we add this value so
        //they can search for recordings/tracks with no length
        if (durations.size() == 0) {
            doc.addField(RecordingIndexField.DURATION, Index.NO_VALUE);
            doc.addField(RecordingIndexField.QUANTIZED_DURATION, Index.NO_VALUE);
        } else {
            for (Integer dur : durations) {
                doc.addNumericField(RecordingIndexField.DURATION, dur);
                qdurs.add(dur / QUANTIZED_DURATION);
            }

            for (Integer qdur : qdurs) {
                doc.addNumericField(RecordingIndexField.QUANTIZED_DURATION, qdur);
            }

        }

        //Allow searching of all unique recording/track names
        for (String next : trackNames) {
            doc.addNonEmptyField(RecordingIndexField.RECORDING, next);
        }

        buildClock.suspend();
        storeClock.resume();
        doc.addField(RecordingIndexField.RECORDING_STORE, MMDSerializer.serialize(recording));
        storeClock.suspend();
        return doc.getLuceneDocument();
    }

    /**
     * Create various artist credits
     *
     * @return
     */
    private ArtistCredit createVariousArtistsCredit() {
        ObjectFactory of = new ObjectFactory();
        Artist artist = of.createArtist();
        artist.setId(VARIOUS_ARTISTS_GUID);
        artist.setName(VARIOUS_ARTISTS_NAME);
        artist.setSortName(VARIOUS_ARTISTS_NAME);
        NameCredit naCredit = of.createNameCredit();
        naCredit.setArtist(artist);
        ArtistCredit vaCredit = of.createArtistCredit();
        vaCredit.getNameCredit().add(naCredit);
        return vaCredit;
    }

}