Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IDUtils class to manage ID generation and validation #473

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
<cql-engine.version>2.9.0</cql-engine.version>
<cql-evaluator.version>2.6.0</cql-evaluator.version>
<cqframework.version>2.9.0</cqframework.version>
<hapi.version>6.0.1</hapi.version>
<core.version>5.6.36</core.version>
<hapi.version>6.2.2</hapi.version>
<core.version>5.6.68</core.version>
<spring-boot.version>2.1.5.RELEASE</spring-boot.version>
</properties>

Expand Down
14 changes: 13 additions & 1 deletion tooling-cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
<groupId>org.opencds.cqf</groupId>
<artifactId>tooling</artifactId>
<version>2.5.0-SNAPSHOT</version>
<!-- <packaging>jar</packaging> -->
</dependency>

<dependency>
Expand All @@ -35,6 +34,19 @@
<artifactId>org.eclipse.persistence.moxy</artifactId>
<version>2.7.7</version>
</dependency>

<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>

<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.7.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
47 changes: 41 additions & 6 deletions tooling-cli/src/main/java/org/opencds/cqf/tooling/cli/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -218,20 +218,55 @@

*/

import org.opencds.cqf.tooling.exception.InvalidOperationArgs;
import org.opencds.cqf.tooling.exception.InvalidOperationInitialization;
import org.opencds.cqf.tooling.exception.OperationNotFound;
import org.opencds.cqf.tooling.operations.ExecutableOperation;
import org.opencds.cqf.tooling.operations.Operation;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);

private static Map<String, Class<?>> operationClassMap;

