/*
* Copyright 2013 Martin Kouba
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.trimou.engine.config;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.trimou.engine.MustacheEngineBuilder;
import org.trimou.engine.cache.ComputingCacheFactory;
import org.trimou.engine.cache.DefaultComputingCacheFactory;
import org.trimou.engine.convert.ValueConverter;
import org.trimou.engine.id.IdentifierGenerator;
import org.trimou.engine.id.SequenceIdentifierGenerator;
import org.trimou.engine.interpolation.DefaultLiteralSupport;
import org.trimou.engine.interpolation.DotKeySplitter;
import org.trimou.engine.interpolation.KeySplitter;
import org.trimou.engine.interpolation.LiteralSupport;
import org.trimou.engine.interpolation.MissingValueHandler;
import org.trimou.engine.interpolation.NoOpMissingValueHandler;
import org.trimou.engine.interpolation.ThrowingExceptionMissingValueHandler;
import org.trimou.engine.listener.MustacheListener;
import org.trimou.engine.locale.DefaultLocaleSupport;
import org.trimou.engine.locale.LocaleSupport;
import org.trimou.engine.locator.TemplateLocator;
import org.trimou.engine.priority.Priorities;
import org.trimou.engine.resolver.Resolver;
import org.trimou.engine.text.DefaultTextSupport;
import org.trimou.engine.text.TextSupport;
import org.trimou.engine.validation.Validateable;
import org.trimou.exception.MustacheException;
import org.trimou.exception.MustacheProblem;
import org.trimou.handlebars.Helper;
import org.trimou.util.ImmutableList;
import org.trimou.util.ImmutableMap;
import org.trimou.util.ImmutableMap.ImmutableMapBuilder;
import org.trimou.util.Strings;
/**
*
* @author Martin Kouba
*/
class DefaultConfiguration implements Configuration {
private static final Logger LOGGER = LoggerFactory
.getLogger(DefaultConfiguration.class);
private static final String RESOURCE_FILE = "/trimou.properties";
private final List<TemplateLocator> templateLocators;
private final List<Resolver> resolvers;
private final Map<String, Object> globalData;
private final TextSupport textSupport;
private final LocaleSupport localeSupport;
private final Map<String, Object> properties;
private final List<MustacheListener> mustacheListeners;
private final KeySplitter keySplitter;
private final MissingValueHandler missingValueHandler;
private final Map<String, Helper> helpers;
private final ComputingCacheFactory computingCacheFactory;
private final IdentifierGenerator identifierGenerator;
private final ExecutorService executorService;
private final LiteralSupport literalSupport;
private final List<ValueConverter> valueConverters;
/**
*
* @param builder
*/
@SuppressWarnings("deprecation")
DefaultConfiguration(MustacheEngineBuilder builder) {
if (!builder.isOmitServiceLoaderConfigurationExtensions()) {
// Process configuration extensions
ClassLoader cl = builder.getConfigurationExtensionClassLoader();
if (cl == null) {
cl = SecurityActions.getContextClassLoader();
if (cl == null) {
cl = SecurityActions
.getClassLoader(DefaultConfiguration.class);
}
}
List<ConfigurationExtension> configurationExtensions = new ArrayList<>();
for (final ConfigurationExtension configurationExtension1 : ServiceLoader
.load(ConfigurationExtension.class, cl)) {
configurationExtensions.add(configurationExtension1);
}
configurationExtensions.sort(Priorities.higherFirst());
for (ConfigurationExtension configurationExtension : configurationExtensions) {
configurationExtension.register(builder);
}
}
// Non-final components
List<Resolver> resolvers = initResolvers(builder);
List<MustacheListener> mustacheListeners = new ArrayList<>(
builder.buildMustacheListeners());
MissingValueHandler missingValueHandler = initMissingValueHandler(
builder);
Map<String, Helper> helpers = builder.buildHelpers();
List<ValueConverter> valueConverters = initValueConverters(builder);
this.textSupport = initTextSupport(builder);
this.localeSupport = initLocaleSupport(builder);
this.keySplitter = initKeySplitter(builder);
this.templateLocators = initTemplateLocators(builder);
Map<String, Object> globalData = builder.buildGlobalData();
if (globalData.isEmpty()) {
this.globalData = null;
} else {
this.globalData = globalData;
}
if (builder.getComputingCacheFactory() != null) {
this.computingCacheFactory = builder.getComputingCacheFactory();
} else {
this.computingCacheFactory = new DefaultComputingCacheFactory();
}
if (builder.getIdentifierGenerator() != null) {
this.identifierGenerator = builder.getIdentifierGenerator();
} else {
this.identifierGenerator = new SequenceIdentifierGenerator();
}
if (builder.getLiteralSupport() != null) {
this.literalSupport = builder.getLiteralSupport();
} else {
this.literalSupport = new DefaultLiteralSupport();
}
// All configuration aware components must be availabe at this time
// so that it's possible to collect all configuration keys
// Preserve the order - some components must be initialized before
// others
Set<ConfigurationAware> components = new LinkedHashSet<>();
components.add(computingCacheFactory);
components.add(identifierGenerator);
components.addAll(resolvers);
components.add(textSupport);
components.add(localeSupport);
components.add(keySplitter);
components.addAll(templateLocators != null ? templateLocators : Collections.emptySet());
components.addAll(mustacheListeners);
components.addAll(helpers.values());
components.add(literalSupport);
components.addAll(valueConverters);
this.properties = initializeProperties(builder,
getConfigurationKeysToProcess(components));
if (getBooleanPropertyValue(
EngineConfigurationKey.NO_VALUE_INDICATES_PROBLEM)) {
LOGGER.warn(
"{}.{} is deprecated, use appropriate MissingValueHandler instance instead",
EngineConfigurationKey.class.getSimpleName(),
EngineConfigurationKey.NO_VALUE_INDICATES_PROBLEM);
// Simulate deprecated settings
this.missingValueHandler = new ThrowingExceptionMissingValueHandler();
} else {
this.missingValueHandler = missingValueHandler;
}
if (!getBooleanPropertyValue(
EngineConfigurationKey.HANDLEBARS_SUPPORT_ENABLED)) {
this.helpers = Collections.emptyMap();
} else {
this.helpers = helpers;
}
initializeConfigurationAwareComponents(components);
// Filter out invalid components
removeInvalidComponents(resolvers);
removeInvalidComponents(mustacheListeners);
removeInvalidComponents(valueConverters);
this.resolvers = ImmutableList.copyOf(resolvers);
this.mustacheListeners = ImmutableList.copyOf(mustacheListeners);
this.executorService = builder.getExecutorService();
this.valueConverters = ImmutableList.copyOf(valueConverters);
}
@Override
public List<Resolver> getResolvers() {
return resolvers;
}
@Override
public Map<String, Object> getGlobalData() {
return globalData;
}
@Override
public List<TemplateLocator> getTemplateLocators() {
return templateLocators;
}
@Override
public TextSupport getTextSupport() {
return textSupport;
}
@Override
public LocaleSupport getLocaleSupport() {
return localeSupport;
}
@Override
public List<MustacheListener> getMustacheListeners() {
return mustacheListeners;
}
@Override
public KeySplitter getKeySplitter() {
return keySplitter;
}
@Override
public MissingValueHandler getMissingValueHandler() {
return missingValueHandler;
}
@Override
public Map<String, Helper> getHelpers() {
return helpers;
}
@Override
public <T extends ConfigurationKey> Long getLongPropertyValue(
T configurationKey) {
Long value = (Long) properties.get(configurationKey.get());
if (value == null) {
value = (Long) configurationKey.getDefaultValue();
}
return value;
}
@Override
public <T extends ConfigurationKey> Integer getIntegerPropertyValue(
T configurationKey) {
Integer value = (Integer) properties.get(configurationKey.get());
if (value == null) {
value = (Integer) configurationKey.getDefaultValue();
}
return value;
}
@Override
public <T extends ConfigurationKey> String getStringPropertyValue(
T configurationKey) {
Object value = properties.get(configurationKey.get());
if (value == null) {
value = configurationKey.getDefaultValue();
}
return value.toString();
}
@Override
public <T extends ConfigurationKey> Boolean getBooleanPropertyValue(
T configurationKey) {
Boolean value = (Boolean) properties.get(configurationKey.get());
if (value == null) {
value = (Boolean) configurationKey.getDefaultValue();
}
return value;
}
public String getInfo() {
StringBuilder builder = new StringBuilder();
if (templateLocators != null) {
builder.append(Strings.LINE_SEPARATOR);
builder.append("[Template locators]");
for (TemplateLocator locator : templateLocators) {
builder.append(Strings.LINE_SEPARATOR);
builder.append(locator.toString());
}
builder.append(Strings.LINE_SEPARATOR);
builder.append("----------");
}
if (resolvers != null) {
builder.append(Strings.LINE_SEPARATOR);
builder.append("[Resolvers]");
for (Resolver resolver : resolvers) {
builder.append(Strings.LINE_SEPARATOR);
builder.append(resolver.toString());
}
builder.append(Strings.LINE_SEPARATOR);
builder.append("----------");
}
builder.append(Strings.LINE_SEPARATOR);
builder.append("[Properties]");
for (Entry<String, Object> entry : properties.entrySet()) {
builder.append(Strings.LINE_SEPARATOR);
builder.append(entry.getKey());
builder.append("=");
builder.append(entry.getValue());
}
builder.append(Strings.LINE_SEPARATOR);
builder.append("----------");
return builder.toString();
}
@Override
public ComputingCacheFactory getComputingCacheFactory() {
return computingCacheFactory;
}
@Override
public IdentifierGenerator getIdentifierGenerator() {
return identifierGenerator;
}
@Override
public ExecutorService geExecutorService() {
return executorService;
}
@Override
public LiteralSupport getLiteralSupport() {
return literalSupport;
}
@Override
public List<ValueConverter> getValueConverters() {
return valueConverters;
}
private void initializeConfigurationAwareComponents(
Set<ConfigurationAware> components) {
for (ConfigurationAware component : components) {
component.init(this);
}
}
private List<Resolver> initResolvers(MustacheEngineBuilder builder) {
Set<Resolver> builderResolvers = builder.buildResolvers();
if (builderResolvers.isEmpty()) {
return Collections.emptyList();
}
List<Resolver> resolvers = new ArrayList<>();
resolvers.addAll(builderResolvers);
resolvers.sort(Priorities.higherFirst());
return resolvers;
}
private Map<String, Object> initializeProperties(
MustacheEngineBuilder engineBuilder,
Set<ConfigurationKey> keysToProcess) {
ImmutableMapBuilder<String, Object> builder = ImmutableMap.builder();
Map<String, Object> builderProperties = engineBuilder.buildProperties();
Properties resourceProperties = new Properties();
try {
InputStream in = this.getClass().getResourceAsStream(RESOURCE_FILE);
if (in != null) {
try {
resourceProperties.load(in);
} finally {
in.close();
}
}
} catch (IOException e) {
// No-op, file is optional
}
for (ConfigurationKey configKey : keysToProcess) {
String key = configKey.get();
// Manually set properties
Object value = builderProperties.get(key);
if (value == null) {
// System properties
value = SecurityActions.getSystemProperty(key);
if (value == null) {
// Resource properties
value = resourceProperties.getProperty(key);
}
}
if (value != null) {
try {
value = ConfigurationProperties.convertConfigValue(
configKey.getDefaultValue().getClass(), value);
} catch (Exception e) {
throw new MustacheException(
MustacheProblem.CONFIG_PROPERTY_INVALID_VALUE, e);
}
} else {
value = configKey.getDefaultValue();
}
builder.put(key, value);
}
return builder.build();
}
private Set<ConfigurationKey> getConfigurationKeysToProcess(
Set<ConfigurationAware> components) {
Set<ConfigurationKey> keys = new HashSet<>();
// Global keys
Collections.addAll(keys, EngineConfigurationKey.values());
for (ConfigurationAware component : components) {
keys.addAll(component.getConfigurationKeys());
}
return keys;
}
private TextSupport initTextSupport(MustacheEngineBuilder builder) {
return builder.getTextSupport() != null ? builder.getTextSupport()
: new DefaultTextSupport();
}
private LocaleSupport initLocaleSupport(MustacheEngineBuilder builder) {
return builder.getLocaleSupport() != null ? builder.getLocaleSupport()
: new DefaultLocaleSupport();
}
private KeySplitter initKeySplitter(MustacheEngineBuilder builder) {
// Factory does not make sense here
return builder.getKeySplitter() != null ? builder.getKeySplitter()
: new DotKeySplitter();
}
private MissingValueHandler initMissingValueHandler(
MustacheEngineBuilder builder) {
return builder.getMissingValueHandler() != null
? builder.getMissingValueHandler()
: new NoOpMissingValueHandler();
}
private List<TemplateLocator> initTemplateLocators(
MustacheEngineBuilder builder) {
Set<TemplateLocator> builderTemplateLocators = builder
.buildTemplateLocators();
if (!builderTemplateLocators.isEmpty()) {
List<TemplateLocator> locators = new ArrayList<>(
builder.buildTemplateLocators());
locators.sort(Priorities.higherFirst());
return ImmutableList.copyOf(locators);
} else {
return null;
}
}
private List<ValueConverter> initValueConverters(
MustacheEngineBuilder builder) {
Set<ValueConverter> builderConverters = builder.buildValueConverters();
if (builderConverters.isEmpty()) {
return Collections.emptyList();
}
List<ValueConverter> converters = new ArrayList<>();
converters.addAll(builderConverters);
converters.sort(Priorities.higherFirst());
return converters;
}
@SuppressWarnings("unused")
private Set<ConfigurationAware> getConfigurationAwareComponents(
Set<ConfigurationAware> initialSet) {
Set<ConfigurationAware> components = new HashSet<>();
components.add(missingValueHandler);
components.addAll(helpers.values());
components.addAll(resolvers);
if (templateLocators != null) {
components.addAll(templateLocators);
}
if (mustacheListeners != null) {
components.addAll(mustacheListeners);
}
components.add(localeSupport);
components.add(textSupport);
components.add(keySplitter);
return components;
}
private <T> void removeInvalidComponents(Iterable<T> iterable) {
for (Iterator<T> iterator = iterable.iterator(); iterator.hasNext();) {
T component = iterator.next();
if (component instanceof Validateable
&& !((Validateable) component).isValid()) {
iterator.remove();
}
}
}
}