Add ability for IDs to be client specified in JPA

This commit is contained in:
jamesagnew 2014-07-10 08:17:55 -04:00
parent 1b7b141396
commit 4d517aa76f
14 changed files with 342 additions and 143 deletions

View File

@ -96,7 +96,7 @@ public class IdDt extends BasePrimitive<String> {
public IdDt(@SimpleSetter.Parameter(name = "theId") String theValue) {
setValue(theValue);
}
/**
* Constructor
*
@ -106,7 +106,7 @@ public class IdDt extends BasePrimitive<String> {
* The ID (e.g. "123")
*/
public IdDt(String theResourceType, String theId) {
this(theResourceType,theId,null);
this(theResourceType, theId, null);
}
/**
@ -133,6 +133,14 @@ public class IdDt extends BasePrimitive<String> {
}
}
/**
* @deprecated Use {@link #getIdPartAsBigDecimal()} instead (this method was deprocated because its name is
* ambiguous)
*/
public BigDecimal asBigDecimal() {
return getIdPartAsBigDecimal();
}
/**
* Returns true if this IdDt matches the given IdDt in terms of resource type and ID, but ignores the URL base
*/
@ -224,15 +232,12 @@ public class IdDt extends BasePrimitive<String> {
return isNotBlank(getIdPart());
}
public boolean hasVersionIdPart() {
return isNotBlank(getVersionIdPart());
public boolean hasResourceType() {
return isNotBlank(myResourceType);
}
/**
* Returns <code>true</code> if the ID is a local reference (in other words, it begins with the '#' character)
*/
public boolean isLocal() {
return myUnqualifiedId != null && myUnqualifiedId.isEmpty() == false && myUnqualifiedId.charAt(0) == '#';
public boolean hasVersionIdPart() {
return isNotBlank(getVersionIdPart());
}
/**
@ -252,6 +257,13 @@ public class IdDt extends BasePrimitive<String> {
return true;
}
/**
* Returns <code>true</code> if the ID is a local reference (in other words, it begins with the '#' character)
*/
public boolean isLocal() {
return myUnqualifiedId != null && myUnqualifiedId.isEmpty() == false && myUnqualifiedId.charAt(0) == '#';
}
/**
* Copies the value from the given IdDt to <code>this</code> IdDt. It is generally not neccesary to use this method
* but it is provided for consistency with the rest of the API.
@ -388,7 +400,7 @@ public class IdDt extends BasePrimitive<String> {
*/
public IdDt withVersion(String theVersion) {
Validate.notBlank(theVersion, "Version may not be null or empty");
int i = myValue.indexOf(Constants.PARAM_HISTORY);
String value;
if (i > 1) {
@ -396,15 +408,8 @@ public class IdDt extends BasePrimitive<String> {
} else {
value = myValue;
}
return new IdDt(value + '/' + Constants.PARAM_HISTORY + '/' + theVersion);
}
/**
* @deprecated Use {@link #getIdPartAsBigDecimal()} instead (this method was deprocated because its name is ambiguous)
*/
public BigDecimal asBigDecimal() {
return getIdPartAsBigDecimal();
}
}

View File

@ -30,6 +30,10 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
@ -37,6 +41,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.entity.BaseHasResource;
import ca.uhn.fhir.jpa.entity.BaseTag;
import ca.uhn.fhir.jpa.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTag;
@ -73,6 +78,7 @@ import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.FhirTerser;
import com.google.common.base.Function;
@ -128,7 +134,7 @@ public abstract class BaseFhirDao implements IDao {
if (theResourceName != null) {
Predicate typePredicate = builder.equal(from.get("myResourceType"), theResourceName);
if (theResourceId != null) {
cq.where(typePredicate, builder.equal(from.get("myResourceId"), theResourceId.getIdPartAsLong()));
cq.where(typePredicate, builder.equal(from.get("myResourceId"), translateForcedIdToPid(theResourceId)));
} else {
cq.where(typePredicate);
}
@ -327,7 +333,7 @@ public abstract class BaseFhirDao implements IDao {
}
Long valueOf;
try {
valueOf = Long.valueOf(id);
valueOf = translateForcedIdToPid(nextValue.getReference());
} catch (Exception e) {
String resName = getContext().getResourceDefinition(type).getName();
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPath + " (this is an invalid ID, must be numeric on this server)");
@ -726,6 +732,9 @@ public abstract class BaseFhirDao implements IDao {
}
}
@Autowired
private PlatformTransactionManager myPlatformTransactionManager;
protected IBundleProvider history(String theResourceName, Long theId, Date theSince) {
final List<HistoryTuple> tuples = new ArrayList<HistoryTuple>();
@ -756,34 +765,40 @@ public abstract class BaseFhirDao implements IDao {
}
@Override
public List<IResource> getResources(int theFromIndex, int theToIndex) {
StopWatch timer = new StopWatch();
List<BaseHasResource> resEntities = Lists.newArrayList();
List<HistoryTuple> tupleSubList = tuples.subList(theFromIndex, theToIndex);
searchHistoryCurrentVersion(tupleSubList, resEntities);
ourLog.info("Loaded history from current versions in {} ms", timer.getMillisAndRestart());
searchHistoryHistory(tupleSubList, resEntities);
ourLog.info("Loaded history from previous versions in {} ms", timer.getMillisAndRestart());
Collections.sort(resEntities, new Comparator<BaseHasResource>() {
public List<IResource> getResources(final int theFromIndex, final int theToIndex) {
final StopWatch timer = new StopWatch();
TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
return template.execute(new TransactionCallback<List<IResource>>() {
@Override
public int compare(BaseHasResource theO1, BaseHasResource theO2) {
return theO2.getUpdated().getValue().compareTo(theO1.getUpdated().getValue());
public List<IResource> doInTransaction(TransactionStatus theStatus) {
List<BaseHasResource> resEntities = Lists.newArrayList();
List<HistoryTuple> tupleSubList = tuples.subList(theFromIndex, theToIndex);
searchHistoryCurrentVersion(tupleSubList, resEntities);
ourLog.info("Loaded history from current versions in {} ms", timer.getMillisAndRestart());
searchHistoryHistory(tupleSubList, resEntities);
ourLog.info("Loaded history from previous versions in {} ms", timer.getMillisAndRestart());
Collections.sort(resEntities, new Comparator<BaseHasResource>() {
@Override
public int compare(BaseHasResource theO1, BaseHasResource theO2) {
return theO2.getUpdated().getValue().compareTo(theO1.getUpdated().getValue());
}
});
int limit = theToIndex - theFromIndex;
if (resEntities.size() > limit) {
resEntities = resEntities.subList(0, limit);
}
ArrayList<IResource> retVal = new ArrayList<IResource>();
for (BaseHasResource next : resEntities) {
retVal.add(toResource(next));
}
return retVal;
}
});
int limit = theToIndex - theFromIndex;
if (resEntities.size() > limit) {
resEntities = resEntities.subList(0, limit);
}
ArrayList<IResource> retVal = new ArrayList<IResource>();
for (BaseHasResource next : resEntities) {
retVal.add(toResource(next));
}
return retVal;
});
}
@Override
@ -867,6 +882,7 @@ public abstract class BaseFhirDao implements IDao {
for (Tag next : tagList) {
TagDefinition tag = getTag(next.getScheme(), next.getTerm(), next.getLabel());
theEntity.addTag(tag);
theEntity.setHasTags(true);
}
}
@ -886,11 +902,48 @@ public abstract class BaseFhirDao implements IDao {
return retVal;
}
protected void createForcedIdIfNeeded(ResourceTable entity, IdDt id) {
if (id.isEmpty() == false && id.hasIdPart()) {
if (isValidPid(id)) {
return;
}
ForcedId fid = new ForcedId();
fid.setForcedId(id.getIdPart());
fid.setResource(entity);
entity.setForcedId(fid);
}
}
protected IResource toResource(BaseHasResource theEntity) {
RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType());
return toResource(type.getImplementingClass(), theEntity);
}
protected Long translateForcedIdToPid(IdDt theId) {
if (isValidPid(theId)) {
return theId.getIdPartAsLong();
} else {
TypedQuery<ForcedId> q = myEntityManager.createNamedQuery("Q_GET_FORCED_ID", ForcedId.class);
q.setParameter("ID", theId.getIdPart());
try {
return q.getSingleResult().getResourcePid();
} catch (NoResultException e) {
throw new ResourceNotFoundException(theId);
}
}
}
protected boolean isValidPid(IdDt theId) {
String idPart = theId.getIdPart();
for (int i = 0; i < idPart.length(); i++) {
char nextChar = idPart.charAt(i);
if (nextChar < '0' || nextChar > '9') {
return false;
}
}
return true;
}
protected <T extends IResource> T toResource(Class<T> theResourceType, BaseHasResource theEntity) {
String resourceText = null;
switch (theEntity.getEncoding()) {
@ -908,7 +961,9 @@ public abstract class BaseFhirDao implements IDao {
IParser parser = theEntity.getEncoding().newParser(getContext());
T retVal = parser.parseResource(theResourceType, resourceText);
retVal.setId(theEntity.getIdDt());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, theEntity.getVersion());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, theEntity.getPublished());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, theEntity.getUpdated());
@ -922,7 +977,7 @@ public abstract class BaseFhirDao implements IDao {
}
Collection<? extends BaseTag> tags = theEntity.getTags();
if (tags.size() > 0) {
if (theEntity.isHasTags()) {
TagList tagList = new TagList();
for (BaseTag next : tags) {
tagList.add(new Tag(next.getTag().getScheme(), next.getTag().getTerm(), next.getTag().getLabel()));
@ -1050,7 +1105,7 @@ public abstract class BaseFhirDao implements IDao {
myEntityManager.flush();
if (theResource != null) {
theResource.setId(new IdDt(entity.getResourceType(), entity.getId().toString(), Long.toString(entity.getVersion())));
theResource.setId(entity.getIdDt());
}
return entity;

View File

@ -29,8 +29,11 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.ConfigurationException;
@ -66,12 +69,12 @@ import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.QualifiedDateParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.FhirTerser;
@Transactional(propagation = Propagation.REQUIRED)
@ -375,8 +378,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed ("
+ ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
}
String likeExpression = normalizeString(rawSearchTerm);
@ -434,12 +436,10 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed ("
+ ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system);
throw new InvalidRequestException("Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system);
}
if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH
+ "): " + code);
throw new InvalidRequestException("Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code);
}
ArrayList<Predicate> singleCodePredicates = (new ArrayList<Predicate>());
@ -512,6 +512,8 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
}
entity.setHasTags(true);
TagDefinition def = getTag(theScheme, theTerm, theLabel);
BaseTag newEntity = entity.addTag(def);
@ -525,6 +527,13 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
ResourceTable entity = new ResourceTable();
entity.setResourceType(toResourceName(theResource));
if (theResource.getId().isEmpty() == false) {
if (isValidPid(theResource.getId())) {
throw new UnprocessableEntityException("This server cannot create an entity with a numeric ID - Numeric IDs are server assigned");
}
createForcedIdIfNeeded(entity, theResource.getId());
}
updateEntity(theResource, entity, false, false);
MethodOutcome outcome = toMethodOutcome(entity);
@ -568,8 +577,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
final T current = currentTmp;
String querySring = "SELECT count(h) FROM ResourceHistoryTable h " + "WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE" + " AND h.myUpdated < :END"
+ (theSince != null ? " AND h.myUpdated >= :SINCE" : "");
String querySring = "SELECT count(h) FROM ResourceHistoryTable h " + "WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE" + " AND h.myUpdated < :END" + (theSince != null ? " AND h.myUpdated >= :SINCE" : "");
TypedQuery<Long> countQuery = myEntityManager.createQuery(querySring, Long.class);
countQuery.setParameter("PID", theId.getIdPartAsLong());
countQuery.setParameter("RESTYPE", resourceType);
@ -607,9 +615,8 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
retVal.add(current);
}
TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery(
"SELECT h FROM ResourceHistoryTable h WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE AND h.myUpdated < :END "
+ (theSince != null ? " AND h.myUpdated >= :SINCE" : "") + " ORDER BY h.myUpdated ASC", ResourceHistoryTable.class);
TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery("SELECT h FROM ResourceHistoryTable h WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE AND h.myUpdated < :END " + (theSince != null ? " AND h.myUpdated >= :SINCE" : "")
+ " ORDER BY h.myUpdated ASC", ResourceHistoryTable.class);
q.setParameter("PID", theId.getIdPartAsLong());
q.setParameter("RESTYPE", resourceType);
q.setParameter("END", end.getValue(), TemporalType.TIMESTAMP);
@ -655,8 +662,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]");
}
if (sp.getParamType() != SearchParamTypeEnum.TOKEN) {
throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName
+ "] is not a token type, only token is supported");
throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "] is not a token type, only token is supported");
}
}
@ -678,7 +684,8 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
@Override
public BaseHasResource readEntity(IdDt theId) {
BaseHasResource entity = myEntityManager.find(ResourceTable.class, theId.getIdPartAsLong());
Long pid = translateForcedIdToPid(theId);
BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid);
if (theId.hasVersionIdPart()) {
if (entity.getVersion() != theId.getVersionIdPartAsLong()) {
entity = null;
@ -687,8 +694,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
if (entity == null) {
if (theId.hasVersionIdPart()) {
TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery(
"SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class);
TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery("SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class);
q.setParameter("RID", theId.getIdPartAsLong());
q.setParameter("RTYP", myResourceName);
q.setParameter("RVER", theId.getVersionIdPartAsLong());
@ -702,7 +708,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
private ResourceTable readEntityLatestVersion(IdDt theId) {
ResourceTable entity = myEntityManager.find(ResourceTable.class, theId.getIdPartAsLong());
ResourceTable entity = myEntityManager.find(ResourceTable.class, translateForcedIdToPid(theId));
if (entity == null) {
throw new ResourceNotFoundException(theId);
}
@ -723,6 +729,10 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
}
if (entity.getTags().isEmpty()) {
entity.setHasTags(false);
}
myEntityManager.merge(entity);
}
@ -770,48 +780,54 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
@Override
public List<IResource> getResources(int theFromIndex, int theToIndex) {
List<Long> pidsSubList = pids.subList(theFromIndex, theToIndex);
public List<IResource> getResources(final int theFromIndex, final int theToIndex) {
TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager);
return template.execute(new TransactionCallback<List<IResource>>() {
@Override
public List<IResource> doInTransaction(TransactionStatus theStatus) {
List<Long> pidsSubList = pids.subList(theFromIndex, theToIndex);
// Execute the query and make sure we return distinct results
List<IResource> retVal = new ArrayList<IResource>();
loadResourcesByPid(pidsSubList, retVal);
// Execute the query and make sure we return distinct results
List<IResource> retVal = new ArrayList<IResource>();
loadResourcesByPid(pidsSubList, retVal);
// Load _include resources
if (theParams.getIncludes() != null && theParams.getIncludes().isEmpty() == false) {
Set<IdDt> includePids = new HashSet<IdDt>();
FhirTerser t = getContext().newTerser();
for (Include next : theParams.getIncludes()) {
for (IResource nextResource : retVal) {
assert myResourceType.isAssignableFrom(nextResource.getClass());
// Load _include resources
if (theParams.getIncludes() != null && theParams.getIncludes().isEmpty() == false) {
Set<IdDt> includePids = new HashSet<IdDt>();
FhirTerser t = getContext().newTerser();
for (Include next : theParams.getIncludes()) {
for (IResource nextResource : retVal) {
assert myResourceType.isAssignableFrom(nextResource.getClass());
List<Object> values = t.getValues(nextResource, next.getValue());
for (Object object : values) {
if (object == null) {
continue;
List<Object> values = t.getValues(nextResource, next.getValue());
for (Object object : values) {
if (object == null) {
continue;
}
if (!(object instanceof ResourceReferenceDt)) {
throw new InvalidRequestException("Path '" + next.getValue() + "' produced non ResourceReferenceDt value: " + object.getClass());
}
ResourceReferenceDt rr = (ResourceReferenceDt) object;
if (rr.getReference().isEmpty()) {
continue;
}
if (rr.getReference().isLocal()) {
continue;
}
includePids.add(rr.getReference().toUnqualified());
}
}
if (!(object instanceof ResourceReferenceDt)) {
throw new InvalidRequestException("Path '" + next.getValue() + "' produced non ResourceReferenceDt value: " + object.getClass());
}
ResourceReferenceDt rr = (ResourceReferenceDt) object;
if (rr.getReference().isEmpty()) {
continue;
}
if (rr.getReference().isLocal()) {
continue;
}
includePids.add(rr.getReference().toUnqualified());
}
if (!includePids.isEmpty()) {
ourLog.info("Loading {} included resources", includePids.size());
loadResourcesById(includePids, retVal);
}
}
}
if (!includePids.isEmpty()) {
ourLog.info("Loading {} included resources", includePids.size());
loadResourcesById(includePids, retVal);
return retVal;
}
}
return retVal;
});
}
@Override
@ -888,7 +904,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
for (IQueryParameterType next : nextValue) {
String value = next.getValueAsQueryToken();
IdDt valueId = new IdDt(value);
long valueLong = valueId.getIdPartAsLong();
long valueLong = translateForcedIdToPid(valueId);
joinPids.add(valueLong);
}
if (joinPids.isEmpty()) {
@ -964,7 +980,8 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
/**
* If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to share the same value.
* If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to
* share the same value.
*/
public void setSecondaryPrimaryKeyParamName(String theSecondaryPrimaryKeyParamName) {
mySecondaryPrimaryKeyParamName = theSecondaryPrimaryKeyParamName;
@ -972,7 +989,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
private MethodOutcome toMethodOutcome(final ResourceTable entity) {
MethodOutcome outcome = new MethodOutcome();
outcome.setId(new IdDt(entity.getResourceType() + '/' + entity.getId() + '/' + Constants.PARAM_HISTORY + '/' + entity.getVersion()));
outcome.setId(entity.getIdDt());
outcome.setVersionId(new IdDt(entity.getVersion()));
return outcome;
}

View File

@ -23,6 +23,7 @@ import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.FhirTerser;
public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao {
@ -37,15 +38,22 @@ public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao {
ourLog.info("Beginning transaction with {} resources", theResources.size());
long start = System.currentTimeMillis();
for (int i =0; i <theResources.size();i++) {
IResource res = theResources.get(i);
if(res.getId().hasIdPart() && !res.getId().hasResourceType()) {
res.setId(new IdDt(toResourceName(res.getClass()), res.getId().getIdPart()));
}
}
FhirTerser terser = getContext().newTerser();
int creations = 0;
int updates = 0;
Map<IdDt, IdDt> idConversions = new HashMap<IdDt, IdDt>();
List<ResourceTable> persistedResources = new ArrayList<ResourceTable>();
for (IResource nextResource : theResources) {
IdDt nextId = nextResource.getId();
if (nextId == null) {
@ -63,16 +71,23 @@ public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao {
ResourceTable entity;
if (nextId.isEmpty()) {
entity = null;
} else if (!nextId.isIdPartValidLong()) {
entity = null;
} else {
entity = myEntityManager.find(ResourceTable.class, nextId.getIdPartAsLong());
try {
Long pid = translateForcedIdToPid(nextId);
entity = myEntityManager.find(ResourceTable.class, pid);
} catch (ResourceNotFoundException e) {
entity = null;
}
}
if (entity == null) {
entity = toEntity(nextResource);
createForcedIdIfNeeded(entity, nextId);
myEntityManager.persist(entity);
// myEntityManager.flush();
if (entity.getForcedId() != null) {
myEntityManager.persist(entity.getForcedId());
}
// myEntityManager.flush();
creations++;
ourLog.info("Resource Type[{}] with ID[{}] does not exist, creating it", resourceName, nextId);
} else {
@ -84,28 +99,31 @@ public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao {
}
ourLog.info("Flushing transaction to database");
myEntityManager.flush();
for (int i = 0; i < persistedResources.size();i++) {
for (int i = 0; i < persistedResources.size(); i++) {
ResourceTable entity = persistedResources.get(i);
String resourceName = toResourceName(theResources.get(i));
IdDt nextId = theResources.get(i).getId();
IdDt newId = new IdDt(resourceName + '/' + entity.getId());
IdDt newId = entity.getIdDt().toUnqualifiedVersionless();
if (nextId == null || nextId.isEmpty()) {
ourLog.info("Transaction resource (with no preexisting ID) has been assigned new ID[{}]", nextId, newId);
} else if (newId.equals(entity.getId())) {
ourLog.info("Transaction resource ID[{}] is being updated", newId);
} else {
if (!nextId.getIdPart().startsWith("#")) {
nextId = new IdDt(resourceName + '/' + nextId.getIdPart());
ourLog.info("Transaction resource ID[{}] has been assigned new ID[{}]", nextId, newId);
idConversions.put(nextId, newId);
if (nextId.toUnqualifiedVersionless().equals(entity.getIdDt().toUnqualifiedVersionless())) {
ourLog.info("Transaction resource ID[{}] is being updated", newId);
} else {
if (!nextId.getIdPart().startsWith("#")) {
nextId = new IdDt(resourceName + '/' + nextId.getIdPart());
ourLog.info("Transaction resource ID[{}] has been assigned new ID[{}]", nextId, newId);
idConversions.put(nextId, newId);
}
}
}
}
for (IResource nextResource : theResources) {
List<ResourceReferenceDt> allRefs = terser.getAllPopulatedChildElementsOfType(nextResource, ResourceReferenceDt.class);
for (ResourceReferenceDt nextRef : allRefs) {
@ -120,15 +138,17 @@ public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao {
}
}
ourLog.info("Re-flushing updated resource references and extracting search criteria");
for (int i = 0; i < theResources.size(); i++) {
IResource resource = theResources.get(i);
ResourceTable table = persistedResources.get(i);
updateEntity(resource, table, table.getId() != null, false);
}
long delay = System.currentTimeMillis() - start;
ourLog.info("Transaction completed in {}ms with {} creations and {} updates", new Object[] {delay, creations, updates});
ourLog.info("Transaction completed in {}ms with {} creations and {} updates", new Object[] { delay, creations, updates });
notifyWriteCompleted();
}
@ -149,7 +169,7 @@ public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao {
Root<?> from = cq.from(ResourceTable.class);
cq.multiselect(from.get("myResourceType").as(String.class), builder.count(from.get("myResourceType")).as(Long.class));
cq.groupBy(from.get("myResourceType"));
TypedQuery<Tuple> q = myEntityManager.createQuery(cq);
Map<String, Long> retVal = new HashMap<String, Long>();

View File

@ -6,8 +6,11 @@ import java.util.Date;
import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.MappedSuperclass;
import javax.persistence.OneToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@ -26,6 +29,13 @@ public abstract class BaseHasResource {
@Column(name = "RES_ENCODING", nullable = false, length = 5)
@Enumerated(EnumType.STRING)
private ResourceEncodingEnum myEncoding;
@OneToOne(optional = true, fetch = FetchType.EAGER, cascade = {}, orphanRemoval = false)
@JoinColumn(name = "FORCED_ID_PID")
private ForcedId myForcedId;
@Column(name = "HAS_TAGS", nullable = false)
private boolean myHasTags;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "RES_PUBLISHED", nullable = false)
@ -52,6 +62,10 @@ public abstract class BaseHasResource {
return myEncoding;
}
public ForcedId getForcedId() {
return myForcedId;
}
public abstract IdDt getIdDt();
public InstantDt getPublished() {
@ -76,6 +90,10 @@ public abstract class BaseHasResource {
public abstract long getVersion();
public boolean isHasTags() {
return myHasTags;
}
public void setDeleted(Date theDate) {
myDeleted = theDate;
}
@ -84,6 +102,14 @@ public abstract class BaseHasResource {
myEncoding = theEncoding;
}
public void setForcedId(ForcedId theForcedId) {
myForcedId = theForcedId;
}
public void setHasTags(boolean theHasTags) {
myHasTags = theHasTags;
}
public void setPublished(Date thePublished) {
myPublished = thePublished;
}

View File

@ -0,0 +1,68 @@
package ca.uhn.fhir.jpa.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@Entity()
@Table(name = "HFJ_FORCED_ID", uniqueConstraints = { @UniqueConstraint(name = "IDX_FORCEDID", columnNames = { "FORCED_ID" }) })
@NamedQueries(@NamedQuery(name = "Q_GET_FORCED_ID", query = "SELECT f FROM ForcedId f WHERE myForcedId = :ID"))
public class ForcedId {
public static final int MAX_FORCED_ID_LENGTH = 100;
@Column(name = "FORCED_ID", nullable = false, length = MAX_FORCED_ID_LENGTH, updatable = false)
private String myForcedId;
@GeneratedValue(strategy = GenerationType.AUTO)
@Id
@Column(name = "PID")
private Long myId;
@JoinColumn(name = "RESOURCE_PID", nullable = false, updatable = false)
@OneToOne()
private ResourceTable myResource;
@Column(name = "RESOURCE_PID", nullable = false, updatable = false, insertable=false)
private Long myResourcePid;
public String getForcedId() {
return myForcedId;
}
public ResourceTable getResource() {
return myResource;
}
public Long getResourcePid() {
if (myResourcePid==null) {
return myResource.getId();
}
return myResourcePid;
}
public void setForcedId(String theForcedId) {
myForcedId = theForcedId;
}
public void setResource(ResourceTable theResource) {
myResource = theResource;
}
public void setResourcePid(Long theResourcePid) {
myResourcePid = theResourcePid;
}
public void setResourcePid(ResourceTable theResourcePid) {
myResource = theResourcePid;
}
}

View File

@ -42,7 +42,7 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
@Column(name = "RES_VER", nullable = false)
private Long myResourceVersion;
@OneToMany(mappedBy = "myResourceHistory", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@OneToMany(mappedBy = "myResourceHistory", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Collection<ResourceHistoryTag> myTags;
public void addTag(ResourceHistoryTag theTag) {
@ -69,7 +69,8 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
@Override
public IdDt getIdDt() {
return new IdDt(getResourceType() + '/' + getResourceId() + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
Object id = getForcedId()==null? getResourceId() : getForcedId().getForcedId();
return new IdDt(getResourceType() + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + getVersion());
}
public Long getResourceId() {

View File

@ -73,7 +73,7 @@ public class ResourceTable extends BaseHasResource implements Serializable {
@Column(name = "RES_TYPE", length = RESTYPE_LEN)
private String myResourceType;
@OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Collection<ResourceTag> myTags;
@Column(name = "RES_VER")
@ -91,7 +91,8 @@ public class ResourceTable extends BaseHasResource implements Serializable {
}
public IdDt getIdDt() {
return new IdDt(myResourceType + '/' + myId + '/' + Constants.PARAM_HISTORY + '/' + myVersion);
Object id = getForcedId() == null ? myId : getForcedId().getForcedId();
return new IdDt(myResourceType + '/' + id + '/' + Constants.PARAM_HISTORY + '/' + myVersion);
}
public Collection<ResourceIndexedSearchParamDate> getParamsDate() {
@ -258,6 +259,7 @@ public class ResourceTable extends BaseHasResource implements Serializable {
retVal.setEncoding(getEncoding());
retVal.setResource(getResource());
retVal.setDeleted(getDeleted());
retVal.setForcedId(getForcedId());
for (ResourceTag next : getTags()) {
retVal.addTag(next);

View File

@ -241,15 +241,15 @@ public class FhirSystemDaoTest {
ourSystemDao.transaction(Arrays.asList((IResource) patient, obs));
long patientId = Long.parseLong(patient.getId().getIdPart());
long patientVersion = Long.parseLong(patient.getId().getVersionIdPart());
long obsId = Long.parseLong(obs.getId().getIdPart());
long obsVersion = Long.parseLong(obs.getId().getVersionIdPart());
String patientId = (patient.getId().getIdPart());
String patientVersion = (patient.getId().getVersionIdPart());
String obsId = (obs.getId().getIdPart());
String obsVersion = (obs.getId().getVersionIdPart());
assertThat(patientId, greaterThan(0L));
assertEquals(patientVersion, 1L);
assertThat(obsId, greaterThan(patientId));
assertEquals(obsVersion, 1L);
// assertThat(patientId, greaterThan(0L));
// assertEquals(patientVersion, 1L);
// assertThat(obsId, greaterThan(patientId));
// assertEquals(obsVersion, 1L);
// Try to search
@ -272,15 +272,15 @@ public class FhirSystemDaoTest {
ourSystemDao.transaction(Arrays.asList((IResource) patient, obs));
long patientId2 = Long.parseLong(patient.getId().getIdPart());
long patientVersion2 = Long.parseLong(patient.getId().getVersionIdPart());
long obsId2 = Long.parseLong(obs.getId().getIdPart());
long obsVersion2 = Long.parseLong(obs.getId().getVersionIdPart());
String patientId2 = (patient.getId().getIdPart());
String patientVersion2 = (patient.getId().getVersionIdPart());
String obsId2 = (obs.getId().getIdPart());
String obsVersion2 = (obs.getId().getVersionIdPart());
assertEquals(patientId, patientId2);
assertEquals(patientVersion2, 2L);
assertEquals(patientVersion2, "2");
assertEquals(obsId, obsId2);
assertEquals(obsVersion2, 2L);
assertEquals(obsVersion2, "2");
}

View File

@ -8,6 +8,7 @@
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- <class>ca.uhn.fhir.jpa.entity.PatientResourceTable</class> -->
<class>ca.uhn.fhir.jpa.entity.ForcedId</class>
<class>ca.uhn.fhir.jpa.entity.ResourceTable</class>
<class>ca.uhn.fhir.jpa.entity.ResourceHistoryTable</class>
<class>ca.uhn.fhir.jpa.entity.ResourceHistoryTag</class>

View File

@ -6,6 +6,8 @@
<persistence-unit name="FHIR_UT" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>ca.uhn.fhir.jpa.entity.ForcedId</class>
<class>ca.uhn.fhir.jpa.entity.ResourceHistoryTable</class>
<class>ca.uhn.fhir.jpa.entity.ResourceHistoryTag</class>
<class>ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate</class>

View File

@ -6,6 +6,8 @@
<persistence-unit name="FHIR_UT" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>ca.uhn.fhir.jpa.entity.ForcedId</class>
<class>ca.uhn.fhir.jpa.entity.ResourceHistoryTable</class>
<class>ca.uhn.fhir.jpa.entity.ResourceHistoryTag</class>
<class>ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate</class>

View File

@ -14,7 +14,7 @@
<value>home , UHN/HAPI Server , http://fhirtest.uhn.ca/base</value>
<value>hi , Health Intersections , http://fhir.healthintersections.com.au/open</value>
<value>furore , Spark - Furore Reference Server , http://spark.furore.com/fhir</value>
<value>blaze , Blaze (Orion Health) , https://his-medicomp-gateway.orionhealth.com/blaze/fhir</value>
<value>blaze , Blaze (Orion Health) , https://fhir.orionhealth.com</value>
<value>oridashi , Oridashi , http://demo.oridashi.com.au:8190</value>
</list>
</property>

View File

@ -7,7 +7,7 @@
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-1395874-5', 'auto');
ga('create', 'UA-1395874-6', 'auto');
ga('require', 'displayfeatures');
ga('require', 'linkid', 'linkid.js');
ga('send', 'pageview');