All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencds.cqf.operation.IgBundler Maven / Gradle / Ivy

package org.opencds.cqf.operation;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.opencds.cqf.Operation;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;

/**
 *
 * The purpose of this operation is to package the examples and artifacts, contained within an IG, into transaction bundles
 * and write to specified output directory.
 *
 * Operation can be called with following 3 args:
 *  -outputpath (-op) Path to directory where Bundles will be written (optional)
 *  -pathtoig (-ptig) Path to ImplementationGuide project root (where ig.json is stored) (required)
 *  -encoding (-e) Preferred encoding. XML and JSON are supported (JSON by default) (optional)
 *
 * */
public class IgBundler extends Operation
{
    private String pathToIg; // -pathtoig | -ptig
    private String encoding = "json"; // -encoding (-e)

    private FhirContext fhirContext; // determined during ig.json processing
    private IParser jsonParser;
    private IParser xmlParser;

    private Gson gson = new Gson();

    private List resourcePaths = new ArrayList<>();
    private List resourceFiles = new ArrayList<>();

    private Map outputBundles = new HashMap<>();

    @Override
    public void execute(String[] args)
    {
        setOutputPath("src/main/resources/org/opencds/cqf/igtools/output"); // default

        for (String arg : args) {
            if (arg.equals("-BundleIg")) continue;
            String[] flagAndValue = arg.split("=");
            if (flagAndValue.length < 2) {
                throw new IllegalArgumentException("Invalid argument: " + arg);
            }
            String flag = flagAndValue[0];
            String value = flagAndValue[1];

            switch (flag.replace("-", "").toLowerCase()) {
                case "outputpath": case "op": setOutputPath(value); break; // -outputpath (-op)
                case "pathtoig": case "ptig": pathToIg = value; break;
                case "encoding": case "e": encoding = value.toLowerCase(); break;
                default: throw new IllegalArgumentException("Unknown flag: " + flag);
            }
        }

        if (pathToIg == null) {
            throw new IllegalArgumentException("The path to the IG is required");
        }

        JsonObject igControl = getIgControl();
        setFhirContext(igControl);
        jsonParser = fhirContext.newJsonParser();
        xmlParser = fhirContext.newXmlParser();
        setResourcePaths(igControl);
        setResourceFiles(igControl);
        resolveDirectoryFiles();
        outputBundles();
    }

    /***
     * Parse the ig.json configuration file.
     * @return JsonObject representation of the ig.json
     */
    private JsonObject getIgControl()
    {
        try
        {
            return gson.fromJson(new FileReader(new File(pathToIg + "/ig.json")), JsonObject.class);
        }
        catch (FileNotFoundException e)
        {
            e.printStackTrace();
            throw new RuntimeException("Error reading control file: " + e.getMessage());
        }
    }

    /***
     * Determine the FHIR version from the ig.json configuration.
     * @param igControl JsonObject representation of the ig.json
     */
    private void setFhirContext(JsonObject igControl)
    {
        if (igControl.has("version") && igControl.get("version").isJsonPrimitive())
        {
            String version = igControl.get("version").getAsString();
            if (version.equals("3.0.0") || version.equals("3.0.1"))
            {
                fhirContext = FhirContext.forDstu3();
            }
            else
            {
                throw new UnsupportedOperationException("The BundleIg operation currently only supports FHIR STU3");
            }
        }
        else
        {
            throw new RuntimeException("Version is required to be populated in the ig.json");
        }
    }

    /***
     * Determine the paths to directories containing resources defined by the IG.
     * @param igControl JsonObject representation of the ig.json
     */
    private void setResourcePaths(JsonObject igControl)
    {
        if (igControl.has("paths") && igControl.get("paths").isJsonObject())
        {
            JsonObject paths = igControl.get("paths").getAsJsonObject();
            if (paths.has("resources"))
            {
                JsonElement resources = paths.get("resources");
                if (resources.isJsonArray())
                {
                    for (JsonElement path : resources.getAsJsonArray())
                    {
                        if (path.isJsonPrimitive())
                        {
                            resourcePaths.add(pathToIg + "/" + path.getAsString());
                            outputBundles.put(path.getAsString(), null);
                        }
                    }
                }
                else
                {
                    resourcePaths.add(pathToIg + "/" + resources.getAsString());
                }
            }
        }
    }

