private void encodeResourceToXmlStreamWriter( IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource) throws XMLStreamException, DataFormatException { String resourceId = null; if (theResource instanceof IResource) { // HAPI structs IResource iResource = (IResource) theResource; if (StringUtils.isNotBlank(iResource.getId().getValue())) { resourceId = iResource.getId().getIdPart(); } } else { // HL7 structs IAnyResource resource = (IAnyResource) theResource; if (StringUtils.isNotBlank(resource.getId())) { resourceId = resource.getId(); } } encodeResourceToXmlStreamWriter(theResource, theEventWriter, theIncludedResource, resourceId); }
@Override public IDeleteTyped resource(IResource theResource) { Validate.notNull(theResource, "theResource can not be null"); IdDt id = theResource.getId(); Validate.notNull(id, "theResource.getId() can not be null"); if (id.hasResourceType() == false || id.hasIdPart() == false) { throw new IllegalArgumentException( "theResource.getId() must contain a resource type and logical ID at a minimum (e.g. Patient/1234), found: " + id.getValue()); } myId = id; return this; }
@Override public MethodOutcome execute() { if (myResource == null) { myResource = parseResourceBody(myResourceBody); } if (myId == null) { myId = myResource.getId(); } if (myId == null || myId.hasIdPart() == false) { throw new InvalidRequestException( "No ID supplied for resource to update, can not invoke server"); } BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext); RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); final String resourceName = def.getName(); OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName); Map<String, List<String>> params = new HashMap<String, List<String>>(); return invoke(params, binding, invocation); }
@SuppressWarnings("unchecked") @Override public Object invokeServer( IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { /* * The design of HAPI's transaction method for DSTU1 support assumed that a transaction was just an update on a * bunch of resources (because that's what it was), but in DSTU2 transaction has become much more broad, so we * no longer hold the user's hand much here. */ if (myTransactionParamStyle == ParamStyle.RESOURCE_BUNDLE) { // This is the DSTU2 style Object response = invokeServerMethod(theServer, theRequest, theMethodParams); return response; } // Grab the IDs of all of the resources in the transaction List<IResource> resources; if (theMethodParams[myTransactionParamIndex] instanceof Bundle) { resources = ((Bundle) theMethodParams[myTransactionParamIndex]).toListOfResources(); } else { resources = (List<IResource>) theMethodParams[myTransactionParamIndex]; } IdentityHashMap<IResource, IdDt> oldIds = new IdentityHashMap<IResource, IdDt>(); for (IResource next : resources) { oldIds.put(next, next.getId()); } // Call the server implementation method Object response = invokeServerMethod(theServer, theRequest, theMethodParams); IBundleProvider retVal = toResourceList(response); /* * int offset = 0; if (retVal.size() != resources.size()) { if (retVal.size() > 0 && retVal.getResources(0, * 1).get(0) instanceof OperationOutcome) { offset = 1; } else { throw new * InternalErrorException("Transaction bundle contained " + resources.size() + * " entries, but server method response contained " + retVal.size() + " entries (must be the same)"); } } */ List<IBaseResource> retResources = retVal.getResources(0, retVal.size()); for (int i = 0; i < retResources.size(); i++) { IdDt oldId = oldIds.get(retResources.get(i)); IBaseResource newRes = retResources.get(i); if (newRes.getIdElement() == null || newRes.getIdElement().isEmpty()) { if (!(newRes instanceof BaseOperationOutcome)) { throw new InternalErrorException( "Transaction method returned resource at index " + i + " with no id specified - IResource#setId(IdDt)"); } } if (oldId != null && !oldId.isEmpty()) { if (!oldId.equals(newRes.getIdElement()) && newRes instanceof IResource) { ((IResource) newRes) .getResourceMetadata() .put(ResourceMetadataKeyEnum.PREVIOUS_ID, oldId); } } } return retVal; }
private void encodeResourceToXmlStreamWriter( IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, String theResourceId) throws XMLStreamException { if (!theContainedResource) { super.containResourcesForEncoding(theResource); } RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource); if (resDef == null) { throw new ConfigurationException("Unknown resource type: " + theResource.getClass()); } theEventWriter.writeStartElement(resDef.getName()); theEventWriter.writeDefaultNamespace(FHIR_NS); if (theResource instanceof IAnyResource) { // HL7.org Structures encodeCompositeElementToStreamWriter( theResource, theResource, theEventWriter, resDef, theContainedResource); } else { if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { // DSTU2+ IResource resource = (IResource) theResource; writeOptionalTagWithValue(theEventWriter, "id", theResourceId); InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); IdDt resourceId = resource.getId(); String versionIdPart = resourceId.getVersionIdPart(); if (isBlank(versionIdPart)) { versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource); } List<BaseCodingDt> securityLabels = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.SECURITY_LABELS); List<IdDt> profiles = extractMetadataListNotNull(resource, ResourceMetadataKeyEnum.PROFILES); TagList tags = ResourceMetadataKeyEnum.TAG_LIST.get(resource); if (ElementUtil.isEmpty(versionIdPart, updated, securityLabels, profiles) == false) { theEventWriter.writeStartElement("meta"); writeOptionalTagWithValue(theEventWriter, "versionId", versionIdPart); if (updated != null) { writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString()); } for (IdDt profile : profiles) { theEventWriter.writeStartElement("profile"); theEventWriter.writeAttribute("value", profile.getValue()); theEventWriter.writeEndElement(); } for (BaseCodingDt securityLabel : securityLabels) { theEventWriter.writeStartElement("security"); BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(securityLabel.getClass()); encodeCompositeElementChildrenToStreamWriter( resource, securityLabel, theEventWriter, def.getChildren(), theContainedResource); theEventWriter.writeEndElement(); } if (tags != null) { for (Tag tag : tags) { theEventWriter.writeStartElement("tag"); writeOptionalTagWithValue(theEventWriter, "system", tag.getScheme()); writeOptionalTagWithValue(theEventWriter, "code", tag.getTerm()); writeOptionalTagWithValue(theEventWriter, "display", tag.getLabel()); theEventWriter.writeEndElement(); } } theEventWriter.writeEndElement(); } if (theResource instanceof IBaseBinary) { IBaseBinary bin = (IBaseBinary) theResource; writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType()); writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64()); } else { encodeResourceToStreamWriterInDstu2Format( resDef, theResource, theResource, theEventWriter, resDef, theContainedResource); } } else { // DSTU1 if (theResourceId != null && theContainedResource) { theEventWriter.writeAttribute("id", theResourceId); } if (theResource instanceof IBaseBinary) { IBaseBinary bin = (IBaseBinary) theResource; if (bin.getContentType() != null) { theEventWriter.writeAttribute("contentType", bin.getContentType()); } theEventWriter.writeCharacters(bin.getContentAsBase64()); } else { encodeCompositeElementToStreamWriter( theResource, theResource, theEventWriter, resDef, theContainedResource); } } } theEventWriter.writeEndElement(); }
@Transactional(propagation = Propagation.REQUIRED) @Override public List<IResource> transaction( RequestDetails theRequestDetails, List<IResource> theResources) { ourLog.info("Beginning transaction with {} resources", theResources.size()); // Notify interceptors ActionRequestDetails requestDetails = new ActionRequestDetails(null, null); notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails); long start = System.currentTimeMillis(); Set<IdDt> allIds = new HashSet<IdDt>(); for (int i = 0; i < theResources.size(); i++) { IResource res = theResources.get(i); if (res.getId().hasIdPart() && !res.getId().hasResourceType() && !isPlaceholder(res.getId())) { res.setId(new IdDt(toResourceName(res.getClass()), res.getId().getIdPart())); } /* * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness */ if (isPlaceholder(res.getId())) { if (!allIds.add(res.getId())) { throw new InvalidRequestException( "Transaction bundle contains multiple resources with ID: " + res.getId()); } } else if (res.getId().hasResourceType() && res.getId().hasIdPart()) { IdDt nextId = res.getId().toUnqualifiedVersionless(); if (!allIds.add(nextId)) { throw new InvalidRequestException( "Transaction bundle contains multiple resources with ID: " + nextId); } } } FhirTerser terser = getContext().newTerser(); int creations = 0; int updates = 0; Map<IdDt, IdDt> idConversions = new HashMap<IdDt, IdDt>(); List<ResourceTable> persistedResources = new ArrayList<ResourceTable>(); List<IResource> retVal = new ArrayList<IResource>(); OperationOutcome oo = new OperationOutcome(); retVal.add(oo); Date updateTime = new Date(); for (int resourceIdx = 0; resourceIdx < theResources.size(); resourceIdx++) { IResource nextResource = theResources.get(resourceIdx); IdDt nextId = nextResource.getId(); if (nextId == null) { nextId = new IdDt(); } String resourceName = toResourceName(nextResource); BundleEntryTransactionMethodEnum nextResouceOperationIn = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(nextResource); if (nextResouceOperationIn == null && hasValue(ResourceMetadataKeyEnum.DELETED_AT.get(nextResource))) { nextResouceOperationIn = BundleEntryTransactionMethodEnum.DELETE; } String matchUrl = ResourceMetadataKeyEnum.LINK_SEARCH.get(nextResource); Set<Long> candidateMatches = null; if (StringUtils.isNotBlank(matchUrl)) { candidateMatches = processMatchUrl(matchUrl, nextResource.getClass()); } ResourceTable entity; if (nextResouceOperationIn == BundleEntryTransactionMethodEnum.POST) { entity = null; } else if (nextResouceOperationIn == BundleEntryTransactionMethodEnum.PUT || nextResouceOperationIn == BundleEntryTransactionMethodEnum.DELETE) { if (candidateMatches == null || candidateMatches.size() == 0) { if (nextId == null || StringUtils.isBlank(nextId.getIdPart())) { throw new InvalidRequestException( getContext() .getLocalizer() .getMessage( BaseHapiFhirSystemDao.class, "transactionOperationFailedNoId", nextResouceOperationIn.name())); } entity = tryToLoadEntity(nextId); if (entity == null) { if (nextResouceOperationIn == BundleEntryTransactionMethodEnum.PUT) { ourLog.debug( "Attempting to UPDATE resource with unknown ID '{}', will CREATE instead", nextId); } else if (candidateMatches == null) { throw new InvalidRequestException( getContext() .getLocalizer() .getMessage( BaseHapiFhirSystemDao.class, "transactionOperationFailedUnknownId", nextResouceOperationIn.name(), nextId)); } else { ourLog.debug("Resource with match URL [{}] already exists, will be NOOP", matchUrl); persistedResources.add(null); retVal.add(nextResource); continue; } } } else if (candidateMatches.size() == 1) { entity = loadFirstEntityFromCandidateMatches(candidateMatches); } else { throw new InvalidRequestException( getContext() .getLocalizer() .getMessage( BaseHapiFhirSystemDao.class, "transactionOperationWithMultipleMatchFailure", nextResouceOperationIn.name(), matchUrl, candidateMatches.size())); } } else if (nextId.isEmpty() || isPlaceholder(nextId)) { entity = null; } else { entity = tryToLoadEntity(nextId); } BundleEntryTransactionMethodEnum nextResouceOperationOut; if (entity == null) { nextResouceOperationOut = BundleEntryTransactionMethodEnum.POST; entity = toEntity(nextResource); entity.setUpdated(updateTime); entity.setPublished(updateTime); if (nextId.isEmpty() == false && "cid:".equals(nextId.getBaseUrl())) { ourLog.debug( "Resource in transaction has ID[{}], will replace with server assigned ID", nextId.getIdPart()); } else if (nextResouceOperationIn == BundleEntryTransactionMethodEnum.POST) { if (nextId.isEmpty() == false) { ourLog.debug( "Resource in transaction has ID[{}] but is marked for CREATE, will ignore ID", nextId.getIdPart()); } if (candidateMatches != null) { if (candidateMatches.size() == 1) { ourLog.debug("Resource with match URL [{}] already exists, will be NOOP", matchUrl); BaseHasResource existingEntity = loadFirstEntityFromCandidateMatches(candidateMatches); IResource existing = (IResource) toResource(existingEntity, false); persistedResources.add(null); retVal.add(existing); continue; } if (candidateMatches.size() > 1) { throw new InvalidRequestException( getContext() .getLocalizer() .getMessage( BaseHapiFhirSystemDao.class, "transactionOperationWithMultipleMatchFailure", BundleEntryTransactionMethodEnum.POST.name(), matchUrl, candidateMatches.size())); } } } else { createForcedIdIfNeeded(entity, nextId); } myEntityManager.persist(entity); if (entity.getForcedId() != null) { myEntityManager.persist(entity.getForcedId()); } creations++; ourLog.info( "Resource Type[{}] with ID[{}] does not exist, creating it", resourceName, nextId); } else { nextResouceOperationOut = nextResouceOperationIn; if (nextResouceOperationOut == null) { nextResouceOperationOut = BundleEntryTransactionMethodEnum.PUT; } updates++; ourLog.info("Resource Type[{}] with ID[{}] exists, updating it", resourceName, nextId); } persistedResources.add(entity); retVal.add(nextResource); ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(nextResource, nextResouceOperationOut); } ourLog.info("Flushing transaction to database"); myEntityManager.flush(); 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; if (entity == null) { newId = retVal.get(i + 1).getId().toUnqualifiedVersionless(); } else { 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 (nextId.toUnqualifiedVersionless().equals(newId)) { ourLog.info("Transaction resource ID[{}] is being updated", newId); } else { if (isPlaceholder(nextId)) { // nextId = new IdDt(resourceName, nextId.getIdPart()); ourLog.info("Transaction resource ID[{}] has been assigned new ID[{}]", nextId, newId); idConversions.put(nextId, newId); idConversions.put(new IdDt(resourceName + "/" + nextId.getValue()), newId); } } } } for (IResource nextResource : theResources) { List<BaseResourceReferenceDt> allRefs = terser.getAllPopulatedChildElementsOfType(nextResource, BaseResourceReferenceDt.class); for (BaseResourceReferenceDt nextRef : allRefs) { IdDt nextId = nextRef.getReference(); if (idConversions.containsKey(nextId)) { IdDt newId = idConversions.get(nextId); ourLog.info(" * Replacing resource ref {} with {}", nextId, newId); nextRef.setReference(newId); } else { ourLog.debug(" * Reference [{}] does not exist in bundle", nextId); } } } 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); if (table == null) { continue; } InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(resource); Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; if (deletedInstantOrNull == null && ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(resource) == BundleEntryTransactionMethodEnum.DELETE) { deletedTimestampOrNull = updateTime; ResourceMetadataKeyEnum.DELETED_AT.put(resource, new InstantDt(deletedTimestampOrNull)); } updateEntity(resource, table, table.getId() != null, deletedTimestampOrNull, updateTime); } long delay = System.currentTimeMillis() - start; ourLog.info( "Transaction completed in {}ms with {} creations and {} updates", new Object[] {delay, creations, updates}); oo.addIssue() .setSeverity(IssueSeverityEnum.INFORMATION) .setDetails( "Transaction completed in " + delay + "ms with " + creations + " creations and " + updates + " updates"); return retVal; }
protected String getPreferredId(IResource theResource, String theId) { if (isNotBlank(theId)) { return theId; } return theResource.getId().getIdPart(); }