RESTful API for configuration

Jens Pelzetter 2020-06-20 16:47:18 +02:00
parent 0b9c2e9b27
commit 98f5d3201b
13 changed files with 818 additions and 6 deletions

View File

@ -20,6 +20,7 @@ import org.libreccm.api.DefaultResponseHeaders;
import org.libreccm.api.PreflightRequestFilter; import org.libreccm.api.PreflightRequestFilter;
import org.libreccm.api.admin.categorization.CategoriesApi; import org.libreccm.api.admin.categorization.CategoriesApi;
import org.libreccm.api.admin.categorization.DomainsApi; import org.libreccm.api.admin.categorization.DomainsApi;
import org.libreccm.configuration.Configuration;
/** /**
* *
@ -40,6 +41,9 @@ public class AdminApi extends Application {
classes.add(DomainsApi.class); classes.add(DomainsApi.class);
classes.add(CategoriesApi.class); classes.add(CategoriesApi.class);
// Configuration API
classes.add(Configuration.class);
// Security API // Security API
classes.add(GroupsApi.class); classes.add(GroupsApi.class);
classes.add(RolesApi.class); classes.add(RolesApi.class);

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2020 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.api.admin.configuration;
import java.math.BigDecimal;
import javax.json.JsonNumber;
import javax.json.JsonValue;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
class BigDecimalConverter implements SettingValueConverter<BigDecimal> {
@Override
public BigDecimal convertSettingValue(final JsonValue value) {
if (value.getValueType() == JsonValue.ValueType.NUMBER) {
return ((JsonNumber) value).bigDecimalValue();
} else {
throw new WebApplicationException(
"The provided value is not a number.",
Response.Status.BAD_REQUEST
);
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2020 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.api.admin.configuration;
import javax.json.JsonValue;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
class BooleanConverter implements SettingValueConverter<Boolean> {
@Override
public Boolean convertSettingValue(final JsonValue value) {
switch(value.getValueType()) {
case FALSE:
return false;
case TRUE:
return true;
default:
throw new WebApplicationException(
"The provided value is not a boolean.",
Response.Status.BAD_REQUEST
);
}
}
}

View File

@ -0,0 +1,296 @@
/*
* Copyright (C) 2020 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.api.admin.configuration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.configuration.ConfigurationInfo;
import org.libreccm.configuration.ConfigurationManager;
import org.libreccm.configuration.SettingInfo;
import org.libreccm.l10n.LocalizedString;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.json.JsonValue;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Path("/configuration")
public class ConfigurationApi {
private static final Logger LOGGER = LogManager.getLogger(
ConfigurationApi.class
);
@Inject
private ConfigurationManager confManager;
private final Map<String, SettingValueConverter<?>> converters;
public ConfigurationApi() {
converters = new HashMap<>();
converters.put(BigDecimal.class.getName(), new BigDecimalConverter());
converters.put(Boolean.class.getName(), new BooleanConverter());
converters.put(Double.class.getName(), new DoubleConverter());
converters.put(EnumConverter.class.getName(), new EnumConverter());
converters.put(
LocalizedString.class.getName(), new LocalizedStringConverter()
);
converters.put(Long.class.getName(), new LongConverter());
converters.put(String.class.getName(), new StringConverter());
converters.put(List.class.getName(), new StringListConverter());
}
@GET
@Path("/")
public List<ConfigurationInfo> getConfigurations() {
return confManager
.findAllConfigurations()
.stream()
.map(confManager::getConfigurationInfo)
.collect(Collectors.toList());
}
@GET
@Path("/{confName}")
public ConfigurationInfo getConfiguration(
@PathParam("confName") final String confName
) {
final Class<?> clazz;
try {
clazz = Class.forName(confName);
} catch (ClassNotFoundException ex) {
throw new WebApplicationException(
String.format("No configuration %s available.", confName),
Response.Status.NOT_FOUND
);
}
try {
return confManager.getConfigurationInfo(clazz);
} catch (IllegalArgumentException ex) {
// We don't expose that we found a class for that name, but that
// it is not an Configuration class.
throw new WebApplicationException(
String.format("No configuration %s available.", confName),
Response.Status.NOT_FOUND
);
}
}
@GET
@Path("/{confName}/{setting}")
@SuppressWarnings("unchecked")
public Object getSetting(
@PathParam("confName") final String confName,
@PathParam("setting") final String setting
) {
final Class<?> clazz;
try {
clazz = Class.forName(confName);
} catch (ClassNotFoundException ex) {
throw new WebApplicationException(
String.format("No configuration %s available.", confName),
Response.Status.NOT_FOUND
);
}
final Object conf;
try {
conf = confManager.findConfiguration(clazz);
} catch (IllegalArgumentException ex) {
// We don't expose that we found a class for that name, but that
// it is not an Configuration class.
throw new WebApplicationException(
String.format("No configuration %s available.", confName),
Response.Status.NOT_FOUND
);
}
final Field field;
try {
field = clazz.getDeclaredField(setting);
} catch (NoSuchFieldException ex) {
throw new WebApplicationException(
String.format(
"The configuration \"%s\" has not setting \"%s\".",
confName,
setting
),
Response.Status.NOT_FOUND
);
}
field.setAccessible(true);
try {
return field.get(conf);
} catch (IllegalAccessException | IllegalArgumentException ex) {
throw new WebApplicationException(
String.format(
"Failed to retrieve setting \"%s\" "
+ "from configuration \"%s\"",
setting,
confName
)
);
}
}
@PUT
@Path("/{confName}/{setting}")
public Response updateSetting(
@PathParam("confName") final String confName,
@PathParam("setting") final String setting,
final JsonValue value
) {
final Class<?> clazz;
try {
clazz = Class.forName(confName);
} catch (ClassNotFoundException ex) {
return Response
.status(Response.Status.NOT_FOUND)
.entity(
String.format("No configuration %s available.", confName))
.build();
}
final Object conf;
try {
conf = confManager.findConfiguration(clazz);
} catch (IllegalArgumentException ex) {
// We don't expose that we found a class for that name, but that
// it is not an Configuration class.
return Response
.status(Response.Status.NOT_FOUND)
.entity(
String.format("No configuration %s available.", confName))
.build();
}
final Field field;
try {
field = clazz.getDeclaredField(setting);
} catch (NoSuchFieldException ex) {
return Response
.status(Response.Status.NOT_FOUND)
.entity(
String.format(
"The configuration \"%s\" has not setting \"%s\".",
confName,
setting
)
)
.build();
}
field.setAccessible(true);
final ConfigurationInfo confInfo = confManager
.getConfigurationInfo(clazz);
final SettingInfo settingInfo = confInfo.getSettings().get(setting);
final String valueType = settingInfo.getValueType();
final SettingValueConverter<?> converter = converters.get(valueType);
if (converter == null) {
return Response.
status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
String.format(
"No converter available for type \"%s\" of "
+ "setting \"%s\" of configuration \"%s\".",
valueType,
setting,
confName
)
)
.build();
}
try {
field.set(conf, converter.convertSettingValue(value));
} catch (IllegalAccessException ex) {
LOGGER.error(
"Unable to set value of setting \"{}\" of "
+ "configuration \"{}\".",
setting,
confName
);
LOGGER.error(ex);
return Response
.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
String.format(
"Unable to set value of setting \"%s\" of "
+ "configuration \"%s\".",
setting,
confName
)
)
.build();
} catch (IllegalArgumentException ex) {
LOGGER.error(
"Provided value of type \"{}\" can't be used for "
+ "setting of type \"{}\".",
Objects.toString(value.getValueType()),
valueType
);
LOGGER.error(ex);
return Response
.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(
String.format(
"Provided value of type \"%s\" can't be used for "
+ "setting of type \"%s\".",
Objects.toString(value.getValueType()),
valueType
)
)
.build();
}
return Response
.ok(
String.format(
"Setting \"%s\" of configuration \"%s\" successfully "
+ "updated",
setting,
confName
)
)
.build();
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 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.api.admin.configuration;
import javax.json.JsonNumber;
import javax.json.JsonValue;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
class DoubleConverter implements SettingValueConverter<Double> {
@Override
public Double convertSettingValue(final JsonValue value) {
if (value.getValueType() == JsonValue.ValueType.NUMBER) {
return ((JsonNumber) value).doubleValue();
} else {
throw new WebApplicationException(
"The provided value is not a number.",
Response.Status.BAD_REQUEST
);
}
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2020 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.api.admin.configuration;
import java.util.Set;
import java.util.stream.Collectors;
import javax.json.JsonArray;
import javax.json.JsonString;
import javax.json.JsonValue;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
class EnumConverter
implements SettingValueConverter<Set<String>> {
@Override
public Set<String> convertSettingValue(final JsonValue value) {
if (value.getValueType() == JsonValue.ValueType.ARRAY) {
final JsonArray array = value.asJsonArray();
return array
.stream()
.map(this::convertEnumValue)
.collect(Collectors.toSet());
} else {
throw new WebApplicationException(
"The provided value is not an array.",
Response.Status.BAD_REQUEST
);
}
}
private String convertEnumValue(final JsonValue value) {
if (value.getValueType() == JsonValue.ValueType.STRING) {
return ((JsonString) value).getString();
} else {
throw new WebApplicationException(
"One of the provided values is not a string.",
Response.Status.BAD_REQUEST
);
}
}
}

View File

@ -0,0 +1,117 @@
/*
* Copyright (C) 2020 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.api.admin.configuration;
import org.libreccm.l10n.LocalizedString;
import java.util.IllformedLocaleException;
import java.util.Locale;
import java.util.Map;
import javax.json.JsonObject;
import javax.json.JsonString;
import javax.json.JsonValue;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class LocalizedStringConverter
implements SettingValueConverter<LocalizedString> {
@Override
public LocalizedString convertSettingValue(final JsonValue value) {
if (value.getValueType() == JsonValue.ValueType.OBJECT) {
final JsonObject object = value.asJsonObject();
final LocalizedString localizedString = new LocalizedString();
object
.entrySet()
.stream()
.map(this::buildLocalizedValue)
.forEach(
localized -> localizedString.addValue(
localized.getLocale(),
localized.getValue()
)
);
return localizedString;
} else {
throw new WebApplicationException(
"One of the provided values is not an object.",
Response.Status.BAD_REQUEST
);
}
}
private LocalizedValue buildLocalizedValue(
final Map.Entry<String, JsonValue> entry
) {
final JsonValue value = entry.getValue();
if (value.getValueType() == JsonValue.ValueType.STRING) {
final Locale.Builder localeBuilder = new Locale.Builder();
final Locale locale;
try {
locale = new Locale.Builder()
.setLanguageTag(entry.getKey())
.build();
} catch (IllformedLocaleException ex) {
throw new WebApplicationException(
String.format(
"\"%s\" is not a valid locale.", entry.getKey()
),
Response.Status.BAD_REQUEST
);
}
return new LocalizedValue(locale, ((JsonString) value).getString());
} else {
throw new WebApplicationException(
String.format(
"Value for locale \"%s\" is not a string.",
entry.getKey()
),
Response.Status.BAD_REQUEST
);
}
}
private class LocalizedValue {
private final Locale locale;
private final String value;
public LocalizedValue(Locale locale, String value) {
this.locale = locale;
this.value = value;
}
public Locale getLocale() {
return locale;
}
public String getValue() {
return value;
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2020 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.api.admin.configuration;
import javax.json.JsonNumber;
import javax.json.JsonValue;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class LongConverter implements SettingValueConverter<Long> {
@Override
public Long convertSettingValue(final JsonValue value) {
if (value.getValueType() == JsonValue.ValueType.NUMBER) {
return ((JsonNumber) value).longValue();
} else {
throw new WebApplicationException(
"The provided value is not a number.",
Response.Status.BAD_REQUEST
);
}
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2020 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.api.admin.configuration;
import javax.json.JsonValue;
/**
* Interface for the setting value converters.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
* @param <T> Value Type
*/
interface SettingValueConverter<T> {
T convertSettingValue(JsonValue value);
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 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.api.admin.configuration;
import javax.json.JsonString;
import javax.json.JsonValue;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
class StringConverter implements SettingValueConverter<String> {
@Override
public String convertSettingValue(final JsonValue value) {
if (value.getValueType() == JsonValue.ValueType.STRING) {
return ((JsonString) value).getString();
} else {
throw new WebApplicationException(
"The provided value is not a string.",
Response.Status.BAD_REQUEST
);
}
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2020 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.api.admin.configuration;
import java.util.List;
import java.util.stream.Collectors;
import javax.json.JsonString;
import javax.json.JsonValue;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class StringListConverter implements SettingValueConverter<List<String>>{
@Override
public List<String> convertSettingValue(final JsonValue value) {
if (value.getValueType() == JsonValue.ValueType.STRING) {
return value
.asJsonArray()
.stream()
.map(this::convertValue)
.collect(Collectors.toList());
} else {
throw new WebApplicationException(
"The provided value is not an array.",
Response.Status.BAD_REQUEST
);
}
}
private String convertValue(final JsonValue value) {
if (value.getValueType() == JsonValue.ValueType.STRING) {
return ((JsonString) value).getString();
} else {
throw new WebApplicationException(
"One of the values in the array is not a string.",
Response.Status.BAD_REQUEST
);
}
}
}

View File

@ -81,18 +81,23 @@ public class ConfigurationManager implements Serializable {
CcmModule.class); CcmModule.class);
final SortedSet<Class<?>> configurations = new TreeSet<>( final SortedSet<Class<?>> configurations = new TreeSet<>(
(conf1, conf2) -> conf1.getName().compareTo(conf2.getName())); (conf1, conf2) -> conf1.getName().compareTo(conf2.getName())
);
for (CcmModule module : modules) { for (CcmModule module : modules) {
final Module annotation = module.getClass().getAnnotation( final Module annotation = module.getClass().getAnnotation(
Module.class); Module.class
);
if (annotation == null) { if (annotation == null) {
continue; continue;
} }
final List<Class<?>> moduleConfs = Arrays.stream( final List<Class<?>> moduleConfs = Arrays.stream(
annotation.configurations()).collect(Collectors.toList()); annotation.configurations()
).collect(
Collectors.toList()
);
configurations.addAll(moduleConfs); configurations.addAll(moduleConfs);
} }

View File

@ -18,12 +18,22 @@
*/ */
package org.libreccm.configuration; package org.libreccm.configuration;
import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import java.util.*;
import static org.libreccm.core.CoreConstants.DB_SCHEMA; import static org.libreccm.core.CoreConstants.DB_SCHEMA;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
/** /**
* A setting class for storing a set of strings. This can be used to generate * A setting class for storing a set of strings. This can be used to generate
* enums which can be configured by the administrator. * enums which can be configured by the administrator.