Improve paging in JPA server for _history operation

This commit is contained in:
James Agnew 2017-02-08 21:42:17 -05:00
parent ef23b45d25
commit 0dd6364fe5
16 changed files with 147 additions and 71 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
[*.java]
charset = utf-8
indent_style = tab

View File

@ -201,7 +201,12 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
@Override @Override
public Integer preferredPageSize() { public Integer preferredPageSize() {
return null; return resources.preferredPageSize();
}
@Override
public String getUuid() {
return resources.getUuid();
} }
}; };
} }

View File

@ -0,0 +1,36 @@
package ca.uhn.fhir.rest.server;
import org.apache.commons.lang3.Validate;
public abstract class BasePagingProvider implements IPagingProvider {
private int myDefaultPageSize = 10;
private int myMaximumPageSize = 50;
public BasePagingProvider() {
super();
}
@Override
public int getDefaultPageSize() {
return myDefaultPageSize;
}
@Override
public int getMaximumPageSize() {
return myMaximumPageSize;
}
public BasePagingProvider setDefaultPageSize(int theDefaultPageSize) {
Validate.isTrue(theDefaultPageSize > 0, "size must be greater than 0");
myDefaultPageSize = theDefaultPageSize;
return this;
}
public BasePagingProvider setMaximumPageSize(int theMaximumPageSize) {
Validate.isTrue(theMaximumPageSize > 0, "size must be greater than 0");
myMaximumPageSize = theMaximumPageSize;
return this;
}
}

View File

@ -64,6 +64,11 @@ public class BundleProviders {
public Integer preferredPageSize() { public Integer preferredPageSize() {
return null; return null;
} }
@Override
public String getUuid() {
return null;
}
}; };
} }

View File