public static void main(String[] args) {
if (args.length == 0) {
System.err.println("cqf-tooling version: " + Main.class.getPackage().getImplementationVersion());
System.err.println("Requests must include which operation to run as a command line argument. See docs for examples on how to use this project.");
return;
if (args == null || args.length == 0) {
logger.error("cqf-tooling version: {}", Main.class.getPackage().getImplementationVersion());
throw new OperationNotFound(
"Requests must include which operation to run as a command line argument. See docs for examples on how to use this project.");
}

// NOTE: we may want to use the Spring Context Library to find the annotated classes
if (operationClassMap == null) {
operationClassMap = new HashMap<>();
Reflections reflections = new Reflections("org.opencds.cqf.tooling.operations");
Set<Class<?>> operationClasses = reflections
.getTypesAnnotatedWith(Operation.class);
operationClasses.forEach(clazz -> operationClassMap.put(clazz.getAnnotation(Operation.class).name(), clazz));
}

String operation = args[0];
if (!operation.startsWith("-")) {
throw new IllegalArgumentException("Invalid operation: " + operation);
throw new InvalidOperationArgs(
"Invalid operation syntax: " + operation + ". Operations must be declared with a \"-\" prefix");
}

OperationFactory.createOperation(operation.substring(1)).execute(args);
try {
ExecutableOperation executableOperation = OperationFactory.createOperation(
operation, operationClassMap.get(operation.substring(1)), args);
if (executableOperation != null) {
executableOperation.execute();
}
} catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) {
throw new InvalidOperationInitialization(e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package org.opencds.cqf.tooling.cli;

//import org.opencds.cqf.tooling.jsonschema.SchemaGenerator;
import org.opencds.cqf.tooling.casereporting.transformer.ErsdTransformer;
import org.opencds.cqf.tooling.dateroller.DataDateRollerOperation;
import org.opencds.cqf.tooling.exception.InvalidOperationArgs;
import org.opencds.cqf.tooling.exception.OperationNotFound;
import org.opencds.cqf.tooling.operations.ExecutableOperation;
import org.opencds.cqf.tooling.operations.OperationParam;
import org.opencds.cqf.tooling.terminology.templateToValueSetGenerator.TemplateToValueSetGenerator;
import org.apache.commons.lang3.NotImplementedException;
import org.opencds.cqf.tooling.Operation;
Expand Down Expand Up @@ -40,9 +43,92 @@
import org.opencds.cqf.tooling.terminology.VSACBatchValueSetGenerator;
import org.opencds.cqf.tooling.terminology.VSACValueSetGenerator;
import org.opencds.cqf.tooling.terminology.distributable.DistributableValueSetGenerator;
import org.opencds.cqf.tooling.utilities.OperationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;


class OperationFactory {
private static final Logger logger = LoggerFactory.getLogger(OperationFactory.class);
private static String operationName;
private static Map<String, String> paramMap;
private static boolean showHelpMenu = false;

private OperationFactory() {

}

private static void processArgs(String[] args) {
paramMap = new HashMap<>();
for (int i = 1; i < args.length; ++i) {
if (OperationUtils.isHelpArg(args[i])) {
showHelpMenu = true;
return;
}
String[] argAndValue = args[i].split("=", 2);
if (argAndValue.length == 2) {
paramMap.put(argAndValue[0].replace("-", ""), argAndValue[1]);
}
else {
throw new InvalidOperationArgs(String.format(
"Invalid argument: %s found for operation: %s", args[i], operationName));
}
}
}

private static ExecutableOperation initialize(ExecutableOperation operation)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
for (Field field : operation.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(OperationParam.class)) {
boolean isInitialized = false;
for (String alias : field.getAnnotation(OperationParam.class).alias()) {
if (paramMap.containsKey(alias)) {
Class<?> paramType = OperationUtils.getParamType(operation,
field.getAnnotation(OperationParam.class).setter());
operation.getClass().getDeclaredMethod(
field.getAnnotation(OperationParam.class).setter(), paramType
).invoke(operation, OperationUtils.mapParamType(paramMap.get(alias), paramType));
isInitialized = true;
}
}
if (!isInitialized) {
if (field.getAnnotation(OperationParam.class).required()) {
throw new InvalidOperationArgs("Missing required argument: " + field.getName());
}
else if (!field.getAnnotation(OperationParam.class).defaultValue().isEmpty()) {
Class<?> paramType = OperationUtils.getParamType(operation,
field.getAnnotation(OperationParam.class).setter());
operation.getClass().getDeclaredMethod(
field.getAnnotation(OperationParam.class).setter(), paramType
).invoke(operation, OperationUtils.mapParamType(
field.getAnnotation(OperationParam.class).defaultValue(), paramType));
}
}
}
}
return operation;
}

static ExecutableOperation createOperation(String operationName, Class<?> operationClass, String[] args)
throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
if (operationClass == null) {
throw new OperationNotFound("Unable to resolve operation: " + operationName);
}
OperationFactory.operationName = operationName;
processArgs(args);
if (showHelpMenu) {
logger.info(OperationUtils.getHelpMenu(
(ExecutableOperation) operationClass.getDeclaredConstructor().newInstance()));
showHelpMenu = false;
return null;
}
return initialize((ExecutableOperation) operationClass.getDeclaredConstructor().newInstance());
}

static Operation createOperation(String operationName) {
switch (operationName) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.opencds.cqf.tooling.cli;

import org.opencds.cqf.tooling.exception.InvalidOperationArgs;
import org.opencds.cqf.tooling.exception.OperationNotFound;
import org.testng.Assert;
import org.testng.annotations.Test;

public class OperationInitializationIT {
@Test
void missingOperationName() {
String[] args = new String[]{};
Assert.assertThrows(OperationNotFound.class, () -> Main.main(args));
}

@Test
void invalidOperationName() {
String[] args = new String[]{ "-NonexistentOperationName" };
Assert.assertThrows(OperationNotFound.class, () -> Main.main(args));
}

@Test
void InvalidOperationDeclaration() {
String[] args = new String[]{ "BundleResources", "-ptr=some/directory/path" };
Assert.assertThrows(InvalidOperationArgs.class, () -> Main.main(args));
}

@Test
void missingRequiredOperationArgs() {
String[] args = new String[]{ "-BundleResources" };
Assert.assertThrows(InvalidOperationArgs.class, () -> Main.main(args));
}
}
40 changes: 37 additions & 3 deletions tooling/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,24 @@
<artifactId>hapi-fhir-base</artifactId>
<version>${hapi.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation</artifactId>
<version>${hapi.version}</version>
</dependency>

<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-converter</artifactId>
<version>${hapi.version}</version>
</dependency>

<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-client</artifactId>
<version>${hapi.version}</version>
</dependency>

<!-- StructureDef to ModelInfo -->
<dependency>
<groupId>com.google.code.gson</groupId>
Expand All @@ -158,6 +169,12 @@
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>com.jakewharton.fliptables</groupId>
<artifactId>fliptables</artifactId>
<version>1.1.0</version>
</dependency>


<!-- BundleResources requirement -->
<dependency>
Expand Down Expand Up @@ -230,14 +247,20 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.30</version>
<version>1.7.33</version>
</dependency>

<!--redirects log4j (used by rckms artifacts) to slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.30</version>
<version>1.7.33</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.33</version>
</dependency>

<!-- FHIR DAL -->
Expand Down Expand Up @@ -325,9 +348,20 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
<version>2.0.5</version>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<version>5.14.0</version>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-client-java</artifactId>
<version>5.14.0</version>
</dependency>
</dependencies>


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,7 @@ public void execute(String[] args) {

private void processWorkbook(Workbook workbook) {
String outputPath = getOutputPath();
try {
ensurePath(outputPath);
}
catch (IOException e) {
throw new IllegalArgumentException(String.format("Could not ensure output path: %s", e.getMessage()), e);
}
ensurePath(outputPath);

// process workbook
if (decisionTablePages != null) {
Expand Down Expand Up @@ -614,12 +609,7 @@ private void writeLibraryHeader(StringBuilder cql, Library library) {
private void writeLibraries(String outputPath) {
if (libraries != null && libraries.size() > 0) {
String outputFilePath = outputPath + File.separator + "input" + File.separator + "resources" + File.separator + "library";
try {
ensurePath(outputFilePath);
}
catch (IOException e) {
throw new IllegalArgumentException(String.format("Could not ensure output path: %s", e.getMessage()), e);
}
ensurePath(outputFilePath);

for (Library library : libraries.values()) {
writeResource(outputFilePath, library);
Expand All @@ -632,12 +622,7 @@ private void writeLibraryCQL(String outputPath) {
for (Map.Entry<String, StringBuilder> entry : libraryCQL.entrySet()) {
String outputDirectoryPath = outputPath + File.separator + "input" + File.separator + "cql";
String outputFilePath = outputDirectoryPath + File.separator + entry.getKey() + ".cql";
try {
ensurePath(outputDirectoryPath);
}
catch (IOException e) {
throw new IllegalArgumentException(String.format("Could not ensure output path: %s", e.getMessage()), e);
}
ensurePath(outputDirectoryPath);

try (FileOutputStream writer = new FileOutputStream(outputFilePath)) {
writer.write(entry.getValue().toString().getBytes());
Expand All @@ -655,12 +640,7 @@ private void writePlanDefinitions(String outputPath) {
if (planDefinitions != null && planDefinitions.size() > 0) {
for (PlanDefinition planDefinition : planDefinitions.values()) {
String outputFilePath = outputPath + File.separator + "input" + File.separator + "resources" + File.separator + "plandefinition";
try {
ensurePath(outputFilePath);
}
catch (IOException e) {
throw new IllegalArgumentException(String.format("Could not ensure output path: %s", e.getMessage()), e);
}
ensurePath(outputFilePath);
writeResource(outputFilePath, planDefinition);
}
}
Expand Down Expand Up @@ -702,12 +682,7 @@ private String buildPlanDefinitionIndex() {

public void writePlanDefinitionIndex(String outputPath) {
String outputFilePath = outputPath + File.separator + "input" + File.separator + "pagecontent"+ File.separator + "PlanDefinitionIndex.md";
try {
ensurePath(outputFilePath);
}
catch (IOException e) {
throw new IllegalArgumentException(String.format("Could not ensure output path: %s", e.getMessage()), e);
}
ensurePath(outputFilePath);

try (FileOutputStream writer = new FileOutputStream(outputFilePath)) {
writer.write(buildPlanDefinitionIndex().getBytes());
Expand Down
Loading