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}