001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.maven.plugin; 007 008import gov.nist.secauto.metaschema.core.model.IConstraintLoader; 009import gov.nist.secauto.metaschema.core.model.IModule; 010import gov.nist.secauto.metaschema.core.model.IModuleLoader; 011import gov.nist.secauto.metaschema.core.model.IResourceLocation; 012import gov.nist.secauto.metaschema.core.model.MetaschemaException; 013import gov.nist.secauto.metaschema.core.model.constraint.ConstraintValidationException; 014import gov.nist.secauto.metaschema.core.model.constraint.ConstraintValidationFinding; 015import gov.nist.secauto.metaschema.core.model.constraint.ExternalConstraintsModulePostProcessor; 016import gov.nist.secauto.metaschema.core.model.constraint.IConstraintSet; 017import gov.nist.secauto.metaschema.core.model.validation.AbstractValidationResultProcessor; 018import gov.nist.secauto.metaschema.core.model.validation.IValidationFinding; 019import gov.nist.secauto.metaschema.core.model.validation.IValidationResult; 020import gov.nist.secauto.metaschema.core.model.validation.JsonSchemaContentValidator.JsonValidationFinding; 021import gov.nist.secauto.metaschema.core.model.validation.XmlSchemaContentValidator.XmlValidationFinding; 022import gov.nist.secauto.metaschema.core.util.CollectionUtil; 023import gov.nist.secauto.metaschema.core.util.ObjectUtils; 024import gov.nist.secauto.metaschema.databind.DefaultBindingContext; 025import gov.nist.secauto.metaschema.databind.IBindingContext; 026import gov.nist.secauto.metaschema.databind.PostProcessingModuleLoaderStrategy; 027import gov.nist.secauto.metaschema.databind.SimpleModuleLoaderStrategy; 028import gov.nist.secauto.metaschema.databind.codegen.IGeneratedClass; 029import gov.nist.secauto.metaschema.databind.codegen.IGeneratedModuleClass; 030import gov.nist.secauto.metaschema.databind.codegen.IModuleBindingGenerator; 031import gov.nist.secauto.metaschema.databind.codegen.IProduction; 032import gov.nist.secauto.metaschema.databind.codegen.JavaCompilerSupport; 033import gov.nist.secauto.metaschema.databind.codegen.JavaGenerator; 034import gov.nist.secauto.metaschema.databind.codegen.ModuleCompilerHelper; 035import gov.nist.secauto.metaschema.databind.codegen.config.DefaultBindingConfiguration; 036import gov.nist.secauto.metaschema.databind.codegen.config.IBindingConfiguration; 037import gov.nist.secauto.metaschema.databind.model.IBoundModule; 038import gov.nist.secauto.metaschema.databind.model.metaschema.BindingModuleLoader; 039import gov.nist.secauto.metaschema.databind.model.metaschema.IBindingMetaschemaModule; 040import gov.nist.secauto.metaschema.databind.model.metaschema.IBindingModuleLoader; 041import gov.nist.secauto.metaschema.databind.model.metaschema.binding.MetaschemaModelModule; 042 043import org.apache.maven.artifact.Artifact; 044import org.apache.maven.artifact.DependencyResolutionRequiredException; 045import org.apache.maven.plugin.AbstractMojo; 046import org.apache.maven.plugin.MojoExecution; 047import org.apache.maven.plugin.MojoExecutionException; 048import org.apache.maven.plugin.logging.Log; 049import org.apache.maven.plugins.annotations.Parameter; 050 051import javax.inject.Inject; 052import org.apache.maven.project.MavenProject; 053import org.codehaus.plexus.util.DirectoryScanner; 054import org.sonatype.plexus.build.incremental.BuildContext; 055import org.xml.sax.SAXParseException; 056 057import java.io.File; 058import java.io.IOException; 059import java.io.OutputStream; 060import java.net.URI; 061import java.nio.charset.Charset; 062import java.nio.file.Files; 063import java.nio.file.Path; 064import java.nio.file.Paths; 065import java.nio.file.StandardOpenOption; 066import java.util.ArrayList; 067import java.util.Collection; 068import java.util.HashSet; 069import java.util.LinkedHashSet; 070import java.util.List; 071import java.util.Objects; 072import java.util.Set; 073import java.util.function.Function; 074import java.util.stream.Collectors; 075import java.util.stream.Stream; 076 077import javax.tools.DiagnosticCollector; 078 079import edu.umd.cs.findbugs.annotations.NonNull; 080import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 081 082public abstract class AbstractMetaschemaMojo 083 extends AbstractMojo { 084 private static final String[] DEFAULT_INCLUDES = { "**/*.xml" }; 085 086 /** 087 * The Maven project context. 088 * 089 * @required 090 * @readonly 091 */ 092 @Parameter(defaultValue = "${project}", required = true, readonly = true) 093 MavenProject mavenProject; 094 095 /** 096 * This will be injected if this plugin is executed as part of the standard 097 * Maven lifecycle. If the mojo is directly invoked, this parameter will not be 098 * injected. 099 */ 100 @Parameter(defaultValue = "${mojoExecution}", readonly = true) 101 private MojoExecution mojoExecution; 102 103 @Inject 104 private BuildContext buildContext; 105 106 @Parameter(defaultValue = "${plugin.artifacts}", readonly = true, required = true) 107 private List<Artifact> pluginArtifacts; 108 109 /** 110 * <p> 111 * The directory where the staleFile is found. The staleFile is used to 112 * determine if re-generation of generated Java classes is needed, by recording 113 * when the last build occurred. 114 * </p> 115 * <p> 116 * This directory is expected to be located within the 117 * <code>${project.build.directory}</code>, to ensure that code (re)generation 118 * occurs after cleaning the project. 119 * </p> 120 */ 121 @Parameter(defaultValue = "${project.build.directory}/metaschema", readonly = true, required = true) 122 protected File staleFileDirectory; 123 124 /** 125 * <p> 126 * Defines the encoding used for generating Java Source files. 127 * </p> 128 * <p> 129 * The algorithm for finding the encoding to use is as follows (where the first 130 * non-null value found is used for encoding): 131 * <ol> 132 * <li>If the configuration property is explicitly given within the plugin's 133 * configuration, use that value. 134 * <li>If the Maven property <code>project.build.sourceEncoding</code> is 135 * defined, use its value. 136 * <li>Otherwise use the value from the system property 137 * <code>file.encoding</code>. 138 * </ol> 139 * </p> 140 * 141 * @see #getEncoding() 142 * @since 2.0 143 */ 144 @Parameter(defaultValue = "${project.build.sourceEncoding}") 145 private String encoding; 146 147 /** 148 * Location to generate Java source files in. 149 */ 150 @Parameter( 151 defaultValue = "${project.build.directory}/generated-sources/metaschema", 152 required = true, 153 property = "outputDirectory") 154 private File outputDirectory; 155 156 /** 157 * The directory to read source metaschema from. 158 */ 159 @Parameter(defaultValue = "${basedir}/src/main/metaschema") 160 private File metaschemaDir; 161 162 /** 163 * A list of <code>files</code> containing Metaschema module constraints files. 164 */ 165 @Parameter(property = "constraints") 166 private File[] constraints; 167 168 /** 169 * A set of inclusion patterns used to select which Metaschema modules are to be 170 * processed. By default, all files are processed. 171 */ 172 @Parameter 173 protected String[] includes; 174 175 /** 176 * A set of exclusion patterns used to prevent certain files from being 177 * processed. By default, this set is empty such that no files are excluded. 178 */ 179 @Parameter 180 protected String[] excludes; 181 182 /** 183 * Indicate if the execution should be skipped. 184 */ 185 @Parameter(property = "metaschema.skip", defaultValue = "false") 186 private boolean skip; 187 188 /** 189 * The BuildContext is used to identify which files or directories were modified 190 * since last build. This is used to determine if Module-based generation must 191 * be performed again. 192 * 193 * @return the active Plexus BuildContext. 194 */ 195 protected final BuildContext getBuildContext() { 196 return buildContext; 197 } 198 199 /** 200 * Retrieve the Maven project context. 201 * 202 * @return The active MavenProject. 203 */ 204 protected final MavenProject getMavenProject() { 205 return mavenProject; 206 } 207 208 protected final List<Artifact> getPluginArtifacts() { 209 return pluginArtifacts; 210 } 211 212 /** 213 * Retrieve the mojo execution context. 214 * 215 * @return The active MojoExecution. 216 */ 217 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "this is a data holder") 218 public MojoExecution getMojoExecution() { 219 return mojoExecution; 220 } 221 222 /** 223 * Retrieve the directory where generated classes will be stored. 224 * 225 * @return the directory 226 */ 227 protected File getOutputDirectory() { 228 return outputDirectory; 229 } 230 231 /** 232 * Set the directory where generated classes will be stored. 233 * 234 * @param outputDirectory 235 * the directory to use 236 */ 237 protected void setOutputDirectory(File outputDirectory) { 238 Objects.requireNonNull(outputDirectory, "outputDirectory"); 239 this.outputDirectory = outputDirectory; 240 } 241 242 /** 243 * Gets the file encoding to use for generated classes. 244 * <p> 245 * The algorithm for finding the encoding to use is as follows (where the first 246 * non-null value found is used for encoding): 247 * </p> 248 * <ol> 249 * <li>If the configuration property is explicitly given within the plugin's 250 * configuration, use that value. 251 * <li>If the Maven property <code>project.build.sourceEncoding</code> is 252 * defined, use its value. 253 * <li>Otherwise use the value from the system property 254 * <code>file.encoding</code>. 255 * </ol> 256 * 257 * @return The encoding to be used by this AbstractJaxbMojo and its tools. 258 */ 259 protected final String getEncoding() { 260 String encoding; 261 if (this.encoding != null) { 262 // first try to use the provided encoding 263 encoding = this.encoding; 264 if (getLog().isDebugEnabled()) { 265 getLog().debug(String.format("Using configured encoding [%s].", encoding)); 266 } 267 } else { 268 encoding = Charset.defaultCharset().displayName(); 269 if (getLog().isWarnEnabled()) { 270 getLog().warn(String.format("Using system encoding [%s]. This build is platform dependent!", encoding)); 271 } 272 } 273 return encoding; 274 } 275 276 /** 277 * Retrieve a stream of Module file sources. 278 * 279 * @return the stream 280 */ 281 protected Stream<File> getModuleSources() { 282 DirectoryScanner ds = new DirectoryScanner(); 283 ds.setBasedir(metaschemaDir); 284 ds.setIncludes(includes != null && includes.length > 0 ? includes : DEFAULT_INCLUDES); 285 ds.setExcludes(excludes != null && excludes.length > 0 ? excludes : null); 286 ds.addDefaultExcludes(); 287 ds.setCaseSensitive(true); 288 ds.setFollowSymlinks(false); 289 ds.scan(); 290 return Stream.of(ds.getIncludedFiles()).map(filename -> new File(metaschemaDir, filename)).distinct(); 291 } 292 293 @NonNull 294 protected IBindingContext newBindingContext( 295 @NonNull IModuleLoader.IModulePostProcessor modulePostProcessor) throws IOException, MetaschemaException { 296 // generate Java sources based on provided metaschema sources 297 return new DefaultBindingContext( 298 new PostProcessingModuleLoaderStrategy( 299 // ensure that the external constraints do not apply to the built in module 300 CollectionUtil.singletonList(modulePostProcessor), 301 new SimpleModuleLoaderStrategy( 302 // this is used instead of the default generator to ensure that plugin classpath 303 // entries are used for compilation 304 new ModuleBindingGenerator( 305 ObjectUtils.notNull(Files.createDirectories(Paths.get("target/metaschema-codegen-modules"))), 306 new DefaultBindingConfiguration())))); 307 } 308 309 /** 310 * Get the configured collection of constraints. 311 * 312 * @return the loaded constraints 313 * @throws MojoExecutionException 314 * if an error occurred while loading the constraints 315 */ 316 @NonNull 317 protected List<IConstraintSet> getConstraints() throws MojoExecutionException { 318 IConstraintLoader loader = IBindingContext.getConstraintLoader(); 319 List<IConstraintSet> constraintSets = new ArrayList<>(constraints.length); 320 for (File constraint : this.constraints) { 321 try { 322 constraintSets.addAll(loader.load(ObjectUtils.notNull(constraint))); 323 } catch (IOException | MetaschemaException ex) { 324 throw new MojoExecutionException("Loading of external constraints failed", ex); 325 } 326 } 327 return CollectionUtil.unmodifiableList(constraintSets); 328 } 329 330 /** 331 * Determine if the execution of this mojo should be skipped. 332 * 333 * @return {@code true} if the mojo execution should be skipped, or 334 * {@code false} otherwise 335 */ 336 protected boolean shouldExecutionBeSkipped() { 337 return skip; 338 } 339 340 /** 341 * Get the name of the file that is used to detect staleness. 342 * 343 * @return the name 344 */ 345 protected abstract String getStaleFileName(); 346 347 /** 348 * Gets the staleFile for this execution. 349 * 350 * @return the staleFile 351 */ 352 protected final File getStaleFile() { 353 StringBuilder builder = new StringBuilder(); 354 if (getMojoExecution() != null) { 355 builder.append(getMojoExecution().getExecutionId()).append('-'); 356 } 357 builder.append(getStaleFileName()); 358 return new File(staleFileDirectory, builder.toString()); 359 } 360 361 /** 362 * Determine if code generation is required. This is done by comparing the last 363 * modified time of each Module source file against the stale file managed by 364 * this plugin. 365 * 366 * @return {@code true} if the code generation is needed, or {@code false} 367 * otherwise 368 */ 369 protected boolean isGenerationRequired() { 370 final File staleFile = getStaleFile(); 371 boolean generate = !staleFile.exists(); 372 if (generate) { 373 if (getLog().isInfoEnabled()) { 374 getLog().info(String.format("Stale file '%s' doesn't exist! Generating source files.", staleFile.getPath())); 375 } 376 generate = true; 377 } else { 378 generate = false; 379 // check for staleness 380 long staleLastModified = staleFile.lastModified(); 381 382 BuildContext buildContext = getBuildContext(); 383 URI metaschemaDirRelative = getMavenProject().getBasedir().toURI().relativize(metaschemaDir.toURI()); 384 385 if (buildContext.isIncremental() && buildContext.hasDelta(metaschemaDirRelative.toString())) { 386 if (getLog().isInfoEnabled()) { 387 getLog().info("metaschemaDirRelative: " + metaschemaDirRelative.toString()); 388 } 389 generate = true; 390 } 391 392 if (!generate) { 393 for (File sourceFile : getModuleSources().collect(Collectors.toList())) { 394 if (getLog().isInfoEnabled()) { 395 getLog().info("Source file: " + sourceFile.getPath()); 396 } 397 if (sourceFile.lastModified() > staleLastModified) { 398 generate = true; 399 } 400 } 401 } 402 } 403 return generate; 404 } 405 406 protected Set<String> getClassPath() throws DependencyResolutionRequiredException { 407 Set<String> pathElements; 408 try { 409 pathElements = new LinkedHashSet<>(getMavenProject().getCompileClasspathElements()); 410 } catch (DependencyResolutionRequiredException ex) { 411 getLog().warn("exception calling getCompileClasspathElements", ex); 412 throw ex; 413 } 414 415 if (pluginArtifacts != null) { 416 for (Artifact a : getPluginArtifacts()) { 417 if (a.getFile() != null) { 418 pathElements.add(a.getFile().getAbsolutePath()); 419 } 420 } 421 } 422 return pathElements; 423 } 424 425 @NonNull 426 protected Set<IModule> getModulesToGenerateFor( 427 @NonNull IBindingContext bindingContext, 428 @NonNull IModuleLoader.IModulePostProcessor modulePostProcessor) 429 throws MetaschemaException, IOException, ConstraintValidationException { 430 431 // Don't use the normal loader, since it attempts to register and compile the 432 // module. 433 // We only care about the module content for generating sources and schemas 434 IBindingModuleLoader loader = new BindingModuleLoader(bindingContext, (module, ctx) -> { 435 modulePostProcessor.processModule(module); 436 }); 437 loader.allowEntityResolution(); 438 439 LoggingValidationHandler validationHandler = new LoggingValidationHandler(); 440 441 Set<IModule> modules = new HashSet<>(); 442 for (File source : getModuleSources().collect(Collectors.toList())) { 443 assert source != null; 444 if (getLog().isInfoEnabled()) { 445 getLog().info("Using metaschema source: " + source.getPath()); 446 } 447 IBindingMetaschemaModule module = loader.load(source); 448 449 IValidationResult result = bindingContext.validate( 450 module.getSourceNodeItem(), 451 loader.getBindingContext().newBoundLoader(), 452 null); 453 454 validationHandler.handleResults(result); 455 456 modules.add(module); 457 } 458 return modules; 459 } 460 461 protected void createStaleFile(@NonNull File staleFile) throws MojoExecutionException { 462 // create the stale file 463 if (!staleFileDirectory.exists() && !staleFileDirectory.mkdirs()) { 464 throw new MojoExecutionException("Unable to create output directory: " + staleFileDirectory); 465 } 466 try (OutputStream os 467 = Files.newOutputStream(staleFile.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE, 468 StandardOpenOption.TRUNCATE_EXISTING)) { 469 os.close(); 470 if (getLog().isInfoEnabled()) { 471 getLog().info("Created stale file: " + staleFile); 472 } 473 } catch (IOException ex) { 474 throw new MojoExecutionException("Failed to write stale file: " + staleFile.getPath(), ex); 475 } 476 } 477 478 @SuppressWarnings("PMD.AvoidCatchingGenericException") 479 @Override 480 public void execute() throws MojoExecutionException { 481 File staleFile = getStaleFile(); 482 try { 483 staleFile = ObjectUtils.notNull(staleFile.getCanonicalFile()); 484 } catch (IOException ex) { 485 if (getLog().isWarnEnabled()) { 486 getLog().warn("Unable to resolve canonical path to stale file. Treating it as not existing.", ex); 487 } 488 } 489 490 boolean generate; 491 if (shouldExecutionBeSkipped()) { 492 if (getLog().isDebugEnabled()) { 493 getLog().debug(String.format("Generation is configured to be skipped. Skipping.")); 494 } 495 generate = false; 496 } else if (staleFile.exists()) { 497 generate = isGenerationRequired(); 498 } else { 499 if (getLog().isInfoEnabled()) { 500 getLog().info(String.format("Stale file '%s' doesn't exist! Generation is required.", staleFile.getPath())); 501 } 502 generate = true; 503 } 504 505 if (generate) { 506 List<IConstraintSet> constraints = getConstraints(); 507 IModuleLoader.IModulePostProcessor modulePostProcessor 508 = new LimitedExternalConstraintsModulePostProcessor(constraints); 509 510 List<File> generatedFiles; 511 try { 512 generatedFiles = performGeneration(modulePostProcessor); 513 } finally { 514 // ensure the stale file is created to ensure that regeneration is only 515 // performed when a 516 // change is made 517 createStaleFile(staleFile); 518 } 519 520 if (getLog().isInfoEnabled()) { 521 getLog().info(String.format("Generated %d files.", generatedFiles.size())); 522 } 523 524 // for m2e 525 for (File file : generatedFiles) { 526 getBuildContext().refresh(file); 527 } 528 } 529 } 530 531 @SuppressWarnings({ "PMD.AvoidCatchingGenericException", "PMD.ExceptionAsFlowControl" }) 532 @NonNull 533 private List<File> performGeneration( 534 @NonNull IModuleLoader.IModulePostProcessor modulePostProcessor) throws MojoExecutionException { 535 File outputDir = getOutputDirectory(); 536 if (getLog().isDebugEnabled()) { 537 getLog().debug(String.format("Using outputDirectory: %s", outputDir.getPath())); 538 } 539 540 if (!outputDir.exists() && !outputDir.mkdirs()) { 541 throw new MojoExecutionException("Unable to create output directory: " + outputDir); 542 } 543 544 IBindingContext bindingContext; 545 try { 546 bindingContext = newBindingContext(modulePostProcessor); 547 } catch (MetaschemaException | IOException ex) { 548 throw new MojoExecutionException("Failed to create the binding context", ex); 549 } 550 551 // generate Java sources based on provided metaschema sources 552 Set<IModule> modules; 553 try { 554 modules = getModulesToGenerateFor(bindingContext, modulePostProcessor); 555 } catch (Exception ex) { 556 throw new MojoExecutionException("Loading of metaschema modules failed", ex); 557 } 558 559 return generate(modules); 560 } 561 562 /** 563 * Perform the generation operation. 564 * 565 * @param modules 566 * the modules to generate resources/sources for 567 * 568 * @return the files generated during the operation 569 * @throws MojoExecutionException 570 * if an error occurred while performing the generation operation 571 */ 572 @NonNull 573 protected abstract List<File> generate(@NonNull Set<IModule> modules) throws MojoExecutionException; 574 575 protected final class LoggingValidationHandler 576 extends AbstractValidationResultProcessor { 577 578 private <T extends IValidationFinding> void handleFinding( 579 @NonNull T finding, 580 @NonNull Function<T, CharSequence> formatter) { 581 582 Log log = getLog(); 583 584 switch (finding.getSeverity()) { 585 case CRITICAL: 586 case ERROR: 587 if (log.isErrorEnabled()) { 588 log.error(formatter.apply(finding), finding.getCause()); 589 } 590 break; 591 case WARNING: 592 if (log.isWarnEnabled()) { 593 getLog().warn(formatter.apply(finding), finding.getCause()); 594 } 595 break; 596 case INFORMATIONAL: 597 if (log.isInfoEnabled()) { 598 getLog().info(formatter.apply(finding), finding.getCause()); 599 } 600 break; 601 default: 602 if (log.isDebugEnabled()) { 603 getLog().debug(formatter.apply(finding), finding.getCause()); 604 } 605 break; 606 } 607 } 608 609 @Override 610 protected void handleJsonValidationFinding(JsonValidationFinding finding) { 611 handleFinding(finding, this::getMessage); 612 } 613 614 @Override 615 protected void handleXmlValidationFinding(XmlValidationFinding finding) { 616 handleFinding(finding, this::getMessage); 617 } 618 619 @Override 620 protected void handleConstraintValidationFinding(ConstraintValidationFinding finding) { 621 handleFinding(finding, this::getMessage); 622 } 623 624 @NonNull 625 private CharSequence getMessage(JsonValidationFinding finding) { 626 StringBuilder builder = new StringBuilder(); 627 builder.append('[') 628 .append(finding.getCause().getPointerToViolation()) 629 .append("] ") 630 .append(finding.getMessage()); 631 632 URI documentUri = finding.getDocumentUri(); 633 if (documentUri != null) { 634 builder.append(" [") 635 .append(documentUri.toString()) 636 .append(']'); 637 } 638 return builder; 639 } 640 641 @NonNull 642 private CharSequence getMessage(XmlValidationFinding finding) { 643 StringBuilder builder = new StringBuilder(); 644 645 builder.append(finding.getMessage()) 646 .append(" ["); 647 648 URI documentUri = finding.getDocumentUri(); 649 if (documentUri != null) { 650 builder.append(documentUri.toString()); 651 } 652 653 SAXParseException ex = finding.getCause(); 654 builder.append(finding.getMessage()) 655 .append('{') 656 .append(ex.getLineNumber()) 657 .append(',') 658 .append(ex.getColumnNumber()) 659 .append("}]"); 660 return builder; 661 } 662 663 @NonNull 664 private CharSequence getMessage(@NonNull ConstraintValidationFinding finding) { 665 StringBuilder builder = new StringBuilder(); 666 builder.append('[') 667 .append(finding.getTarget().getMetapath()) 668 .append(']'); 669 670 String id = finding.getIdentifier(); 671 if (id != null) { 672 builder.append(' ') 673 .append(id); 674 } 675 676 builder.append(' ') 677 .append(finding.getMessage()); 678 679 URI documentUri = finding.getTarget().getBaseUri(); 680 IResourceLocation location = finding.getLocation(); 681 if (documentUri != null || location != null) { 682 builder.append(" ["); 683 } 684 685 if (documentUri != null) { 686 builder.append(documentUri.toString()); 687 } 688 689 if (location != null) { 690 builder.append('{') 691 .append(location.getLine()) 692 .append(',') 693 .append(location.getColumn()) 694 .append('}'); 695 } 696 if (documentUri != null || location != null) { 697 builder.append(']'); 698 } 699 return builder; 700 } 701 } 702 703 public class ModuleBindingGenerator implements IModuleBindingGenerator { 704 @NonNull 705 private final Path compilePath; 706 @NonNull 707 private final ClassLoader classLoader; 708 @NonNull 709 private final IBindingConfiguration bindingConfiguration; 710 711 public ModuleBindingGenerator( 712 @NonNull Path compilePath, 713 @NonNull IBindingConfiguration bindingConfiguration) { 714 this.compilePath = compilePath; 715 this.classLoader = ModuleCompilerHelper.newClassLoader( 716 compilePath, 717 ObjectUtils.notNull(Thread.currentThread().getContextClassLoader())); 718 this.bindingConfiguration = bindingConfiguration; 719 } 720 721 @NonNull 722 public IProduction generateClasses(@NonNull IModule module) throws MetaschemaException { 723 IProduction production; 724 try { 725 production = JavaGenerator.generate(module, compilePath, bindingConfiguration); 726 } catch (IOException ex) { 727 throw new MetaschemaException( 728 String.format("Unable to generate and compile classes for module '%s'.", module.getLocation()), 729 ex); 730 } 731 return production; 732 } 733 734 private void compileClasses(@NonNull IProduction production, @NonNull Path classDir) 735 throws IOException, DependencyResolutionRequiredException { 736 List<IGeneratedClass> classesToCompile = production.getGeneratedClasses().collect(Collectors.toList()); 737 738 List<Path> classes = ObjectUtils.notNull(classesToCompile.stream() 739 .map(IGeneratedClass::getClassFile) 740 .collect(Collectors.toUnmodifiableList())); 741 742 JavaCompilerSupport compiler = new JavaCompilerSupport(classDir); 743 compiler.setLogger(new JavaCompilerSupport.Logger() { 744 745 @Override 746 public boolean isDebugEnabled() { 747 return getLog().isDebugEnabled(); 748 } 749 750 @Override 751 public boolean isInfoEnabled() { 752 return getLog().isInfoEnabled(); 753 } 754 755 @Override 756 public void debug(String msg) { 757 getLog().debug(msg); 758 } 759 760 @Override 761 public void info(String msg) { 762 getLog().info(msg); 763 } 764 }); 765 766 getClassPath().forEach(compiler::addToClassPath); 767 768 JavaCompilerSupport.CompilationResult result = compiler.compile(classes); 769 770 if (!result.isSuccessful()) { 771 DiagnosticCollector<?> diagnostics = new DiagnosticCollector<>(); 772 if (getLog().isErrorEnabled()) { 773 getLog().error("diagnostics: " + diagnostics.getDiagnostics().toString()); 774 } 775 throw new IllegalStateException(String.format("failed to compile classes: %s", 776 classesToCompile.stream() 777 .map(clazz -> clazz.getClassName().canonicalName()) 778 .collect(Collectors.joining(",")))); 779 } 780 } 781 782 @Override 783 public Class<? extends IBoundModule> generate(IModule module) throws MetaschemaException { 784 IProduction production = generateClasses(module); 785 try { 786 compileClasses(production, compilePath); 787 } catch (IOException | DependencyResolutionRequiredException ex) { 788 throw new IllegalStateException("failed to compile classes", ex); 789 } 790 IGeneratedModuleClass moduleClass = ObjectUtils.requireNonNull(production.getModuleProduction(module)); 791 792 try { 793 return moduleClass.load(classLoader); 794 } catch (ClassNotFoundException ex) { 795 throw new IllegalStateException(ex); 796 } 797 } 798 } 799 800 private static class LimitedExternalConstraintsModulePostProcessor 801 extends ExternalConstraintsModulePostProcessor { 802 803 public LimitedExternalConstraintsModulePostProcessor( 804 @NonNull Collection<IConstraintSet> additionalConstraintSets) { 805 super(additionalConstraintSets); 806 } 807 808 /** 809 * This method ensures that constraints are not applied to the built-in 810 * Metaschema module module twice, when this module is selected as the source 811 * for generation. 812 */ 813 @Override 814 public void processModule(IModule module) { 815 if (!(module instanceof MetaschemaModelModule)) { 816 super.processModule(module); 817 } 818 } 819 } 820}