View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2015 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.caravan.maven.plugins.haldocs;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.net.URLClassLoader;
25  import java.util.Set;
26  
27  import org.apache.maven.plugin.MojoExecutionException;
28  import org.apache.maven.plugin.MojoFailureException;
29  import org.apache.maven.plugins.annotations.LifecyclePhase;
30  import org.apache.maven.plugins.annotations.Mojo;
31  import org.apache.maven.plugins.annotations.Parameter;
32  import org.codehaus.plexus.util.SelectorUtils;
33  
34  import com.fasterxml.jackson.databind.ObjectMapper;
35  import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
36  import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper;
37  import com.google.common.reflect.ClassPath;
38  
39  /**
40   * Generates JSON schema files for Java POJOs using Jackson 'jackson-module-jsonSchema'.
41   * The generated files are attached as resources and included in the JAR file.
42   */
43  @Mojo(name = "generate-json-schema", defaultPhase = LifecyclePhase.PROCESS_CLASSES, requiresProject = true, threadSafe = true)
44  public class GenerateJsonSchemaMojo extends AbstractBaseMojo {
45  
46    /**
47     * Paths containing the class files to generate JSON schema for.
48     */
49    @Parameter(defaultValue = "${project.build.directory}/classes")
50    private String source;
51  
52    /**
53     * Ant-style include patterns for java package names to include. Use "." as separator.
54     */
55    @Parameter(required = true)
56    private Set<String> includes;
57  
58    /**
59     * Ant-style exclude patterns for java package names to exclude. Use "." as separator.
60     */
61    @Parameter
62    private Set<String> excludes;
63  
64    /**
65     * Relative target path for the generated resources.
66     */
67    @Parameter(defaultValue = "JSON-SCHEMA-INF")
68    private String target;
69  
70    @Parameter(defaultValue = "generated-json-schema-resources")
71    private String generatedResourcesDirectory;
72  
73    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
74  
75    @Override
76    public void execute() throws MojoExecutionException, MojoFailureException {
77      try {
78        File classesDirectory = getSourceDirectory();
79        if (!classesDirectory.exists()) {
80          return;
81        }
82  
83        // build class loader to get classes to generate resources for
84        // set plugin classloader as parent classloader e.g. to support jackson annotations
85        ClassLoader compileClassLoader = URLClassLoader.newInstance(getCompileClasspathElementURLs(), getClass().getClassLoader());
86  
87        // generate schema for all classes that match includes/excludes
88        ClassPath.from(compileClassLoader).getTopLevelClasses().stream()
89        .filter(info -> isIncluded(info.getName()) && !isExcluded(info.getName()))
90        .map(info -> info.load())
91        .forEach(this::generateSchema);
92  
93        // add as resources to classpath
94        addResource(getGeneratedResourcesDirectory().getPath(), target);
95      }
96      catch (Throwable ex) {
97        throw new MojoExecutionException("Generating JSON Schema files failed: " + ex.getMessage(), ex);
98      }
99    }
100 
101   /**
102    * Check is class is included.
103    * @param className Class name
104    * @return true if included
105    */
106   private boolean isIncluded(String className) {
107     if (includes == null || includes.size() == 0) {
108       // generate nothing if no include defined - it makes no sense to generate for all classes from classpath
109       return false;
110     }
111     return includes.stream()
112         .filter(pattern -> SelectorUtils.matchPath(pattern, className, ".", true))
113         .count() > 0;
114   }
115 
116   /**
117    * Check if class is excluded
118    * @param className Class name
119    * @return true if excluded
120    */
121   private boolean isExcluded(String className) {
122     if (excludes == null || excludes.size() == 0) {
123       return false;
124     }
125     return excludes.stream()
126         .filter(pattern -> SelectorUtils.matchPath(pattern, className, ".", true))
127         .count() == 0;
128   }
129 
130   /**
131    * Generate JSON schema for given POJO
132    * @param clazz POJO class
133    */
134   private void generateSchema(Class clazz) {
135     try {
136       File targetFile = new File(getGeneratedResourcesDirectory(), clazz.getName() + ".json");
137       getLog().info("Generate JSON schema: " + targetFile.getName());
138       SchemaFactoryWrapper schemaFactory = new SchemaFactoryWrapper();
139       OBJECT_MAPPER.acceptJsonFormatVisitor(OBJECT_MAPPER.constructType(clazz), schemaFactory);
140       JsonSchema jsonSchema = schemaFactory.finalSchema();
141       OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValue(targetFile, jsonSchema);
142     }
143     catch (IOException ex) {
144       throw new RuntimeException(ex);
145     }
146   }
147 
148   /**
149    * Get directory containing source i18n files.
150    * @return directory containing source i18n files.
151    * @throws IOException
152    */
153   private File getSourceDirectory() throws IOException {
154     File file = new File(source);
155     if (!file.isDirectory()) {
156       getLog().debug("Could not find directory at '" + source + "'");
157     }
158     return file.getCanonicalFile();
159   }
160 
161   @Override
162   protected String getGeneratedResourcesDirectoryPath() {
163     return generatedResourcesDirectory;
164   }
165 
166 }