/*
* Copyright (C) 2011 Laurent Caillette
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 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, see <http://www.gnu.org/licenses/>.
*/
package org.novelang.configuration;
import java.io.File;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FopFactory;
import org.novelang.configuration.parse.BatchParameters;
import org.novelang.configuration.parse.DaemonParameters;
import org.novelang.configuration.parse.DocumentGeneratorParameters;
import org.novelang.configuration.parse.GenericParameters;
import org.novelang.configuration.parse.GenericParametersConstants;
import org.novelang.configuration.parse.LevelExploderParameters;
import org.novelang.logger.Logger;
import org.novelang.logger.LoggerFactory;
import org.novelang.outfit.DefaultCharset;
import org.novelang.outfit.LogbackConfigurationTools;
import org.novelang.outfit.loader.AbstractResourceLoader;
import org.novelang.outfit.loader.ClasspathResourceLoader;
import org.novelang.outfit.loader.CompositeResourceLoader;
import org.novelang.outfit.loader.ResourceLoader;
import org.novelang.outfit.loader.UrlResourceLoader;
import org.novelang.produce.DocumentRequest;
/**
* Creates various Configuration objects from {@link GenericParameters}.
* The main contract of this class is that for each created Configuration object, all information
* needed is held by the Parameters object. No access to system property, no static variable, no
* hidden state.
* <p>
* Each configured value comes from (in order):
* <ol>
* <li>User-defined value (through some parameter).</li>
* <li>Fallback value (applies to a directory with known name).</li>
* <li>Default value (null or default number).</li>
* </ol>
* Each time parameters provide a value, log this origin at INFO level.
*
* @author Laurent Caillette
*/
public class ConfigurationTools {
private static final Logger LOGGER = LoggerFactory.getLogger( ConfigurationTools.class ) ;
public static final int DEFAULT_HTTP_DAEMON_PORT = 8080 ;
public static final boolean DEFAULT_HTTP_DAEMON_SERVE_REMOTES = false ;
public static final String DEFAULT_FONTS_DIRECTORY_NAME = "fonts" ;
public static final String DEFAULT_HYPHENATION_DIRECTORY_NAME = "hyphenation" ;
public static final String BUNDLED_STYLE_DIR = "style" ;
public static final String DEFAULT_STYLE_DIR = "style" ;
public static final String DEFAULT_OUTPUT_DIRECTORY_NAME = "output" ;
public static final Charset DEFAULT_RENDERING_CHARSET = DefaultCharset.RENDERING ;
private static final ThreadGroup EXECUTOR_THREAD_GROUP = new ThreadGroup( "Executor" ) ;
private static final ThreadFactory EXECUTOR_THREAD_FACTORY = new ThreadFactory() {
@Override
public Thread newThread( final Runnable runnable ) {
final Thread thread = new Thread( EXECUTOR_THREAD_GROUP, runnable ) ;
thread.setDaemon( true ) ;
return thread ;
}
} ;
private ConfigurationTools() { }
public static ThreadFactory getExecutorThreadFactory() {
return EXECUTOR_THREAD_FACTORY ;
}
private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors(), EXECUTOR_THREAD_FACTORY ) ;
public static ProducerConfiguration createProducerConfiguration(
final GenericParameters parameters,
final RenditionKinematic renderingKinematic
)
throws FOPException
{
// Does nothing but triggers some useful logging.
extractLogDirectory( parameters ) ;
final RenderingConfiguration renderingConfiguration =
createRenderingConfiguration( parameters, renderingKinematic ) ;
final ContentConfiguration contentConfiguration =
createContentConfiguration( parameters ) ;
return new ProducerConfiguration() {
@Override
public RenderingConfiguration getRenderingConfiguration() {
return renderingConfiguration ;
}
@Override
public ContentConfiguration getContentConfiguration() {
return contentConfiguration ;
}
@Override
public ExecutorService getExecutorService() {
return EXECUTOR_SERVICE;
}
};
}
public static DaemonConfiguration createDaemonConfiguration( final DaemonParameters parameters )
throws FOPException
{
final ProducerConfiguration producerConfiguration = createProducerConfiguration(
parameters,
RenditionKinematic.DAEMON
) ;
final int port ;
final Integer customPort = parameters.getHttpDaemonPort() ;
if( null == customPort ) {
port = DEFAULT_HTTP_DAEMON_PORT ;
LOGGER.info(
"Got port number from default value [",
DEFAULT_HTTP_DAEMON_PORT,
"] (option not set: ",
parameters.getHttpDaemonPortOptionDescription(),
")."
) ;
} else {
port = customPort ;
LOGGER.info(
"Got port number from custom value '",
customPort,
"' (from option: ",
parameters.getHttpDaemonPortOptionDescription(),
")."
) ;
}
final boolean serveRemotes ;
final Boolean serveRemotesAsBoolean = parameters.getServeRemotes() ;
if( null == serveRemotesAsBoolean ) {
serveRemotes = DEFAULT_HTTP_DAEMON_SERVE_REMOTES;
LOGGER.info(
"Got restriction to localhost from default value [",
DEFAULT_HTTP_DAEMON_SERVE_REMOTES,
"] (option not set: ",
parameters.getHttpDaemonServeRemotesOptionDescription(),
")."
) ;
} else {
serveRemotes = serveRemotesAsBoolean ;
LOGGER.info(
"Got restriction to localhost from custom value '",
customPort,
"' (from option: ",
parameters.getHttpDaemonServeRemotesOptionDescription(),
")."
) ;
}
return new DaemonConfiguration() {
@Override
public int getPort() {
return port ;
}
@Override
public boolean getServeRemotes() {
return serveRemotes ;
}
@Override
public ProducerConfiguration getProducerConfiguration() {
return producerConfiguration ;
}
} ;
}
public static LevelExploderConfiguration createExplodeLevelsConfiguration(
final LevelExploderParameters parameters
) throws FOPException {
final ProducerConfiguration producerConfiguration = createProducerConfiguration(
parameters,
RenditionKinematic.BATCH
) ;
final File outputDirectory ;
outputDirectory = extractOutputDirectory( parameters ) ;
return new LevelExploderConfiguration() {
@Override
public ProducerConfiguration getProducerConfiguration() {
return producerConfiguration ;
}
@Override
public DocumentRequest getDocumentRequest() {
return parameters.getDocumentRequest() ;
}
@Override
public File getOutputDirectory() {
return outputDirectory ;
}
} ;
}
public static DocumentGeneratorConfiguration createDocumentGeneratorConfiguration(
final DocumentGeneratorParameters parameters
)
throws FOPException, IllegalArgumentException
{
final ProducerConfiguration producerConfiguration = createProducerConfiguration(
parameters, RenditionKinematic.BATCH ) ;
final File outputDirectory ;
outputDirectory = extractOutputDirectory( parameters ) ;
return new DocumentGeneratorConfiguration() {
@Override
public ProducerConfiguration getProducerConfiguration() {
return producerConfiguration ;
}
@Override
public Iterable<DocumentRequest> getDocumentRequests() {
return parameters.getDocumentRequests() ;
}
@Override
public File getOutputDirectory() {
return outputDirectory ;
}
} ;
}
private static File extractOutputDirectory( final BatchParameters parameters ) {
final File outputDirectory;
if( null == parameters.getOutputDirectory() ) {
outputDirectory = new File( parameters.getBaseDirectory(), DEFAULT_OUTPUT_DIRECTORY_NAME ) ;
LOGGER.info(
"Got output directory from default value '",
DEFAULT_OUTPUT_DIRECTORY_NAME,
"' (option not set: ",
parameters.getOutputDirectoryOptionDescription(),
")."
) ;
} else {
outputDirectory = parameters.getOutputDirectory() ;
LOGGER.info(
"Got output directory from custom value '",
outputDirectory,
"' (from option: ",
parameters.getOutputDirectoryOptionDescription(),
")."
) ;
}
return outputDirectory;
}
private static File extractLogDirectory( final GenericParameters parameters ) {
return LogbackConfigurationTools.prepareLogDirectory( parameters.getLogDirectory(), LOGGER ) ;
}
public static ContentConfiguration createContentConfiguration(
final GenericParameters parameters
) {
final Charset defaultSourceCharset ;
{
final Charset charset = parameters.getDefaultSourceCharset() ;
if( null == charset ) {
defaultSourceCharset = DefaultCharset.SOURCE ;
LOGGER.info(
"Source charset is ",
defaultSourceCharset.name(),
" by default (option not set: ",
GenericParametersConstants.getDefaultSourceCharsetOptionDescription(),
")."
) ;
} else {
defaultSourceCharset = charset ;
LOGGER.info(
"Source charset set as ",
defaultSourceCharset.name(),
"( from option ",
GenericParametersConstants.getDefaultSourceCharsetOptionDescription(),
")."
) ;
}
}
final File contentRoot ;
{
if( null == parameters.getContentRoot() ) {
contentRoot = parameters.getBaseDirectory() ;
LOGGER.info( "Content root set as ", contentRoot ) ;
} else {
contentRoot = parameters.getContentRoot() ;
LOGGER.info(
"Content root is '",
contentRoot,
"' (from option '",
GenericParametersConstants.getContentRootOptionDescription(),
"')"
) ;
}
}
return new ContentConfiguration() {
@Override
public File getContentRoot() {
return contentRoot ;
}
@Override
public Charset getSourceCharset() {
return defaultSourceCharset ;
}
} ;
}
public static RenderingConfiguration createRenderingConfiguration(
final GenericParameters parameters,
final RenditionKinematic renditionKinematic
)
throws FOPException
{
final Iterable< File > fontDirectories = findMultipleDirectoriesWithDefault(
parameters.getBaseDirectory(),
parameters.getFontDirectories(),
"font directories",
DEFAULT_FONTS_DIRECTORY_NAME,
GenericParametersConstants.getFontDirectoriesOptionDescription()
) ;
final File hyphenationDirectory = findDefaultDirectoryIfNeeded(
parameters.getBaseDirectory(),
parameters.getHyphenationDirectory(),
"hyphenation directory",
GenericParametersConstants.getHyphenationDirectoryOptionDescription(),
DEFAULT_HYPHENATION_DIRECTORY_NAME
) ;
final FopFactory fopFactory = FopTools
.createFopFactory( fontDirectories, hyphenationDirectory ) ;
final ResourceLoader resourceLoader = createResourceLoader( parameters ) ;
Preconditions.checkNotNull( renditionKinematic ) ;
final Charset defaultRenderingCharset ;
{
final Charset charset = parameters.getDefaultRenderingCharset() ;
if( null == charset ) {
defaultRenderingCharset = DefaultCharset.RENDERING ;
LOGGER.info(
"Rendering charset is ",
defaultRenderingCharset.name(),
" by default (option not set: ",
GenericParametersConstants.getDefaultRenderingCharsetOptionDescription(),
")."
) ;
} else {
defaultRenderingCharset = charset ;
LOGGER.info(
"Rendering charset set as ",
defaultRenderingCharset.name(),
"( from option ",
GenericParametersConstants.getDefaultRenderingCharsetOptionDescription(),
")."
) ;
}
}
return new RenderingConfiguration() {
@Override
public ResourceLoader getResourceLoader() {
return resourceLoader ;
}
@Override
public FopFactory getFopFactory() {
return fopFactory ;
}
@Override
public FopFontStatus getCurrentFopFontStatus() {
try {
return FopTools.createGlobalFontStatus( fopFactory, fontDirectories ) ;
} catch( FOPException e ) {
throw new RuntimeException( e ) ;
}
}
@Override
public Charset getDefaultCharset() {
return defaultRenderingCharset ;
}
@Override
public RenditionKinematic getRenderingKinematic() {
return renditionKinematic ;
}
} ;
}
private static Iterable< File > findMultipleDirectoriesWithDefault(
final File baseDirectory,
final Iterable< File > userDirectories,
final String directoriesName,
final String defaultSingleDirectoryName,
final String directoriesOptionDescription
) {
final Iterable<File> directories;
if( userDirectories.iterator().hasNext() ) {
directories = userDirectories ;
LOGGER.info(
"Got " + directoriesName + " from custom value '",
directories,
"' (from option: ",
directoriesOptionDescription,
")."
) ;
} else {
final File maybeDefaultDirectory = findDefaultDirectoryIfNeeded(
baseDirectory,
null,
directoriesName,
directoriesOptionDescription,
defaultSingleDirectoryName
) ;
if( null == maybeDefaultDirectory ) {
directories = ImmutableList.of() ;
} else {
directories = Lists.newArrayList( maybeDefaultDirectory ) ;
}
}
return directories;
}
public static ResourceLoader createResourceLoader( final GenericParameters parameters ) {
final Iterable< File > userDefinedDirectories = findMultipleDirectoriesWithDefault(
parameters.getBaseDirectory(),
parameters.getStyleDirectories(),
"style directories",
DEFAULT_STYLE_DIR,
GenericParametersConstants.getStyleDirectoriesDescription()
) ;
return createResourceLoader( userDefinedDirectories ) ;
}
public static ResourceLoader createResourceLoader(
final Iterable< File > userDefinedDirectories
) {
final ClasspathResourceLoader classpathResourceLoader =
new ClasspathResourceLoader( BUNDLED_STYLE_DIR ) ;
final ImmutableList.Builder<AbstractResourceLoader> resourceLoaders =
ImmutableList.builder() ;
for( final File file : userDefinedDirectories ) {
final UrlResourceLoader urlResourceLoader ;
try {
urlResourceLoader = new UrlResourceLoader( file.toURI().toURL() ) ;
} catch( MalformedURLException e ) {
throw new RuntimeException( e ) ;
}
resourceLoaders.add( urlResourceLoader ) ;
}
resourceLoaders.add( classpathResourceLoader ) ;
return new CompositeResourceLoader( resourceLoaders.build() ) ;
}
protected static File findDefaultDirectoryIfNeeded(
final File baseDirectory,
final File userDefinedDirectory,
final String topic,
final String directoryDescription,
final String defaultDirectoryName
) {
if( null == userDefinedDirectory ) {
final File defaultDirectory = new File( baseDirectory, defaultDirectoryName ) ;
if( defaultDirectory.exists() ) {
LOGGER.info(
"Got ",
topic,
" from default value '",
defaultDirectory.getAbsolutePath(),
"' (option not set: ",
directoryDescription,
")."
) ;
return defaultDirectory ;
} else {
LOGGER.info(
"Got no ",
topic,
" (no default directory '",
defaultDirectoryName,
"' nor was set this option: ",
directoryDescription,
")."
) ;
return null ;
}
} else {
LOGGER.info(
"Got ",
topic,
" from user value '",
userDefinedDirectory,
"' (from option: ",
directoryDescription,
")."
) ;
return userDefinedDirectory ;
}
}
}