@ -25,11 +25,9 @@ import java.util.UUID;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
public class FifoMemoryPagingProvider implements IPagingProvider { public class FifoMemoryPagingProvider extends BasePagingProvider implements IPagingProvider {
private LinkedHashMap<String, IBundleProvider> myBundleProviders; private LinkedHashMap<String, IBundleProvider> myBundleProviders;
private int myDefaultPageSize=10;
private int myMaximumPageSize=50;
private int mySize; private int mySize;
public FifoMemoryPagingProvider(int theSize) { public FifoMemoryPagingProvider(int theSize) {
@ -39,33 +37,11 @@ public class FifoMemoryPagingProvider implements IPagingProvider {
myBundleProviders = new LinkedHashMap<String, IBundleProvider>(mySize); myBundleProviders = new LinkedHashMap<String, IBundleProvider>(mySize);
} }
@Override
public int getDefaultPageSize() {
return myDefaultPageSize;
}
@Override
public int getMaximumPageSize() {
return myMaximumPageSize;
}
@Override @Override
public synchronized IBundleProvider retrieveResultList(String theId) { public synchronized IBundleProvider retrieveResultList(String theId) {
return myBundleProviders.get(theId); return myBundleProviders.get(theId);
} }
public FifoMemoryPagingProvider setDefaultPageSize(int theDefaultPageSize) {
Validate.isTrue(theDefaultPageSize > 0, "size must be greater than 0");
myDefaultPageSize = theDefaultPageSize;
return this;
}
public FifoMemoryPagingProvider setMaximumPageSize(int theMaximumPageSize) {
Validate.isTrue(theMaximumPageSize > 0, "size must be greater than 0");
myMaximumPageSize = theMaximumPageSize;
return this;
}
@Override @Override
public synchronized String storeResultList(IBundleProvider theList) { public synchronized String storeResultList(IBundleProvider theList) {
while (myBundleProviders.size() > mySize) { while (myBundleProviders.size() > mySize) {

View File

@ -62,4 +62,15 @@ public interface IBundleProvider {
*/ */
InstantDt getPublished(); InstantDt getPublished();
/**
* Returns the UUID associated with this search. Note that this
* does not need to return a non-null value unless it a
* {@link IPagingProvider} is being used that requires UUIDs
* being returned.
* <p>
* Otherwise you may simply return {@code null}
* </p>
*/
public String getUuid();
} }

View File

@ -66,4 +66,9 @@ public class SimpleBundleProvider implements IBundleProvider {
return null; return null;
} }
@Override
public String getUuid() {
return null;
}
} }

View File

@ -56,20 +56,34 @@ public abstract class BaseSearchParamRegistry implements ISearchParamRegistry {
// nothing by default // nothing by default
} }
public Map<String, Map<String, RuntimeSearchParam>> getBuiltInSearchParams() { @Override
public Map<String, Map<String, RuntimeSearchParam>> getActiveSearchParams() {
return myBuiltInSearchParams; return myBuiltInSearchParams;
} }
@Override @Override
public Map<String,RuntimeSearchParam> getActiveSearchParams(String theResourceName) { public Map<String, RuntimeSearchParam> getActiveSearchParams(String theResourceName) {
Validate.notBlank(theResourceName, "theResourceName must not be blank or null"); Validate.notBlank(theResourceName, "theResourceName must not be blank or null");
return myBuiltInSearchParams.get(theResourceName); return myBuiltInSearchParams.get(theResourceName);
} }
@Override
public Collection<RuntimeSearchParam> getAllSearchParams(String theResourceName) {
Validate.notBlank(theResourceName, "theResourceName must not be null or blank");
Map<String, RuntimeSearchParam> map = myBuiltInSearchParams.get(theResourceName);
map = ObjectUtils.defaultIfNull(map, EMPTY_SP_MAP);
return Collections.unmodifiableCollection(map.values());
}
public Map<String, Map<String, RuntimeSearchParam>> getBuiltInSearchParams() {
return myBuiltInSearchParams;
}
@PostConstruct @PostConstruct
public void postConstruct() { public void postConstruct() {
Map<String, Map<String, RuntimeSearchParam>> resourceNameToSearchParams = new HashMap<String, Map<String,RuntimeSearchParam>>(); Map<String, Map<String, RuntimeSearchParam>> resourceNameToSearchParams = new HashMap<String, Map<String, RuntimeSearchParam>>();
for (IFhirResourceDao<?> nextDao : myDaos) { for (IFhirResourceDao<?> nextDao : myDaos) {
RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(nextDao.getResourceType()); RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(nextDao.getResourceType());
@ -85,13 +99,4 @@ public abstract class BaseSearchParamRegistry implements ISearchParamRegistry {
myBuiltInSearchParams = Collections.unmodifiableMap(resourceNameToSearchParams); myBuiltInSearchParams = Collections.unmodifiableMap(resourceNameToSearchParams);
} }
@Override
public Collection<RuntimeSearchParam> getAllSearchParams(String theResourceName) {
Validate.notBlank(theResourceName, "theResourceName must not be null or blank");
Map<String, RuntimeSearchParam> map = myBuiltInSearchParams.get(theResourceName);
map = ObjectUtils.defaultIfNull(map, EMPTY_SP_MAP);
return Collections.unmodifiableCollection(map.values());
}
} }

View File

@ -27,10 +27,12 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
public interface ISearchParamRegistry { public interface ISearchParamRegistry {
void forceRefresh();
Map<String, Map<String, RuntimeSearchParam>> getActiveSearchParams();
Map<String,RuntimeSearchParam> getActiveSearchParams(String theResourceName); Map<String,RuntimeSearchParam> getActiveSearchParams(String theResourceName);
Collection<RuntimeSearchParam> getAllSearchParams(String theResourceName); Collection<RuntimeSearchParam> getAllSearchParams(String theResourceName);
void forceRefresh();
} }

View File

@ -2309,6 +2309,11 @@ public class SearchBuilder {
public int size() { public int size() {
return myPids.size(); return myPids.size();
} }
@Override
public String getUuid() {
return null;
}
} }
} }

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.search;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
@ -30,10 +31,12 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IDao; import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
import ca.uhn.fhir.rest.server.BasePagingProvider;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IPagingProvider;
public class DatabaseBackedPagingProvider extends FifoMemoryPagingProvider { public class DatabaseBackedPagingProvider extends BasePagingProvider implements IPagingProvider {
@Autowired @Autowired
private PlatformTransactionManager thePlatformTransactionManager; private PlatformTransactionManager thePlatformTransactionManager;
@ -46,29 +49,36 @@ public class DatabaseBackedPagingProvider extends FifoMemoryPagingProvider {
@Autowired @Autowired
private IFhirSystemDao<?, ?> theDao; private IFhirSystemDao<?, ?> theDao;
/**
* Constructor
* @deprecated Use {@link DatabaseBackedPagingProvider} as this constructor has no purpose
*/
@Deprecated
public DatabaseBackedPagingProvider(int theSize) { public DatabaseBackedPagingProvider(int theSize) {
super(theSize); this();
}
/**
* Constructor
*/
public DatabaseBackedPagingProvider() {
super();
} }
@Override @Override
public synchronized IBundleProvider retrieveResultList(String theId) { public synchronized IBundleProvider retrieveResultList(String theId) {
IBundleProvider retVal = super.retrieveResultList(theId);
if (retVal == null) {
PersistedJpaBundleProvider provider = new PersistedJpaBundleProvider(theId, theDao); PersistedJpaBundleProvider provider = new PersistedJpaBundleProvider(theId, theDao);
if (!provider.ensureSearchEntityLoaded()) { if (!provider.ensureSearchEntityLoaded()) {
return null; return null;
} }
retVal = provider; return provider;
}
return retVal;
} }
@Override @Override
public synchronized String storeResultList(IBundleProvider theList) { public synchronized String storeResultList(IBundleProvider theList) {
if (theList instanceof PersistedJpaBundleProvider) { String uuid = theList.getUuid();
return ((PersistedJpaBundleProvider)theList).getSearchUuid(); Validate.notNull(uuid);
} return uuid;
return super.storeResultList(theList);
} }
} }

View File

@ -19,13 +19,7 @@ package ca.uhn.fhir.jpa.search;
* limitations under the License. * limitations under the License.
* #L% * #L%
*/ */
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.NoResultException; import javax.persistence.NoResultException;
@ -49,11 +43,7 @@ import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
import ca.uhn.fhir.jpa.entity.BaseHasResource; import ca.uhn.fhir.jpa.entity.*;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchResult;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IBundleProvider;
@ -220,7 +210,7 @@ public final class PersistedJpaBundleProvider implements IBundleProvider {
}); });
} }
public String getSearchUuid() { public String getUuid() {
return myUuid; return myUuid;
} }

