/* * Licensed under the Apache License, Version 2.0 (the "License"); * * You may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * limitations under the License. * * Contributions from 2013-2017 where performed either by US government * employees, or under US Veterans Health Administration contracts. * * US Veterans Health Administration contributions by government employees * are work of the U.S. Government and are not subject to copyright * protection in the United States. Portions contributed by government * employees are USGovWork (17USC ยง105). Not subject to copyright. * * Contribution by contractors to the US Veterans Health Administration * during this period are contractually contributed under the * Apache License, Version 2.0. * * See: https://www.usa.gov/government-works * * Contributions prior to 2013: * * Copyright (C) International Health Terminology Standards Development Organisation. * Licensed under the Apache License, Version 2.0. * */ package sh.isaac.pombuilder.converter; //~--- JDK imports ------------------------------------------------------------ import java.io.File; import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; //~--- non-JDK imports -------------------------------------------------------- import javafx.util.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import sh.isaac.api.util.UUIDUtil; import sh.isaac.pombuilder.FileUtil; import sh.isaac.pombuilder.GitPublish; import sh.isaac.pombuilder.VersionFinder; import sh.isaac.pombuilder.artifacts.Converter; import sh.isaac.pombuilder.artifacts.IBDFFile; import sh.isaac.pombuilder.artifacts.SDOSourceContent; //~--- classes ---------------------------------------------------------------- /** * * {@link ContentConverterCreator} * * A class that has the convenience methods that will construct and publish a pom project - which when executed, will * convert SDO source content into IBDF content. The convenience methods in this class carry all of the documentation * and information necessary to create various conversion types. * * @author <a href="mailto:daniel.armbrust.list@gmail.com">Dan Armbrust</a> */ public class ContentConverterCreator { /** The Constant LOG. */ private static final Logger LOG = LogManager.getLogger(); //~--- methods ------------------------------------------------------------- /** * Create a source conversion project which is executable via maven. * * @param sourceContent - The artifact information for the content to be converted. The artifact information must follow known naming conventions - group id should * be sh.isaac.terminology.source. Currently supported artifactIds are 'loinc-src-data', 'loinc-src-data-tech-preview', 'rf2-src-data-*', 'vhat' * @param converterVersion - The version number of the content converter code to utilize. The jar file for this converter must be available to the * maven execution environment at the time when the conversion is run. * @param additionalSourceDependencies - Some converters require additional data files to satisfy dependencies. See {@link #getSupportedConversions()} * for accurate dependencies for any given conversion type. * @param additionalIBDFDependencies - Some converters require additional data files to satisfy dependencies. See {@link #getSupportedConversions()} * for accurate dependencies for any given conversion type. * @param converterOptionValues a map of converter options (fetched via {@link #getConverterOptions(Converter, String, String, String)} to a set of values. * The values are the items that the user selected / entered. This may be blank, depending on the converter and/or the user choices. * @param gitRepositoryURL - The URL to publish this built project to * @param gitUsername - The username to utilize to publish this project * @param gitPassword - the password to utilize to publish this project * @return the tag created in the repository that carries the created project * @throws Exception the exception */ public static String createContentConverter(SDOSourceContent sourceContent, String converterVersion, SDOSourceContent[] additionalSourceDependencies, IBDFFile[] additionalIBDFDependencies, Map<ConverterOptionParam, Set<String>> converterOptionValues, String gitRepositoryURL, String gitUsername, char[] gitPassword) throws Exception { LOG.debug("Creating a content converter for '{}' with converter version '{}' on the server '{}'", sourceContent, converterVersion, gitRepositoryURL); final File f = Files.createTempDirectory("converter-builder") .toFile(); try { final Pair<SupportedConverterTypes, String> artifactInfo = getConverterType(sourceContent.getArtifactId()); final SupportedConverterTypes conversionType = artifactInfo.getKey(); final String extensionSuffix = artifactInfo.getValue(); final StringBuilder extraProperties = new StringBuilder(); FileUtil.writeFile("converterProjectTemplate", "src/assembly/MANIFEST.MF", f, new HashMap<>(), ""); FileUtil.writeFile("shared", "LICENSE.txt", f, new HashMap<>(), ""); final StringBuffer noticeAppend = new StringBuffer(); final HashMap<String, String> pomSwaps = new HashMap<>(); pomSwaps.put("#VERSION#", sourceContent.getVersion() + "-loader-" + converterVersion); pomSwaps.put("#NAME#", conversionType.getNiceName() + " Artifact Converter"); pomSwaps.put("#SOURCE_DATA_VERSION#", sourceContent.getVersion()); pomSwaps.put("#LOADER_VERSION#", converterVersion); pomSwaps.put("#SCM_URL#", GitPublish.constructChangesetRepositoryURL(gitRepositoryURL)); String temp = FileUtil.readFile("converterProjectTemplate/pomSnippits/fetchExecution.xml"); temp = temp.replace("#GROUPID#", sourceContent.getGroupId()); temp = temp.replace("#ARTIFACTID#", sourceContent.getArtifactId()); temp = temp.replace("#VERSION#", sourceContent.getVersion()); final StringBuilder fetches = new StringBuilder(temp); for (final SDOSourceContent ac: additionalSourceDependencies) { temp = FileUtil.readFile("converterProjectTemplate/pomSnippits/fetchExecution.xml"); temp = temp.replace("#GROUPID#", ac.getGroupId()); temp = temp.replace("#ARTIFACTID#", ac.getArtifactId()); temp = temp.replace("#VERSION#", ac.getVersion()); fetches.append(temp); extraProperties.append("<" + ac.getArtifactId() + ".version>" + ac.getVersion() + "</" + ac.getArtifactId() + ".version>\n"); } pomSwaps.put("#FETCH_EXECUTION#", fetches.toString()); final StringBuilder dependencies = new StringBuilder(); final StringBuilder unpackArtifacts = new StringBuilder(); String unpackDependencies = ""; if (additionalIBDFDependencies.length > 0) { unpackDependencies = FileUtil.readFile("converterProjectTemplate/pomSnippits/unpackDependency.xml"); for (final IBDFFile ibdf: additionalIBDFDependencies) { temp = FileUtil.readFile("converterProjectTemplate/pomSnippits/ibdfDependency.xml"); temp = temp.replace("#GROUPID#", ibdf.getGroupId()); temp = temp.replace("#ARTIFACTID#", ibdf.getArtifactId()); temp = temp.replace("#CLASSIFIER#", (ibdf.hasClassifier() ? ibdf.getClassifier() : "")); temp = temp.replace("#VERSION#", ibdf.getVersion()); dependencies.append(temp); unpackArtifacts.append(ibdf.getArtifactId()); unpackArtifacts.append(","); } temp = FileUtil.readFile("converterProjectTemplate/pomSnippits/ibdfDependency.xml"); temp = temp.replace("#GROUPID#", "sh.isaac.core"); temp = temp.replace("#ARTIFACTID#", "metadata"); temp = temp.replace("#CLASSIFIER#", "all"); temp = temp.replace("#VERSION#", VersionFinder.findProjectVersion()); dependencies.append(temp); unpackArtifacts.append("ochre-metadata"); unpackDependencies = unpackDependencies.replace("#UNPACK_ARTIFACTS#", unpackArtifacts.toString()); } pomSwaps.put("#IBDF_DEPENDENCY#", dependencies.toString()); pomSwaps.put("#UNPACK_DEPENDENCIES#", unpackDependencies); final String goal = conversionType.getConverterMojoName(); pomSwaps.put("#LOADER_ARTIFACT#", conversionType.getConverterArtifactId()); pomSwaps.put("#ARTIFACTID#", conversionType.getConverterOutputArtifactId() + extensionSuffix); final StringBuffer licenseInfo = new StringBuffer(); for (final String s: conversionType.getLicenseInformation()) { licenseInfo.append(s); } pomSwaps.put("#LICENSE#", licenseInfo.toString()); for (final String s: conversionType.getNoticeInformation()) { noticeAppend.append(s); } final StringBuilder userOptions = new StringBuilder(); if (converterOptionValues != null) { final String optionIndent = " "; for (final Entry<ConverterOptionParam, Set<String>> option: converterOptionValues.entrySet()) { if (option.getValue() != null) { if (!option.getKey().isAllowMultiSelect() && (option.getValue().size() > 1)) { LOG.info("Throwing exception back because the option " + option.getKey().getDisplayName() + " allows at most, one value"); throw new Exception("The option " + option.getKey().getDisplayName() + " allows at most, one value"); } if (!option.getKey().isAllowNoSelection() && (option.getValue().size() == 0)) { LOG.info("Throwing exception back because This option " + option.getKey().getDisplayName() + " requires a value"); throw new Exception("This option " + option.getKey().getDisplayName() + " requires a value"); } if (option.getValue() .size() > 0) { if (option.getKey() .isAllowMultiSelect()) { userOptions.append(optionIndent + "<" + option.getKey().getInternalName() + "s>\n"); for (final String value: option.getValue()) { userOptions.append(optionIndent + "\t<" + option.getKey().getInternalName() + ">"); if (UUIDUtil.isUUID(value)) { userOptions.append("\n"); userOptions.append(optionIndent + "\t\t<description></description>\n"); // Its ok not to populate this userOptions.append(optionIndent + "\t\t<uuid>" + value + "</uuid>\n"); userOptions.append(optionIndent + "\t"); } else { userOptions.append(value); } userOptions.append("</" + option.getKey().getInternalName() + ">\n"); } userOptions.append(optionIndent + "</" + option.getKey().getInternalName() + "s>"); } else { final String value = option.getValue() .iterator() .next(); userOptions.append(optionIndent + "<" + option.getKey().getInternalName() + ">"); if (UUIDUtil.isUUID(value)) { userOptions.append("\n"); userOptions.append(optionIndent + "\t<description></description>\n"); // Its ok not to populate this userOptions.append(optionIndent + "\t<uuid>" + value + "</uuid>\n"); userOptions.append(optionIndent); } else { userOptions.append(value); } userOptions.append("</" + option.getKey().getInternalName() + ">"); } } } else if (!option.getKey() .isAllowNoSelection()) { LOG.info("Throwing exception back because this option " + option.getKey().getDisplayName() + " requires a value"); throw new Exception("This option " + option.getKey().getDisplayName() + " requires a value"); } } } final StringBuilder profiles = new StringBuilder(); String[] classifiers = new String[] {}; switch (conversionType) { case SCT: case SCT_EXTENSION: classifiers = new String[] { "Snapshot", "Delta", "Full" }; break; default: classifiers = new String[] { "" }; break; } for (final String classifier: classifiers) { temp = FileUtil.readFile("converterProjectTemplate/pomSnippits/profile.xml"); temp = temp.replaceAll("#CLASSIFIER#", classifier); temp = temp.replaceAll("#CONVERTER#", conversionType.getConverterArtifactId()); temp = temp.replaceAll("#CONVERTER_VERSION#", converterVersion); temp = temp.replaceAll("#GOAL#", goal); temp = temp.replaceAll("#USER_CONFIGURATION_OPTIONS#", userOptions.toString()); profiles.append(temp); String assemblyInfo = FileUtil.readFile("converterProjectTemplate/src/assembly/assembly.xml"); final StringBuilder assemblySnippits = new StringBuilder(); for (final String classifier2: classifiers) { String assemblyRef = FileUtil.readFile("converterProjectTemplate/src/assembly/assemblySnippits/assemblyRef.xml"); assemblyRef = assemblyRef.replace("#ASSEMBLY#", "assembly-" + classifier2 + ".xml"); assemblySnippits.append(assemblyRef); } assemblyInfo = assemblyInfo.replace("#ASSEMBLY_FILES#", assemblySnippits.toString()); if (classifier.length() == 0) { assemblyInfo = assemblyInfo.replaceAll("#CLASSIFIER#", classifier); } else { assemblyInfo = assemblyInfo.replaceAll("#CLASSIFIER#", classifier + "*"); } final File assemblyFile = new File(f, "src/assembly/assembly-" + classifier + ".xml"); assemblyFile.getParentFile() .mkdirs(); Files.write(assemblyFile.toPath(), assemblyInfo.getBytes(), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); } pomSwaps.put("#PROFILE#", profiles.toString()); final String tagWithoutRevNumber = "sh.isaac.terminology.converted" + "/" + pomSwaps.get("#ARTIFACTID#") + "/" + pomSwaps.get("#VERSION#"); LOG.debug("Generated tag (without rev number): '{}'", tagWithoutRevNumber); final ArrayList<String> existingTags = GitPublish.readTags(gitRepositoryURL, gitUsername, gitPassword); if (LOG.isDebugEnabled()) { LOG.debug("Currently Existing tags in '{}': {} ", gitRepositoryURL, Arrays.toString(existingTags.toArray(new String[existingTags.size()]))); } final int highestBuildRevision = GitPublish.readHighestRevisionNumber(existingTags, tagWithoutRevNumber); String tag; // Fix version number if (highestBuildRevision == -1) { // No tag at all - create without rev number, don't need to change our pomSwaps tag = tagWithoutRevNumber; } else { // If we are a SNAPSHOT, don't embed a build number, because nexus won't allow the upload, otherwise, embed a rev number if (!pomSwaps.get("#VERSION#") .endsWith("SNAPSHOT")) { pomSwaps.put("#VERSION#", pomSwaps.get("#VERSION#") + "-" + (highestBuildRevision + 1)); } tag = tagWithoutRevNumber + "-" + (highestBuildRevision + 1); } LOG.info("Final calculated tag: '{}'", tag); pomSwaps.put("#SCM_TAG#", tag); if (extraProperties.length() > 0) { extraProperties.setLength(extraProperties.length() - 1); } pomSwaps.put("#EXTRA_PROPERTIES#", extraProperties.toString()); FileUtil.writeFile("shared", "NOTICE.txt", f, new HashMap<>(), noticeAppend.toString()); FileUtil.writeFile("converterProjectTemplate", "pom.xml", f, pomSwaps, ""); GitPublish.publish(f, gitRepositoryURL, gitUsername, gitPassword, tag); return tag; } finally { try { FileUtil.recursiveDelete(f); } catch (final Exception e) { LOG.error("Problem cleaning up temp folder " + f, e); } } } //~--- get methods --------------------------------------------------------- /** * Will return an Artifact with only the groupID and artifactID populated - this represents the * artifact that is capable of handling the conversion of the specified source content. * @param artifactId - the artifactid of the source content that the user desires to converter. * @return - the group and artifact id of the converter tool that is capable of handling that content. */ public static Converter getConverterForSourceArtifact(String artifactId) { final SupportedConverterTypes supportedConverterType = getConverterType(artifactId).getKey(); return new Converter(supportedConverterType.getConverterGroupId(), supportedConverterType.getConverterArtifactId(), ""); } /** * Gets the converter options. * * @param converter the converter * @param repositoryBaseURL the repository base URL * @param repositoryUsername the repository username * @param repositoryPassword the repository password * @return the converter options * @throws Exception the exception * @see {@link ConverterOptionParam#fromArtifact(Converter, String, String, String)}; */ public static ConverterOptionParam[] getConverterOptions(Converter converter, String repositoryBaseURL, String repositoryUsername, String repositoryPassword) throws Exception { return ConverterOptionParam.fromArtifact(converter, repositoryBaseURL, repositoryUsername, repositoryPassword); } /** * Gets the converter type. * * @param artifactId the artifact id * @return the converter type */ private static Pair<SupportedConverterTypes, String> getConverterType(String artifactId) { SupportedConverterTypes conversionType = null; String extensionSuffix = ""; for (final SupportedConverterTypes type: SupportedConverterTypes.values()) { if (type.getArtifactId() .equals(artifactId)) { conversionType = type; break; } if (type.getArtifactId() .contains("*")) { final String[] temp = type.getArtifactId() .split("\\*"); if (artifactId.startsWith(temp[0]) && artifactId.endsWith(temp[1])) { conversionType = type; extensionSuffix = artifactId.substring(temp[0].length(), artifactId.length()); break; } } } if (conversionType == null) { LOG.info( "Throwing Runtime exception back from getConverterType, as the artifact {} could not be matched to a content artifact type", artifactId); throw new RuntimeException("Unuspported source content artifact type"); } return new Pair<>(conversionType, extensionSuffix); } /** * Return information about all of the supported conversion types, including all of the information types * that must be supplied with each converter. * * @return the supported conversions */ public static SupportedConverterTypes[] getSupportedConversions() { return SupportedConverterTypes.values(); } }