Skip to content

Commit

Permalink
SDK-269: Added ValidateConfigurationsMojo. (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
mks-d authored Apr 24, 2023
1 parent f1e9310 commit 409aebf
Show file tree
Hide file tree
Showing 4 changed files with 319 additions and 4 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ The top-level POM serves two purposes:
<goal>package-configurations</goal>
</goals>
</execution>
<execution>
<id>validate-configurations</id>
<phase>validate</phase>
<goals>
<goal>validate-configurations</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package org.openmrs.maven.plugins.packager.config;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.powermock.api.mockito.PowerMockito.doThrow;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.maven.plugin.MojoExecutionException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.Result;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.openmrs.module.initializer.validator.Validator;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(Validator.class)
public class ValidateConfigurationsTest {

private File logDir = Mockito.mock(File.class);

private class TestMojo extends ValidateConfigurationsMojo {
@Override
protected File getSourceDir() {
return new File(getClass().getClassLoader().getResource("config-test-parent/configuration").getPath());
}
@Override
protected File getBuildDir() {
return logDir;
}
}

private ValidateConfigurationsMojo mojo = new TestMojo();

private Result result;

@Before
public void before() {
PowerMockito.mockStatic(Validator.class);
}

@Rule
public ExpectedException exceptionRule = ExpectedException.none();

@Test(expected = Test.None.class)
public void execute_successfulJUnitResultShouldNotThrowMojoExecutionException() throws Exception {
// setup
result = new Result();
when(Validator.getJUnitResult(any(String[].class))).thenReturn(result);

// replay
mojo.execute();
verify(logDir, times(1)).getAbsolutePath();
}

@Test
public void execute_failedJUnitResultShouldThrowMojoExecutionException() throws Exception {
// setup
result = new Result() {
@Override
public boolean wasSuccessful() {
return false;
}
};
when(Validator.getJUnitResult(any(String[].class))).thenReturn(result);

// replay
exceptionRule.expect(MojoExecutionException.class);
mojo.execute();
}

@Test
public void execute_throwingValidatorShouldThrowMojoExecutionException() throws Exception {
// setup
doThrow(new RuntimeException()).when(Validator.class);
Validator.getJUnitResult(any(String[].class));

// replay
exceptionRule.expect(MojoExecutionException.class);
mojo.execute();
}

@Test
public void addValidatorCliOptions_shouldParseExtraValidatorArgs() throws MojoExecutionException {
// setup
String extraArgs = "--opt1 --opt2='foo bar' --opt3=bar --unsafe --ciel-file='/path/to/ciel.sql' --config-dir='/path/to/config_dir'";
List<String> args = new ArrayList<>();

// replay
new ValidateConfigurationsMojo().addValidatorCliArguments(extraArgs, args);

// verify
assertThat(args.size(), is(3));
assertThat(args, containsInAnyOrder("--opt1", "--opt2='foo bar'", "--opt3=bar"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.maven.plugins.packager.config;

import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.openmrs.module.initializer.validator.Validator.ARG_CIEL_FILE;
import static org.openmrs.module.initializer.validator.Validator.ARG_CONFIG_DIR;
import static org.openmrs.module.initializer.validator.Validator.ARG_UNSAFE;
import static org.openmrs.module.initializer.validator.Validator.ARG_LOG_DIR;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import javax.persistence.Cache;
import javax.persistence.EntityGraph;
import javax.persistence.PersistenceException;
import javax.persistence.PersistenceUnitUtil;
import javax.persistence.Query;
import javax.persistence.TransactionRequiredException;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.metamodel.Metamodel;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.junit.runner.Result;
import org.openmrs.module.initializer.validator.Validator;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
import org.springframework.orm.jpa.JpaOptimisticLockingFailureException;
import org.springframework.orm.jpa.JpaSystemException;

/**
* The purpose of this Mojo is to validate configurations.
*/
@Mojo( name = "validate-configurations" )
public class ValidateConfigurationsMojo extends AbstractPackagerConfigMojo {

// Configuration Directory
@Parameter(property = "sourceDir", defaultValue = "configuration")
private File sourceDir;

@Parameter(property = "cielFile")
private File cielFile;

// Extra Validator CLI options
@Parameter(property = "extraValidatorArgs")
private String extraValidatorArgs;

protected File getSourceDir() {
return sourceDir;
}

/*
* To avoid NoClassDefFoundError on EntityManagerFactoryUtils and related classes.
*/
private void findClassDefinitions() {
EntityManagerFactoryUtils.class.toString();
PersistenceException.class.toString();
TransactionRequiredException.class.toString();
JpaObjectRetrievalFailureException.class.toString();
EmptyResultDataAccessException.class.toString();
JpaOptimisticLockingFailureException.class.toString();
JpaSystemException.class.toString();
Cache.class.toString();
CriteriaBuilder.class.toString();
Metamodel.class.toString();
PersistenceUnitUtil.class.toString();
Query.class.toString();
EntityGraph.class.toString();
}

/**
* @throws MojoExecutionException if the Maven build should be errored.
*/
public void execute() throws MojoExecutionException {

findClassDefinitions(); // TODO: figure out why this is needed

List<String> args = new ArrayList<>();
if (getSourceDir() == null || !getSourceDir().isDirectory()) {
throw new MojoExecutionException(getSourceDir().getAbsolutePath() + " does not point to a valid directory.");
}
args.add("--" + ARG_CONFIG_DIR + "=" + getSourceDir().getAbsolutePath());

// The build directory to be the default log directory.
if (extraValidatorArgs == null || !extraValidatorArgs.contains(ARG_LOG_DIR)) {
args.add("--" + ARG_LOG_DIR + "=" + getBuildDir().getAbsolutePath());
}

if (cielFile != null) {
args.add("--" + ARG_CIEL_FILE + "=" + cielFile.getAbsolutePath());
}

if (!isEmpty(extraValidatorArgs)) {
addValidatorCliArguments(extraValidatorArgs, args);

}

Result result;
try {
args.add("--" + ARG_UNSAFE);
result = Validator.getJUnitResult(args.toArray(new String[0]));
}
catch (Exception e) {
throw new MojoExecutionException(e.getMessage(), e);
}

if (!result.wasSuccessful()) {
throw new MojoExecutionException("The configuration could not be validated, scroll up the Maven build logs for details.");
}

}

/**
* Parses a one liner string of Validators arguments into a list of arguments supported by the plugin.
*
* @param opts The string Validators args/options, eg. "--domains='concepts,locations' --exclude.concepts='*diags*,*interventions*'"
* @param args (ouput) The list of Validator args.
*/
protected void addValidatorCliArguments(String opts, List<String> args) {
Stream.of(opts.split("--")).map(o -> o.trim()).filter(o -> !isEmpty(o)).filter(o -> includeOption(o)).forEach(o -> {
args.add("--" + o);
});
}

protected boolean includeOption(String opt) {
if (opt.startsWith(ARG_CONFIG_DIR)) {
getLog().warn("--" + ARG_CONFIG_DIR + " cannot be provided as an extra Validator argument, use <sourceDir/> instead in the plugin configuration.");
return false;
}
if (opt.startsWith(ARG_CIEL_FILE)) {
getLog().warn("--" + ARG_CIEL_FILE + " cannot be provided as an extra Validator argument, use <cielFile/> instead in the plugin configuration.");
return false;
}
if (opt.startsWith(ARG_UNSAFE)) {
getLog().info("--" + ARG_UNSAFE + " is redundant since the plugin always validates configurations in unsafe mode.");
return false;
}
return true;
}

}
54 changes: 50 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.2.1</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<type>jar</type>
<version>31.0.1-jre</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
Expand All @@ -56,6 +68,19 @@
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
Expand All @@ -65,9 +90,9 @@

<!-- Commons utilities -->
<dependency>
<groupId>org.apache.commons</groupId>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
<version>2.5</version>
</dependency>

<!-- For programmatically invoking maven -->
Expand All @@ -83,6 +108,13 @@
<version>2.3.1</version>
</dependency>

<!-- For configs validation -->
<dependency>
<groupId>org.openmrs.module</groupId>
<artifactId>initializer-validator</artifactId>
<version>2.5.1</version>
</dependency>

<!-- For reading YAML and JSON files -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
Expand Down Expand Up @@ -134,13 +166,27 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>7</source>
<target>7</target>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>

<repositories>
<repository>
<id>mks-nexus-public</id>
<url>https://nexus.mekomsolutions.net/repository/maven-public/</url>
</repository>
<repository>
<id>mks-nexus-snapshots</id>
<url>https://nexus.mekomsolutions.net/repository/maven-snapshots/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>

<distributionManagement>
<repository>
<id>openmrs-repo-releases</id>
Expand Down

0 comments on commit 409aebf

Please sign in to comment.