001/*
002 * Copyright 2017-2021 original authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.micronaut.maven.jsonschema;
017
018import io.micronaut.core.util.StringUtils;
019import io.micronaut.maven.AbstractMicronautMojo;
020import org.apache.maven.plugin.MojoExecutionException;
021import org.apache.maven.plugin.MojoFailureException;
022import org.apache.maven.plugins.annotations.LifecyclePhase;
023import org.apache.maven.plugins.annotations.Mojo;
024import org.apache.maven.plugins.annotations.Parameter;
025import org.apache.maven.project.MavenProject;
026
027import java.io.File;
028import java.nio.file.Path;
029import java.util.List;
030
031import io.micronaut.jsonschema.generator.SourceGenerator;
032import io.micronaut.jsonschema.generator.utils.SourceGeneratorConfigBuilder;
033import io.micronaut.jsonschema.generator.loaders.UrlLoader;
034
035import javax.inject.Inject;
036
037/**
038 * Json Schema generator mojo provides the parameters for all generators and the invoker logic.
039 * <p>
040 * Expects single or multiple schema files as input via a URL, file, or directory;
041 * and generates all required source code representing the validation form in the targeted language.
042 * </p>
043 */
044@Mojo(name = JsonSchemaGeneratorMojo.MOJO_NAME, defaultPhase = LifecyclePhase.GENERATE_SOURCES)
045public class JsonSchemaGeneratorMojo extends AbstractMicronautMojo {
046    public static final String MOJO_NAME = "generate-jsonschema";
047
048    static final String MICRONAUT_SCHEMA_PREFIX = "micronaut.jsonschema.generator";
049    static final String IO_MICRONAUT_SCHEMA_PREFIX = "io.micronaut.jsonschema";
050
051    /**
052     * The URL to an input resource, pointing to a JSON schema.
053     */
054    @Parameter(property = MICRONAUT_SCHEMA_PREFIX + ".input-url")
055    private String inputURL;
056
057    /**
058     * The input file containing the schema.
059     * This file will be used as a source for generating or processing schemas.
060     */
061    @Parameter(property = MICRONAUT_SCHEMA_PREFIX + ".input-file")
062    private File inputFile;
063
064    /**
065     * The directory containing multiple input files or schema files.
066     * The Mojo will process all schema files in this directory.
067     */
068    @Parameter(property = MICRONAUT_SCHEMA_PREFIX + ".input-directory")
069    private File inputDirectory;
070
071    /**
072     * The programming language to be used for schema generation. Default is "JAVA".
073     * Other values may be supported depending on the version of micronaut-sourcegen module.
074     */
075    @Parameter(property = MICRONAUT_SCHEMA_PREFIX + ".language", defaultValue = "JAVA")
076    private String language;
077
078    /**
079     * The output directory where generated sources or files will be placed.
080     * By default, this points to `${project.build.directory}/generated-sources/jsonschema`.
081     */
082    @Parameter(defaultValue = "${project.build.directory}/generated-sources/jsonschema")
083    private File outputDirectory;
084
085    /**
086     * The package name for the generated classes or schemas.
087     * Default value is specified as "io.micronaut.jsonschema".
088     */
089    @Parameter(property = MICRONAUT_SCHEMA_PREFIX + ".output-package-name", defaultValue = IO_MICRONAUT_SCHEMA_PREFIX)
090    private String outputPackageName;
091
092    /**
093     * The name of the output file where the generated schema or data will be saved only if there is a single output source.
094     */
095    @Parameter(property = MICRONAUT_SCHEMA_PREFIX + ".output-file-name")
096    private String outputFileName;
097
098    /**
099     * A list of accepted URL patterns. Used to filter or validate input resources
100     * based on their URL. URLs matching at least one pattern will be accepted.
101     * Default value is "^https://.* /.*.json".
102     */
103    @Parameter(property = MICRONAUT_SCHEMA_PREFIX + ".accepted-url-patterns")
104    private List<String> acceptedUrlPatterns;
105
106    /**
107     * The property that defines if this mojo is used.
108     */
109    @Parameter(property = MICRONAUT_SCHEMA_PREFIX + ".enabled", defaultValue = StringUtils.FALSE)
110    private boolean enabled;
111
112    private final MavenProject project;
113
114    @SuppressWarnings("CdiInjectionPointsInspection")
115    @Inject
116    public JsonSchemaGeneratorMojo(final MavenProject project) {
117        this.project = project;
118    }
119
120    @Override
121    public void execute() throws MojoExecutionException, MojoFailureException {
122        if (!enabled) {
123            if (getLog().isDebugEnabled()) {
124                getLog().debug(MOJO_NAME + " is disabled");
125            }
126            return;
127        }
128
129        var langGenerator = new SourceGenerator(language.toUpperCase());
130
131        if (acceptedUrlPatterns != null && !acceptedUrlPatterns.isEmpty()) {
132            UrlLoader.addAllowedUrlPatterns(acceptedUrlPatterns);
133        }
134
135        Path outputDirPath = getSourceDirectory(language);
136        project.addCompileSourceRoot(outputDirPath.toString());
137
138        var builder = new SourceGeneratorConfigBuilder()
139                .withOutputFolder(outputDirPath)
140                .withOutputPackageName(outputPackageName)
141                .withOutputFileName(outputFileName);
142
143        var message = "Generating sources for JSON schema from %s in the directory: %s";
144        var relativePath = relativize(outputDirPath);
145        if (inputURL != null) {
146            builder.withJsonUrl(inputURL);
147            message = message.formatted("URL [" + inputURL + "]", relativePath);
148        } else if (inputFile != null) {
149            builder.withJsonFile(inputFile);
150            message = message.formatted("file [" + relativize(inputFile.toPath()) + "]", relativePath);
151        } else if (inputDirectory != null) {
152            builder.withInputFolder(inputDirectory.toPath());
153            message = message.formatted("directory [" + relativize(inputDirectory.toPath()) + "]", relativePath);
154        } else {
155            var msg = new StringBuilder("In the generate-jsonschema goal, one of the following parameters needs to be specified:")
156                    .append(System.lineSeparator())
157                    .append("%s.input-file".formatted(MICRONAUT_SCHEMA_PREFIX))
158                    .append(System.lineSeparator())
159                    .append("%s.input-url".formatted(MICRONAUT_SCHEMA_PREFIX))
160                    .append(System.lineSeparator())
161                    .append("%s.input-directory".formatted(MICRONAUT_SCHEMA_PREFIX))
162                    .append(System.lineSeparator());
163            throw new MojoFailureException(msg.toString());
164        }
165
166        try {
167            getLog().info(message);
168            langGenerator.generate(builder.build());
169        } catch (Exception e) {
170            throw new MojoExecutionException("Error when generating JSON schema", e);
171        }
172    }
173
174    private String relativize(Path path) {
175        return project.getBasedir().toPath().relativize(path).toString();
176    }
177
178    private Path getSourceDirectory(String language) {
179        return outputDirectory.toPath().resolve("src/main/" + language.toLowerCase());
180    }
181}