/*******************************************************************************
* 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.rest.controllers;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tyndalehouse.step.core.service.AppManagerService;
import com.yammer.metrics.annotation.Timed;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.crosswire.jsword.book.Book;
import org.crosswire.jsword.book.BookCategory;
import org.crosswire.jsword.passage.Key;
import org.crosswire.jsword.versification.BibleBook;
import org.crosswire.jsword.versification.Versification;
import com.tyndalehouse.step.core.data.EntityDoc;
import com.tyndalehouse.step.core.data.EntityIndexReader;
import com.tyndalehouse.step.core.data.EntityManager;
import com.tyndalehouse.step.core.exceptions.StepInternalException;
import com.tyndalehouse.step.core.models.BibleVersion;
import com.tyndalehouse.step.core.service.jsword.JSwordVersificationService;
import com.tyndalehouse.step.core.utils.JSwordUtils;
/**
* Gets the sitemap for STEP.
*/
@Singleton
public class SiteMapController extends HttpServlet {
private static final long serialVersionUID = 5514500537490695745L;
private final ModuleController modules;
private String stepBase;
private final JSwordVersificationService versificationService;
private final AppManagerService appManagerService;
private final EntityIndexReader definitions;
/** Site map */
private enum SiteMapType {
SITEMAP_BIBLE,
SITEMAP_COMMENTARY,
}
/**
* Instantiates a new site map controller.
*
* @param modules the modules
* @param versificationService the versification service
*/
@Inject
public SiteMapController(final ModuleController modules,
final JSwordVersificationService versificationService,
final EntityManager entityManager,
final AppManagerService appManagerService) {
this.modules = modules;
this.versificationService = versificationService;
this.appManagerService = appManagerService;
this.definitions = entityManager.getReader("definition");
}
@Override
@Timed(name = "sitemap", group = "analysis", rateUnit = TimeUnit.SECONDS, durationUnit = TimeUnit.MILLISECONDS)
protected void doGet(final HttpServletRequest req, final HttpServletResponse response)
throws ServletException, IOException {
if (this.stepBase == null) {
this.stepBase = String.format("http://%s/", appManagerService.getAppDomain());
}
// response.setContentType("application/x-gzip");
final String filePath = req.getRequestURL().toString();
if (filePath.endsWith("sitemap.xml")) {
response.getWriter().write(getSiteMapIndex());
response.getWriter().close();
} else {
// get the last bit of the path
final String siteNameWithExtension = filePath.substring(filePath.lastIndexOf('/') + 1);
final String indexName = siteNameWithExtension.replace(".xml", "");
SiteMapType mapType = SiteMapType.SITEMAP_BIBLE;
final char specifier = indexName.charAt(indexName.length() - 1);
if (indexName.contains("SITEMAP_BIBLE")) {
mapType = SiteMapType.SITEMAP_BIBLE;
} else if (indexName.contains("SITEMAP_COMMENTARY")) {
mapType = SiteMapType.SITEMAP_COMMENTARY;
}
response.setHeader("Content-Disposition", "attachment; filename=" + siteNameWithExtension);
response.getOutputStream().write(getSiteMap(mapType, specifier));
}
}
/**
* Gets the site map index.
*
* @return the site map index
*/
private String getSiteMapIndex() {
final StringBuilder siteMap = new StringBuilder(5 * 1024 * 1024);
initSiteMapIndex(siteMap);
addSubMaps(siteMap);
closeSiteMapIndex(siteMap);
return siteMap.toString();
}
/**
* Adds the sub maps.
*
* @param siteMap the site map
*/
private void addSubMaps(final StringBuilder siteMap) {
for (final SiteMapType smt : SiteMapType.values()) {
for (int ii = 0; ii < 26; ii++) {
siteMap.append("<sitemap><loc>");
siteMap.append(this.stepBase);
siteMap.append(smt.name());
siteMap.append('_');
siteMap.append((char) ('A' + ii));
siteMap.append(".xml");
siteMap.append("</loc></sitemap>");
}
}
// add lexicon map
siteMap.append("<sitemap><loc>");
siteMap.append(this.stepBase);
siteMap.append("SITEMAP_LEXICON.xml");
siteMap.append("</loc></sitemap>");
}
/**
* Close site map index.
*
* @param siteMap the site map
*/
private void closeSiteMapIndex(final StringBuilder siteMap) {
siteMap.append("</sitemapindex>");
}
/**
* Inits the site map index.
*
* @param siteMap the site map
*/
private void initSiteMapIndex(final StringBuilder siteMap) {
siteMap.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">");
}
/**
* Gets the site map.
*
* @param mapType the map type
* @param specifier the specifier that says which initial should be generated
* @return the site map
*/
public byte[] getSiteMap(final SiteMapType mapType, final char specifier) {
final StringBuilder siteMap = new StringBuilder(10 * 1024 * 1024);
initSiteMap(siteMap);
switch (mapType) {
case SITEMAP_COMMENTARY:
addVersions(siteMap, BookCategory.COMMENTARY, specifier);
break;
case SITEMAP_BIBLE:
addUrl(siteMap, null, null, null, "versions.jsp");
addVersions(siteMap, BookCategory.BIBLE, specifier);
break;
default:
break;
}
closeSiteMap(siteMap);
try {
return siteMap.toString().getBytes("UTF-8");
} catch (final UnsupportedEncodingException e) {
throw new StepInternalException("Unable to convert to UTF-8", e);
}
}
/**
* Inits the site map.
*
* @param siteMap the site map
*/
private void initSiteMap(final StringBuilder siteMap) {
siteMap.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">");
}
/**
* Close site map.
*
* @param siteMap the site map
*/
private void closeSiteMap(final StringBuilder siteMap) {
siteMap.append("</urlset>");
}
/**
* Adds the versions.
*
* @param siteMap the site map
* @param category the category
* @param specifier the letter that should be included
*/
private void addVersions(final StringBuilder siteMap, final BookCategory category, final char specifier) {
final List<BibleVersion> allModules = this.modules.getAllModules();
if (specifier == 'A') {
addUrl(siteMap, null, null, null, "versions.jsp");
}
for (final BibleVersion version : allModules) {
if (!isInScope(category, version, specifier)) {
continue;
}
addVersion(siteMap, version);
}
}
/**
* Checks if is not in scope.
*
* @param category the category
* @param version the version
* @param letterSpecifier the up to letter which decides how far to include book
* @return true, if is not in scope
*/
private boolean isInScope(final BookCategory category, final BibleVersion version,
final char letterSpecifier) {
if (!version.getCategory().equalsIgnoreCase(category.name())) {
// no we do not have the kind of book we're interested in
return false;
}
return version.getInitials().charAt(0) == letterSpecifier;
}
/**
* Adds the version of the Bible
*
* @param siteMap the site map
* @param version the version
*/
private void addVersion(final StringBuilder siteMap, final BibleVersion version) {
boolean mainFile = false;
final Book book = this.versificationService.getBookFromVersion(version.getInitials());
final Versification versificationForVersion = this.versificationService
.getVersificationForVersion(book);
final Key globalKeyList = book.getGlobalKeyList();
final Iterator<BibleBook> books = versificationForVersion.getBookIterator();
while (books.hasNext()) {
final BibleBook bb = books.next();
if (JSwordUtils.isIntro(bb)) {
continue;
}
Key keyToBook;
try {
keyToBook = book.getValidKey(versificationForVersion.getShortName(bb));
keyToBook.retainAll(globalKeyList);
if (keyToBook.getCardinality() == 0) {
continue;
}
} catch (final Exception ex) {
return;
}
// if we got here, then we have been able to read the module
if (!mainFile) {
addUrl(siteMap, null, null, null, "version.jsp?version=", version.getInitials());
mainFile = true;
}
// append a new URL
final int lastChapter = versificationForVersion.getLastChapter(bb);
for (int ii = 1; ii <= lastChapter; ii++) {
addUrl(siteMap, null, null, null, "?q=version=", book.getInitials(),
"%7Creference=", versificationForVersion.getShortName(bb), ".",
Integer.toString(ii));
}
}
}
/**
* Adds the url to the sitemap
*
* @param siteMap the site map
* @param lastmod the lastmod
* @param changefreq the changefreq
* @param priority the priority
* @param locArgs the loc args
*/
private void addUrl(final StringBuilder siteMap, final String lastmod, final String changefreq,
final String priority, final String... locArgs) {
siteMap.append("<url>");
siteMap.append("<loc>");
siteMap.append(this.stepBase);
for (final String loc : locArgs) {
siteMap.append(loc);
}
siteMap.append("</loc>");
if (lastmod != null) {
siteMap.append("<lastmod>");
siteMap.append(lastmod);
siteMap.append("</lastmod>");
}
if (changefreq != null) {
siteMap.append("<changefreq>");
siteMap.append("</changefreq>");
}
if (priority != null) {
siteMap.append("<priority>");
siteMap.append(priority);
siteMap.append("</priority>");
}
siteMap.append("</url>");
}
}