001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.cli.commands;
007
008import org.apache.commons.cli.CommandLine;
009import org.apache.commons.cli.Option;
010import org.json.JSONObject;
011import org.json.JSONTokener;
012import org.xml.sax.SAXException;
013
014import java.io.IOException;
015import java.io.Reader;
016import java.io.StringReader;
017import java.io.StringWriter;
018import java.net.URL;
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.List;
022import java.util.Set;
023
024import javax.xml.transform.stream.StreamSource;
025
026import dev.metaschema.cli.processor.CallingContext;
027import dev.metaschema.cli.processor.command.CommandExecutionException;
028import dev.metaschema.cli.processor.command.ICommandExecutor;
029import dev.metaschema.core.configuration.DefaultConfiguration;
030import dev.metaschema.core.configuration.IMutableConfiguration;
031import dev.metaschema.core.model.IModule;
032import dev.metaschema.core.model.constraint.IConstraintSet;
033import dev.metaschema.core.model.validation.JsonSchemaContentValidator;
034import dev.metaschema.core.model.validation.XmlSchemaContentValidator;
035import dev.metaschema.core.util.CollectionUtil;
036import dev.metaschema.core.util.ObjectUtils;
037import dev.metaschema.databind.IBindingContext;
038import dev.metaschema.databind.IBindingContext.ISchemaValidationProvider;
039import dev.metaschema.schemagen.ISchemaGenerator;
040import dev.metaschema.schemagen.ISchemaGenerator.SchemaFormat;
041import dev.metaschema.schemagen.SchemaGenerationFeature;
042import edu.umd.cs.findbugs.annotations.NonNull;
043
044/**
045 * This command implementation supports validation of a content instance based
046 * on a provided Metaschema module.
047 */
048public class ValidateContentUsingModuleCommand
049    extends AbstractValidateContentCommand {
050  @NonNull
051  private static final String COMMAND = "validate-content";
052
053  @Override
054  public String getName() {
055    return COMMAND;
056  }
057
058  @Override
059  public String getDescription() {
060    return "Verify that the provided resource is well-formed and valid to the provided Module-based model.";
061  }
062
063  @Override
064  public Collection<? extends Option> gatherOptions() {
065    Collection<? extends Option> orig = super.gatherOptions();
066
067    List<Option> retval = new ArrayList<>(orig.size() + 1);
068    retval.addAll(orig);
069    retval.add(MetaschemaCommands.METASCHEMA_REQUIRED_OPTION);
070
071    return CollectionUtil.unmodifiableCollection(retval);
072  }
073
074  @Override
075  public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine commandLine) {
076    return new CommandExecutor(callingContext, commandLine);
077  }
078
079  private final class CommandExecutor
080      extends AbstractValidationCommandExecutor {
081
082    private CommandExecutor(
083        @NonNull CallingContext callingContext,
084        @NonNull CommandLine commandLine) {
085      super(callingContext, commandLine);
086    }
087
088    @Override
089    protected IBindingContext getBindingContext(@NonNull Set<IConstraintSet> constraintSets)
090        throws CommandExecutionException {
091      return MetaschemaCommands.newBindingContextWithDynamicCompilation(constraintSets);
092    }
093
094    @SuppressWarnings("synthetic-access")
095    @Override
096    protected IModule getModule(
097        CommandLine commandLine,
098        IBindingContext bindingContext) throws CommandExecutionException {
099      return MetaschemaCommands.loadModule(
100          commandLine,
101          MetaschemaCommands.METASCHEMA_REQUIRED_OPTION,
102          ObjectUtils.notNull(getCurrentWorkingDirectory().toUri()),
103          bindingContext);
104    }
105
106    @Override
107    protected ISchemaValidationProvider getSchemaValidationProvider(
108        IModule module,
109        CommandLine commandLine,
110        IBindingContext bindingContext) {
111      return new ModuleValidationProvider(module);
112    }
113
114  }
115
116  private static final class ModuleValidationProvider implements ISchemaValidationProvider {
117    @NonNull
118    private final IModule module;
119
120    public ModuleValidationProvider(@NonNull IModule module) {
121      this.module = module;
122    }
123
124    @Override
125    public XmlSchemaContentValidator getXmlSchemas(
126        @NonNull URL targetResource,
127        @NonNull IBindingContext bindingContext) throws IOException, SAXException {
128      IMutableConfiguration<SchemaGenerationFeature<?>> configuration = new DefaultConfiguration<>();
129
130      try (StringWriter writer = new StringWriter()) {
131        ISchemaGenerator.generateSchema(module, writer, SchemaFormat.XML, configuration);
132        try (Reader reader = new StringReader(writer.toString())) {
133          return new XmlSchemaContentValidator(
134              ObjectUtils.notNull(List.of(new StreamSource(reader))));
135        }
136      }
137    }
138
139    @Override
140    public JsonSchemaContentValidator getJsonSchema(
141        @NonNull JSONObject json,
142        @NonNull IBindingContext bindingContext) throws IOException {
143      IMutableConfiguration<SchemaGenerationFeature<?>> configuration = new DefaultConfiguration<>();
144
145      try (StringWriter writer = new StringWriter()) {
146        ISchemaGenerator.generateSchema(module, writer, SchemaFormat.JSON, configuration);
147        return new JsonSchemaContentValidator(
148            new JSONObject(new JSONTokener(writer.toString())));
149      }
150    }
151  }
152}