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}