
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}