001/*-
002 * #%L
003 * HAPI FHIR Storage api
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.binstore;
021
022import ca.uhn.fhir.jpa.binary.api.IBinaryStorageSvc;
023import ca.uhn.fhir.jpa.binary.api.StoredDetails;
024import ca.uhn.fhir.jpa.binary.svc.BaseBinaryStorageSvcImpl;
025import ca.uhn.fhir.rest.api.server.RequestDetails;
026import com.google.common.hash.HashingInputStream;
027import jakarta.annotation.Nonnull;
028import org.apache.commons.io.IOUtils;
029import org.apache.commons.io.input.CountingInputStream;
030import org.hl7.fhir.instance.model.api.IIdType;
031
032import java.io.IOException;
033import java.io.InputStream;
034import java.io.OutputStream;
035import java.util.Date;
036import java.util.concurrent.ConcurrentHashMap;
037
038/**
039 * Purely in-memory implementation of binary storage service. This is really
040 * only appropriate for testing, since it doesn't persist anywhere and is
041 * limited by the amount of available RAM.
042 */
043public class MemoryBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
044
045        private final ConcurrentHashMap<String, byte[]> myDataMap = new ConcurrentHashMap<>();
046        private final ConcurrentHashMap<String, StoredDetails> myDetailsMap = new ConcurrentHashMap<>();
047
048        /**
049         * Constructor
050         */
051        public MemoryBinaryStorageSvcImpl() {
052                super();
053        }
054
055        @Nonnull
056        @Override
057        public StoredDetails storeBinaryContent(
058                        IIdType theResourceId,
059                        String theBlobIdOrNull,
060                        String theContentType,
061                        InputStream theInputStream,
062                        RequestDetails theRequestDetails)
063                        throws IOException {
064
065                HashingInputStream hashingIs = createHashingInputStream(theInputStream);
066                CountingInputStream countingIs = createCountingInputStream(hashingIs);
067
068                byte[] bytes = IOUtils.toByteArray(countingIs);
069                String id = super.provideIdForNewBinaryContent(theBlobIdOrNull, bytes, theRequestDetails, theContentType);
070                String key = toKey(theResourceId, id);
071                theInputStream.close();
072                myDataMap.put(key, bytes);
073                StoredDetails storedDetails =
074                                new StoredDetails(id, countingIs.getByteCount(), theContentType, hashingIs, new Date());
075                myDetailsMap.put(key, storedDetails);
076                return storedDetails;
077        }
078
079        @Override
080        public StoredDetails fetchBinaryContentDetails(IIdType theResourceId, String theBlobId) {
081                String key = toKey(theResourceId, theBlobId);
082                return myDetailsMap.get(key);
083        }
084
085        @Override
086        public boolean writeBinaryContent(IIdType theResourceId, String theBlobId, OutputStream theOutputStream)
087                        throws IOException {
088                String key = toKey(theResourceId, theBlobId);
089                byte[] bytes = myDataMap.get(key);
090                if (bytes == null) {
091                        return false;
092                }
093                theOutputStream.write(bytes);
094                return true;
095        }
096
097        @Override
098        public void expungeBinaryContent(IIdType theResourceId, String theBlobId) {
099                String key = toKey(theResourceId, theBlobId);
100                myDataMap.remove(key);
101                myDetailsMap.remove(key);
102        }
103
104        @Override
105        public byte[] fetchBinaryContent(IIdType theResourceId, String theBlobId) {
106                String key = toKey(theResourceId, theBlobId);
107                return myDataMap.get(key);
108        }
109
110        private String toKey(IIdType theResourceId, String theBlobId) {
111                return theBlobId + '-' + theResourceId.toUnqualifiedVersionless().getValue();
112        }
113
114        public void clear() {
115                myDetailsMap.clear();
116                myDataMap.clear();
117        }
118}