001/*-
002 * #%L
003 * HAPI FHIR Storage api
004 * %%
005 * Copyright (C) 2014 - 2023 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 org.apache.commons.io.IOUtils;
028import org.apache.commons.io.input.CountingInputStream;
029import org.hl7.fhir.instance.model.api.IIdType;
030
031import javax.annotation.Nonnull;
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 storeBlob(IIdType theResourceId, String theBlobIdOrNull, String theContentType,
058                                                                                         InputStream theInputStream, RequestDetails theRequestDetails) throws IOException {
059
060                HashingInputStream hashingIs = createHashingInputStream(theInputStream);
061                CountingInputStream countingIs = createCountingInputStream(hashingIs);
062
063                byte[] bytes = IOUtils.toByteArray(countingIs);
064                String id = super.provideIdForNewBlob(theBlobIdOrNull, bytes, theRequestDetails, theContentType);
065                String key = toKey(theResourceId, id);
066                theInputStream.close();
067                myDataMap.put(key, bytes);
068                StoredDetails storedDetails = new StoredDetails(id, countingIs.getByteCount(), theContentType, hashingIs, new Date());
069                myDetailsMap.put(key, storedDetails);
070                return storedDetails;
071        }
072
073        @Override
074        public StoredDetails fetchBlobDetails(IIdType theResourceId, String theBlobId) {
075                String key = toKey(theResourceId, theBlobId);
076                return myDetailsMap.get(key);
077        }
078
079        @Override
080        public boolean writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException {
081                String key = toKey(theResourceId, theBlobId);
082                byte[] bytes = myDataMap.get(key);
083                if (bytes == null) {
084                        return false;
085                }
086                theOutputStream.write(bytes);
087                return true;
088        }
089
090        @Override
091        public void expungeBlob(IIdType theResourceId, String theBlobId) {
092                String key = toKey(theResourceId, theBlobId);
093                myDataMap.remove(key);
094                myDetailsMap.remove(key);
095        }
096
097        @Override
098        public byte[] fetchBlob(IIdType theResourceId, String theBlobId) {
099                String key = toKey(theResourceId, theBlobId);
100                return myDataMap.get(key);
101        }
102
103        private String toKey(IIdType theResourceId, String theBlobId) {
104                return theBlobId + '-' + theResourceId.toUnqualifiedVersionless().getValue();
105        }
106
107        public void clear() {
108                myDetailsMap.clear();
109                myDataMap.clear();
110        }
111}