Java tutorial
/** * Copyright (c) Codice Foundation * * <p>This 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 any later version. * * <p>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 Lesser General Public License for more details. A copy of the GNU Lesser General Public * License is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package ddf.catalog.definition.impl; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.codice.gsonsupport.GsonTypeAdapters.LIST_STRING; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import ddf.catalog.data.AttributeDescriptor; import ddf.catalog.data.AttributeRegistry; import ddf.catalog.data.DefaultAttributeValueRegistry; import ddf.catalog.data.InjectableAttribute; import ddf.catalog.data.MetacardType; import ddf.catalog.data.impl.AttributeDescriptorImpl; import ddf.catalog.data.impl.BasicTypes; import ddf.catalog.data.impl.InjectableAttributeImpl; import ddf.catalog.data.impl.MetacardImpl; import ddf.catalog.data.impl.MetacardTypeImpl; import ddf.catalog.data.impl.types.AssociationsAttributes; import ddf.catalog.data.impl.types.ContactAttributes; import ddf.catalog.data.impl.types.CoreAttributes; import ddf.catalog.data.impl.types.DateTimeAttributes; import ddf.catalog.data.impl.types.LocationAttributes; import ddf.catalog.data.impl.types.MediaAttributes; import ddf.catalog.data.impl.types.SecurityAttributes; import ddf.catalog.data.impl.types.TopicAttributes; import ddf.catalog.data.impl.types.ValidationAttributes; import ddf.catalog.data.impl.types.VersionAttributes; import ddf.catalog.validation.AttributeValidator; import ddf.catalog.validation.AttributeValidatorRegistry; import ddf.catalog.validation.MetacardValidator; import ddf.catalog.validation.ReportingMetacardValidator; import ddf.catalog.validation.impl.validator.EnumerationValidator; import ddf.catalog.validation.impl.validator.FutureDateValidator; import ddf.catalog.validation.impl.validator.ISO3CountryCodeValidator; import ddf.catalog.validation.impl.validator.MatchAnyValidator; import ddf.catalog.validation.impl.validator.PastDateValidator; import ddf.catalog.validation.impl.validator.PatternValidator; import ddf.catalog.validation.impl.validator.RangeValidator; import ddf.catalog.validation.impl.validator.RelationshipValidator; import ddf.catalog.validation.impl.validator.RequiredAttributesMetacardValidator; import ddf.catalog.validation.impl.validator.SizeValidator; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.Serializable; import java.lang.reflect.Type; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Dictionary; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.felix.fileinstall.ArtifactInstaller; import org.codice.ddf.configuration.DictionaryMap; import org.codice.gsonsupport.GsonTypeAdapters.LongDoubleTypeAdapter; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DefinitionParser implements ArtifactInstaller { private static final Logger LOGGER = LoggerFactory.getLogger(DefinitionParser.class); private static final String METACARD_VALIDATORS_PROPERTY = "metacardvalidators"; private static final String REQUIRED_ATTRIBUTE_VALIDATOR_PROPERTY = "requiredattributes"; private static final String SINGLE_VALIDATOR_PROPERTY = "validator"; private static final String VALIDATORS_PROPERTY = "validators"; private static final String METACARD_TYPES_PROPERTY = "Metacard Types"; private static final String DEFAULTS_PROPERTY = "Defaults"; private static final String INJECTIONS_PROPERTY = "Injections"; private static final String MATCH_ANY = "match_any"; private static final String NAME_PROPERTY = "name"; private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_INSTANT; public static final Type OUTER_VALIDATOR_TYPE = new TypeToken<List<Outer.Validator>>() { }.getType(); private static final Gson GSON = new GsonBuilder().disableHtmlEscaping() .registerTypeAdapterFactory(LongDoubleTypeAdapter.FACTORY) .registerTypeHierarchyAdapter(Outer.Validator.class, new ValidatorHierarchyAdapter()).setLenient() .create(); private final AttributeRegistry attributeRegistry; private final AttributeValidatorRegistry attributeValidatorRegistry; private final DefaultAttributeValueRegistry defaultAttributeValueRegistry; private final Map<String, Changeset> changesetsByFile = new ConcurrentHashMap<>(); private final Function<Class, Bundle> bundleLookup; private final List<MetacardType> metacardTypes; private final List<MetacardType> coreTypes = ImmutableList.of(new AssociationsAttributes(), new ContactAttributes(), new CoreAttributes(), new DateTimeAttributes(), new LocationAttributes(), new MediaAttributes(), new SecurityAttributes(), new TopicAttributes(), new ValidationAttributes(), new VersionAttributes()); public DefinitionParser(AttributeRegistry attributeRegistry, AttributeValidatorRegistry attributeValidatorRegistry, DefaultAttributeValueRegistry defaultAttributeValueRegistry, List<MetacardType> metacardTypes) { this(attributeRegistry, attributeValidatorRegistry, defaultAttributeValueRegistry, metacardTypes, FrameworkUtil::getBundle); } @VisibleForTesting DefinitionParser(AttributeRegistry attributeRegistry, AttributeValidatorRegistry attributeValidatorRegistry, DefaultAttributeValueRegistry defaultAttributeValueRegistry, List<MetacardType> metacardTypes, Function<Class, Bundle> bundleLookup) { this.attributeRegistry = attributeRegistry; this.attributeValidatorRegistry = attributeValidatorRegistry; this.defaultAttributeValueRegistry = defaultAttributeValueRegistry; this.metacardTypes = metacardTypes; this.bundleLookup = bundleLookup; } @Override public void install(File file) throws Exception { apply(file); } @Override public void update(File file) throws Exception { undo(file); apply(file); } @Override public void uninstall(File file) throws Exception { undo(file); } @Override public boolean canHandle(File file) { return file.getName().endsWith(".json"); } private void apply(File file) throws Exception { String data; try (InputStream input = new FileInputStream(file)) { data = IOUtils.toString(input, StandardCharsets.UTF_8.name()); LOGGER.debug("Installing file [{}]. Contents:\n{}", file.getAbsolutePath(), data); } if (StringUtils.isEmpty(data)) { LOGGER.debug("File is empty [{}]. Nothing to install.", file.getAbsolutePath()); return; /* nothing to install */ } Outer outer = GSON.fromJson(data, Outer.class); final String filename = file.getName(); final Changeset changeset = new Changeset(); changesetsByFile.put(filename, changeset); handleSection(changeset, "Attribute Types", outer.attributeTypes, this::parseAttributeTypes); handleSection(changeset, METACARD_TYPES_PROPERTY, outer.metacardTypes, this::parseMetacardTypes); handleSection(changeset, VALIDATORS_PROPERTY, outer.validators, this::parseValidators); handleSection(changeset, DEFAULTS_PROPERTY, outer.defaults, this::parseDefaults); handleSection(changeset, INJECTIONS_PROPERTY, outer.inject, this::parseInjections); handleSection(changeset, METACARD_VALIDATORS_PROPERTY, outer.metacardvalidators, this::registerMetacardValidators); } private <T> void handleSection(Changeset changeset, String sectionName, T sectionData, BiFunction<Changeset, T, List<Callable<Boolean>>> parser) throws Exception { if (sectionData != null) { List<Callable<Boolean>> stagedAdds; try { stagedAdds = parser.apply(changeset, sectionData); } catch (Exception e) { throw new IllegalArgumentException(String.format("Could not parse %s section.", sectionName), e); } LOGGER.debug("Committing {}", sectionName); commitStaged(stagedAdds); } } private void commitStaged(List<Callable<Boolean>> stagedAdds) throws Exception { for (Callable<Boolean> staged : stagedAdds) { try { staged.call(); } catch (RuntimeException e) { LOGGER.debug("Error adding staged item {}", staged, e); } } } private List<Callable<Boolean>> parseAttributeTypes(Changeset changeset, Map<String, Outer.AttributeType> attributeTypes) { List<Callable<Boolean>> staged = new ArrayList<>(); for (Map.Entry<String, Outer.AttributeType> entry : attributeTypes.entrySet()) { final AttributeDescriptor descriptor = new AttributeDescriptorImpl(entry.getKey(), entry.getValue().indexed, entry.getValue().stored, entry.getValue().tokenized, entry.getValue().multivalued, BasicTypes.getAttributeType(entry.getValue().type.replace("_TYPE", ""))); staged.add(() -> { attributeRegistry.register(descriptor); changeset.attributes.add(descriptor); return true; }); } return staged; } @SuppressWarnings("squid:S1149" /* Confined by underlying contract */) private List<Callable<Boolean>> parseMetacardTypes(Changeset changeset, List<Outer.MetacardType> incomingMetacardTypes) { List<Callable<Boolean>> staged = new ArrayList<>(); BundleContext context = getBundleContext(); List<MetacardType> stagedTypes = new ArrayList<>(); for (Outer.MetacardType metacardType : incomingMetacardTypes) { Set<AttributeDescriptor> attributeDescriptors = new HashSet<>( MetacardImpl.BASIC_METACARD.getAttributeDescriptors()); Set<String> requiredAttributes = new HashSet<>(); Set<AttributeDescriptor> extendedAttributes = Optional.of(metacardType).map(omt -> omt.extendsTypes) .orElse(Collections.emptyList()).stream().flatMap(getSpecifiedTypes(stagedTypes)) .collect(Collectors.toSet()); attributeDescriptors.addAll(extendedAttributes); Optional.ofNullable(metacardType.attributes).orElse(Collections.emptyMap()) .forEach((attributeName, attribute) -> processAttribute(metacardType, attributeDescriptors, requiredAttributes, attributeName, attribute)); if (!requiredAttributes.isEmpty()) { final MetacardValidator validator = new RequiredAttributesMetacardValidator(metacardType.type, requiredAttributes); staged.add(() -> { ServiceRegistration<MetacardValidator> registration = context .registerService(MetacardValidator.class, validator, null); changeset.metacardValidatorServices.add(registration); return registration != null; }); } Dictionary<String, Object> properties = new DictionaryMap<>(); properties.put(NAME_PROPERTY, metacardType.type); MetacardType type = new MetacardTypeImpl(metacardType.type, attributeDescriptors); stagedTypes.add(type); staged.add(() -> { ServiceRegistration<MetacardType> registration = context.registerService(MetacardType.class, type, properties); changeset.metacardTypeServices.add(registration); return registration != null; }); } return staged; } private void processAttribute(Outer.MetacardType metacardType, /*Mutable*/ Set<AttributeDescriptor> attributeDescriptors, /*Mutable*/ Set<String> requiredAttributes, String attributeName, Outer.MetacardAttribute attribute) { AttributeDescriptor descriptor = attributeRegistry.lookup(attributeName) .orElseThrow(() -> new IllegalStateException(String.format( "Metacard type '%s' includes the attribute '%s', but that attribute is not in the attribute registry.", metacardType.type, attributeName))); attributeDescriptors.add(descriptor); if (attribute.required) { requiredAttributes.add(attributeName); } } private Function<String, Stream<? extends AttributeDescriptor>> getSpecifiedTypes( List<MetacardType> stagedTypes) { return type -> new ImmutableList.Builder<MetacardType>().addAll(metacardTypes).addAll(stagedTypes) .addAll(coreTypes).build().stream().filter(mt -> mt.getName().equals(type)).findFirst() .orElseThrow(() -> new RuntimeException(String.format( "Could not find a metacard type by name '%s'. Was the type already defined or defined first in the list of definitions?", type))) .getAttributeDescriptors().stream(); } private List<Callable<Boolean>> registerMetacardValidators(Changeset changeset, List<Map<String, List<MetacardValidatorDefinition>>> metacardValidatorDefinitions) { List<Callable<Boolean>> staged = new ArrayList<>(); BundleContext context = getBundleContext(); for (Map<String, List<MetacardValidatorDefinition>> mvdMap : metacardValidatorDefinitions) { for (Entry<String, List<MetacardValidatorDefinition>> row : mvdMap.entrySet()) { try { List<MetacardValidator> metacardValidators = getMetacardValidators(row.getValue().get(0)); metacardValidators.forEach(metacardValidator -> staged.add(() -> { ServiceRegistration<MetacardValidator> registration = context .registerService(MetacardValidator.class, metacardValidator, null); changeset.metacardValidatorServices.add(registration); return registration != null; })); } catch (IllegalStateException ise) { LOGGER.error("Could not register metacard validator for definition: {} {}", row.getKey(), row.getValue(), ise); } } } return staged; } private List<Callable<Boolean>> parseValidators(Changeset changeset, Map<String, List<Outer.Validator>> validators) { List<Callable<Boolean>> staged = new ArrayList<>(); for (Map.Entry<String, List<Outer.Validator>> entry : validators.entrySet()) { Set<ValidatorWrapper> validatorWrappers = validatorFactory(entry.getKey(), entry.getValue()); Set<AttributeValidator> attributeValidators = validatorWrappers.stream() .map(ValidatorWrapper::getAttributeValidators).flatMap(Collection::stream) .collect(Collectors.toSet()); String attributeName = entry.getKey(); staged.add(() -> { attributeValidatorRegistry.registerValidators(attributeName, attributeValidators); changeset.attributeValidators.put(attributeName, attributeValidators); return true; }); validatorWrappers.stream().map(ValidatorWrapper::getMetacardValidators).flatMap(Collection::stream) .collect(Collectors.toSet()).forEach(validator -> staged.add(() -> { ServiceRegistration<MetacardValidator> registration = getBundleContext() .registerService(MetacardValidator.class, validator, null); changeset.metacardValidatorServices.add(registration); return registration != null; })); validatorWrappers.stream().map(ValidatorWrapper::getReportingMetacardValidators) .flatMap(Collection::stream).collect(Collectors.toSet()).forEach(validator -> staged.add(() -> { ServiceRegistration<ReportingMetacardValidator> registration = getBundleContext() .registerService(ReportingMetacardValidator.class, validator, null); changeset.reportingMetacardValidatorServices.add(registration); return registration != null; })); } return staged; } private Set<ValidatorWrapper> validatorFactory(String attribute, List<Outer.Validator> validators) { return validators.stream().filter(Objects::nonNull).filter(v -> StringUtils.isNotBlank(v.validator)) .map((Outer.Validator validator) -> getValidator(attribute, validator)).collect(toSet()); } private List<MetacardValidator> getMetacardValidators(MetacardValidatorDefinition validatorDefinition) { if (!REQUIRED_ATTRIBUTE_VALIDATOR_PROPERTY.equals(validatorDefinition.validator)) { throw new IllegalStateException( String.format("Validator does not exist. (%s)", validatorDefinition.validator)); } if (CollectionUtils.isEmpty(validatorDefinition.requiredattributes)) { throw new IllegalStateException("Required Attributes Validator received invalid configuration"); } return ImmutableList.of(new RequiredAttributesMetacardValidator(validatorDefinition.validator, ImmutableSet.copyOf(validatorDefinition.requiredattributes))); } private ValidatorWrapper getValidator(String key, Outer.Validator validator) { ValidatorWrapper wrapper = new ValidatorWrapper(); List<String> arguments = validator.arguments; switch (validator.validator) { case "size": { long lmin = Long.parseLong(arguments.get(0)); long lmax = Long.parseLong(arguments.get(1)); wrapper.attributeValidator(new SizeValidator(lmin, lmax)); break; } case "pattern": { String regex = arguments.get(0); wrapper.attributeValidator(new PatternValidator(regex)); break; } case "pastdate": { wrapper.attributeValidator(PastDateValidator.getInstance()); break; } case "futuredate": { wrapper.attributeValidator(FutureDateValidator.getInstance()); break; } case "enumeration": { Set<String> values = new HashSet<>(arguments); wrapper.attributeValidator(new EnumerationValidator(values, false)); break; } case "enumerationignorecase": { Set<String> values = new HashSet<>(arguments); wrapper.attributeValidator(new EnumerationValidator(values, true)); break; } case "range": { BigDecimal min = new BigDecimal(arguments.get(0)); BigDecimal max = new BigDecimal(arguments.get(1)); if (arguments.size() > 2) { BigDecimal epsilon = new BigDecimal(arguments.get(2)); wrapper.attributeValidator(new RangeValidator(min, max, epsilon)); } wrapper.attributeValidator(new RangeValidator(min, max)); break; } case "iso3_country": { wrapper.attributeValidator(new ISO3CountryCodeValidator(false)); break; } case "iso3_countryignorecase": { wrapper.attributeValidator(new ISO3CountryCodeValidator(true)); break; } case MATCH_ANY: { List<Outer.Validator> collection = ((Outer.ValidatorCollection) validator).validators; List<AttributeValidator> attributeValidators = collection.stream() .map((Outer.Validator key1) -> getValidator(key, key1)) .map(ValidatorWrapper::getAttributeValidators).flatMap(Collection::stream) .collect(Collectors.toList()); MatchAnyValidator matchAnyValidator = new MatchAnyValidator(attributeValidators); wrapper.attributeValidator(matchAnyValidator); break; } case "relationship": { if (arguments.size() < 4) { throw new IllegalArgumentException("Not enough parameters for relationship validator"); } RelationshipValidator relationshipValidator = new RelationshipValidator(key, arguments.get(0), arguments.get(1), arguments.get(2), arguments.subList(3, arguments.size()).toArray(new String[] {})); wrapper.metacardValidator(relationshipValidator); wrapper.reportingMetacardValidator(relationshipValidator); break; } default: throw new IllegalStateException("Validator does not exist. (" + validator.validator + ")"); } return wrapper; } /** TODO DDF-3578 once MetacardValidator is eliminated, this pattern can be cleaned up */ private class ValidatorWrapper { private List<MetacardValidator> metacardValidators = new ArrayList<>(); private List<ReportingMetacardValidator> reportingMetacardValidators = new ArrayList<>(); private List<AttributeValidator> attributeValidators = new ArrayList<>(); void attributeValidator(AttributeValidator validator) { attributeValidators.add(validator); } void metacardValidator(MetacardValidator validator) { metacardValidators.add(validator); } void reportingMetacardValidator(ReportingMetacardValidator validator) { reportingMetacardValidators.add(validator); } List<MetacardValidator> getMetacardValidators() { return metacardValidators; } List<ReportingMetacardValidator> getReportingMetacardValidators() { return reportingMetacardValidators; } List<AttributeValidator> getAttributeValidators() { return attributeValidators; } } private Serializable parseDefaultValue(AttributeDescriptor descriptor, String defaultValue) { switch (descriptor.getType().getAttributeFormat()) { case BOOLEAN: return Boolean.parseBoolean(defaultValue); case DATE: return Date.from(Instant.from(DATE_FORMATTER.parse(defaultValue))); case DOUBLE: return Double.parseDouble(defaultValue); case FLOAT: return Float.parseFloat(defaultValue); case SHORT: return Short.parseShort(defaultValue); case INTEGER: return Integer.parseInt(defaultValue); case LONG: return Long.parseLong(defaultValue); case BINARY: return defaultValue.getBytes(StandardCharsets.UTF_8); default: return defaultValue; } } private List<Callable<Boolean>> parseDefaults(Changeset changeset, List<Outer.Default> defaults) { return defaults.stream().map(defaultObj -> { String attribute = defaultObj.attribute; AttributeDescriptor descriptor = attributeRegistry.lookup(attribute) .orElseThrow(() -> new IllegalStateException(String.format( "The default value for the attribute '%s' cannot be parsed because that attribute has not been registered in the attribute registry", attribute))); Serializable defaultValue = parseDefaultValue(descriptor, defaultObj.value); List<String> metacardTypes = defaultObj.metacardTypes; if (CollectionUtils.isEmpty(metacardTypes)) { return (Callable<Boolean>) () -> { defaultAttributeValueRegistry.setDefaultValue(attribute, defaultValue); changeset.defaults.add(defaultObj); return true; }; } else { return (Callable<Boolean>) () -> { metacardTypes.forEach(metacardType -> defaultAttributeValueRegistry .setDefaultValue(metacardType, attribute, defaultValue)); changeset.defaults.add(defaultObj); return true; }; } }).collect(toList()); } private List<Callable<Boolean>> parseInjections(Changeset changeset, List<Outer.Injection> injections) { BundleContext context = getBundleContext(); return injections.stream().map(injection -> (Callable<Boolean>) () -> { String attribute = injection.attribute; InjectableAttribute injectableAttribute = new InjectableAttributeImpl(attribute, injection.metacardTypes); ServiceRegistration<InjectableAttribute> injectableAttributeService = context .registerService(InjectableAttribute.class, injectableAttribute, null); changeset.injectableAttributeServices.add(injectableAttributeService); return true; }).collect(toList()); } private BundleContext getBundleContext() { return Optional.ofNullable(bundleLookup.apply(getClass())).map(Bundle::getBundleContext).orElseThrow( () -> new IllegalStateException("Could not get the bundle for " + getClass().getName())); } private void undo(File file) { final String filename = file.getName(); LOGGER.debug("Reversing the changes applied by file [{}].", filename); final Changeset changeset = changesetsByFile.get(filename); if (changeset != null) { undoMetacardTypes(changeset.metacardTypeServices); undoMetacardValidators(changeset.metacardValidatorServices); undoReportingMetacardValidators(changeset.reportingMetacardValidatorServices); undoAttributes(changeset.attributes); undoDefaults(changeset.defaults); undoAttributeValidators(changeset.attributeValidators); undoInjectableAttributes(changeset.injectableAttributeServices); changesetsByFile.remove(filename); } } private void undoMetacardTypes(List<ServiceRegistration<MetacardType>> metacardTypeServices) { metacardTypeServices.forEach(ServiceRegistration::unregister); } private void undoMetacardValidators(List<ServiceRegistration<MetacardValidator>> metacardValidatorServices) { metacardValidatorServices.forEach(ServiceRegistration::unregister); } private void undoReportingMetacardValidators( List<ServiceRegistration<ReportingMetacardValidator>> reportingMetacardValidatorServices) { reportingMetacardValidatorServices.forEach(ServiceRegistration::unregister); } private void undoAttributes(Set<AttributeDescriptor> attributes) { attributes.forEach(attributeRegistry::deregister); } private void undoDefaults(List<Outer.Default> defaults) { defaults.forEach(theDefault -> { if (CollectionUtils.isEmpty(theDefault.metacardTypes)) { defaultAttributeValueRegistry.removeDefaultValue(theDefault.attribute); } else { theDefault.metacardTypes.forEach( type -> defaultAttributeValueRegistry.removeDefaultValue(type, theDefault.attribute)); } }); } private void undoAttributeValidators(Map<String, Set<AttributeValidator>> attributeValidators) { attributeValidators.forEach((attributeName, validatorsToRemove) -> { Set<AttributeValidator> currentValidators = attributeValidatorRegistry.getValidators(attributeName); Set<AttributeValidator> resultingValidators = Sets.difference(currentValidators, validatorsToRemove); attributeValidatorRegistry.deregisterValidators(attributeName); if (!resultingValidators.isEmpty()) { attributeValidatorRegistry.registerValidators(attributeName, resultingValidators); } }); } private void undoInjectableAttributes( List<ServiceRegistration<InjectableAttribute>> injectableAttributeServices) { injectableAttributeServices.forEach(ServiceRegistration::unregister); } private static class Outer { List<Outer.MetacardType> metacardTypes; Map<String, Outer.AttributeType> attributeTypes; Map<String, List<Outer.Validator>> validators; List<Outer.Default> defaults; List<Outer.Injection> inject; // Name casing matches JSON expectations; do not change List<Map<String, List<MetacardValidatorDefinition>>> metacardvalidators; private static class MetacardType { String type; List<String> extendsTypes; Map<String, MetacardAttribute> attributes; } private static class MetacardAttribute { boolean required; } private static class AttributeType { String type; boolean tokenized; boolean stored; boolean indexed; boolean multivalued; } private static class Default { String attribute; String value; List<String> metacardTypes; } private static class Injection { String attribute; List<String> metacardTypes; } private static class Validator { @SuppressWarnings("squid:S1700" /* Required to match expected JSON structure */) String validator; List<String> arguments; Validator(String validator) { this.validator = validator; } Validator(String validator, List<String> arguments) { this(validator); this.arguments = arguments; } } private static class ValidatorCollection extends Validator { List<Outer.Validator> validators; ValidatorCollection(String validatorName, List<Validator> validators) { super(validatorName); this.validators = validators; } } } private static class MetacardValidatorDefinition { String validator; List<String> requiredattributes; } private class Changeset { private final List<ServiceRegistration<MetacardType>> metacardTypeServices = new ArrayList<>(); private final List<ServiceRegistration<MetacardValidator>> metacardValidatorServices = new ArrayList<>(); private final List<ServiceRegistration<ReportingMetacardValidator>> reportingMetacardValidatorServices = new ArrayList<>(); private final Set<AttributeDescriptor> attributes = new HashSet<>(); private final List<Outer.Default> defaults = new ArrayList<>(); private final Map<String, Set<AttributeValidator>> attributeValidators = new HashMap<>(); private final List<ServiceRegistration<InjectableAttribute>> injectableAttributeServices = new ArrayList<>(); } private static class ValidatorHierarchyAdapter implements JsonDeserializer<Outer.Validator> { @Override public Outer.Validator deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) { JsonObject object = jsonElement.getAsJsonObject(); String validatorName = context.deserialize(object.get(SINGLE_VALIDATOR_PROPERTY), String.class); Outer.Validator result = null; // If the validator has a collection of validators, it must be a collection type JsonElement validators = object.get(VALIDATORS_PROPERTY); if (validators != null) { result = new Outer.ValidatorCollection(validatorName, context.deserialize(validators, OUTER_VALIDATOR_TYPE)); } if (result == null) { result = new Outer.Validator(validatorName, context.deserialize(object.get("arguments"), LIST_STRING)); } return result; } } }