/******************************************************************************* * Copyright (c) 2012, Directors of the Tyndale STEP Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * Neither the name of the Tyndale House, Cambridge (www.TyndaleHouse.com) * nor the names of its contributors may be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************/ package com.tyndalehouse.step.core.service.impl; import com.tyndalehouse.step.core.data.DirectoryInstaller; import com.tyndalehouse.step.core.data.EntityManager; import com.tyndalehouse.step.core.data.StepHttpSwordInstaller; import com.tyndalehouse.step.core.models.BibleInstaller; import com.tyndalehouse.step.core.models.BibleVersion; import com.tyndalehouse.step.core.models.BookName; import com.tyndalehouse.step.core.models.EnrichedLookupOption; import com.tyndalehouse.step.core.models.InterlinearMode; import com.tyndalehouse.step.core.models.KeyWrapper; import com.tyndalehouse.step.core.models.LookupOption; import com.tyndalehouse.step.core.models.OsisWrapper; import com.tyndalehouse.step.core.models.TrimmedLookupOption; import com.tyndalehouse.step.core.models.search.StrongCountsAndSubjects; import com.tyndalehouse.step.core.service.BibleInformationService; import com.tyndalehouse.step.core.service.PassageOptionsValidationService; import com.tyndalehouse.step.core.service.StrongAugmentationService; import com.tyndalehouse.step.core.service.helpers.VersionResolver; import com.tyndalehouse.step.core.service.jsword.JSwordMetadataService; import com.tyndalehouse.step.core.service.jsword.JSwordModuleService; import com.tyndalehouse.step.core.service.jsword.JSwordPassageService; import com.tyndalehouse.step.core.service.jsword.JSwordSearchService; import com.tyndalehouse.step.core.service.jsword.JSwordVersificationService; import com.tyndalehouse.step.core.service.jsword.helpers.JSwordStrongNumberHelper; import com.tyndalehouse.step.core.utils.StringUtils; import com.yammer.metrics.annotation.Timed; import org.crosswire.jsword.book.Book; import org.crosswire.jsword.book.BookCategory; import org.crosswire.jsword.book.install.Installer; import org.crosswire.jsword.passage.KeyUtil; import org.crosswire.jsword.passage.NoSuchKeyException; import org.crosswire.jsword.passage.Verse; import org.crosswire.jsword.passage.VerseFactory; import org.crosswire.jsword.versification.Versification; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.TimeUnit; import static com.tyndalehouse.step.core.models.InterlinearMode.INTERLINEAR; import static com.tyndalehouse.step.core.models.InterlinearMode.NONE; import static com.tyndalehouse.step.core.utils.JSwordUtils.getSortedSerialisableList; import static com.tyndalehouse.step.core.utils.StringUtils.isNotBlank; /** * Command handler returning all available bible versions. * * @author CJBurrell */ @Singleton public class BibleInformationServiceImpl implements BibleInformationService { private static final String VERSION_SEPARATOR = ","; private static final Logger LOGGER = LoggerFactory.getLogger(BibleInformationServiceImpl.class); private final List<String> defaultVersions; private final PassageOptionsValidationService optionsValidationService; private final JSwordPassageService jswordPassage; private final JSwordModuleService jswordModule; private final JSwordMetadataService jswordMetadata; private final JSwordSearchService jswordSearch; private final EntityManager entityManager; private final JSwordVersificationService jswordVersification; private final VersionResolver resolver; private final StrongAugmentationService strongAugmentationService; /** * The bible information service, retrieving content and meta data. * * @param defaultVersions a list of the default versions that should be installed * @param jswordPassage the jsword service * @param jswordModule provides information and handles information relating to module installation, * etc. * @param jswordMetadata provides metadata on jsword modules * @param jswordSearch * @param entityManager the entity manager * @param jswordVersification the jsword versification * @param strongAugmentationService to augment strong numbers */ @Inject public BibleInformationServiceImpl(@Named("defaultVersions") final List<String> defaultVersions, final PassageOptionsValidationService optionsValidationService, final JSwordPassageService jswordPassage, final JSwordModuleService jswordModule, final JSwordMetadataService jswordMetadata, final JSwordSearchService jswordSearch, final EntityManager entityManager, final JSwordVersificationService jswordVersification, final StrongAugmentationService strongAugmentationService, final VersionResolver resolver) { this.optionsValidationService = optionsValidationService; this.jswordPassage = jswordPassage; this.defaultVersions = defaultVersions; this.jswordModule = jswordModule; this.jswordMetadata = jswordMetadata; this.jswordSearch = jswordSearch; this.entityManager = entityManager; this.jswordVersification = jswordVersification; this.strongAugmentationService = strongAugmentationService; this.resolver = resolver; } /** * Gets the available modules. * * @param allVersions the all versions * @param locale the locale * @param userLocale the user locale * @return the available modules */ @Override public List<BibleVersion> getAvailableModules(final boolean allVersions, final String locale, final Locale userLocale) { LOGGER.debug("Getting bible versions with locale [{}] and allVersions=[{}]", locale, allVersions); return getSortedSerialisableList(this.jswordModule.getInstalledModules(allVersions, locale, BookCategory.BIBLE, BookCategory.COMMENTARY), userLocale, this.resolver); } /** * Gets the passage text. * * @param version the version * @param startVerseId the start verse id * @param endVerseId the end verse id * @param options the options * @param interlinearVersion the interlinear version * @param roundUp the round up * @return the passage text */ @Override public OsisWrapper getPassageText(final String version, final int startVerseId, final int endVerseId, final String options, final String interlinearVersion, final Boolean roundUp) { final List<String> extraVersions = getExtraVersionsFromString(interlinearVersion); final Set<LookupOption> lookupOptions = this.optionsValidationService.trim(this.optionsValidationService.getLookupOptions(options), version, extraVersions, InterlinearMode.NONE, null); final OsisWrapper passage = this.jswordPassage.getOsisTextByVerseNumbers(version, version, startVerseId, endVerseId, new ArrayList<LookupOption>(lookupOptions), interlinearVersion, roundUp, false); return passage; } /** * Gets the passage text. * * @param version the version * @param reference the reference * @param options the options * @param interlinearVersion the interlinear version * @param interlinearMode the interlinear mode * @return the passage text */ //TODO: this could be optimized. last call to get options is very similar to 'getLookupOptions' // as they share some of the same stuff. @Override @Timed(name = "passage-lookup", group = "service", rateUnit = TimeUnit.SECONDS, durationUnit = TimeUnit.MILLISECONDS) public OsisWrapper getPassageText(final String version, final String reference, final String options, final String interlinearVersion, final String interlinearMode) { final List<String> extraVersions = getExtraVersionsFromString(interlinearVersion); final InterlinearMode desiredModeOfDisplay = this.optionsValidationService.getDisplayMode(interlinearMode, version, extraVersions); OsisWrapper passageText; final List<TrimmedLookupOption> removedOptions = new ArrayList<TrimmedLookupOption>(4); final List<LookupOption> inputLookupOptions = this.optionsValidationService.getLookupOptions(options); final InterlinearMode realModeOfDisplay = this.optionsValidationService.determineDisplayMode(inputLookupOptions, desiredModeOfDisplay, true); final Set<LookupOption> lookupOptions = this.optionsValidationService.trim(inputLookupOptions, version, extraVersions, desiredModeOfDisplay, realModeOfDisplay, removedOptions); if (INTERLINEAR != desiredModeOfDisplay && NONE != desiredModeOfDisplay) { // split the versions lookupOptions.add(LookupOption.VERSE_NUMBERS); final String[] versions = getInterleavedVersions(version, interlinearVersion); passageText = this.jswordPassage.getInterleavedVersions(versions, reference, new ArrayList<>(lookupOptions), desiredModeOfDisplay); } else { passageText = this.jswordPassage.getOsisText(version, reference, new ArrayList(lookupOptions), interlinearVersion, desiredModeOfDisplay); } passageText.setRemovedOptions(removedOptions); passageText.setPreviousChapter(this.jswordPassage.getSiblingChapter(passageText.getOsisId(), version, true)); passageText.setNextChapter(this.jswordPassage.getSiblingChapter(passageText.getOsisId(), version, false)); passageText.setOptions(this.optionsValidationService.optionsToString( this.optionsValidationService.getAvailableFeaturesForVersion(version, extraVersions, interlinearMode, realModeOfDisplay).getOptions())); //the passage lookup wasn't made with the removed options, however, the client needs to think these were selected. passageText.setSelectedOptions(this.optionsValidationService.optionsToString(lookupOptions) + getRemovedOptions(removedOptions)); return passageText; } /** * Gets the removed option lookup options and returns their representation. * * @param removedOptions a set of options that were removed * @return */ private String getRemovedOptions(final List<TrimmedLookupOption> removedOptions) { List<LookupOption> options = new ArrayList<LookupOption>(removedOptions.size()); for (TrimmedLookupOption o : removedOptions) { options.add(o.getOption()); } return this.optionsValidationService.optionsToString(options); } @Override public String getPlainText(final String version, final String reference, final boolean firstVerseOnly) { return jswordPassage.getPlainText(version, reference, firstVerseOnly); } @Override public StrongCountsAndSubjects getStrongNumbersAndSubjects(final String version, final String reference) { boolean isMultipleVerses = false; Verse key = null; final Versification versificationForVersion = this.jswordVersification.getVersificationForVersion(version); try { key = VerseFactory.fromString(versificationForVersion, reference); } catch (NoSuchKeyException e) { //perhaps we're looking at multiple verses.... try { //currently not supporting multiple verses key = KeyUtil.getVerse(this.jswordVersification.getBookFromVersion(version).getKey(reference)); } catch (NoSuchKeyException e1) { //try reversifying essentially try { key = KeyUtil.getVerse(this.jswordVersification.getBookFromVersion(JSwordPassageService.REFERENCE_BOOK).getKey(reference)); } catch (NoSuchKeyException ex) { LOGGER.error("Unable to look up strongs for [{}]", reference, e); return new StrongCountsAndSubjects(); } } } final StrongCountsAndSubjects verseStrongs = new JSwordStrongNumberHelper(this.entityManager, key, this.jswordVersification, this.jswordSearch, this.strongAugmentationService).getVerseStrongs(); verseStrongs.setVerse(key.getName()); verseStrongs.setMultipleVerses(true); return verseStrongs; } @Override public KeyWrapper convertReferenceForBook(final String reference, final String sourceVersion, final String targetVersion) { return jswordVersification.convertReference(reference, sourceVersion, targetVersion); } /** * Joins version with interlinear version and returns an upper case array * * @param version the base version * @param interlinearVersion the interlinear version * @return the array of well-formatted versions for use in the stylesheet */ @SuppressWarnings("PMD") private String[] getInterleavedVersions(final String version, final String interlinearVersion) { final String[] versions = StringUtils .split(version + VERSION_SEPARATOR + interlinearVersion, "[, ]+"); for (int i = 0; i < versions.length; i++) { versions[i] = versions[i]; } return versions; } /** * Gets the all features. * * @return the all features */ @Override public List<EnrichedLookupOption> getAllFeatures() { final LookupOption[] lo = LookupOption.values(); final List<EnrichedLookupOption> elo = new ArrayList<EnrichedLookupOption>(lo.length + 1); for (final LookupOption lookupOption : lo) { final String displayName = lookupOption.name(); if (isNotBlank(displayName)) { elo.add(new EnrichedLookupOption(displayName, lookupOption.toString(), lookupOption.isEnabledByDefault())); } } return elo; } /** * @param extraVersions the string of extra versions * @return the equivalent list */ private List<String> getExtraVersionsFromString(final String extraVersions) { if (extraVersions == null) { return new ArrayList<String>(0); } return Arrays.asList(StringUtils.split(extraVersions, ",")); } /** * Checks for core modules. * * @return true, if successful */ @Override public boolean hasCoreModules() { for (final String version : this.defaultVersions) { if (!this.jswordModule.isInstalled(version)) { return false; } } return true; } /** * Install default modules. */ @Override public void installDefaultModules() { // we install the module for every core module in the list for (final String book : this.defaultVersions) { this.jswordModule.installBook(book); } } @Override public void installModules(final int installerIndex, final String reference) { this.jswordModule.installBook(installerIndex, reference); } @Override public void addDirectoryInstaller(final String directoryPath) { this.jswordModule.addDirectoryInstaller(directoryPath); } @Override public List<BibleInstaller> getInstallers() { List<BibleInstaller> bibleInstallers = new ArrayList<BibleInstaller>(); final List<Installer> installers = this.jswordModule.getInstallers(); for (int ii = 0; ii < installers.size(); ii++) { final Installer installer = installers.get(ii); String name = installer.getInstallerDefinition(); boolean accessesInternet = true; if (installer instanceof StepHttpSwordInstaller) { name = ((StepHttpSwordInstaller) installer).getInstallerName(); accessesInternet = true; } else if (installer instanceof DirectoryInstaller) { name = ((DirectoryInstaller) installer).getInstallerName(); accessesInternet = false; } bibleInstallers.add(new BibleInstaller(ii, name, accessesInternet)); } return bibleInstallers; } @Override public List<BookName> getBibleBookNames(final String bookStart, final String version, final String bookScope) { return this.jswordMetadata.getBibleBookNames(bookStart, version, bookScope); } @Override public List<BookName> getBibleBookNames(final String bookStart, final String version, final boolean autoLookup) { return this.jswordMetadata.getBibleBookNames(bookStart, version, autoLookup); } /** * Gets the sibling chapter. * * @param reference the reference * @param version the version * @param previousChapter the previous chapter * @return the sibling chapter */ @Override public KeyWrapper getSiblingChapter(final String reference, final String version, final boolean previousChapter) { return this.jswordPassage.getSiblingChapter(reference, version, previousChapter); } /** * Gets the key info. * * @param reference the reference * @param sourceVersion the version attached to the reference * @param version the version * @return the key info */ @Override public KeyWrapper getKeyInfo(final String reference, final String sourceVersion, final String version) { return this.jswordPassage.getKeyInfo(reference, sourceVersion, version); } /** * Index. * * @param initials the initials */ @Override public void index(final String initials) { this.jswordModule.index(initials); } /** * Re index. * * @param initials the initials */ @Override public void reIndex(final String initials) { this.jswordModule.reIndex(initials); } @Override public KeyWrapper expandKeyToChapter(final String sourceVersion, final String version, final String reference) { //convert first to the correct key, then expand to chapter String newRef = this.jswordVersification.convertReference(reference, sourceVersion, version).getOsisKeyId(); return this.jswordPassage.expandToChapter(version, newRef); } /** * Gets the progress on installation. * * @param version the version * @return the progress on installation */ @Override public double getProgressOnInstallation(final String version) { return this.jswordModule.getProgressOnInstallation(version); } /** * Gets the progress on indexing. * * @param version the version * @return the progress on indexing */ @Override public double getProgressOnIndexing(final String version) { return this.jswordModule.getProgressOnIndexing(version); } /** * Removes the module. * * @param initials the initials */ @Override public void removeModule(final String initials) { this.jswordModule.removeModule(initials); } /** * Index all. */ @Override public void indexAll() { final List<Book> installedModules = this.jswordModule.getInstalledModules(BookCategory.BIBLE); for (final Book b : installedModules) { final String initials = b.getInitials(); LOGGER.error("Indexing [{}]", initials); this.jswordModule.index(b.getInitials()); this.jswordModule.waitForIndexes(initials); } } }