001package ca.uhn.fhir.jpa.term;
002
003/*-
004 * #%L
005 * HAPI FHIR JPA Server
006 * %%
007 * Copyright (C) 2014 - 2021 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
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(e);
075                                                                                                        }
076                                                                                                }
077                                                                                        });
078                                                                                        myTemporaryFiles.add(nextTemporaryFile);
079                                                                                }
080                                                                        }
081                                                                }
082                                                        }
083                                                }
084                                        }
085                                } else {
086                                        myUncompressedFileDescriptors.add(next);
087                                }
088
089                        }
090                } catch (IOException e) {
091                        throw new InternalErrorException(e);
092                }
093
094        }
095
096        public boolean hasFile(String theFilename) {
097                return myUncompressedFileDescriptors
098                        .stream()
099                        .map(t -> t.getFilename().replaceAll(".*[\\\\/]", "")) // Strip the path from the filename
100                        .anyMatch(t -> t.equals(theFilename));
101        }
102
103        @Override
104        public void close() {
105                for (File next : myTemporaryFiles) {
106                        ourLog.info("Deleting temporary file: {}", next.getAbsolutePath());
107                        FileUtils.deleteQuietly(next);
108                }
109        }
110
111        List<ITermLoaderSvc.FileDescriptor> getUncompressedFileDescriptors() {
112                return myUncompressedFileDescriptors;
113        }
114
115        private List<String> notFound(List<String> theExpectedFilenameFragments) {
116                Set<String> foundFragments = new HashSet<>();
117                for (String nextExpected : theExpectedFilenameFragments) {
118                        for (ITermLoaderSvc.FileDescriptor next : myUncompressedFileDescriptors) {
119                                if (next.getFilename().contains(nextExpected)) {
120                                        foundFragments.add(nextExpected);
121                                        break;
122                                }
123                        }
124                }
125
126                ArrayList<String> notFoundFileNameFragments = new ArrayList<>(theExpectedFilenameFragments);
127                notFoundFileNameFragments.removeAll(foundFragments);
128                return notFoundFileNameFragments;
129        }
130
131        void verifyMandatoryFilesExist(List<String> theExpectedFilenameFragments) {
132                List<String> notFound = notFound(theExpectedFilenameFragments);
133                if (!notFound.isEmpty()) {
134                        throw new UnprocessableEntityException("Could not find the following mandatory files in input: " + notFound);
135                }
136        }
137
138        void verifyOptionalFilesExist(List<String> theExpectedFilenameFragments) {
139                List<String> notFound = notFound(theExpectedFilenameFragments);
140                if (!notFound.isEmpty()) {
141                        ourLog.warn("Could not find the following optional files: " + notFound);
142                }
143        }
144
145        void verifyPartLinkFilesExist(List<String> theMultiPartLinkFiles, String theSinglePartLinkFile) {
146                List<String> notFoundMulti = notFound(theMultiPartLinkFiles);
147                List<String> notFoundSingle = notFound(Arrays.asList(theSinglePartLinkFile));
148                // Expect all of the files in theMultiPartLinkFiles to be found and theSinglePartLinkFile to not be found,
149                // or none of the files in theMultiPartLinkFiles to be found and the SinglePartLinkFile to be found.
150                boolean multiPartFilesFound = notFoundMulti.isEmpty();
151                boolean singlePartFilesFound = notFoundSingle.isEmpty();
152                if (!LogicUtil.multiXor(multiPartFilesFound, singlePartFilesFound)) {
153                        String msg;
154                        if (!multiPartFilesFound && !singlePartFilesFound) {
155                                msg = "Could not find any of the PartLink files: " + notFoundMulti + " nor " + notFoundSingle;
156                        } else {
157                                msg = "Only either the single PartLink file or the split PartLink files can be present. Found both the single PartLink file, " + theSinglePartLinkFile + ", and the split PartLink files: " + theMultiPartLinkFiles;
158                        }
159                        throw new UnprocessableEntityException(msg);
160                }
161        }
162
163
164        private static class NonClosableBOMInputStream extends BOMInputStream {
165                NonClosableBOMInputStream(InputStream theWrap) {
166                        super(theWrap);
167                }
168
169                @Override
170                public void close() {
171                        // nothing
172                }
173        }
174}