001package org.hl7.fhir.r4.utils; 002 003/*- 004 * #%L 005 * org.hl7.fhir.r4 006 * %% 007 * Copyright (C) 2014 - 2019 Health Level 7 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023 024import java.io.BufferedOutputStream; 025import java.io.ByteArrayOutputStream; 026import java.io.File; 027import java.io.IOException; 028import java.io.UnsupportedEncodingException; 029import java.util.ArrayList; 030import java.util.Calendar; 031import java.util.GregorianCalendar; 032import java.util.HashSet; 033import java.util.List; 034import java.util.Set; 035import java.util.TimeZone; 036 037import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 038import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; 039import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; 040import org.hl7.fhir.exceptions.FHIRException; 041import org.hl7.fhir.r4.model.ContactDetail; 042import org.hl7.fhir.r4.model.ContactPoint; 043import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem; 044import org.hl7.fhir.r4.model.Enumeration; 045import org.hl7.fhir.r4.model.Enumerations.FHIRVersion; 046import org.hl7.fhir.r4.model.ImplementationGuide; 047import org.hl7.fhir.r4.model.ImplementationGuide.ImplementationGuideDependsOnComponent; 048import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 049import org.hl7.fhir.utilities.TextFile; 050import org.hl7.fhir.utilities.Utilities; 051import org.hl7.fhir.utilities.cache.PackageGenerator.PackageType; 052import org.hl7.fhir.utilities.cache.ToolsVersion; 053 054import com.google.gson.Gson; 055import com.google.gson.GsonBuilder; 056import com.google.gson.JsonArray; 057import com.google.gson.JsonObject; 058 059public class NPMPackageGenerator { 060 061 public enum Category { 062 RESOURCE, EXAMPLE, OPENAPI, SCHEMATRON, RDF, OTHER, TOOL, TEMPLATE, JEKYLL; 063 064 private String getDirectory() { 065 switch (this) { 066 case RESOURCE: return "/package/"; 067 case EXAMPLE: return "/example/"; 068 case OPENAPI: return "/openapi/"; 069 case SCHEMATRON: return "/xml/"; 070 case RDF: return "/rdf/"; 071 case OTHER: return "/other/"; 072 case TEMPLATE: return "/other/"; 073 case JEKYLL: return "/jekyll/"; 074 case TOOL: return "/bin/"; 075 } 076 return "/"; 077 } 078 } 079 080 private String destFile; 081 private Set<String> created = new HashSet<String>(); 082 private TarArchiveOutputStream tar; 083 private ByteArrayOutputStream OutputStream; 084 private BufferedOutputStream bufferedOutputStream; 085 private GzipCompressorOutputStream gzipOutputStream; 086 private JsonObject packageJ; 087 088 public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, String genDate) throws FHIRException, IOException { 089 super(); 090 System.out.println("create package file at "+destFile); 091 this.destFile = destFile; 092 start(); 093 List<String> fhirVersion = new ArrayList<>(); 094 for (Enumeration<FHIRVersion> v : ig.getFhirVersion()) 095 fhirVersion.add(v.asStringValue()); 096 buildPackageJson(canonical, kind, url, genDate, ig, fhirVersion); 097 } 098 099 public static NPMPackageGenerator subset(NPMPackageGenerator master, String destFile, String id, String name) throws FHIRException, IOException { 100 JsonObject p = master.packageJ.deepCopy(); 101 p.remove("name"); 102 p.addProperty("name", id); 103 p.remove("type"); 104 p.addProperty("type", PackageType.SUBSET.getCode()); 105 p.remove("title"); 106 p.addProperty("title", name); 107 return new NPMPackageGenerator(destFile, p); 108 } 109 110 public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, String genDate, List<String> fhirVersion) throws FHIRException, IOException { 111 super(); 112 System.out.println("create package file at "+destFile); 113 this.destFile = destFile; 114 start(); 115 buildPackageJson(canonical, kind, url, genDate, ig, fhirVersion); 116 } 117 118 public NPMPackageGenerator(String destFile, JsonObject npm) throws FHIRException, IOException { 119 super(); 120 System.out.println("create package file at "+destFile); 121 this.destFile = destFile; 122 start(); 123 Gson gson = new GsonBuilder().setPrettyPrinting().create(); 124 String json = gson.toJson(npm); 125 try { 126 addFile(Category.RESOURCE, "package.json", json.getBytes("UTF-8")); 127 } catch (UnsupportedEncodingException e) { 128 } 129 packageJ = npm; 130 } 131 132 private void buildPackageJson(String canonical, PackageType kind, String web, String genDate, ImplementationGuide ig, List<String> fhirVersion) throws FHIRException, IOException { 133 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 134 if (!ig.hasPackageId()) 135 b.append("packageId"); 136 if (!ig.hasVersion()) 137 b.append("version"); 138 if (!ig.hasFhirVersion()) 139 b.append("fhirVersion"); 140 if (!ig.hasLicense()) 141 b.append("license"); 142 for (ImplementationGuideDependsOnComponent d : ig.getDependsOn()) { 143 if (!d.hasVersion()) { 144 b.append("dependsOn.version("+d.getUri()+")"); 145 } 146 } 147 148 JsonObject npm = new JsonObject(); 149 npm.addProperty("name", ig.getPackageId()); 150 npm.addProperty("version", ig.getVersion()); 151 npm.addProperty("tools-version", ToolsVersion.TOOLS_VERSION); 152 npm.addProperty("type", kind.getCode()); 153 if (ig.hasLicense()) 154 npm.addProperty("license", ig.getLicense().toCode()); 155 npm.addProperty("canonical", canonical); 156 npm.addProperty("url", web); 157 if (ig.hasTitle()) 158 npm.addProperty("title", ig.getTitle()); 159 if (ig.hasDescription()) 160 npm.addProperty("description", ig.getDescription()+ " (built "+genDate+timezone()+")"); 161 if (kind != PackageType.CORE) { 162 JsonObject dep = new JsonObject(); 163 npm.add("dependencies", dep); 164 for (String v : fhirVersion) { // TODO: fix for multiple versions 165 dep.addProperty("hl7.fhir.core", v); 166 } 167 for (ImplementationGuideDependsOnComponent d : ig.getDependsOn()) { 168 dep.addProperty(d.getPackageId(), d.getVersion()); 169 } 170 } 171 if (ig.hasPublisher()) 172 npm.addProperty("author", ig.getPublisher()); 173 JsonArray m = new JsonArray(); 174 for (ContactDetail t : ig.getContact()) { 175 String email = email(t.getTelecom()); 176 String url = url(t.getTelecom()); 177 if (t.hasName() & (email != null || url != null)) { 178 JsonObject md = new JsonObject(); 179 m.add(md); 180 md.addProperty("name", t.getName()); 181 if (email != null) 182 md.addProperty("email", email); 183 if (url != null) 184 md.addProperty("url", url); 185 } 186 } 187 if (m.size() > 0) 188 npm.add("maintainers", m); 189 if (ig.getManifest().hasRendering()) 190 npm.addProperty("homepage", ig.getManifest().getRendering()); 191 JsonObject dir = new JsonObject(); 192 npm.add("directories", dir); 193 dir.addProperty("lib", "package"); 194 dir.addProperty("example", "example"); 195 Gson gson = new GsonBuilder().setPrettyPrinting().create(); 196 String json = gson.toJson(npm); 197 try { 198 addFile(Category.RESOURCE, "package.json", json.getBytes("UTF-8")); 199 } catch (UnsupportedEncodingException e) { 200 } 201 packageJ = npm; 202 } 203 204 205 private String timezone() { 206 TimeZone tz = TimeZone.getDefault(); 207 Calendar cal = GregorianCalendar.getInstance(tz); 208 int offsetInMillis = tz.getOffset(cal.getTimeInMillis()); 209 210 String offset = String.format("%02d:%02d", Math.abs(offsetInMillis / 3600000), Math.abs((offsetInMillis / 60000) % 60)); 211 offset = (offsetInMillis >= 0 ? "+" : "-") + offset; 212 213 return offset; 214 } 215 216 217 private String url(List<ContactPoint> telecom) { 218 for (ContactPoint cp : telecom) { 219 if (cp.getSystem() == ContactPointSystem.URL) 220 return cp.getValue(); 221 } 222 return null; 223 } 224 225 226 private String email(List<ContactPoint> telecom) { 227 for (ContactPoint cp : telecom) { 228 if (cp.getSystem() == ContactPointSystem.EMAIL) 229 return cp.getValue(); 230 } 231 return null; 232 } 233 234 private void start() throws IOException { 235 OutputStream = new ByteArrayOutputStream(); 236 bufferedOutputStream = new BufferedOutputStream(OutputStream); 237 gzipOutputStream = new GzipCompressorOutputStream(bufferedOutputStream); 238 tar = new TarArchiveOutputStream(gzipOutputStream); 239 } 240 241 242 public void addFile(Category cat, String name, byte[] content) throws IOException { 243 String path = cat.getDirectory()+name; 244 if (created.contains(path)) 245 System.out.println("Duplicate package file "+path); 246 else { 247 created.add(path); 248 TarArchiveEntry entry = new TarArchiveEntry(path); 249 entry.setSize(content.length); 250 tar.putArchiveEntry(entry); 251 tar.write(content); 252 tar.closeArchiveEntry(); 253 } 254 } 255 256 public void finish() throws IOException { 257 tar.finish(); 258 tar.close(); 259 gzipOutputStream.close(); 260 bufferedOutputStream.close(); 261 OutputStream.close(); 262 TextFile.bytesToFile(OutputStream.toByteArray(), destFile); 263 } 264 265 public String filename() { 266 return destFile; 267 } 268 269 public void loadDir(String rootDir, String name) throws IOException { 270 loadFiles(rootDir, new File(Utilities.path(rootDir, name))); 271 } 272 273 public void loadFiles(String root, File dir, String... noload) throws IOException { 274 for (File f : dir.listFiles()) { 275 if (!Utilities.existsInList(f.getName(), noload)) { 276 if (f.isDirectory()) { 277 loadFiles(root, f); 278 } else { 279 String path = f.getAbsolutePath().substring(root.length()+1); 280 byte[] content = TextFile.fileToBytes(f); 281 if (created.contains(path)) 282 System.out.println("Duplicate package file "+path); 283 else { 284 created.add(path); 285 TarArchiveEntry entry = new TarArchiveEntry(path); 286 entry.setSize(content.length); 287 tar.putArchiveEntry(entry); 288 tar.write(content); 289 tar.closeArchiveEntry(); 290 } 291 } 292 } 293 } 294 } 295 296 297}