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 org.apache.commons.compress.archivers.tar.TarArchiveEntry;
046import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
047import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
048import org.hl7.fhir.exceptions.FHIRException;
049import org.hl7.fhir.r4.model.ContactDetail;
050import org.hl7.fhir.r4.model.ContactPoint;
051import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem;
052import org.hl7.fhir.r4.model.Enumeration;
053import org.hl7.fhir.r4.model.Enumerations.FHIRVersion;
054import org.hl7.fhir.r4.model.ImplementationGuide;
055import org.hl7.fhir.r4.model.ImplementationGuide.ImplementationGuideDependsOnComponent;
056import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
057import org.hl7.fhir.utilities.FileUtilities;
058import org.hl7.fhir.utilities.Utilities;
059import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
060import org.hl7.fhir.utilities.npm.PackageGenerator.PackageType;
061import org.hl7.fhir.utilities.npm.ToolsVersion;
062
063import com.google.gson.Gson;
064import com.google.gson.GsonBuilder;
065import com.google.gson.JsonArray;
066import com.google.gson.JsonObject;
067
068@Deprecated
069public class NPMPackageGenerator {
070
071  public enum Category {
072    RESOURCE, EXAMPLE, OPENAPI, SCHEMATRON, RDF, OTHER, TOOL, TEMPLATE, JEKYLL;
073
074    private String getDirectory() {
075      switch (this) {
076      case RESOURCE:
077        return "/package/";
078      case EXAMPLE:
079        return "/example/";
080      case OPENAPI:
081        return "/openapi/";
082      case SCHEMATRON:
083        return "/xml/";
084      case RDF:
085        return "/rdf/";
086      case OTHER:
087        return "/other/";
088      case TEMPLATE:
089        return "/other/";
090      case JEKYLL:
091        return "/jekyll/";
092      case TOOL:
093        return "/bin/";
094      }
095      return "/";
096    }
097  }
098
099  private String destFile;
100  private Set<String> created = new HashSet<String>();
101  private TarArchiveOutputStream tar;
102  private ByteArrayOutputStream OutputStream;
103  private BufferedOutputStream bufferedOutputStream;
104  private GzipCompressorOutputStream gzipOutputStream;
105  private JsonObject packageJ;
106
107  public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig,
108      String genDate) throws FHIRException, IOException {
109    super();
110    System.out.println("create package file at " + destFile);
111    this.destFile = destFile;
112    start();
113    List<String> fhirVersion = new ArrayList<>();
114    for (Enumeration<FHIRVersion> v : ig.getFhirVersion())
115      fhirVersion.add(v.asStringValue());
116    buildPackageJson(canonical, kind, url, genDate, ig, fhirVersion);
117  }
118
119  public static NPMPackageGenerator subset(NPMPackageGenerator master, String destFile, String id, String name)
120      throws FHIRException, IOException {
121    JsonObject p = master.packageJ.deepCopy();
122    p.remove("name");
123    p.addProperty("name", id);
124    p.remove("type");
125    p.addProperty("type", PackageType.CONFORMANCE.getCode());
126    p.remove("title");
127    p.addProperty("title", name);
128    return new NPMPackageGenerator(destFile, p);
129  }
130
131  public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig,
132      String genDate, List<String> fhirVersion) throws FHIRException, IOException {
133    super();
134    System.out.println("create package file at " + destFile);
135    this.destFile = destFile;
136    start();
137    buildPackageJson(canonical, kind, url, genDate, ig, fhirVersion);
138  }
139
140  public NPMPackageGenerator(String destFile, JsonObject npm) throws FHIRException, IOException {
141    super();
142    System.out.println("create package file at " + destFile);
143    this.destFile = destFile;
144    start();
145    Gson gson = new GsonBuilder().setPrettyPrinting().create();
146    String json = gson.toJson(npm);
147    try {
148      addFile(Category.RESOURCE, "package.json", json.getBytes("UTF-8"));
149    } catch (UnsupportedEncodingException e) {
150    }
151    packageJ = npm;
152  }
153
154  private void buildPackageJson(String canonical, PackageType kind, String web, String genDate, ImplementationGuide ig,
155      List<String> fhirVersion) throws FHIRException, IOException {
156    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
157    if (!ig.hasPackageId())
158      b.append("packageId");
159    if (!ig.hasVersion())
160      b.append("version");
161    if (!ig.hasFhirVersion())
162      b.append("fhirVersion");
163    if (!ig.hasLicense())
164      b.append("license");
165    for (ImplementationGuideDependsOnComponent d : ig.getDependsOn()) {
166      if (!d.hasVersion()) {
167        b.append("dependsOn.version(" + d.getUri() + ")");
168      }
169    }
170
171    JsonObject npm = new JsonObject();
172    npm.addProperty("name", ig.getPackageId());
173    npm.addProperty("version", ig.getVersion());
174    npm.addProperty("tools-version", ToolsVersion.TOOLS_VERSION);
175    npm.addProperty("type", kind.getCode());
176    if (ig.hasLicense())
177      npm.addProperty("license", ig.getLicense().toCode());
178    npm.addProperty("canonical", canonical);
179    npm.addProperty("url", web);
180    if (ig.hasTitle())
181      npm.addProperty("title", ig.getTitle());
182    if (ig.hasDescription())
183      npm.addProperty("description", ig.getDescription() + " (built " + genDate + timezone() + ")");
184    if (kind != PackageType.CORE) {
185      JsonObject dep = new JsonObject();
186      npm.add("dependencies", dep);
187      for (String v : fhirVersion) { // TODO: fix for multiple versions
188        dep.addProperty("hl7.fhir.core", v);
189      }
190      for (ImplementationGuideDependsOnComponent d : ig.getDependsOn()) {
191        dep.addProperty(d.getPackageId(), d.getVersion());
192      }
193    }
194    if (ig.hasPublisher())
195      npm.addProperty("author", ig.getPublisher());
196    JsonArray m = new JsonArray();
197    for (ContactDetail t : ig.getContact()) {
198      String email = email(t.getTelecom());
199      String url = url(t.getTelecom());
200      if (t.hasName() & (email != null || url != null)) {
201        JsonObject md = new JsonObject();
202        m.add(md);
203        md.addProperty("name", t.getName());
204        if (email != null)
205          md.addProperty("email", email);
206        if (url != null)
207          md.addProperty("url", url);
208      }
209    }
210    if (m.size() > 0)
211      npm.add("maintainers", m);
212    if (ig.getManifest().hasRendering())
213      npm.addProperty("homepage", ig.getManifest().getRendering());
214    JsonObject dir = new JsonObject();
215    npm.add("directories", dir);
216    dir.addProperty("lib", "package");
217    dir.addProperty("example", "example");
218    Gson gson = new GsonBuilder().setPrettyPrinting().create();
219    String json = gson.toJson(npm);
220    try {
221      addFile(Category.RESOURCE, "package.json", json.getBytes("UTF-8"));
222    } catch (UnsupportedEncodingException e) {
223    }
224    packageJ = npm;
225  }
226
227  private String timezone() {
228    TimeZone tz = TimeZone.getDefault();
229    Calendar cal = GregorianCalendar.getInstance(tz);
230    int offsetInMillis = tz.getOffset(cal.getTimeInMillis());
231
232    String offset = String.format("%02d:%02d", Math.abs(offsetInMillis / 3600000),
233        Math.abs((offsetInMillis / 60000) % 60));
234    offset = (offsetInMillis >= 0 ? "+" : "-") + offset;
235
236    return offset;
237  }
238
239  private String url(List<ContactPoint> telecom) {
240    for (ContactPoint cp : telecom) {
241      if (cp.getSystem() == ContactPointSystem.URL)
242        return cp.getValue();
243    }
244    return null;
245  }
246
247  private String email(List<ContactPoint> telecom) {
248    for (ContactPoint cp : telecom) {
249      if (cp.getSystem() == ContactPointSystem.EMAIL)
250        return cp.getValue();
251    }
252    return null;
253  }
254
255  private void start() throws IOException {
256    OutputStream = new ByteArrayOutputStream();
257    bufferedOutputStream = new BufferedOutputStream(OutputStream);
258    gzipOutputStream = new GzipCompressorOutputStream(bufferedOutputStream);
259    tar = new TarArchiveOutputStream(gzipOutputStream);
260  }
261
262  public void addFile(Category cat, String name, byte[] content) throws IOException {
263    String path = cat.getDirectory() + name;
264    if (created.contains(path))
265      System.out.println("Duplicate package file " + path);
266    else {
267      created.add(path);
268      TarArchiveEntry entry = new TarArchiveEntry(path);
269      entry.setSize(content.length);
270      tar.putArchiveEntry(entry);
271      tar.write(content);
272      tar.closeArchiveEntry();
273    }
274  }
275
276  public void finish() throws IOException {
277    tar.finish();
278    tar.close();
279    gzipOutputStream.close();
280    bufferedOutputStream.close();
281    OutputStream.close();
282    FileUtilities.bytesToFile(OutputStream.toByteArray(), destFile);
283  }
284
285  public String filename() {
286    return destFile;
287  }
288
289  public void loadDir(String rootDir, String name) throws IOException {
290    loadFiles(rootDir, ManagedFileAccess.file(Utilities.path(rootDir, name)));
291  }
292
293  public void loadFiles(String root, File dir, String... noload) throws IOException {
294    for (File f : dir.listFiles()) {
295      if (!Utilities.existsInList(f.getName(), noload)) {
296        if (f.isDirectory()) {
297          loadFiles(root, f);
298        } else {
299          String path = f.getAbsolutePath().substring(root.length() + 1);
300          byte[] content = FileUtilities.fileToBytes(f);
301          if (created.contains(path))
302            System.out.println("Duplicate package file " + path);
303          else {
304            created.add(path);
305            TarArchiveEntry entry = new TarArchiveEntry(path);
306            entry.setSize(content.length);
307            tar.putArchiveEntry(entry);
308            tar.write(content);
309            tar.closeArchiveEntry();
310          }
311        }
312      }
313    }
314  }
315
316}