001/*-
002 * #%L
003 * HAPI FHIR - Core Library
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.util;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.rest.api.EncodingEnum;
025import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
026import com.google.common.base.Charsets;
027import jakarta.annotation.Nonnull;
028import org.apache.commons.io.IOUtils;
029import org.apache.commons.io.input.BOMInputStream;
030import org.hl7.fhir.instance.model.api.IBaseResource;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import java.io.IOException;
035import java.io.InputStream;
036import java.io.InputStreamReader;
037import java.io.Reader;
038import java.nio.charset.StandardCharsets;
039import java.util.function.Function;
040import java.util.zip.GZIPInputStream;
041
042/**
043 * Use this API with caution, it may change!
044 */
045public class ClasspathUtil {
046
047        private static final Logger ourLog = LoggerFactory.getLogger(ClasspathUtil.class);
048
049        /**
050         * Non instantiable
051         */
052        private ClasspathUtil() {
053                // nothing
054        }
055
056        /**
057         * Load a classpath resource, throw an {@link InternalErrorException} if not found
058         *
059         * @throws InternalErrorException If the resource can't be found
060         */
061        public static String loadResource(String theClasspath) throws InternalErrorException {
062                return loadResource(theClasspath, Function.identity());
063        }
064
065        /**
066         * Load a classpath resource, throw an {@link InternalErrorException} if not found
067         *
068         * @throws InternalErrorException If the resource can't be found
069         */
070        @Nonnull
071        public static InputStream loadResourceAsStream(String theClasspath) throws InternalErrorException {
072                String classpath = theClasspath;
073                if (classpath.startsWith("classpath:")) {
074                        classpath = classpath.substring("classpath:".length());
075                }
076
077                InputStream retVal = ClasspathUtil.class.getResourceAsStream(classpath);
078                if (retVal == null) {
079                        if (classpath.startsWith("/")) {
080                                retVal = ClasspathUtil.class.getResourceAsStream(classpath.substring(1));
081                        } else {
082                                retVal = ClasspathUtil.class.getResourceAsStream("/" + classpath);
083                        }
084                        if (retVal == null) {
085                                throw new InternalErrorException(Msg.code(1758) + "Unable to find classpath resource: " + classpath);
086                        }
087                }
088                return retVal;
089        }
090
091        public static Reader loadResourceAsReader(String theClasspath) {
092                return new InputStreamReader(loadResourceAsStream(theClasspath), StandardCharsets.UTF_8);
093        }
094
095        /**
096         * Load a classpath resource, throw an {@link InternalErrorException} if not found
097         */
098        @Nonnull
099        public static String loadResource(String theClasspath, Function<InputStream, InputStream> theStreamTransform) {
100                try (InputStream stream = loadResourceAsStream(theClasspath)) {
101                        InputStream newStream = theStreamTransform.apply(stream);
102                        return IOUtils.toString(newStream, Charsets.UTF_8);
103                } catch (IOException e) {
104                        throw new InternalErrorException(Msg.code(1759) + e);
105                }
106        }
107
108        @Nonnull
109        public static String loadCompressedResource(String theClasspath) {
110                Function<InputStream, InputStream> streamTransform = t -> {
111                        try {
112                                return new GZIPInputStream(t);
113                        } catch (IOException e) {
114                                throw new InternalErrorException(Msg.code(1760) + e);
115                        }
116                };
117                return loadResource(theClasspath, streamTransform);
118        }
119
120        /**
121         * Load a classpath resource, throw an {@link InternalErrorException} if not found
122         *
123         * @since 6.4.0
124         */
125        @Nonnull
126        public static <T extends IBaseResource> T loadCompressedResource(
127                        FhirContext theCtx, Class<T> theType, String theClasspath) {
128                String resource = loadCompressedResource(theClasspath);
129                return parseResource(theCtx, theType, resource);
130        }
131
132        @Nonnull
133        public static <T extends IBaseResource> T loadResource(FhirContext theCtx, Class<T> theType, String theClasspath) {
134                String raw = loadResource(theClasspath);
135                return parseResource(theCtx, theType, raw);
136        }
137
138        private static <T extends IBaseResource> T parseResource(FhirContext theCtx, Class<T> theType, String raw) {
139                return EncodingEnum.detectEncodingNoDefault(raw).newParser(theCtx).parseResource(theType, raw);
140        }
141
142        public static void close(InputStream theInput) {
143                try {
144                        if (theInput != null) {
145                                theInput.close();
146                        }
147                } catch (IOException e) {
148                        ourLog.debug("Closing InputStream threw exception", e);
149                }
150        }
151
152        public static Function<InputStream, InputStream> withBom() {
153                return t -> new BOMInputStream(t);
154        }
155
156        public static byte[] loadResourceAsByteArray(String theClasspath) {
157                InputStream stream = loadResourceAsStream(theClasspath);
158                try {
159                        return IOUtils.toByteArray(stream);
160                } catch (IOException e) {
161                        throw new InternalErrorException(Msg.code(1761) + e);
162                } finally {
163                        close(stream);
164                }
165        }
166}