
001package org.hl7.fhir.r4.utils; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032import java.io.BufferedOutputStream; 033import java.io.ByteArrayOutputStream; 034import java.io.File; 035import java.io.IOException; 036import java.io.UnsupportedEncodingException; 037import java.util.ArrayList; 038import java.util.Calendar; 039import java.util.GregorianCalendar; 040import java.util.HashSet; 041import java.util.List; 042import java.util.Set; 043import java.util.TimeZone; 044 045import lombok.extern.slf4j.Slf4j; 046import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 047import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; 048import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; 049import org.hl7.fhir.exceptions.FHIRException; 050import org.hl7.fhir.r4.model.ContactDetail; 051import org.hl7.fhir.r4.model.ContactPoint; 052import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem; 053import org.hl7.fhir.r4.model.Enumeration; 054import org.hl7.fhir.r4.model.Enumerations.FHIRVersion; 055import org.hl7.fhir.r4.model.ImplementationGuide; 056import org.hl7.fhir.r4.model.ImplementationGuide.ImplementationGuideDependsOnComponent; 057import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 058import org.hl7.fhir.utilities.FileUtilities; 059import org.hl7.fhir.utilities.Utilities; 060import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; 061import org.hl7.fhir.utilities.npm.PackageGenerator.PackageType; 062import org.hl7.fhir.utilities.npm.ToolsVersion; 063 064import com.google.gson.Gson; 065import com.google.gson.GsonBuilder; 066import com.google.gson.JsonArray; 067import com.google.gson.JsonObject; 068 069@Deprecated 070@Slf4j 071public class NPMPackageGenerator { 072 073 public enum Category { 074 RESOURCE, EXAMPLE, OPENAPI, SCHEMATRON, RDF, OTHER, TOOL, TEMPLATE, JEKYLL; 075 076 private String getDirectory() { 077 switch (this) { 078 case RESOURCE: 079 return "/package/"; 080 case EXAMPLE: 081 return "/example/"; 082 case OPENAPI: 083 return "/openapi/"; 084 case SCHEMATRON: 085 return "/xml/"; 086 case RDF: 087 return "/rdf/"; 088 case OTHER: 089 return "/other/"; 090 case TEMPLATE: 091 return "/other/"; 092 case JEKYLL: 093 return "/jekyll/"; 094 case TOOL: 095 return "/bin/"; 096 } 097 return "/"; 098 } 099 } 100 101 private String destFile; 102 private Set<String> created = new HashSet<String>(); 103 private TarArchiveOutputStream tar; 104 private ByteArrayOutputStream OutputStream; 105 private BufferedOutputStream bufferedOutputStream; 106 private GzipCompressorOutputStream gzipOutputStream; 107 private JsonObject packageJ; 108 109 public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, 110 String genDate) throws FHIRException, IOException { 111 super(); 112 log.info("create package file at " + destFile); 113 this.destFile = destFile; 114 start(); 115 List<String> fhirVersion = new ArrayList<>(); 116 for (Enumeration<FHIRVersion> v : ig.getFhirVersion()) 117 fhirVersion.add(v.asStringValue()); 118 buildPackageJson(canonical, kind, url, genDate, ig, fhirVersion); 119 } 120 121 public static NPMPackageGenerator subset(NPMPackageGenerator master, String destFile, String id, String name) 122 throws FHIRException, IOException { 123 JsonObject p = master.packageJ.deepCopy(); 124 p.remove("name"); 125 p.addProperty("name", id); 126 p.remove("type"); 127 p.addProperty("type", PackageType.CONFORMANCE.getCode()); 128 p.remove("title"); 129 p.addProperty("title", name); 130 return new NPMPackageGenerator(destFile, p); 131 } 132 133 public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, 134 String genDate, List<String> fhirVersion) throws FHIRException, IOException { 135 super(); 136 log.info("create package file at " + destFile); 137 this.destFile = destFile; 138 start(); 139 buildPackageJson(canonical, kind, url, genDate, ig, fhirVersion); 140 } 141 142 public NPMPackageGenerator(String destFile, JsonObject npm) throws FHIRException, IOException { 143 super(); 144 log.info("create package file at " + destFile); 145 this.destFile = destFile; 146 start(); 147 Gson gson = new GsonBuilder().setPrettyPrinting().create(); 148 String json = gson.toJson(npm); 149 try { 150 addFile(Category.RESOURCE, "package.json", json.getBytes("UTF-8")); 151 } catch (UnsupportedEncodingException e) { 152 } 153 packageJ = npm; 154 } 155 156 private void buildPackageJson(String canonical, PackageType kind, String web, String genDate, ImplementationGuide ig, 157 List<String> fhirVersion) throws FHIRException, IOException { 158 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 159 if (!ig.hasPackageId()) 160 b.append("packageId"); 161 if (!ig.hasVersion()) 162 b.append("version"); 163 if (!ig.hasFhirVersion()) 164 b.append("fhirVersion"); 165 if (!ig.hasLicense()) 166 b.append("license"); 167 for (ImplementationGuideDependsOnComponent d : ig.getDependsOn()) { 168 if (!d.hasVersion()) { 169 b.append("dependsOn.version(" + d.getUri() + ")"); 170 } 171 } 172 173 JsonObject npm = new JsonObject(); 174 npm.addProperty("name", ig.getPackageId()); 175 npm.addProperty("version", ig.getVersion()); 176 npm.addProperty("tools-version", ToolsVersion.TOOLS_VERSION); 177 npm.addProperty("type", kind.getCode()); 178 if (ig.hasLicense()) 179 npm.addProperty("license", ig.getLicense().toCode()); 180 npm.addProperty("canonical", canonical); 181 npm.addProperty("url", web); 182 if (ig.hasTitle()) 183 npm.addProperty("title", ig.getTitle()); 184 if (ig.hasDescription()) 185 npm.addProperty("description", ig.getDescription() + " (built " + genDate + timezone() + ")"); 186 if (kind != PackageType.CORE) { 187 JsonObject dep = new JsonObject(); 188 npm.add("dependencies", dep); 189 for (String v : fhirVersion) { // TODO: fix for multiple versions 190 dep.addProperty("hl7.fhir.core", v); 191 } 192 for (ImplementationGuideDependsOnComponent d : ig.getDependsOn()) { 193 dep.addProperty(d.getPackageId(), d.getVersion()); 194 } 195 } 196 if (ig.hasPublisher()) 197 npm.addProperty("author", ig.getPublisher()); 198 JsonArray m = new JsonArray(); 199 for (ContactDetail t : ig.getContact()) { 200 String email = email(t.getTelecom()); 201 String url = url(t.getTelecom()); 202 if (t.hasName() & (email != null || url != null)) { 203 JsonObject md = new JsonObject(); 204 m.add(md); 205 md.addProperty("name", t.getName()); 206 if (email != null) 207 md.addProperty("email", email); 208 if (url != null) 209 md.addProperty("url", url); 210 } 211 } 212 if (m.size() > 0) 213 npm.add("maintainers", m); 214 if (ig.getManifest().hasRendering()) 215 npm.addProperty("homepage", ig.getManifest().getRendering()); 216 JsonObject dir = new JsonObject(); 217 npm.add("directories", dir); 218 dir.addProperty("lib", "package"); 219 dir.addProperty("example", "example"); 220 Gson gson = new GsonBuilder().setPrettyPrinting().create(); 221 String json = gson.toJson(npm); 222 try { 223 addFile(Category.RESOURCE, "package.json", json.getBytes("UTF-8")); 224 } catch (UnsupportedEncodingException e) { 225 } 226 packageJ = npm; 227 } 228 229 private String timezone() { 230 TimeZone tz = TimeZone.getDefault(); 231 Calendar cal = GregorianCalendar.getInstance(tz); 232 int offsetInMillis = tz.getOffset(cal.getTimeInMillis()); 233 234 String offset = String.format("%02d:%02d", Math.abs(offsetInMillis / 3600000), 235 Math.abs((offsetInMillis / 60000) % 60)); 236 offset = (offsetInMillis >= 0 ? "+" : "-") + offset; 237 238 return offset; 239 } 240 241 private String url(List<ContactPoint> telecom) { 242 for (ContactPoint cp : telecom) { 243 if (cp.getSystem() == ContactPointSystem.URL) 244 return cp.getValue(); 245 } 246 return null; 247 } 248 249 private String email(List<ContactPoint> telecom) { 250 for (ContactPoint cp : telecom) { 251 if (cp.getSystem() == ContactPointSystem.EMAIL) 252 return cp.getValue(); 253 } 254 return null; 255 } 256 257 private void start() throws IOException { 258 OutputStream = new ByteArrayOutputStream(); 259 bufferedOutputStream = new BufferedOutputStream(OutputStream); 260 gzipOutputStream = new GzipCompressorOutputStream(bufferedOutputStream); 261 tar = new TarArchiveOutputStream(gzipOutputStream); 262 } 263 264 public void addFile(Category cat, String name, byte[] content) throws IOException { 265 String path = cat.getDirectory() + name; 266 if (created.contains(path)) 267 log.warn("Duplicate package file " + path); 268 else { 269 created.add(path); 270 TarArchiveEntry entry = new TarArchiveEntry(path); 271 entry.setSize(content.length); 272 tar.putArchiveEntry(entry); 273 tar.write(content); 274 tar.closeArchiveEntry(); 275 } 276 } 277 278 public void finish() throws IOException { 279 tar.finish(); 280 tar.close(); 281 gzipOutputStream.close(); 282 bufferedOutputStream.close(); 283 OutputStream.close(); 284 FileUtilities.bytesToFile(OutputStream.toByteArray(), destFile); 285 } 286 287 public String filename() { 288 return destFile; 289 } 290 291 public void loadDir(String rootDir, String name) throws IOException { 292 loadFiles(rootDir, ManagedFileAccess.file(Utilities.path(rootDir, name))); 293 } 294 295 public void loadFiles(String root, File dir, String... noload) throws IOException { 296 for (File f : dir.listFiles()) { 297 if (!Utilities.existsInList(f.getName(), noload)) { 298 if (f.isDirectory()) { 299 loadFiles(root, f); 300 } else { 301 String path = f.getAbsolutePath().substring(root.length() + 1); 302 byte[] content = FileUtilities.fileToBytes(f); 303 if (created.contains(path)) 304 log.warn("Duplicate package file " + path); 305 else { 306 created.add(path); 307 TarArchiveEntry entry = new TarArchiveEntry(path); 308 entry.setSize(content.length); 309 tar.putArchiveEntry(entry); 310 tar.write(content); 311 tar.closeArchiveEntry(); 312 } 313 } 314 } 315 } 316 } 317 318}