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}