View File

@ -63,6 +63,11 @@ public class SearchParamExtractorDstu3Test {
public Collection<RuntimeSearchParam> getAllSearchParams(String theResourceName) { public Collection<RuntimeSearchParam> getAllSearchParams(String theResourceName) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public Map<String, Map<String, RuntimeSearchParam>> getActiveSearchParams() {
throw new UnsupportedOperationException();
}
}; };
SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(ourCtx, ourValidationSupport, searchParamRegistry); SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(ourCtx, ourValidationSupport, searchParamRegistry);

View File

@ -575,6 +575,11 @@ public class SearchDstu2Test {
public int size() { public int size() {
return 0; return 0;
} }
@Override
public String getUuid() {
return null;
}
}; };
} }

View File

@ -138,6 +138,13 @@
JPA server did not return an OperationOutcome in the response for JPA server did not return an OperationOutcome in the response for
a normal delete operation. a normal delete operation.
</action> </action>
<action type="fix">
Fix an issue in JPA server where _history results were kept in memory instead
of being spooled to the database as they should be. Note that as a part of this fix
a new method was added to
<![CDATA[<code>IBundleProvider</code> called <code>getUuid()</code>]]>. This
method may return <![CDATA[<code>null</code>]]> in any current cases.
</action>
</release> </release>
<release version="2.2" date="2016-12-20"> <release version="2.2" date="2016-12-20">
<action type="add"> <action type="add">