001/*- 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.term; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; 024import ca.uhn.fhir.jpa.util.LogicUtil; 025import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 026import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; 027import org.apache.commons.io.FileUtils; 028import org.apache.commons.io.IOUtils; 029import org.apache.commons.io.input.BOMInputStream; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033import java.io.BufferedInputStream; 034import java.io.Closeable; 035import java.io.File; 036import java.io.FileInputStream; 037import java.io.FileNotFoundException; 038import java.io.FileOutputStream; 039import java.io.IOException; 040import java.io.InputStream; 041import java.util.ArrayList; 042import java.util.Arrays; 043import java.util.HashSet; 044import java.util.List; 045import java.util.Set; 046import java.util.zip.ZipEntry; 047import java.util.zip.ZipInputStream; 048 049public class LoadedFileDescriptors implements Closeable { 050 private static final Logger ourLog = LoggerFactory.getLogger(LoadedFileDescriptors.class); 051 private List<File> myTemporaryFiles = new ArrayList<>(); 052 private List<ITermLoaderSvc.FileDescriptor> myUncompressedFileDescriptors = new ArrayList<>(); 053 054 LoadedFileDescriptors(List<ITermLoaderSvc.FileDescriptor> theFileDescriptors) { 055 try { 056 for (ITermLoaderSvc.FileDescriptor next : theFileDescriptors) { 057 if (next.getFilename().toLowerCase().endsWith(".zip")) { 058 ourLog.info("Uncompressing {} into temporary files", next.getFilename()); 059 try (InputStream inputStream = next.getInputStream()) { 060 try (BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) { 061 try (ZipInputStream zis = new ZipInputStream(bufferedInputStream)) { 062 for (ZipEntry nextEntry; (nextEntry = zis.getNextEntry()) != null; ) { 063 try (BOMInputStream fis = new NonClosableBOMInputStream(zis)) { 064 File nextTemporaryFile = File.createTempFile("hapifhir", ".tmp"); 065 ourLog.info("Creating temporary file: {}", nextTemporaryFile.getAbsolutePath()); 066 nextTemporaryFile.deleteOnExit(); 067 try (FileOutputStream fos = new FileOutputStream(nextTemporaryFile, false)) { 068 IOUtils.copy(fis, fos); 069 String nextEntryFileName = nextEntry.getName(); 070 myUncompressedFileDescriptors.add(new ITermLoaderSvc.FileDescriptor() { 071 @Override 072 public String getFilename() { 073 return nextEntryFileName; 074 } 075 076 @Override 077 public InputStream getInputStream() { 078 try { 079 return new FileInputStream(nextTemporaryFile); 080 } catch (FileNotFoundException e) { 081 throw new InternalErrorException(Msg.code(860) + e); 082 } 083 } 084 }); 085 myTemporaryFiles.add(nextTemporaryFile); 086 } 087 } 088 } 089 } 090 } 091 } 092 } else { 093 myUncompressedFileDescriptors.add(next); 094 } 095 } 096 } catch (IOException e) { 097 throw new InternalErrorException(Msg.code(861) + e); 098 } 099 } 100 101 public boolean hasFile(String theFilename) { 102 return myUncompressedFileDescriptors.stream() 103 .map(t -> t.getFilename().replaceAll(".*[\\\\/]", "")) // Strip the path from the filename 104 .anyMatch(t -> t.equals(theFilename)); 105 } 106 107 @Override 108 public void close() { 109 for (File next : myTemporaryFiles) { 110 ourLog.info("Deleting temporary file: {}", next.getAbsolutePath()); 111 FileUtils.deleteQuietly(next); 112 } 113 } 114 115 List<ITermLoaderSvc.FileDescriptor> getUncompressedFileDescriptors() { 116 return myUncompressedFileDescriptors; 117 } 118 119 private List<String> notFound(List<String> theExpectedFilenameFragments) { 120 Set<String> foundFragments = new HashSet<>(); 121 for (String nextExpected : theExpectedFilenameFragments) { 122 for (ITermLoaderSvc.FileDescriptor next : myUncompressedFileDescriptors) { 123 if (next.getFilename().contains(nextExpected)) { 124 foundFragments.add(nextExpected); 125 break; 126 } 127 } 128 } 129 130 ArrayList<String> notFoundFileNameFragments = new ArrayList<>(theExpectedFilenameFragments); 131 notFoundFileNameFragments.removeAll(foundFragments); 132 return notFoundFileNameFragments; 133 } 134 135 void verifyMandatoryFilesExist(List<String> theExpectedFilenameFragments) { 136 List<String> notFound = notFound(theExpectedFilenameFragments); 137 if (!notFound.isEmpty()) { 138 throw new UnprocessableEntityException( 139 Msg.code(862) + "Could not find the following mandatory files in input: " + notFound); 140 } 141 } 142 143 void verifyOptionalFilesExist(List<String> theExpectedFilenameFragments) { 144 List<String> notFound = notFound(theExpectedFilenameFragments); 145 if (!notFound.isEmpty()) { 146 ourLog.warn("Could not find the following optional files: " + notFound); 147 } 148 } 149 150 boolean isOptionalFilesExist(List<String> theFileList) { 151 return notFound(theFileList).isEmpty(); 152 } 153 154 void verifyPartLinkFilesExist(List<String> theMultiPartLinkFiles, String theSinglePartLinkFile) { 155 List<String> notFoundMulti = notFound(theMultiPartLinkFiles); 156 List<String> notFoundSingle = notFound(Arrays.asList(theSinglePartLinkFile)); 157 // Expect all of the files in theMultiPartLinkFiles to be found and theSinglePartLinkFile to not be found, 158 // or none of the files in theMultiPartLinkFiles to be found and the SinglePartLinkFile to be found. 159 boolean multiPartFilesFound = notFoundMulti.isEmpty(); 160 boolean singlePartFilesFound = notFoundSingle.isEmpty(); 161 if (!LogicUtil.multiXor(multiPartFilesFound, singlePartFilesFound)) { 162 String msg; 163 if (!multiPartFilesFound && !singlePartFilesFound) { 164 msg = "Could not find any of the PartLink files: " + notFoundMulti + " nor " + notFoundSingle; 165 } else { 166 msg = 167 "Only either the single PartLink file or the split PartLink files can be present. Found both the single PartLink file, " 168 + theSinglePartLinkFile + ", and the split PartLink files: " + theMultiPartLinkFiles; 169 } 170 throw new UnprocessableEntityException(Msg.code(863) + msg); 171 } 172 } 173 174 private static class NonClosableBOMInputStream extends BOMInputStream { 175 NonClosableBOMInputStream(InputStream theWrap) { 176 super(theWrap); 177 } 178 179 @Override 180 public void close() { 181 // nothing 182 } 183 } 184}