JsSearchIndexMojo.java

/*-
 * #%L
 * io.earcam.maven.plugin.site.search.offline
 * %%
 * Copyright (C) 2017 earcam
 * %%
 * SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
 *
 * You <b>must</b> choose to accept, in full - any individual or combination of
 * the following licenses:
 * <ul>
 * 	<li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
 * 	<li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
 * 	<li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
 * 	<li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
 * </ul>
 * #L%
 */
package io.earcam.maven.plugin.site.search.offline;

import static io.earcam.utilitarian.site.search.offline.Resources.PROPERTY_USE_SCRIPT_ENGINE;

import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.earcam.utilitarian.site.search.offline.ConfigurationModel;
import io.earcam.utilitarian.site.search.offline.ConfigurationModel.Crawling;
import io.earcam.utilitarian.site.search.offline.ConfigurationModel.Indexing;
import io.earcam.utilitarian.site.search.offline.Resources;
import io.earcam.utilitarian.site.search.offline.jsonb.JsonBind;

/**
 * Generate a <a href="https://lunrjs.com">lunr.js</a> index for offline/static web search.
 */
@Mojo(name = JsSearchIndexMojo.NAME, requiresProject = true, threadSafe = true, inheritByDefault = true, defaultPhase = LifecyclePhase.SITE)
public class JsSearchIndexMojo extends AbstractMojo {

	private static final Logger LOG = LoggerFactory.getLogger(JsSearchIndexMojo.class);

	static final String NAME = "index";

	@Parameter(defaultValue = "${project}", readonly = true, required = true)
	MavenProject project;

	/**
	 * Use the default settings for {@link #crawler} and {@link #indexer} (if true then
	 * {@link #crawler} or {@link #indexer} must not be specified)
	 */
	@Parameter(name = "useDefaultConfiguration", required = false, defaultValue = "true")
	boolean useDefaultConfiguration;

	/**
	 * The crawler configuration (({@link #useDefaultConfiguration} must be false)
	 */
	@Parameter(name = "crawler", required = false)
	ConfigurationModel.Crawling crawler;

	/**
	 * The indexer configuration (({@link #useDefaultConfiguration} must be false)
	 */
	@Parameter(name = "indexer", required = false)
	ConfigurationModel.Indexing indexer;

	/**
	 * Skip execution of this plugin
	 */
	@Parameter(property = "skip", defaultValue = "false")
	protected boolean skip;

	/**
	 * THe character encoding to use
	 */
	@Parameter(name = "outputCharset", required = true, defaultValue = "${project.reporting.outputEncoding}")
	String outputCharset;


	@Override
	public void execute() throws MojoExecutionException, MojoFailureException
	{
		if(skip) {
			LOG.warn("search index - skipping execution, as configured");
			return;
		}
		workaroundForMavenVersusJdk9AndNashorn();
		validate();

		if(useDefaultConfiguration) {
			configureDefaults();

		}
		JsSearchLifecycleParticipant.indexer(indexer);
		JsSearchLifecycleParticipant.addDocuments(crawler.build().documents());
	}


	/**
	 * Workaround for Maven vs JDK>=9 issue https://issues.apache.org/jira/browse/MNG-6275
	 * 
	 * Where Nashorn cannot be loaded via SPI (had previously tried a zero-code
	 * workaround of defining {@code META-INF/services/javax.script.ScriptEngineFactory}
	 * in this module - but that wasn't picked up).
	 * 
	 * It's public as we need to call it in the verify.groovy integration tests
	 */
	public static void workaroundForMavenVersusJdk9AndNashorn()
	{
		System.setProperty(PROPERTY_USE_SCRIPT_ENGINE, "jdk.nashorn.api.scripting.NashornScriptEngineFactory");
	}


	private void validate()
	{
		if(useDefaultConfiguration && (crawler != null || indexer != null)) {
			throw new IllegalStateException(
					"Either specifiy the 'crawler' AND 'indexer' configuration OR set 'useDefaultConfiguration' to 'true'");
		}
	}


	private void configureDefaults()
	{
		Map<String, String> searchReplace = searchReplaceMap();

		Charset charset = Charset.forName(outputCharset);
		String crawlerJson = Resources.getResource(Resources.DEFAULT_CRAWLER_JSON, charset, searchReplace);
		String indexerJson = Resources.getResource(Resources.DEFAULT_INDEXER_JSON, charset, searchReplace);
		crawler = JsonBind.readJson(crawlerJson, Crawling.class);
		indexer = JsonBind.readJson(indexerJson, Indexing.class);
	}


	private Map<String, String> searchReplaceMap()
	{
		Map<String, String> searchReplace = new HashMap<>();
		searchReplace.put("${outputCharset}", outputCharset);
		searchReplace.put("${jsonDir}", project.getModel().getReporting().getOutputDirectory());
		searchReplace.put("${baseDir}", project.getModel().getReporting().getOutputDirectory());
		searchReplace.put("${baseUri}", project.getModel().getDistributionManagement().getSite().getUrl());
		return searchReplace;
	}
}