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}