    /***
     * Determine the ig resources file names specified in the ig.json.
     * @param igControl JsonObject representation of the ig.json
     */
    private void setResourceFiles(JsonObject igControl)
    {
        if (igControl.has("resources") && igControl.get("resources").isJsonObject())
        {
            for (Map.Entry resources : igControl.get("resources").getAsJsonObject().entrySet())
            {
                if (resources.getKey().startsWith("Bundle") || resources.getKey().startsWith("StructureDefinition"))
                {
                    continue;
                }
                if (resources.getValue().isJsonObject()
                        && resources.getValue().getAsJsonObject().has("source")
                        && resources.getValue().getAsJsonObject().get("source").isJsonPrimitive())
                {
                    resourceFiles.add(resources.getValue().getAsJsonObject().get("source").getAsString());
                }
            }
        }
    }

    /***
     * Retrieve the resource files and build Bundle(s)
     */
    private void resolveDirectoryFiles()
    {
        for (String path : resourcePaths)
        {
            // Assuming STU3... TODO: will need to extend this logic to handle different FHIR versions
            Bundle bundle = new Bundle();
            bundle.setType(Bundle.BundleType.TRANSACTION);

            try (Stream paths = Files.walk(Paths.get(path)))
            {
                paths
                        .filter(Files::isRegularFile)
                        .filter(x -> resourceFiles.contains(x.getFileName().toString()))
                        .forEach(x -> addArtifactToBundle(x, bundle));
            }
            catch (IOException ioe)
            {
                throw new RuntimeException(ioe.getMessage());
            }

            for (Map.Entry entry : outputBundles.entrySet())
            {
                if (path.endsWith(entry.getKey()) && entry.getValue() == null)
                {
                    outputBundles.put(entry.getKey(), bundle);
                    break;
                }
            }
        }
    }

    /***
     * Parse artifact file and add to Bundle. NOTE: this method assumes FHIR STU3 artifacts are being used.
     * @param path Path to artifact
     * @param bundle Bundle to populate
     */
    private void addArtifactToBundle(Path path, Bundle bundle)
    {
        IBaseResource resource;
        try
        {
            if (path.toString().endsWith(".xml"))
            {
                resource = xmlParser.parseResource(new FileReader(new File(path.toString())));
            }
            else if (path.toString().endsWith(".json"))
            {
                resource = jsonParser.parseResource(new FileReader(new File(path.toString())));
            }
            else
            {
                throw new RuntimeException("Unknown file type: " + path.toString());
            }
        }
        catch (FileNotFoundException fnfe)
        {
            throw new RuntimeException("Error reading file: " + path.toString());
        }

        bundle.addEntry(
                new Bundle.BundleEntryComponent()
                        .setResource((Resource) resource)
                        .setRequest(
                                new Bundle.BundleEntryRequestComponent()
                                        .setMethod(Bundle.HTTPVerb.PUT)
                                        .setUrl(((Resource) resource).getId())
                        )
        );
    }

    /***
     * Write Bundles to output directory.
     */
    private void outputBundles()
    {
        for (Map.Entry set : outputBundles.entrySet())
        {
            try (FileOutputStream writer = new FileOutputStream(getOutputPath() + "/" + set.getKey() + "." + encoding))
            {
                writer.write(
                        encoding.equals("json")
                                ? jsonParser.setPrettyPrint(true).encodeResourceToString(set.getValue()).getBytes()
                                : xmlParser.setPrettyPrint(true).encodeResourceToString(set.getValue()).getBytes()
                );
                writer.flush();
            }
            catch (IOException e)
            {
                e.printStackTrace();
                throw new RuntimeException("Error writing Bundle to file: " + e.getMessage());
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy