CCM NG: Loader for configuration parameters from the integration.properties file

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@4190 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2016-07-06 13:22:39 +00:00
parent 1b3479a1e8
commit 741e0e5dc0
12 changed files with 580 additions and 25 deletions

View File

@ -95,6 +95,9 @@ public class ConfigurationManager {
* *
* @return An instance of the configuration class with all settings set to * @return An instance of the configuration class with all settings set to
* the values stored in the registry. * the values stored in the registry.
*
* @throws IllegalArgumentException if the provided class is not annotated
* with {@link Configuration}.
*/ */
public <T> T findConfiguration(final Class<T> confClass) { public <T> T findConfiguration(final Class<T> confClass) {
if (confClass == null) { if (confClass == null) {

View File

@ -67,12 +67,17 @@ public class EnumSetting
} }
public void addEnumValue(final String value) { public void addEnumValue(final String value) {
if (value == null) {
this.value = new HashSet<>();
}
this.value.add(value); this.value.add(value);
} }
public void removeEnumValue(final String value) { public void removeEnumValue(final String value) {
if (this.value != null) {
this.value.remove(value); this.value.remove(value);
} }
}
@Override @Override
public int hashCode() { public int hashCode() {

View File

@ -53,6 +53,11 @@ public class LocalizedStringSetting
@JoinColumn(name = "ENTRY_ID")})) @JoinColumn(name = "ENTRY_ID")}))
private LocalizedString value; private LocalizedString value;
public LocalizedStringSetting() {
super();
value = new LocalizedString();
}
@Override @Override
public LocalizedString getValue() { public LocalizedString getValue() {
return value; return value;

View File

@ -44,11 +44,14 @@ public class StringListSetting extends AbstractSetting<List<String>> {
@ElementCollection(fetch = FetchType.EAGER) @ElementCollection(fetch = FetchType.EAGER)
@JoinTable(name = "SETTINGS_STRING_LIST", @JoinTable(name = "SETTINGS_STRING_LIST",
schema = DB_SCHEMA, schema = DB_SCHEMA,
joinColumns = {@JoinColumn(name = "LIST_ID")}) joinColumns = {
@JoinColumn(name = "LIST_ID")})
private List<String> value; private List<String> value;
/** /**
* Returns a <em>copy</em> of the list managed by this setting. * Returns a <em>copy</em> of the list managed by this setting.
*
* @return
*/ */
@Override @Override
public List<String> getValue() { public List<String> getValue() {
@ -75,6 +78,9 @@ public class StringListSetting extends AbstractSetting<List<String>> {
* @param value The value to add. * @param value The value to add.
*/ */
public void addListValue(final String value) { public void addListValue(final String value) {
if (this.value == null) {
this.value = new ArrayList<>();
}
this.value.add(value); this.value.add(value);
} }
@ -84,8 +90,10 @@ public class StringListSetting extends AbstractSetting<List<String>> {
* @param value the value to add. * @param value the value to add.
*/ */
public void removeListValue(final String value) { public void removeListValue(final String value) {
if (this.value != null) {
this.value.remove(value); this.value.remove(value);
} }
}
@Override @Override
public int hashCode() { public int hashCode() {

View File

@ -0,0 +1,264 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.modules;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.configuration.AbstractSetting;
import org.libreccm.configuration.BigDecimalSetting;
import org.libreccm.configuration.BooleanSetting;
import org.libreccm.configuration.Configuration;
import org.libreccm.configuration.DoubleSetting;
import org.libreccm.configuration.EnumSetting;
import org.libreccm.configuration.LocalizedStringSetting;
import org.libreccm.configuration.LongSetting;
import org.libreccm.configuration.Setting;
import org.libreccm.configuration.StringListSetting;
import org.libreccm.configuration.StringSetting;
import org.libreccm.l10n.LocalizedString;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.transaction.Transaction;
/**
* A helper class used by the {@link ModuleManager} to load the initial
* configuration values for the configurations of a module from the bundle and
* store them in the database.
*
* If no entry for a setting is found in the integration properties the default
* value defined in the configuration class is not changed. If value defined in
* the bundle can not be converted to the correct type an warning is written
* into the Log and the value is ignored.
*
* If the bundle does not provide integration properties, a warning is written
* into the log.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
class ConfigurationLoader {
private static final Logger LOGGER = LogManager.getLogger(
ConfigurationLoader.class);
private final EntityManager entityManager;
private final Properties integration;
public ConfigurationLoader(final EntityManager entityManager) {
this.entityManager = entityManager;
integration = new Properties();
try (final InputStream inputStream = getClass().getResourceAsStream(
"/integration.properties")) {
if (inputStream == null) {
LOGGER.warn("No integration properties found. Using empty "
+ "properties.");
} else {
LOGGER.info("Loading integration properties.");
integration.load(inputStream);
}
} catch (IOException ex) {
LOGGER.warn(
"Failed to load integration properties file. Using empty"
+ "integration properties", ex);
}
}
public void loadConfigurations(final CcmModule ccmModule) {
final Module module = ccmModule.getClass().getAnnotation(Module.class);
final Class<?>[] configurations = module.configurations();
for (final Class<?> configuration : configurations) {
loadConfiguration(configuration);
}
}
/**
* Loads the values for a specific configuration class from the integration
* properties. The method will iterate over all settings defined in the
* provided configuration class and try to find an entry for the setting in
* the integration properties. If an entry in the integration properties is
* found the method will try to convert the value to the type of the
* setting. If this fails a warning is written to the log.
*
* @param confClass
*/
public void loadConfiguration(final Class<?> confClass) {
if (confClass.getAnnotation(Configuration.class) == null) {
throw new IllegalArgumentException(String.format(
"Provided class \"%s\" is not annotated with \"%s\".",
confClass.getName(),
Configuration.class.getName()));
}
final String confName = confClass.getName();
final Field[] fields = confClass.getDeclaredFields();
for (final Field field : fields) {
field.setAccessible(true);
if (field.getAnnotation(Setting.class) == null) {
continue;
}
final String settingName = getSettingName(field);
final String propertyName = String.join(".", confName, settingName);
if (integration.containsKey(propertyName)) {
createSetting(confName,
settingName,
field,
integration.getProperty(propertyName));
}
}
}
/**
* Helper method for generating the name of a setting. This method does not
* check if the provided field is annotated with {@link Setting}. The caller
* is responsible to do that. Passing a field without the {@code Setting}
* annotation to this method will result in a {@code NullPointerException}.
*
* @param field The setting field.
*
* @return The name of the field or if the {@link Setting} annotation of the
* field has a name value, the value of that field.
*/
private String getSettingName(final Field field) {
LOGGER.debug(String.format("Trying to get setting name from field: "
+ "\"%s\"",
field.getName()));
final Setting annotation = field.getAnnotation(Setting.class);
if (annotation.name() == null || annotation.name().isEmpty()) {
return field.getName();
} else {
return annotation.name();
}
}
private void createSetting(final String confName,
final String settingName,
final Field field,
final String valueStr) {
final String settingType = field.getType().getName();
final AbstractSetting<?> setting;
if (BigDecimal.class.getName().equals(settingType)) {
try {
final BigDecimal value = new BigDecimal(valueStr);
final BigDecimalSetting bigDecimalSetting
= new BigDecimalSetting();
bigDecimalSetting.setValue(value);
setting = bigDecimalSetting;
} catch (NumberFormatException ex) {
LOGGER.warn("Can't convert value \"{}\" for setting {}.{} from "
+ "integration properties to BigDecimal.",
valueStr, confName, settingName);
return;
}
} else if (Boolean.class.getName().equals(settingType)
|| "boolean".equals(settingType)) {
final Boolean value = Boolean.valueOf(valueStr);
final BooleanSetting booleanSetting = new BooleanSetting();
booleanSetting.setValue(value);
setting = booleanSetting;
} else if (Double.class.getName().equals(settingType)
|| "double".equals(settingType)) {
try {
final Double value = Double.valueOf(valueStr);
final DoubleSetting doubleSetting = new DoubleSetting();
doubleSetting.setValue(value);
setting = doubleSetting;
} catch (NumberFormatException ex) {
LOGGER.warn("Can't convert value \"{}\" for setting {}.{} from "
+ "integration properties to Double.",
valueStr, confName, settingName);
return;
}
} else if (Set.class.getName().equals(settingType)) {
final String[] tokens = valueStr.split(",");
final EnumSetting enumSetting = new EnumSetting();
for (final String token : tokens) {
enumSetting.addEnumValue(token.trim());
}
setting = enumSetting;
} else if (LocalizedString.class.getName().equals(settingType)) {
final String[] tokens = valueStr.split(",");
final LocalizedStringSetting l10nSetting
= new LocalizedStringSetting();
for (final String token : tokens) {
final String[] parts = token.split(":");
if (parts.length != 2) {
LOGGER.warn("Error reading values for "
+ "LocalizedStringSetting {}. Token \"{}\" "
+ "does not have the correct format "
+ "\"locale:value\".",
confName, settingName, token);
continue;
}
l10nSetting.getValue().addValue(new Locale(parts[0]), parts[1]);
}
setting = l10nSetting;
} else if (Long.class.getName().equals(settingType)
|| "long".equals(settingType)) {
try {
final Long value = Long.parseLong(valueStr);
final LongSetting longSetting = new LongSetting();
longSetting.setValue(value);
setting = longSetting;
} catch (NumberFormatException ex) {
LOGGER.warn("Can't convert value \"{}\" for setting {}.{} from "
+ "integration properties to Long.",
valueStr, confName, settingName);
return;
}
} else if (List.class.getName().equals(settingType)) {
final String[] tokens = valueStr.split(",");
final StringListSetting listSetting = new StringListSetting();
for (final String token : tokens) {
listSetting.addListValue(token.trim());
}
setting = listSetting;
} else if (String.class.getName().equals(settingType)) {
final StringSetting strSetting = new StringSetting();
strSetting.setValue(valueStr);
setting = strSetting;
} else {
throw new IllegalArgumentException(String.format(
"No setting type for field type \"%s\" available.",
settingType));
}
setting.setConfigurationClass(confName);
setting.setName(settingName);
entityManager.persist(setting);
}
}

View File

@ -35,7 +35,6 @@ import org.jboss.arquillian.persistence.UsingDataSet;
import org.jboss.arquillian.transaction.api.annotation.TransactionMode; import org.jboss.arquillian.transaction.api.annotation.TransactionMode;
import org.jboss.arquillian.transaction.api.annotation.Transactional; import org.jboss.arquillian.transaction.api.annotation.Transactional;
import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.resolver.api.maven.Maven; import org.jboss.shrinkwrap.resolver.api.maven.Maven;
import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage; import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage;
@ -160,8 +159,8 @@ public class ConfigurationManagerTest {
"datasets/org/libreccm/configuration/ConfigurationManagerTest/data.yml") "datasets/org/libreccm/configuration/ConfigurationManagerTest/data.yml")
@InSequence(1100) @InSequence(1100)
public void loadConfiguration() { public void loadConfiguration() {
final ExampleConfiguration configuration = configurationManager final TestExampleConfiguration configuration = configurationManager
.findConfiguration(ExampleConfiguration.class); .findConfiguration(TestExampleConfiguration.class);
assertThat(configuration, is(not(nullValue()))); assertThat(configuration, is(not(nullValue())));
assertThat(configuration.getPrice(), assertThat(configuration.getPrice(),
@ -184,8 +183,8 @@ public class ConfigurationManagerTest {
+ "after-save-changed.yml") + "after-save-changed.yml")
@InSequence(1200) @InSequence(1200)
public void saveConfiguration() { public void saveConfiguration() {
final ExampleConfiguration configuration = configurationManager final TestExampleConfiguration configuration = configurationManager
.findConfiguration(ExampleConfiguration.class); .findConfiguration(TestExampleConfiguration.class);
configuration.setPrice(new BigDecimal("109.99")); configuration.setPrice(new BigDecimal("109.99"));
configuration.setItemsPerPage(30L); configuration.setItemsPerPage(30L);

View File

@ -27,7 +27,7 @@ import java.util.Set;
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
@Configuration @Configuration
public class ExampleConfiguration { public class TestExampleConfiguration {
@Setting @Setting
private BigDecimal price; private BigDecimal price;

View File

@ -0,0 +1,141 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.modules;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.persistence.CreateSchema;
import org.jboss.arquillian.persistence.PersistenceTest;
import org.jboss.arquillian.persistence.ShouldMatchDataSet;
import org.jboss.arquillian.transaction.api.annotation.TransactionMode;
import org.jboss.arquillian.transaction.api.annotation.Transactional;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.resolver.api.maven.Maven;
import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.libreccm.configuration.ExampleConfiguration;
import org.libreccm.tests.categories.IntegrationTest;
import java.io.File;
import javax.inject.Inject;
import javax.persistence.EntityManager;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Category(IntegrationTest.class)
@RunWith(Arquillian.class)
@PersistenceTest
@Transactional(TransactionMode.COMMIT)
@CreateSchema({"create_ccm_core_schema.sql"})
public class ConfigurationLoaderTest {
@Inject
private EntityManager entityManager;
public ConfigurationLoaderTest() {
}
@BeforeClass
public static void setUpClass() {
}
@AfterClass
public static void tearDownClass() {
}
@Before
public void setUp() {
}
@After
public void tearDown() {
}
@Deployment
public static WebArchive createDeployment() {
final PomEquippedResolveStage pom = Maven
.resolver()
.loadPomFromFile("pom.xml");
final PomEquippedResolveStage dependencies = pom
.importCompileAndRuntimeDependencies();
final File[] libs = dependencies.resolve().withTransitivity().asFile();
for (File lib : libs) {
System.err.printf("Adding file '%s' to test archive...%n",
lib.getName());
}
return ShrinkWrap
.create(WebArchive.class,
"LibreCCM-org.libreccm.modules.ConfigurationLoaderTest.war")
//.addPackage(org.libreccm.categorization.Category.class.getPackage())
.addPackage(org.libreccm.configuration.Configuration.class
.getPackage())
//.addPackage(org.libreccm.core.CcmObject.class.getPackage())
.addPackage(org.libreccm.jpa.EntityManagerProducer.class
.getPackage())
.addPackage(org.libreccm.jpa.utils.MimeTypeConverter.class
.getPackage())
.addPackage(org.libreccm.l10n.LocalizedString.class.getPackage())
.addClass(org.libreccm.modules.ConfigurationLoader.class)
//.addPackage(org.libreccm.security.Permission.class.getPackage())
//.addPackage(org.libreccm.workflow.Workflow.class.getPackage())
.addPackage(org.libreccm.tests.categories.IntegrationTest.class
.getPackage())
.addPackage(org.libreccm.testutils.EqualsVerifier.class.
getPackage())
.addPackage(org.libreccm.cdi.utils.CdiUtil.class.getPackage())
.addAsLibraries(libs)
.addAsResource("test-persistence.xml",
"META-INF/persistence.xml")
.addAsResource(
"configs/org/libreccm/modules/ConfigurationLoaderTest/"
+ "log4j2.xml",
"log4j2.xml")
// .addAsWebInfResource(EmptyAsset.INSTANCE, "web.xml")
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsResource(
"configs/org/libreccm/modules/ConfigurationLoaderTest/"
+ "test-integration.properties", "integration.properties");
}
@Test
@ShouldMatchDataSet(
value = "datasets/org/libreccm/modules/ConfigurationLoaderTest/"
+ "after-load.yml",
excludeColumns = {"setting_id", "entry_id"})
public void loadConfiguration() {
final ConfigurationLoader confLoader = new ConfigurationLoader(
entityManager);
confLoader.loadConfiguration(ExampleConfiguration.class);
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2016 LibreCCM Foundation.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.libreccm.modules;
import static org.libreccm.testutils.DatasetType.*;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.libreccm.tests.categories.UnitTest;
import org.libreccm.testutils.DatasetType;
import org.libreccm.testutils.DatasetsVerifier;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.*;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RunWith(Parameterized.class)
@Category(UnitTest.class)
public class DatasetsTest extends DatasetsVerifier {
@Parameterized.Parameters(name = "Dataset {0}")
public static Collection<String> data() {
return Arrays.asList(new String[]{
"/datasets/org/libreccm/modules/ConfigurationLoaderTest/after-load.yml",});
}
public DatasetsTest(final String datasetPath) {
super(datasetPath);
}
@Override
public String[] getSchemas() {
return new String[]{"ccm_core"};
}
@Override
public DatasetType getDatasetType() {
return YAML;
}
@BeforeClass
public static void setUpClass() {
}
@AfterClass
public static void tearDownClass() {
}
@Before
public void setUp() {
}
@After
public void tearDown() {
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="DEBUG">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
<Logger name="org.libreccm.modules.ConfigurationLoader"
level="debug">
<AppenderRef ref="Console"/>
</Logger>
</Loggers>
</Configuration>

View File

@ -0,0 +1,5 @@
foo.bar=This should be ignored
org.libreccm.configuration.ExampleConfiguration.enabled=true
org.libreccm.configuration.ExampleConfiguration.itemsPerPage=100
org.libreccm.configuration.ExampleConfiguration.title=de:Versuch,en:Test

View File

@ -0,0 +1,23 @@
ccm_core.settings:
- setting_id: -100
configuration_class: org.libreccm.configuration.ExampleConfiguration
name: enabled
setting_value_boolean: true
dtype: BooleanSetting
- setting_id: -110
configuration_class: org.libreccm.configuration.ExampleConfiguration
name: itemsPerPage
setting_value_long: 100
dtype: LongSetting
- setting_id: -120
configuration_class: org.libreccm.configuration.ExampleConfiguration
name: title
dtype: LocalizedStringSetting
ccm_core.settings_l10n_str_values:
- entry_id: -120
locale: de
localized_value: Versuch
- entry_id: -120
locale: en
localized_value: Test