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.*; 034import java.util.ArrayList; 035import java.util.Arrays; 036import java.util.HashSet; 037import java.util.List; 038import java.util.Set; 039import java.util.zip.ZipEntry; 040import java.util.zip.ZipInputStream; 041 042public class LoadedFileDescriptors implements Closeable { 043 private static final Logger ourLog = LoggerFactory.getLogger(LoadedFileDescriptors.class); 044 private List<File> myTemporaryFiles = new ArrayList<>(); 045 private List<ITermLoaderSvc.FileDescriptor> myUncompressedFileDescriptors = new ArrayList<>(); 046 047 LoadedFileDescriptors(List<ITermLoaderSvc.FileDescriptor> theFileDescriptors) { 048 try { 049 for (ITermLoaderSvc.FileDescriptor next : theFileDescriptors) { 050 if (next.getFilename().toLowerCase().endsWith(".zip")) { 051 ourLog.info("Uncompressing {} into temporary files", next.getFilename()); 052 try (InputStream inputStream = next.getInputStream()) { 053 try (BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) { 054 try (ZipInputStream zis = new ZipInputStream(bufferedInputStream)) { 055 for (ZipEntry nextEntry; (nextEntry = zis.getNextEntry()) != null; ) { 056 try (BOMInputStream fis = new NonClosableBOMInputStream(zis)) { 057 File nextTemporaryFile = File.createTempFile("hapifhir", ".tmp"); 058 ourLog.info("Creating temporary file: {}", nextTemporaryFile.getAbsolutePath()); 059 nextTemporaryFile.deleteOnExit(); 060 try (FileOutputStream fos = new FileOutputStream(nextTemporaryFile, false)) { 061 IOUtils.copy(fis, fos); 062 String nextEntryFileName = nextEntry.getName(); 063 myUncompressedFileDescriptors.add(new ITermLoaderSvc.FileDescriptor() { 064 @Override 065 public String getFilename() { 066 return nextEntryFileName; 067 } 068 069 @Override 070 public InputStream getInputStream() { 071 try { 072 return new FileInputStream(nextTemporaryFile); 073 } catch (FileNotFoundException e) { 074 throw new InternalErrorException(Msg.code(860) + e); 075 } 076 } 077 }); 078 myTemporaryFiles.add(nextTemporaryFile); 079 } 080 } 081 } 082 } 083 } 084 } 085 } else { 086 myUncompressedFileDescriptors.add(next); 087 } 088 } 089 } catch (IOException e) { 090 throw new InternalErrorException(Msg.code(861) + e); 091 } 092 } 093 094 public boolean hasFile(String theFilename) { 095 return myUncompressedFileDescriptors.stream() 096 .map(t -> t.getFilename().replaceAll(".*[\\\\/]", "")) // Strip the path from the filename 097 .anyMatch(t -> t.equals(theFilename)); 098 } 099 100 @Override 101 public void close() { 102 for (File next : myTemporaryFiles) { 103 ourLog.info("Deleting temporary file: {}", next.getAbsolutePath()); 104 FileUtils.deleteQuietly(next); 105 } 106 } 107 108 List<ITermLoaderSvc.FileDescriptor> getUncompressedFileDescriptors() { 109 return myUncompressedFileDescriptors; 110 } 111 112 private List<String> notFound(List<String> theExpectedFilenameFragments) { 113 Set<String> foundFragments = new HashSet<>(); 114 for (String nextExpected : theExpectedFilenameFragments) { 115 for (ITermLoaderSvc.FileDescriptor next : myUncompressedFileDescriptors) { 116 if (next.getFilename().contains(nextExpected)) { 117 foundFragments.add(nextExpected); 118 break; 119 } 120 } 121 } 122 123 ArrayList<String> notFoundFileNameFragments = new ArrayList<>(theExpectedFilenameFragments); 124 notFoundFileNameFragments.removeAll(foundFragments); 125 return notFoundFileNameFragments; 126 } 127 128 void verifyMandatoryFilesExist(List<String> theExpectedFilenameFragments) { 129 List<String> notFound = notFound(theExpectedFilenameFragments); 130 if (!notFound.isEmpty()) { 131 throw new UnprocessableEntityException( 132 Msg.code(862) + "Could not find the following mandatory files in input: " + notFound); 133 } 134 } 135 136 void verifyOptionalFilesExist(List<String> theExpectedFilenameFragments) { 137 List<String> notFound = notFound(theExpectedFilenameFragments); 138 if (!notFound.isEmpty()) { 139 ourLog.warn("Could not find the following optional files: " + notFound); 140 } 141 } 142 143 boolean isOptionalFilesExist(List<String> theFileList) { 144 return notFound(theFileList).isEmpty(); 145 } 146 147 void verifyPartLinkFilesExist(List<String> theMultiPartLinkFiles, String theSinglePartLinkFile) { 148 List<String> notFoundMulti = notFound(theMultiPartLinkFiles); 149 List<String> notFoundSingle = notFound(Arrays.asList(theSinglePartLinkFile)); 150 // Expect all of the files in theMultiPartLinkFiles to be found and theSinglePartLinkFile to not be found, 151 // or none of the files in theMultiPartLinkFiles to be found and the SinglePartLinkFile to be found. 152 boolean multiPartFilesFound = notFoundMulti.isEmpty(); 153 boolean singlePartFilesFound = notFoundSingle.isEmpty(); 154 if (!LogicUtil.multiXor(multiPartFilesFound, singlePartFilesFound)) { 155 String msg; 156 if (!multiPartFilesFound && !singlePartFilesFound) { 157 msg = "Could not find any of the PartLink files: " + notFoundMulti + " nor " + notFoundSingle; 158 } else { 159 msg = 160 "Only either the single PartLink file or the split PartLink files can be present. Found both the single PartLink file, " 161 + theSinglePartLinkFile + ", and the split PartLink files: " + theMultiPartLinkFiles; 162 } 163 throw new UnprocessableEntityException(Msg.code(863) + msg); 164 } 165 } 166 167 private static class NonClosableBOMInputStream extends BOMInputStream { 168 NonClosableBOMInputStream(InputStream theWrap) { 169 super(theWrap); 170 } 171 172 @Override 173 public void close() { 174 // nothing 175 } 176 } 177}