diff --git a/example-projects/hapi-fhir-jpaserver-cds-example/pom.xml b/example-projects/hapi-fhir-jpaserver-cds-example/pom.xml index 1164963c477..e4f0fb505b7 100644 --- a/example-projects/hapi-fhir-jpaserver-cds-example/pom.xml +++ b/example-projects/hapi-fhir-jpaserver-cds-example/pom.xml @@ -181,8 +181,8 @@ test - com.phloc - phloc-schematron + com.helger + ph-schematron Saxon-HE diff --git a/example-projects/hapi-fhir-jpaserver-cds-example/src/main/java/ca/uhn/fhir/jpa/cds/example/FhirServerConfig.java b/example-projects/hapi-fhir-jpaserver-cds-example/src/main/java/ca/uhn/fhir/jpa/cds/example/FhirServerConfig.java index e12d5906b79..70f6b4a926f 100644 --- a/example-projects/hapi-fhir-jpaserver-cds-example/src/main/java/ca/uhn/fhir/jpa/cds/example/FhirServerConfig.java +++ b/example-projects/hapi-fhir-jpaserver-cds-example/src/main/java/ca/uhn/fhir/jpa/cds/example/FhirServerConfig.java @@ -60,7 +60,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu3 { @Bean() public LocalContainerEntityManagerFactoryBean entityManagerFactory() { - LocalContainerEntityManagerFactoryBean retVal = entityManagerFactory(); + LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); retVal.setPersistenceUnitName("HAPI_PU"); retVal.setDataSource(dataSource()); retVal.setJpaProperties(jpaProperties()); diff --git a/example-projects/hapi-fhir-jpaserver-dynamic/pom.xml b/example-projects/hapi-fhir-jpaserver-dynamic/pom.xml index e4d5ebb0734..7277e817e6a 100644 --- a/example-projects/hapi-fhir-jpaserver-dynamic/pom.xml +++ b/example-projects/hapi-fhir-jpaserver-dynamic/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-fhir 3.4.0-SNAPSHOT - ../pom.xml + ../../pom.xml hapi-fhir-jpaserver-dynamic diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index 4ec22a88188..587ca67c7d3 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -100,8 +100,8 @@ javax.mail-api - com.phloc - phloc-commons + com.helger + ph-schematron commons-logging @@ -140,6 +140,7 @@ changelog.txt javac.bat about.html + changelog.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index 435a965f6ea..7e0641341c3 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -45,27 +45,14 @@ - com.phloc - phloc-schematron + com.helger + ph-schematron true - - - com.phloc - phloc-commons + com.helger + ph-commons true - - - - com.google.code.findbugs - annotations - - - javax.xml.bind - jaxb-api - ${jaxb_api_version} - - - com.sun.xml.bind - jaxb-core - ${jaxb_core_version} - - - com.sun.xml.bind - jaxb-impl - ${jaxb_core_version} + org.glassfish.jaxb + jaxb-runtime + ${jaxb_runtime_version} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 6ea8c368ea3..4e5d499a599 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -73,7 +73,10 @@ import org.hl7.fhir.r4.model.BaseResource; import org.hl7.fhir.r4.model.Bundle.HTTPVerb; import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.Reference; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -100,7 +103,7 @@ import static org.apache.commons.lang3.StringUtils.*; @SuppressWarnings("WeakerAccess") @Repository -public abstract class BaseHapiFhirDao implements IDao { +public abstract class BaseHapiFhirDao implements IDao, ApplicationContextAware { public static final long INDEX_STATUS_INDEXED = 1L; public static final long INDEX_STATUS_INDEXING_FAILED = 2L; @@ -186,9 +189,6 @@ public abstract class BaseHapiFhirDao implements IDao { @Autowired private PlatformTransactionManager myPlatformTransactionManager; @Autowired - private List> myResourceDaos; - private Map, IFhirResourceDao> myResourceTypeToDao; - @Autowired private ISearchDao mySearchDao; @Autowired private ISearchParamExtractor mySearchParamExtractor; @@ -200,6 +200,8 @@ public abstract class BaseHapiFhirDao implements IDao { private ISearchResultDao mySearchResultDao; @Autowired private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao; + private ApplicationContext myApplicationContext; + private Map, IFhirResourceDao> myResourceTypeToDao; protected void clearRequestAsProcessingSubRequest(ServletRequestDetails theRequestDetails) { if (theRequestDetails != null) { @@ -695,6 +697,29 @@ public abstract class BaseHapiFhirDao implements IDao { return retVal; } + + @SuppressWarnings("unchecked") + public IFhirResourceDao getDao(Class theType) { + if (myResourceTypeToDao == null) { + Map, IFhirResourceDao> theResourceTypeToDao = new HashMap<>(); + Map daos = myApplicationContext.getBeansOfType(IFhirResourceDao.class, false, false); + for (IFhirResourceDao next : daos.values()) { + theResourceTypeToDao.put(next.getResourceType(), next); + } + + if (this instanceof IFhirResourceDao) { + IFhirResourceDao thiz = (IFhirResourceDao) this; + theResourceTypeToDao.put(thiz.getResourceType(), thiz); + } + + myResourceTypeToDao = theResourceTypeToDao; + } + + IFhirResourceDao dao = (IFhirResourceDao) myResourceTypeToDao.get(theType); + return dao; + } + + protected Set extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) { return mySearchParamExtractor.extractSearchParamCoords(theEntity, theResource); } @@ -896,6 +921,11 @@ public abstract class BaseHapiFhirDao implements IDao { return myConfig; } + @Override + public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException { + myApplicationContext = theApplicationContext; + } + public void setConfig(DaoConfig theConfig) { myConfig = theConfig; } @@ -922,26 +952,6 @@ public abstract class BaseHapiFhirDao implements IDao { } } - @SuppressWarnings("unchecked") - public IFhirResourceDao getDao(Class theType) { - if (myResourceTypeToDao == null) { - Map, IFhirResourceDao> theResourceTypeToDao = new HashMap<>(); - for (IFhirResourceDao next : myResourceDaos) { - theResourceTypeToDao.put(next.getResourceType(), next); - } - - if (this instanceof IFhirResourceDao) { - IFhirResourceDao thiz = (IFhirResourceDao) this; - theResourceTypeToDao.put(thiz.getResourceType(), thiz); - } - - myResourceTypeToDao = theResourceTypeToDao; - } - - IFhirResourceDao dao = (IFhirResourceDao) myResourceTypeToDao.get(theType); - return dao; - } - public IResourceIndexedCompositeStringUniqueDao getResourceIndexedCompositeStringUniqueDao() { return myResourceIndexedCompositeStringUniqueDao; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java index 70fe17ac5da..144f62c77e6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java @@ -159,7 +159,7 @@ public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao getResourceCounts() { Map retVal = new HashMap<>(); @@ -172,7 +172,7 @@ public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao getResourceCountsFromCache() { @@ -265,7 +265,7 @@ public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao implements ISearchParamRegistry { +public abstract class BaseSearchParamRegistry implements ISearchParamRegistry, ApplicationContextAware { private static final int MAX_MANAGED_PARAM_COUNT = 10000; private static final Logger ourLog = LoggerFactory.getLogger(BaseSearchParamRegistry.class); @@ -49,12 +52,12 @@ public abstract class BaseSearchParamRegistry implemen private volatile Map, List>> myActiveParamNamesToUniqueSearchParams = Collections.emptyMap(); @Autowired private FhirContext myCtx; - @Autowired - private Collection> myDaos; + private Collection> myResourceDaos; private volatile Map> myActiveSearchParams; @Autowired private DaoConfig myDaoConfig; private volatile long myLastRefresh; + private ApplicationContext myApplicationContext; public BaseSearchParamRegistry() { super(); @@ -212,7 +215,13 @@ public abstract class BaseSearchParamRegistry implemen public void postConstruct() { Map> resourceNameToSearchParams = new HashMap<>(); - for (IFhirResourceDao nextDao : myDaos) { + myResourceDaos = new ArrayList<>(); + Map daos = myApplicationContext.getBeansOfType(IFhirResourceDao.class, false, false); + for (IFhirResourceDao next : daos.values()) { + myResourceDaos.add(next); + } + + for (IFhirResourceDao nextDao : myResourceDaos) { RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(nextDao.getResourceType()); String nextResourceName = nextResDef.getName(); HashMap nameToParam = new HashMap<>(); @@ -321,6 +330,11 @@ public abstract class BaseSearchParamRegistry implemen refreshCacheIfNecessary(); } + @Override + public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException { + myApplicationContext = theApplicationContext; + } + protected abstract RuntimeSearchParam toRuntimeSp(SP theNextSp); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java index 7b6e38baeea..e9deeca33da 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java @@ -33,6 +33,8 @@ import ca.uhn.fhir.model.primitive.CodeDt; import org.apache.commons.lang3.time.DateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.Arrays; @@ -58,6 +60,7 @@ public class FhirResourceDaoSearchParameterDstu2 extends FhirResourceDaoDstu2 { Bundle subRequestBundle = new Bundle(); subRequestBundle.setType(BundleTypeEnum.TRANSACTION); subRequestBundle.addEntry(nextRequestEntry); - - Bundle subResponseBundle = transaction((ServletRequestDetails) theRequestDetails, subRequestBundle, "Batch sub-request"); - return subResponseBundle; + return transaction((ServletRequestDetails) theRequestDetails, subRequestBundle, "Batch sub-request"); } }; BaseServerResponseException caughtEx; try { - Bundle nextResponseBundle = txTemplate.execute(callback); + Bundle nextResponseBundle; + if (nextRequestEntry.getRequest().getMethodElement().getValueAsEnum() == HTTPVerbEnum.GET) { + // Don't process GETs in a transaction because they'll + // create their own + nextResponseBundle = callback.doInTransaction(null); + } else { + nextResponseBundle = txTemplate.execute(callback); + } caughtEx = null; Entry subResponseEntry = nextResponseBundle.getEntry().get(0); @@ -156,13 +162,17 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { return batch(theRequestDetails, theRequest); } - if (transactionType == null) { - String message = "Transactiion Bundle did not specify valid Bundle.type, assuming " + BundleTypeEnum.TRANSACTION.getCode(); + return doTransaction(theRequestDetails, theRequest, theActionName, transactionType); + } + + private Bundle doTransaction(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, BundleTypeEnum theTransactionType) { + if (theTransactionType == null) { + String message = "Transaction Bundle did not specify valid Bundle.type, assuming " + BundleTypeEnum.TRANSACTION.getCode(); ourLog.warn(message); - transactionType = BundleTypeEnum.TRANSACTION; + theTransactionType = BundleTypeEnum.TRANSACTION; } - if (transactionType != BundleTypeEnum.TRANSACTION) { - throw new InvalidRequestException("Unable to process transaction where incoming Bundle.type = " + transactionType.getCode()); + if (theTransactionType != BundleTypeEnum.TRANSACTION) { + throw new InvalidRequestException("Unable to process transaction where incoming Bundle.type = " + theTransactionType.getCode()); } ourLog.info("Beginning {} with {} resources", theActionName, theRequest.getEntry().size()); @@ -186,7 +196,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { */ Bundle response = new Bundle(); List getEntries = new ArrayList(); - IdentityHashMap originalRequestOrder = new IdentityHashMap(); + IdentityHashMap originalRequestOrder = new IdentityHashMap(); for (int i = 0; i < theRequest.getEntry().size(); i++) { originalRequestOrder.put(theRequest.getEntry().get(i), i); response.addEntry(); @@ -203,225 +213,13 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { Set updatedEntities = new HashSet<>(); /* - * Loop through the request and process any entries of type - * PUT, POST or DELETE + * Handle: GET/PUT/POST */ - for (int i = 0; i < theRequest.getEntry().size(); i++) { - - if (i % 100 == 0) { - ourLog.debug("Processed {} non-GET entries out of {}", i, theRequest.getEntry().size()); - } - - Entry nextReqEntry = theRequest.getEntry().get(i); - IResource res = nextReqEntry.getResource(); - IdDt nextResourceId = null; - if (res != null) { - - nextResourceId = res.getId(); - - if (!nextResourceId.hasIdPart()) { - if (isNotBlank(nextReqEntry.getFullUrl())) { - nextResourceId = new IdDt(nextReqEntry.getFullUrl()); - } - } - - if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+\\:.*") && !isPlaceholder(nextResourceId)) { - throw new InvalidRequestException("Invalid placeholder ID found: " + nextResourceId.getIdPart() + " - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'"); - } - - if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !isPlaceholder(nextResourceId)) { - nextResourceId = new IdDt(toResourceName(res.getClass()), nextResourceId.getIdPart()); - res.setId(nextResourceId); - } - - /* - * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness - */ - if (isPlaceholder(nextResourceId)) { - if (!allIds.add(nextResourceId)) { - throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId)); - } - } else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) { - IdDt nextId = nextResourceId.toUnqualifiedVersionless(); - if (!allIds.add(nextId)) { - throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId)); - } - } - - } - - HTTPVerbEnum verb = nextReqEntry.getRequest().getMethodElement().getValueAsEnum(); - if (verb == null) { - throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionEntryHasInvalidVerb", nextReqEntry.getRequest().getMethod())); - } - - String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null; - Entry nextRespEntry = response.getEntry().get(originalRequestOrder.get(nextReqEntry)); - - switch (verb) { - case POST: { - // CREATE - @SuppressWarnings("rawtypes") - IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass()); - res.setId((String) null); - DaoMethodOutcome outcome; - outcome = resourceDao.create(res, nextReqEntry.getRequest().getIfNoneExist(), false, theRequestDetails); - handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res); - entriesToProcess.put(nextRespEntry, outcome.getEntity()); - if (outcome.getCreated() == false) { - nonUpdatedEntities.add(outcome.getEntity()); - } - break; - } - case DELETE: { - // DELETE - String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); - UrlParts parts = UrlUtil.parseUrl(url); - ca.uhn.fhir.jpa.dao.IFhirResourceDao dao = toDao(parts, verb.getCode(), url); - int status = Constants.STATUS_HTTP_204_NO_CONTENT; - if (parts.getResourceId() != null) { - DaoMethodOutcome outcome = dao.delete(new IdDt(parts.getResourceType(), parts.getResourceId()), deleteConflicts, theRequestDetails); - if (outcome.getEntity() != null) { - deletedResources.add(outcome.getId().toUnqualifiedVersionless()); - entriesToProcess.put(nextRespEntry, outcome.getEntity()); - } - } else { - DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(parts.getResourceType() + '?' + parts.getParams(), deleteConflicts, theRequestDetails); - List allDeleted = deleteOutcome.getDeletedEntities(); - for (ResourceTable deleted : allDeleted) { - deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless()); - } - if (allDeleted.isEmpty()) { - status = Constants.STATUS_HTTP_404_NOT_FOUND; - } - } - - nextRespEntry.getResponse().setStatus(toStatusString(status)); - break; - } - case PUT: { - // UPDATE - @SuppressWarnings("rawtypes") - IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass()); - - DaoMethodOutcome outcome; - - String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); - - UrlParts parts = UrlUtil.parseUrl(url); - if (isNotBlank(parts.getResourceId())) { - res.setId(new IdDt(parts.getResourceType(), parts.getResourceId())); - outcome = resourceDao.update(res, null, false, theRequestDetails); - } else { - res.setId((String) null); - outcome = resourceDao.update(res, parts.getResourceType() + '?' + parts.getParams(), false, theRequestDetails); - } - - if (outcome.getCreated() == Boolean.FALSE) { - updatedEntities.add(outcome.getEntity()); - } - - handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res); - entriesToProcess.put(nextRespEntry, outcome.getEntity()); - break; - } - case GET: - break; - } - } - - /* - * Make sure that there are no conflicts from deletions. E.g. we can't delete something - * if something else has a reference to it.. Unless the thing that has a reference to it - * was also deleted as a part of this transaction, which is why we check this now at the - * end. - */ - - deleteConflicts.removeIf(next -> deletedResources.contains(next.getTargetId().toVersionless())); - validateDeleteConflictsEmptyOrThrowException(deleteConflicts); - - /* - * Perform ID substitutions and then index each resource we have saved - */ - - FhirTerser terser = getContext().newTerser(); - for (DaoMethodOutcome nextOutcome : idToPersistedOutcome.values()) { - IResource nextResource = (IResource) nextOutcome.getResource(); - if (nextResource == null) { - continue; - } - - // References - List allRefs = terser.getAllPopulatedChildElementsOfType(nextResource, BaseResourceReferenceDt.class); - for (BaseResourceReferenceDt nextRef : allRefs) { - IdDt nextId = nextRef.getReference(); - if (!nextId.hasIdPart()) { - continue; - } - if (idSubstitutions.containsKey(nextId)) { - IdDt newId = idSubstitutions.get(nextId); - ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId); - nextRef.setReference(newId); - } else { - ourLog.debug(" * Reference [{}] does not exist in bundle", nextId); - } - } - - // URIs - List allUris = terser.getAllPopulatedChildElementsOfType(nextResource, UriDt.class); - for (UriDt nextRef : allUris) { - if (nextRef instanceof IIdType) { - continue; // No substitution on the resource ID itself! - } - IdDt nextUriString = new IdDt(nextRef.getValueAsString()); - if (idSubstitutions.containsKey(nextUriString)) { - IdDt newId = idSubstitutions.get(nextUriString); - ourLog.debug(" * Replacing resource ref {} with {}", nextUriString, newId); - nextRef.setValue(newId.getValue()); - } else { - ourLog.debug(" * Reference [{}] does not exist in bundle", nextUriString); - } - } - - - InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(nextResource); - Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; - if (updatedEntities.contains(nextOutcome.getEntity())) { - updateInternal(theRequestDetails, nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource()); - } else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) { - updateEntity(theRequestDetails, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, updateTime, false, true); - } - } - - myEntityManager.flush(); - - /* - * Double check we didn't allow any duplicates we shouldn't have - */ - for (Entry nextEntry : theRequest.getEntry()) { - if (nextEntry.getRequest().getMethodElement().getValueAsEnum() == HTTPVerbEnum.POST) { - String matchUrl = nextEntry.getRequest().getIfNoneExist(); - if (isNotBlank(matchUrl)) { - IFhirResourceDao resourceDao = getDao(nextEntry.getResource().getClass()); - Set val = resourceDao.processMatchUrl(matchUrl); - if (val.size() > 1) { - throw new InvalidRequestException( - "Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?"); - } - } - } - } - - for (IdDt next : allIds) { - IdDt replacement = idSubstitutions.get(next); - if (replacement == null) { - continue; - } - if (replacement.equals(next)) { - continue; - } - ourLog.debug("Placeholder resource ID \"{}\" was replaced with permanent ID \"{}\"", next, replacement); - } + TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); + txTemplate.execute(t->{ + handleTransactionWriteOperations(theRequestDetails, theRequest, theActionName, updateTime, allIds, idSubstitutions, idToPersistedOutcome, response, originalRequestOrder, deletedResources, deleteConflicts, entriesToProcess, nonUpdatedEntities, updatedEntities); + return null; + }); /* * Loop through the request and process any entries of type GET @@ -447,7 +245,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { for (NameValuePair next : parameters) { paramValues.put(next.getName(), next.getValue()); } - for (java.util.Map.Entry> nextParamEntry : paramValues.asMap().entrySet()) { + for (Map.Entry> nextParamEntry : paramValues.asMap().entrySet()) { String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]); requestDetails.addParameter(nextParamEntry.getKey(), nextValue); } @@ -490,10 +288,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { } - ourLog.info("Flushing context after {}", theActionName); - myEntityManager.flush(); - - for (java.util.Map.Entry nextEntry : entriesToProcess.entrySet()) { + for (Map.Entry nextEntry : entriesToProcess.entrySet()) { nextEntry.getKey().getResponse().setLocation(nextEntry.getValue().getIdDt().toUnqualified().getValue()); nextEntry.getKey().getResponse().setEtag(nextEntry.getValue().getIdDt().getVersionIdPart()); } @@ -507,6 +302,229 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { return response; } + private void handleTransactionWriteOperations(ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName, Date theUpdateTime, Set theAllIds, Map theIdSubstitutions, Map theIdToPersistedOutcome, Bundle theResponse, IdentityHashMap theOriginalRequestOrder, List theDeletedResources, List theDeleteConflicts, Map theEntriesToProcess, Set theNonUpdatedEntities, Set theUpdatedEntities) { + /* + * Loop through the request and process any entries of type + * PUT, POST or DELETE + */ + for (int i = 0; i < theRequest.getEntry().size(); i++) { + + if (i % 100 == 0) { + ourLog.debug("Processed {} non-GET entries out of {}", i, theRequest.getEntry().size()); + } + + Entry nextReqEntry = theRequest.getEntry().get(i); + IResource res = nextReqEntry.getResource(); + IdDt nextResourceId = null; + if (res != null) { + + nextResourceId = res.getId(); + + if (!nextResourceId.hasIdPart()) { + if (isNotBlank(nextReqEntry.getFullUrl())) { + nextResourceId = new IdDt(nextReqEntry.getFullUrl()); + } + } + + if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+\\:.*") && !isPlaceholder(nextResourceId)) { + throw new InvalidRequestException("Invalid placeholder ID found: " + nextResourceId.getIdPart() + " - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'"); + } + + if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !isPlaceholder(nextResourceId)) { + nextResourceId = new IdDt(toResourceName(res.getClass()), nextResourceId.getIdPart()); + res.setId(nextResourceId); + } + + /* + * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness + */ + if (isPlaceholder(nextResourceId)) { + if (!theAllIds.add(nextResourceId)) { + throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextResourceId)); + } + } else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) { + IdDt nextId = nextResourceId.toUnqualifiedVersionless(); + if (!theAllIds.add(nextId)) { + throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionContainsMultipleWithDuplicateId", nextId)); + } + } + + } + + HTTPVerbEnum verb = nextReqEntry.getRequest().getMethodElement().getValueAsEnum(); + if (verb == null) { + throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionEntryHasInvalidVerb", nextReqEntry.getRequest().getMethod())); + } + + String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null; + Entry nextRespEntry = theResponse.getEntry().get(theOriginalRequestOrder.get(nextReqEntry)); + + switch (verb) { + case POST: { + // CREATE + @SuppressWarnings("rawtypes") + IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass()); + res.setId((String) null); + DaoMethodOutcome outcome; + outcome = resourceDao.create(res, nextReqEntry.getRequest().getIfNoneExist(), false, theRequestDetails); + handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res); + theEntriesToProcess.put(nextRespEntry, outcome.getEntity()); + if (outcome.getCreated() == false) { + theNonUpdatedEntities.add(outcome.getEntity()); + } + break; + } + case DELETE: { + // DELETE + String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); + UrlParts parts = UrlUtil.parseUrl(url); + IFhirResourceDao dao = toDao(parts, verb.getCode(), url); + int status = Constants.STATUS_HTTP_204_NO_CONTENT; + if (parts.getResourceId() != null) { + DaoMethodOutcome outcome = dao.delete(new IdDt(parts.getResourceType(), parts.getResourceId()), theDeleteConflicts, theRequestDetails); + if (outcome.getEntity() != null) { + theDeletedResources.add(outcome.getId().toUnqualifiedVersionless()); + theEntriesToProcess.put(nextRespEntry, outcome.getEntity()); + } + } else { + DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(parts.getResourceType() + '?' + parts.getParams(), theDeleteConflicts, theRequestDetails); + List allDeleted = deleteOutcome.getDeletedEntities(); + for (ResourceTable deleted : allDeleted) { + theDeletedResources.add(deleted.getIdDt().toUnqualifiedVersionless()); + } + if (allDeleted.isEmpty()) { + status = Constants.STATUS_HTTP_404_NOT_FOUND; + } + } + + nextRespEntry.getResponse().setStatus(toStatusString(status)); + break; + } + case PUT: { + // UPDATE + @SuppressWarnings("rawtypes") + IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass()); + + DaoMethodOutcome outcome; + + String url = extractTransactionUrlOrThrowException(nextReqEntry, verb); + + UrlParts parts = UrlUtil.parseUrl(url); + if (isNotBlank(parts.getResourceId())) { + res.setId(new IdDt(parts.getResourceType(), parts.getResourceId())); + outcome = resourceDao.update(res, null, false, theRequestDetails); + } else { + res.setId((String) null); + outcome = resourceDao.update(res, parts.getResourceType() + '?' + parts.getParams(), false, theRequestDetails); + } + + if (outcome.getCreated() == Boolean.FALSE) { + theUpdatedEntities.add(outcome.getEntity()); + } + + handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res); + theEntriesToProcess.put(nextRespEntry, outcome.getEntity()); + break; + } + case GET: + break; + } + } + + /* + * Make sure that there are no conflicts from deletions. E.g. we can't delete something + * if something else has a reference to it.. Unless the thing that has a reference to it + * was also deleted as a part of this transaction, which is why we check this now at the + * end. + */ + + theDeleteConflicts.removeIf(next -> theDeletedResources.contains(next.getTargetId().toVersionless())); + validateDeleteConflictsEmptyOrThrowException(theDeleteConflicts); + + /* + * Perform ID substitutions and then index each resource we have saved + */ + + FhirTerser terser = getContext().newTerser(); + for (DaoMethodOutcome nextOutcome : theIdToPersistedOutcome.values()) { + IResource nextResource = (IResource) nextOutcome.getResource(); + if (nextResource == null) { + continue; + } + + // References + List allRefs = terser.getAllPopulatedChildElementsOfType(nextResource, BaseResourceReferenceDt.class); + for (BaseResourceReferenceDt nextRef : allRefs) { + IdDt nextId = nextRef.getReference(); + if (!nextId.hasIdPart()) { + continue; + } + if (theIdSubstitutions.containsKey(nextId)) { + IdDt newId = theIdSubstitutions.get(nextId); + ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId); + nextRef.setReference(newId); + } else { + ourLog.debug(" * Reference [{}] does not exist in bundle", nextId); + } + } + + // URIs + List allUris = terser.getAllPopulatedChildElementsOfType(nextResource, UriDt.class); + for (UriDt nextRef : allUris) { + if (nextRef instanceof IIdType) { + continue; // No substitution on the resource ID itself! + } + IdDt nextUriString = new IdDt(nextRef.getValueAsString()); + if (theIdSubstitutions.containsKey(nextUriString)) { + IdDt newId = theIdSubstitutions.get(nextUriString); + ourLog.debug(" * Replacing resource ref {} with {}", nextUriString, newId); + nextRef.setValue(newId.getValue()); + } else { + ourLog.debug(" * Reference [{}] does not exist in bundle", nextUriString); + } + } + + + InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(nextResource); + Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; + if (theUpdatedEntities.contains(nextOutcome.getEntity())) { + updateInternal(theRequestDetails, nextResource, true, false, theRequestDetails, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource()); + } else if (!theNonUpdatedEntities.contains(nextOutcome.getEntity())) { + updateEntity(theRequestDetails, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theUpdateTime, false, true); + } + } + + myEntityManager.flush(); + + /* + * Double check we didn't allow any duplicates we shouldn't have + */ + for (Entry nextEntry : theRequest.getEntry()) { + if (nextEntry.getRequest().getMethodElement().getValueAsEnum() == HTTPVerbEnum.POST) { + String matchUrl = nextEntry.getRequest().getIfNoneExist(); + if (isNotBlank(matchUrl)) { + IFhirResourceDao resourceDao = getDao(nextEntry.getResource().getClass()); + Set val = resourceDao.processMatchUrl(matchUrl); + if (val.size() > 1) { + throw new InvalidRequestException( + "Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?"); + } + } + } + } + + for (IdDt next : theAllIds) { + IdDt replacement = theIdSubstitutions.get(next); + if (replacement == null) { + continue; + } + if (replacement.equals(next)) { + continue; + } + ourLog.debug("Placeholder resource ID \"{}\" was replaced with permanent ID \"{}\"", next, replacement); + } + } + private String extractTransactionUrlOrThrowException(Entry nextEntry, HTTPVerbEnum verb) { String url = nextEntry.getRequest().getUrl(); if (isBlank(url)) { @@ -595,7 +613,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { return retVal; } - @Transactional(propagation = Propagation.REQUIRED) + @Transactional(propagation = Propagation.NEVER) @Override public Bundle transaction(RequestDetails theRequestDetails, Bundle theRequest) { if (theRequestDetails != null) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java index a24fa216865..0b8fe0975a8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.Constants; @@ -41,6 +42,7 @@ import org.hibernate.search.query.dsl.BooleanJunction; import org.hibernate.search.query.dsl.QueryBuilder; import org.hl7.fhir.dstu3.model.BaseResource; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; @@ -50,12 +52,15 @@ import java.util.*; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public class FulltextSearchSvcImpl extends BaseHapiFhirDao implements IFulltextSearchSvc { +public class FulltextSearchSvcImpl implements IFulltextSearchSvc { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FulltextSearchSvcImpl.class); @PersistenceContext(type = PersistenceContextType.TRANSACTION) private EntityManager myEntityManager; + @Autowired + protected IForcedIdDao myForcedIdDao; + /** * Constructor */ diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java index 599f96e4476..51d7ac83894 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java @@ -112,8 +112,6 @@ public interface IFhirResourceDao extends IDao { TagList getAllResourceTags(RequestDetails theRequestDetails); - IFhirResourceDao getDao(Class theType); - Class getResourceType(); TagList getTags(IIdType theResourceId, RequestDetails theRequestDetails); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java index 2a1e88a81af..8d75646bb01 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirSystemDao.java @@ -25,8 +25,6 @@ import ca.uhn.fhir.jpa.util.ExpungeOutcome; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import javax.annotation.Nullable; import java.util.Date; @@ -40,12 +38,13 @@ public interface IFhirSystemDao extends IDao { ExpungeOutcome expunge(ExpungeOptions theExpungeOptions); + @SuppressWarnings("unchecked") IFhirResourceDao getDao(Class theType); Map getResourceCounts(); /** - *Returns a cached count of resources using a cache that regularly + * Returns a cached count of resources using a cache that regularly * refreshes in the background. This method will never */ @Nullable diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java index dd41bc48524..86bd6fa3f9f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java @@ -14,6 +14,8 @@ import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.dstu3.model.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -63,6 +65,7 @@ public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3 myStructureDefinitionDao; - - @Autowired - @Qualifier("myValueSetDaoDstu3") private IFhirResourceDao myValueSetDao; - - @Autowired - @Qualifier("myQuestionnaireDaoDstu3") private IFhirResourceDao myQuestionnaireDao; - - @Autowired - @Qualifier("myCodeSystemDaoDstu3") private IFhirResourceDao myCodeSystemDao; - @Autowired private FhirContext myDstu3Ctx; + private ApplicationContext myApplicationContext; + /** + * Constructor + */ public JpaValidationSupportDstu3() { super(); } - @Override @Transactional(value = TxType.SUPPORTS) public ValueSetExpansionComponent expandValueSet(FhirContext theCtx, ConceptSetComponent theInclude) { @@ -168,6 +162,19 @@ public class JpaValidationSupportDstu3 implements IJpaValidationSupportDstu3 { return false; } + @Override + public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException { + myApplicationContext = theApplicationContext; + } + + @PostConstruct + public void start() { + myStructureDefinitionDao = myApplicationContext.getBean("myStructureDefinitionDaoDstu3", IFhirResourceDao.class); + myValueSetDao = myApplicationContext.getBean("myValueSetDaoDstu3", IFhirResourceDao.class); + myQuestionnaireDao = myApplicationContext.getBean("myQuestionnaireDaoDstu3", IFhirResourceDao.class); + myCodeSystemDao = myApplicationContext.getBean("myCodeSystemDaoDstu3", IFhirResourceDao.class); + } + @Override @Transactional(value = TxType.SUPPORTS) public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java index 91861ac567b..9be9b5988c1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4.java @@ -13,6 +13,8 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -57,6 +59,7 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4 myStructureDefinitionDao; - - @Autowired - @Qualifier("myValueSetDaoR4") private IFhirResourceDao myValueSetDao; - - @Autowired - @Qualifier("myQuestionnaireDaoR4") private IFhirResourceDao myQuestionnaireDao; - - @Autowired - @Qualifier("myCodeSystemDaoR4") private IFhirResourceDao myCodeSystemDao; @Autowired private FhirContext myR4Ctx; + private ApplicationContext myApplicationContext; + /** + * Constructor + */ public JpaValidationSupportR4() { super(); } @@ -163,6 +159,19 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4 { return false; } + @Override + public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException { + myApplicationContext = theApplicationContext; + } + + @PostConstruct + public void start() { + myStructureDefinitionDao = myApplicationContext.getBean("myStructureDefinitionDaoR4", IFhirResourceDao.class); + myValueSetDao = myApplicationContext.getBean("myValueSetDaoR4", IFhirResourceDao.class); + myQuestionnaireDao = myApplicationContext.getBean("myQuestionnaireDaoR4", IFhirResourceDao.class); + myCodeSystemDao = myApplicationContext.getBean("myCodeSystemDaoR4", IFhirResourceDao.class); + } + @Override @Transactional(value = TxType.SUPPORTS) public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java index cbf41cf6c42..44640269b60 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/StaleSearchDeletingSvcImpl.java @@ -77,7 +77,7 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc { } @Override - @Transactional(propagation = Propagation.NOT_SUPPORTED) + @Transactional(propagation = Propagation.NEVER) public void pollForStaleSearchesAndDeleteThem() { if (!myDaoConfig.isExpireSearchResults()) { return; @@ -127,7 +127,7 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc { } @Scheduled(fixedDelay = DEFAULT_CUTOFF_SLACK) - @Transactional(propagation = Propagation.NOT_SUPPORTED) + @Transactional(propagation = Propagation.NEVER) @Override public synchronized void schedulePollForStaleSearches() { if (!myDaoConfig.isSchedulingDisabled()) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDeliverySubscriber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDeliverySubscriber.java index 3b3dfcaf3a7..800d88b1b48 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDeliverySubscriber.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDeliverySubscriber.java @@ -67,7 +67,7 @@ public abstract class BaseSubscriptionDeliverySubscriber extends BaseSubscriptio // can have placeholder IDs in them. IIdType payloadId = msg.getPayloadId(getContext()); Class type = getContext().getResourceDefinition(payloadId.getResourceType()).getImplementingClass(); - IFhirResourceDao dao = getSubscriptionDao().getDao(type); + IFhirResourceDao dao = getSubscriptionInterceptor().getDao(type); IBaseResource loadedPayload; try { loadedPayload = dao.read(payloadId); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java index c427ed579a9..d5b76d15448 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java @@ -28,7 +28,6 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails; import ca.uhn.fhir.jpa.util.JpaConstants; -import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -37,6 +36,7 @@ import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter; +import ca.uhn.fhir.util.StopWatch; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.concurrent.BasicThreadFactory; @@ -99,6 +99,7 @@ public abstract class BaseSubscriptionInterceptor exten @Autowired @Qualifier(BaseConfig.TASK_EXECUTOR_NAME) private AsyncTaskExecutor myAsyncTaskExecutor; + private Map, IFhirResourceDao> myResourceTypeToDao; /** * Constructor @@ -243,6 +244,26 @@ public abstract class BaseSubscriptionInterceptor exten public abstract Subscription.SubscriptionChannelType getChannelType(); + @SuppressWarnings("unchecked") + public IFhirResourceDao getDao(Class theType) { + if (myResourceTypeToDao == null) { + Map, IFhirResourceDao> theResourceTypeToDao = new HashMap<>(); + for (IFhirResourceDao next : myResourceDaos) { + theResourceTypeToDao.put(next.getResourceType(), next); + } + + if (this instanceof IFhirResourceDao) { + IFhirResourceDao thiz = (IFhirResourceDao) this; + theResourceTypeToDao.put(thiz.getResourceType(), thiz); + } + + myResourceTypeToDao = theResourceTypeToDao; + } + + IFhirResourceDao dao = (IFhirResourceDao) myResourceTypeToDao.get(theType); + return dao; + } + public SubscribableChannel getDeliveryChannel() { return myDeliveryChannel; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionCheckingSubscriber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionCheckingSubscriber.java index 054da70044b..af5678ce1b0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionCheckingSubscriber.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionCheckingSubscriber.java @@ -140,7 +140,7 @@ public class SubscriptionCheckingSubscriber extends BaseSubscriptionSubscriber { RequestDetails req = new ServletSubRequestDetails(); req.setSubRequest(true); - IFhirResourceDao responseDao = getSubscriptionDao().getDao(responseResourceDef.getImplementingClass()); + IFhirResourceDao responseDao = getSubscriptionInterceptor().getDao(responseResourceDef.getImplementingClass()); responseCriteriaUrl.setLoadSynchronousUpTo(1); IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java index 65881b7d2b3..37ed373598c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java @@ -123,7 +123,7 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe IBaseResource payloadResource = theMsg.getPayload(getContext()); if (theSubscription.getRestHookDetails().isDeliverLatestVersion()) { - IFhirResourceDao dao = getSubscriptionDao().getDao(payloadResource.getClass()); + IFhirResourceDao dao = getSubscriptionInterceptor().getDao(payloadResource.getClass()); try { payloadResource = dao.read(payloadResource.getIdElement().toVersionless()); } catch (ResourceGoneException e) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java index 1c96b893a20..b1153bc5975 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java @@ -31,7 +31,6 @@ import ca.uhn.fhir.jpa.util.ScrollableResultsIterator; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.ObjectUtil; import ca.uhn.fhir.util.StopWatch; @@ -56,7 +55,10 @@ import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ValueSet; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.scheduling.annotation.Scheduled; @@ -80,7 +82,7 @@ import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc { +public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc, ApplicationContextAware { public static final int DEFAULT_FETCH_SIZE = 250; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiTerminologySvcImpl.class); @@ -123,14 +125,11 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc private boolean myProcessDeferred = true; @Autowired private PlatformTransactionManager myTransactionMgr; - @Autowired(required = false) private IFhirResourceDaoCodeSystem myCodeSystemResourceDao; - private Cache> myTranslationCache; private Cache> myTranslationWithReverseCache; - - private int myFetchSize = DEFAULT_FETCH_SIZE; + private ApplicationContext myApplicationContext; private void addCodeIfNotAlreadyAdded(String theCodeSystem, ValueSet.ValueSetExpansionComponent theExpansionComponent, Set theAddedCodes, TermConcept theConcept) { if (theAddedCodes.add(theConcept.getCode())) { @@ -207,6 +206,32 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc .build(); } + /** + * This method is present only for unit tests, do not call from client code + */ + @VisibleForTesting + public void clearDeferred() { + myDeferredValueSets.clear(); + myDeferredConceptMaps.clear(); + myDeferredConcepts.clear(); + } + + /** + * This method is present only for unit tests, do not call from client code + */ + @VisibleForTesting + public void clearTranslationCache() { + myTranslationCache.invalidateAll(); + } + + /** + * This method is present only for unit tests, do not call from client code + */ + @VisibleForTesting() + public void clearTranslationWithReverseCache() { + myTranslationWithReverseCache.invalidateAll(); + } + protected abstract IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource); protected abstract void createOrUpdateConceptMap(ConceptMap theNextConceptMap); @@ -788,11 +813,21 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc } + @Override + public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException { + myApplicationContext = theApplicationContext; + } + @Override public void setProcessDeferred(boolean theProcessDeferred) { myProcessDeferred = theProcessDeferred; } + @PostConstruct + public void start() { + myCodeSystemResourceDao = myApplicationContext.getBean(IFhirResourceDaoCodeSystem.class); + } + @Override @Transactional(propagation = Propagation.REQUIRED) public void storeNewCodeSystemVersion(Long theCodeSystemResourcePid, String theSystemUri, String theSystemName, TermCodeSystemVersion theCodeSystemVersion) { @@ -1258,32 +1293,6 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc ourLastResultsFromTranslationWithReverseCache = false; } - /** - * This method is present only for unit tests, do not call from client code - */ - @VisibleForTesting - public void clearTranslationCache() { - myTranslationCache.invalidateAll(); - } - - /** - * This method is present only for unit tests, do not call from client code - */ - @VisibleForTesting - public void clearDeferred() { - myDeferredValueSets.clear(); - myDeferredConceptMaps.clear(); - myDeferredConcepts.clear(); - } - - /** - * This method is present only for unit tests, do not call from client code - */ - @VisibleForTesting() - public void clearTranslationWithReverseCache() { - myTranslationWithReverseCache.invalidateAll(); - } - /** * This method is present only for unit tests, do not call from client code */ diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java index 09dfe3372ab..97fd4da07b6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java @@ -113,6 +113,7 @@ public class LoincHandler implements IRecordHandler { ourLog.warn("Unable to find part code with TYPE[{}] and NAME[{}]", key.getPartType(), key.getPartName()); } break; + case DECIMAL: case CODE: case INTEGER: case BOOLEAN: diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/ConnectionWrapper.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/ConnectionWrapper.java index cd9fc5b444d..bd2f194cd77 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/ConnectionWrapper.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/ConnectionWrapper.java @@ -252,6 +252,7 @@ public class ConnectionWrapper implements Connection { @Override public void setReadOnly(boolean theReadOnly) throws SQLException { + ourLog.info("Setting connection as readonly"); myWrap.setReadOnly(theReadOnly); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java index 23e48a0b305..3de0c6202fe 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu2Config.java @@ -4,8 +4,11 @@ import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; +import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder; +import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import org.apache.commons.dbcp2.BasicDataSource; -import org.hibernate.jpa.HibernatePersistenceProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; @@ -15,11 +18,35 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; @Configuration @EnableTransactionManagement() public class TestDstu2Config extends BaseJavaConfigDstu2 { + private static final Logger ourLog = LoggerFactory.getLogger(TestDstu2Config.class); + private static int ourMaxThreads; + + static { + /* + * We use a randomized number of maximum threads in order to try + * and catch any potential deadlocks caused by database connection + * starvation + */ + ourMaxThreads = (int) (Math.random() * 6.0) + 1; + } + + private Exception myLastStackTrace; + private String myLastStackTraceThreadName; + + @Bean(name="maxDatabaseThreadsForTest") + public Integer getMaxThread(){ + return ourMaxThreads; + } @Bean() public DaoConfig daoConfig() { @@ -28,21 +55,73 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { @Bean() public DataSource dataSource() { - BasicDataSource retVal = new BasicDataSource(); + BasicDataSource retVal = new BasicDataSource() { + + + @Override + public Connection getConnection() throws SQLException { + ConnectionWrapper retVal; + try { + retVal = new ConnectionWrapper(super.getConnection()); + } catch (Exception e) { + ourLog.error("Exceeded maximum wait for connection", e); + logGetConnectionStackTrace(); + +// if ("true".equals(System.getStringProperty("ci"))) { + fail("Exceeded maximum wait for connection: " + e.toString()); +// } +// System.exit(1); + retVal = null; + } + + try { + throw new Exception(); + } catch (Exception e) { + myLastStackTrace = e; + myLastStackTraceThreadName = Thread.currentThread().getName(); + } + + return retVal; + } + + private void logGetConnectionStackTrace() { + StringBuilder b = new StringBuilder(); + b.append("Last connection request stack trace:"); + for (StackTraceElement next : myLastStackTrace.getStackTrace()) { + b.append("\n "); + b.append(next.getClassName()); + b.append("."); + b.append(next.getMethodName()); + b.append("("); + b.append(next.getFileName()); + b.append(":"); + b.append(next.getLineNumber()); + b.append(")"); + } + ourLog.info(b.toString()); + ourLog.info("Last connection thread: {}", myLastStackTraceThreadName); + } + + }; retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver()); retVal.setUrl("jdbc:derby:memory:myUnitTestDBDstu2;create=true"); + retVal.setMaxWaitMillis(10000); retVal.setUsername(""); retVal.setPassword(""); - return retVal; - } - - @Bean() - public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { - JpaTransactionManager retVal = new JpaTransactionManager(); - retVal.setEntityManagerFactory(entityManagerFactory); - return retVal; + + retVal.setMaxTotal(ourMaxThreads); + + DataSource dataSource = ProxyDataSourceBuilder + .create(retVal) +// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") + .logSlowQueryBySlf4j(10, TimeUnit.SECONDS) + .countQuery(new ThreadQueryCountHolder()) + .build(); + + return dataSource; } + @Override @Bean() public LocalContainerEntityManagerFactoryBean entityManagerFactory() { LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); @@ -60,7 +139,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { extraProperties.put("hibernate.dialect", "ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect"); extraProperties.put("hibernate.search.model_mapping", LuceneSearchMappingFactory.class.getName()); extraProperties.put("hibernate.search.default.directory_provider", "ram"); - extraProperties.put("hibernate.search.lucene_version","LUCENE_CURRENT"); + extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); return extraProperties; } @@ -79,4 +158,11 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 { return requestValidator; } + @Bean() + public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { + JpaTransactionManager retVal = new JpaTransactionManager(); + retVal.setEntityManagerFactory(entityManagerFactory); + return retVal; + } + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java index 19a554590d2..0528c8d806e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java @@ -20,6 +20,10 @@ import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.jdbc.Work; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Resource; import org.hl7.fhir.instance.model.api.IBaseBundle; @@ -30,6 +34,8 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; import org.mockito.Mockito; +import org.springframework.orm.hibernate5.HibernateTransactionManager; +import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; @@ -38,8 +44,11 @@ import org.springframework.transaction.support.TransactionTemplate; import java.io.IOException; import java.io.InputStream; +import java.sql.Connection; +import java.sql.SQLException; import java.util.*; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; import static ca.uhn.fhir.util.TestUtil.randomizeLocale; import static org.junit.Assert.*; @@ -64,7 +73,7 @@ public abstract class BaseJpaTest { protected IRequestOperationCallback myRequestOperationCallback = mock(IRequestOperationCallback.class); @After - public final void afterPerformCleanup() { + public void afterPerformCleanup() { BaseHapiFhirResourceDao.setDisableIncrementOnUpdateForUnitTest(false); } @@ -78,6 +87,32 @@ public abstract class BaseJpaTest { when(mySrd.getHeaders(eq(JpaConstants.HEADER_META_SNAPSHOT_MODE))).thenReturn(new ArrayList<>()); } + @After + public void afterValidateNoTransaction() { + PlatformTransactionManager txManager = getTxManager(); + if (txManager != null) { + JpaTransactionManager hibernateTxManager = (JpaTransactionManager) txManager; + SessionFactory sessionFactory = (SessionFactory) hibernateTxManager.getEntityManagerFactory(); + AtomicBoolean isReadOnly = new AtomicBoolean(); + Session currentSession; + try { + currentSession = sessionFactory.getCurrentSession(); + } catch (HibernateException e) { + currentSession = null; + } + if (currentSession != null) { + currentSession.doWork(new Work() { + + public void execute(Connection connection) throws SQLException { + isReadOnly.set(connection.isReadOnly()); + } + }); + + assertFalse(isReadOnly.get()); + } + } + } + @Before public void beforeRandomizeLocale() { randomizeLocale(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirDaoConcurrencyDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirDaoConcurrencyDstu3Test.java index fd0624ae78f..867ef18fa72 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirDaoConcurrencyDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirDaoConcurrencyDstu3Test.java @@ -1,10 +1,9 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.TestUtil; -import com.phloc.commons.compare.ReverseComparator; +import org.apache.commons.collections4.comparators.ReverseComparator; import org.apache.commons.dbcp2.BasicDataSource; import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle.BundleType; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java index 22ea61b467d..a5e11d536c2 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java @@ -44,11 +44,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.util.AopTestUtils; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionCallbackWithoutResult; -import org.springframework.transaction.support.TransactionTemplate; import javax.persistence.EntityManager; import java.io.IOException; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java index 993a1923503..0fff1fceb19 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java @@ -49,6 +49,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test { protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor; protected static DatabaseBackedPagingProvider ourPagingProvider; protected static PlatformTransactionManager ourTxManager; + protected static Integer ourConnectionPoolSize; public BaseResourceProviderDstu2Test() { super(); @@ -84,6 +85,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test { ourRestServer.setServerConformanceProvider(confProvider); ourPagingProvider = myAppCtx.getBean(DatabaseBackedPagingProvider.class); + ourConnectionPoolSize = myAppCtx.getBean("maxDatabaseThreadsForTest", Integer.class); ourRestServer.setPagingProvider(ourPagingProvider); Server server = new Server(ourPort); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java index 2d56930f026..8574762e9af 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java @@ -1,41 +1,58 @@ package ca.uhn.fhir.jpa.provider; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import java.io.*; -import java.net.*; -import java.nio.charset.StandardCharsets; -import java.util.*; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.methods.*; -import org.apache.http.entity.*; -import org.hl7.fhir.instance.model.api.IBaseBundle; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.*; -import org.junit.Test; -import org.springframework.test.util.AopTestUtils; - -import com.google.common.base.Charsets; - import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; -import ca.uhn.fhir.model.api.*; -import ca.uhn.fhir.model.dstu2.composite.*; +import ca.uhn.fhir.model.api.ExtensionDt; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; +import ca.uhn.fhir.model.dstu2.composite.CodingDt; +import ca.uhn.fhir.model.dstu2.composite.MetaDt; +import ca.uhn.fhir.model.dstu2.composite.PeriodDt; +import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu2.resource.*; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.model.dstu2.valueset.*; import ca.uhn.fhir.model.primitive.*; import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.api.*; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.param.*; -import ca.uhn.fhir.rest.server.exceptions.*; -import ca.uhn.fhir.util.*; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.fhir.util.BundleUtil; +import ca.uhn.fhir.util.StopWatch; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.UrlUtil; +import com.google.common.base.Charsets; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.*; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.*; +import org.springframework.test.util.AopTestUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { @@ -50,7 +67,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis()); - + mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(null); mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(SearchCoordinatorSvcImpl.DEFAULT_SYNC_SIZE); mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(false); @@ -107,7 +124,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); } - + @Test public void testBundleCreateWithTypeTransaction() throws Exception { IGenericClient client = ourClient; @@ -182,22 +199,22 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { ourClient.transaction().withResources(resources).prettyPrint().encodedXml().execute(); Bundle found = ourClient - .search() - .forResource(Organization.class) - .where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")) - .count(10) - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource(Organization.class) + .where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")) + .count(10) + .returnBundle(Bundle.class) + .execute(); assertEquals(100, found.getTotalElement().getValue().intValue()); assertEquals(10, found.getEntry().size()); found = ourClient - .search() - .forResource(Organization.class) - .where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")) - .count(999) - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource(Organization.class) + .where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")) + .count(999) + .returnBundle(Bundle.class) + .execute(); assertEquals(100, found.getTotalElement().getValue().intValue()); assertEquals(50, found.getEntry().size()); @@ -208,15 +225,15 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { */ @Test public void testCreateAndUpdateBinary() throws ClientProtocolException, Exception { - byte[] arr = { 1, 21, 74, 123, -44 }; + byte[] arr = {1, 21, 74, 123, -44}; Binary binary = new Binary(); binary.setContent(arr); binary.setContentType("dansk"); binary.addUndeclaredExtension(true, "bobobo", new StringDt("hey there")); - + IIdType resource = ourClient.create().resource(binary).execute().getId(); - + Binary fromDB = ourClient.read().resource(Binary.class).withId(resource.toVersionless()).execute(); assertEquals("1", fromDB.getId().getVersionIdPart()); @@ -231,7 +248,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { } finally { IOUtils.closeQuietly(resp); } - + fromDB = ourClient.read().resource(Binary.class).withId(resource.toVersionless()).execute(); assertEquals("2", fromDB.getId().getVersionIdPart()); @@ -247,12 +264,12 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { } finally { IOUtils.closeQuietly(resp); } - + fromDB = ourClient.read().resource(Binary.class).withId(resource.toVersionless()).execute(); assertEquals("3", fromDB.getId().getVersionIdPart()); // Now an update with the wrong ID in the body - + arr[0] = 4; binary.setContent(arr); binary.setId(""); @@ -265,7 +282,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { } finally { IOUtils.closeQuietly(resp); } - + fromDB = ourClient.read().resource(Binary.class).withId(resource.toVersionless()).execute(); assertEquals("3", fromDB.getId().getVersionIdPart()); @@ -441,7 +458,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { assertEquals(400, response.getStatusLine().getStatusCode()); OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, responseString); assertEquals("Can not create resource with ID \"2\", ID must not be supplied on a create (POST) operation (use an HTTP PUT / update operation if you wish to supply an ID)", - oo.getIssue().get(0).getDiagnostics()); + oo.getIssue().get(0).getDiagnostics()); } finally { response.getEntity().getContent().close(); response.close(); @@ -494,7 +511,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { assertEquals(e1id.toUnqualifiedVersionless(), BundleUtil.toListOfResourcesOfType(myFhirCtx, res, Encounter.class).get(0).getIdElement().toUnqualifiedVersionless()); } - + @Test public void testDeleteConditionalMultiple() { String methodName = "testDeleteConditionalMultiple"; @@ -522,7 +539,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { fail(); } catch (PreconditionFailedException e) { assertEquals("HTTP 412 Precondition Failed: Failed to DELETE resource with match URL \"Patient?identifier=testDeleteConditionalMultiple\" because this search matched 2 resources", - e.getMessage()); + e.getMessage()); } // Not deleted yet.. @@ -682,12 +699,12 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { IGenericClient client = ourClient; int initialSize = client - .search() - .forResource(DiagnosticOrder.class) - .returnBundle(Bundle.class) - .execute() - .getEntry() - .size(); + .search() + .forResource(DiagnosticOrder.class) + .returnBundle(Bundle.class) + .execute() + .getEntry() + .size(); DiagnosticOrder res = new DiagnosticOrder(); res.addIdentifier().setSystem("urn:foo").setValue("123"); @@ -695,12 +712,12 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { client.create().resource(res).execute(); int newSize = client - .search() - .forResource(DiagnosticOrder.class) - .returnBundle(Bundle.class) - .execute() - .getEntry() - .size(); + .search() + .forResource(DiagnosticOrder.class) + .returnBundle(Bundle.class) + .execute() + .getEntry() + .size(); assertEquals(1, newSize - initialSize); @@ -717,23 +734,23 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { IGenericClient client = ourClient; int initialSize = client - .search() - .forResource(DocumentManifest.class) - .returnBundle(Bundle.class) - .execute() - .getEntry() - .size(); + .search() + .forResource(DocumentManifest.class) + .returnBundle(Bundle.class) + .execute() + .getEntry() + .size(); String resBody = IOUtils.toString(ResourceProviderDstu2Test.class.getResource("/documentmanifest.json"), StandardCharsets.UTF_8); client.create().resource(resBody).execute(); int newSize = client - .search() - .forResource(DocumentManifest.class) - .returnBundle(Bundle.class) - .execute() - .getEntry() - .size(); + .search() + .forResource(DocumentManifest.class) + .returnBundle(Bundle.class) + .execute() + .getEntry() + .size(); assertEquals(1, newSize - initialSize); @@ -747,23 +764,23 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { IGenericClient client = ourClient; int initialSize = client - .search() - .forResource(DocumentReference.class) - .returnBundle(Bundle.class) - .execute() - .getEntry() - .size(); + .search() + .forResource(DocumentReference.class) + .returnBundle(Bundle.class) + .execute() + .getEntry() + .size(); String resBody = IOUtils.toString(ResourceProviderDstu2Test.class.getResource("/documentreference.json"), Charsets.UTF_8); client.create().resource(resBody).execute(); int newSize = client - .search() - .forResource(DocumentReference.class) - .returnBundle(Bundle.class) - .execute() - .getEntry() - .size(); + .search() + .forResource(DocumentReference.class) + .returnBundle(Bundle.class) + .execute() + .getEntry() + .size(); assertEquals(1, newSize - initialSize); @@ -934,7 +951,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { List actual; StringAndListParam param; - ourLog.info("Pt1:{} Pt2:{} Obs1:{} Obs2:{} Obs3:{}", new Object[] { ptId1.getIdPart(), ptId2.getIdPart(), obsId1.getIdPart(), obsId2.getIdPart(), obsId3.getIdPart() }); + ourLog.info("Pt1:{} Pt2:{} Obs1:{} Obs2:{} Obs3:{}", new Object[] {ptId1.getIdPart(), ptId2.getIdPart(), obsId1.getIdPart(), obsId2.getIdPart(), obsId3.getIdPart()}); param = new StringAndListParam(); param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1"))); @@ -1203,7 +1220,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { } get = new HttpGet(ourServerBase + "/Patient/" + pId.getIdPart() + "/$everything?_lastUpdated=%3E" + new InstantDt(new Date(time2)).getValueAsString() + "&_lastUpdated=%3C" - + new InstantDt(new Date(time3)).getValueAsString()); + + new InstantDt(new Date(time3)).getValueAsString()); response = ourHttpClient.execute(get); try { assertEquals(200, response.getStatusLine().getStatusCode()); @@ -1264,24 +1281,24 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { IdDt o2id = (IdDt) ourClient.create().resource(o2).execute().getId().toUnqualifiedVersionless(); Bundle actual = ourClient.search() - .forResource(Organization.class) - .withProfile("http://profile1") - .withProfile("http://profileX") - .encodedJson() - .prettyPrint() - .returnBundle(Bundle.class) - .execute(); + .forResource(Organization.class) + .withProfile("http://profile1") + .withProfile("http://profileX") + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); assertEquals("nothing matches profile x", Collections.emptyList(), actual.getEntry()); actual = ourClient.search() - .forResource(Organization.class) - .withProfile("http://profile1") - .withProfile("http://profile2") - .returnBundle(Bundle.class) - .encodedJson() - .prettyPrint() - .execute(); + .forResource(Organization.class) + .withProfile("http://profile1") + .withProfile("http://profile2") + .returnBundle(Bundle.class) + .encodedJson() + .prettyPrint() + .execute(); Set expectedIds = new HashSet(); expectedIds.add(o1id.getIdPart()); @@ -1292,13 +1309,13 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { assertEquals("Expects to retrieve the 1 orgination matching on Org1's profiles", expectedIds, actualIds); actual = ourClient.search() - .forResource(Organization.class) - .withProfile("http://profile1") - .withAnyProfile(Arrays.asList("http://profile3", "http://profile2")) - .encodedJson() - .prettyPrint() - .returnBundle(Bundle.class) - .execute(); + .forResource(Organization.class) + .withProfile("http://profile1") + .withAnyProfile(Arrays.asList("http://profile3", "http://profile2")) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); expectedIds = new HashSet(); expectedIds.add(o1id.getIdPart()); @@ -1346,7 +1363,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { ourClient.update().resource(patient).execute(); ca.uhn.fhir.model.dstu2.resource.Bundle history = ourClient.history().onInstance(id).andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class).prettyPrint().summaryMode(SummaryEnum.DATA) - .execute(); + .execute(); assertEquals(3, history.getEntry().size()); assertEquals(id.withVersion("3"), history.getEntry().get(0).getResource().getId()); assertEquals(1, ((Patient) history.getEntry().get(0).getResource()).getName().size()); @@ -1367,23 +1384,23 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { IGenericClient client = ourClient; int initialSize = client - .search() - .forResource(ImagingStudy.class) - .returnBundle(Bundle.class) - .execute() - .getEntry() - .size(); + .search() + .forResource(ImagingStudy.class) + .returnBundle(Bundle.class) + .execute() + .getEntry() + .size(); String resBody = IOUtils.toString(ResourceProviderDstu2Test.class.getResource("/imagingstudy.json"), StandardCharsets.UTF_8); client.create().resource(resBody).execute(); int newSize = client - .search() - .forResource(ImagingStudy.class) - .returnBundle(Bundle.class) - .execute() - .getEntry() - .size(); + .search() + .forResource(ImagingStudy.class) + .returnBundle(Bundle.class) + .execute() + .getEntry() + .size(); assertEquals(1, newSize - initialSize); @@ -1397,14 +1414,14 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { //@formatter:off String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; //@formatter:on HttpPost post = new HttpPost(ourServerBase + "/Patient/" + id.getIdPart() + "/$meta-add"); @@ -1471,6 +1488,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { } } + @Test public void testPagingOverEverythingSet() throws InterruptedException { Patient p = new Patient(); @@ -1488,6 +1506,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(10); mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(true); + StopWatch sw = new StopWatch(); ca.uhn.fhir.model.dstu2.resource.Bundle response = ourClient .operation() .onInstance(new IdDt(pid)) @@ -1498,7 +1517,9 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { .execute(); assertEquals(10, response.getEntry().size()); - assertEquals(null, response.getTotalElement().getValueAsString()); + if (ourConnectionPoolSize > 1) { + assertEquals("Total should be null but was " + response.getTotalElement().getValueAsString() + " in " + sw.toString(), null, response.getTotalElement().getValueAsString()); + } assertThat(response.getLink("next").getUrl(), not(emptyString())); // Load page 2 @@ -1571,21 +1592,21 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { ourClient.create().resource(pat).prettyPrint().encodedXml().execute().getId(); { Bundle returned = ourClient - .search() - .forResource(Patient.class) - .returnBundle(Bundle.class) - .encodedXml() - .execute(); + .search() + .forResource(Patient.class) + .returnBundle(Bundle.class) + .encodedXml() + .execute(); assertThat(returned.getEntry().size(), greaterThan(1)); assertEquals(ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum.SEARCH_RESULTS, returned.getTypeElement().getValueAsEnum()); } { Bundle returned = ourClient - .search() - .forResource(Patient.class) - .encodedJson() - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource(Patient.class) + .encodedJson() + .returnBundle(Bundle.class) + .execute(); assertThat(returned.getEntry().size(), greaterThan(1)); } } @@ -1633,12 +1654,12 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { assertThat(actual.getText().getDiv().getValueAsString(), containsString("IdentifiertestSaveAndRetrieveWithContained01")); Bundle b = ourClient - .search() - .forResource("Patient") - .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system:rpdstu2", "testSaveAndRetrieveWithContained01")) - .prettyPrint() - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system:rpdstu2", "testSaveAndRetrieveWithContained01")) + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); assertEquals(1, b.getEntry().size()); } @@ -1708,13 +1729,13 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { //@formatter:off ca.uhn.fhir.model.dstu2.resource.Bundle actual = ourClient - .search() - .forResource(Patient.class) - .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testSearchByIdentifier01")) - .encodedJson() - .prettyPrint() - .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) - .execute(); + .search() + .forResource(Patient.class) + .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testSearchByIdentifier01")) + .encodedJson() + .prettyPrint() + .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .execute(); //@formatter:on assertEquals(1, actual.getEntry().size()); @@ -1731,13 +1752,13 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { IdDt p1Id = (IdDt) ourClient.create().resource(p1).execute().getId(); Bundle actual = ourClient - .search() - .forResource(Patient.class) - .where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")) - .encodedJson() - .prettyPrint() - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource(Patient.class) + .where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); assertEquals(1, actual.getEntry().size()); assertEquals(p1Id.getIdPart(), actual.getEntry().get(0).getResource().getId().getIdPart()); @@ -1765,12 +1786,12 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { IdDt p2Id = (IdDt) ourClient.create().resource(p2).execute().getId(); Bundle actual = ourClient.search() - .forResource(Patient.class) - .where(Patient.ORGANIZATION.hasAnyOfIds(Arrays.asList(o1id.getIdPart(), o2id.getIdPart()))) - .encodedJson() - .prettyPrint() - .returnBundle(Bundle.class) - .execute(); + .forResource(Patient.class) + .where(Patient.ORGANIZATION.hasAnyOfIds(Arrays.asList(o1id.getIdPart(), o2id.getIdPart()))) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); Set expectedIds = new HashSet(); expectedIds.add(p1Id.getIdPart()); @@ -1796,23 +1817,23 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { IdDt p1Id = (IdDt) ourClient.create().resource(p1).execute().getId(); Bundle actual = ourClient.search() - .forResource(Patient.class) - .where(Patient.ORGANIZATION.hasId(o1id.getIdPart())) - .encodedJson() - .prettyPrint() - .returnBundle(Bundle.class) - .execute(); + .forResource(Patient.class) + .where(Patient.ORGANIZATION.hasId(o1id.getIdPart())) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); assertEquals(1, actual.getEntry().size()); assertEquals(p1Id.getIdPart(), actual.getEntry().get(0).getResource().getId().getIdPart()); actual = ourClient - .search() - .forResource(Patient.class) - .where(Patient.ORGANIZATION.hasId(o1id.getValue())) - .encodedJson() - .prettyPrint() - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource(Patient.class) + .where(Patient.ORGANIZATION.hasId(o1id.getValue())) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); assertEquals(1, actual.getEntry().size()); assertEquals(p1Id.getIdPart(), actual.getEntry().get(0).getResource().getId().getIdPart()); @@ -1857,44 +1878,44 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { ourLog.info("Before: {}", beforeAny.getValue()); { Bundle found = ourClient - .search() - .forResource(Patient.class) - .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) - .lastUpdated(new DateRangeParam(beforeAny, null)) - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource(Patient.class) + .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) + .lastUpdated(new DateRangeParam(beforeAny, null)) + .returnBundle(Bundle.class) + .execute(); List patients = toIdListUnqualifiedVersionless(found); assertThat(patients, hasItems(id1a, id1b, id2)); } { Bundle found = ourClient.search() - .forResource(Patient.class) - .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) - .returnBundle(Bundle.class) - .execute(); + .forResource(Patient.class) + .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) + .returnBundle(Bundle.class) + .execute(); List patients = toIdListUnqualifiedVersionless(found); assertThat(patients, hasItems(id1a, id1b, id2)); } { Bundle found = ourClient.search() - .forResource(Patient.class) - .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) - .lastUpdated(new DateRangeParam(beforeR2, null)) - .returnBundle(Bundle.class) - .execute(); + .forResource(Patient.class) + .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) + .lastUpdated(new DateRangeParam(beforeR2, null)) + .returnBundle(Bundle.class) + .execute(); List patients = toIdListUnqualifiedVersionless(found); assertThat(patients, hasItems(id2)); assertThat(patients, not(hasItems(id1a, id1b))); } { Bundle found = ourClient.search() - .forResource(Patient.class) - .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) - .lastUpdated(new DateRangeParam(beforeAny, beforeR2)) - .returnBundle(Bundle.class) - .execute(); + .forResource(Patient.class) + .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) + .lastUpdated(new DateRangeParam(beforeAny, beforeR2)) + .returnBundle(Bundle.class) + .execute(); List patients = toIdListUnqualifiedVersionless(found); assertThat(patients.toString(), patients, not(hasItems(id2))); @@ -1902,11 +1923,11 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { } { Bundle found = ourClient.search() - .forResource(Patient.class) - .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) - .lastUpdated(new DateRangeParam(null, beforeR2)) - .returnBundle(Bundle.class) - .execute(); + .forResource(Patient.class) + .where(Patient.NAME.matches().value("testSearchLastUpdatedParamRp")) + .lastUpdated(new DateRangeParam(null, beforeR2)) + .returnBundle(Bundle.class) + .execute(); List patients = toIdListUnqualifiedVersionless(found); assertThat(patients, (hasItems(id1a, id1b))); @@ -1951,7 +1972,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { assertThat(matches, greaterThan(0)); } - + @Test public void testSearchReturnsSearchDate() throws Exception { ourLog.info("Starting testSearchReturnsSearchDate"); @@ -1960,11 +1981,11 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { Thread.sleep(100); ca.uhn.fhir.model.dstu2.resource.Bundle found = ourClient - .search() - .forResource(Patient.class) - .prettyPrint() - .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) - .execute(); + .search() + .forResource(Patient.class) + .prettyPrint() + .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .execute(); Thread.sleep(100); Date after = new Date(); @@ -1978,7 +1999,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { assertTrue(value.after(before)); assertTrue(value.before(after)); } - + @Test public void testSearchWithInclude() throws Exception { Organization org = new Organization(); @@ -1991,13 +2012,13 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { ourClient.create().resource(pat).prettyPrint().encodedXml().execute().getId(); Bundle found = ourClient - .search() - .forResource(Patient.class) - .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("urn:system:rpdstu2","testSearchWithInclude02")) - .include(Patient.INCLUDE_ORGANIZATION) - .prettyPrint() - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource(Patient.class) + .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("urn:system:rpdstu2", "testSearchWithInclude02")) + .include(Patient.INCLUDE_ORGANIZATION) + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); assertEquals(2, found.getEntry().size()); assertEquals(Patient.class, found.getEntry().get(0).getResource().getClass()); @@ -2054,13 +2075,13 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { { Bundle found = ourClient - .search() - .forResource(Organization.class) - .where(Organization.NAME.isMissing(false)) - .count(100) - .prettyPrint() - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource(Organization.class) + .where(Organization.NAME.isMissing(false)) + .count(100) + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); List list = toIdListUnqualifiedVersionless(found); ourLog.info(methodName + ": " + list.toString()); @@ -2071,13 +2092,13 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { } Bundle found = ourClient - .search() - .forResource(Organization.class) - .where(Organization.NAME.isMissing(true)) - .count(100) - .prettyPrint() - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource(Organization.class) + .where(Organization.NAME.isMissing(true)) + .count(100) + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); List list = toIdListUnqualifiedVersionless(found); ourLog.info(methodName + " found: " + list.toString() + " - Wanted " + orgMissing + " but not " + orgNotMissing); @@ -2094,7 +2115,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { checkParamMissing(Observation.SP_ENCOUNTER); checkParamMissing(Observation.SP_DATE); } - + @Test public void testSearchWithTextInexactMatch() throws Exception { Observation obs = new Observation(); @@ -2295,7 +2316,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { CloseableHttpResponse resp = ourHttpClient.execute(post); try { assertEquals(200, resp.getStatusLine().getStatusCode()); - String output= IOUtils.toString(resp.getEntity().getContent()); + String output = IOUtils.toString(resp.getEntity().getContent()); ourLog.info(output); } finally { resp.close(); @@ -2387,7 +2408,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { response.close(); } - pt.addName().addFamily(methodName+"2"); + pt.addName().addFamily(methodName + "2"); resource = myFhirCtx.newXmlParser().encodeResourceToString(pt); HttpPut put = new HttpPut(ourServerBase + "/Patient?name=" + methodName); put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); @@ -2493,7 +2514,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { } } - + @Test public void testUpdateWithClientSuppliedIdWhichDoesntExist() { Patient p1 = new Patient(); @@ -2505,13 +2526,13 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { assertThat(p1Id.getValue(), containsString("Patient/testUpdateWithClientSuppliedIdWhichDoesntExistRpDstu2/_history")); Bundle actual = ourClient - .search() - .forResource(Patient.class) - .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testUpdateWithClientSuppliedIdWhichDoesntExistRpDstu2")) - .encodedJson() - .prettyPrint() - .returnBundle(Bundle.class) - .execute(); + .search() + .forResource(Patient.class) + .where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testUpdateWithClientSuppliedIdWhichDoesntExistRpDstu2")) + .encodedJson() + .prettyPrint() + .returnBundle(Bundle.class) + .execute(); assertEquals(1, actual.getEntry().size()); assertEquals(p1Id.getIdPart(), actual.getEntry().get(0).getResource().getId().getIdPart()); @@ -2578,7 +2599,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { } } - + /** * From a Skype message from Brian Postlethwaite */ @@ -2725,18 +2746,18 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { assertThat(resp, stringContainsInOrder("", "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", "" - )); + )); //@formatter:on } finally { IOUtils.closeQuietly(response.getEntity().getContent()); @@ -2755,8 +2776,8 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { assertEquals(200, response.getStatusLine().getStatusCode()); //@formatter:off assertThat(resp, stringContainsInOrder( - "", - "")); + "", + "")); //@formatter:on } finally { IOUtils.closeQuietly(response.getEntity().getContent()); @@ -2775,9 +2796,9 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { assertEquals(200, response.getStatusLine().getStatusCode()); //@formatter:off assertThat(resp, stringContainsInOrder( - "", - "" - )); + "", + "" + )); //@formatter:on } finally { IOUtils.closeQuietly(response.getEntity().getContent()); @@ -2785,7 +2806,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { } } - + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java index 6489ad95d90..920febf026c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java @@ -11,14 +11,12 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; import org.hl7.fhir.r4.model.UriType; -import org.junit.After; import org.junit.AfterClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.test.util.AopTestUtils; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; @@ -102,16 +100,14 @@ public class TerminologySvcImplR4Test extends BaseJpaR4Test { .addTarget() .setCode("34567"); - runInTransaction(() -> { - try { - runInTransaction(() -> { - myConceptMapDao.create(conceptMap); - }); - fail(); - } catch (UnprocessableEntityException e) { - assertEquals("ConceptMap has no value for ConceptMap.url", e.getMessage()); - } - }); + try { + runInTransaction(() -> { + myConceptMapDao.create(conceptMap); + }); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals("ConceptMap has no value for ConceptMap.url", e.getMessage()); + } } diff --git a/hapi-fhir-jpaserver-example/pom.xml b/hapi-fhir-jpaserver-example/pom.xml index 8ff7d691083..fed6e611868 100644 --- a/hapi-fhir-jpaserver-example/pom.xml +++ b/hapi-fhir-jpaserver-example/pom.xml @@ -187,8 +187,8 @@ test - com.phloc - phloc-schematron + com.helger + ph-schematron Saxon-HE diff --git a/hapi-fhir-jpaserver-examples/hapi-fhir-jpaserver-example-postgres/pom.xml b/hapi-fhir-jpaserver-examples/hapi-fhir-jpaserver-example-postgres/pom.xml index 3cb4741f06c..06610ca6feb 100644 --- a/hapi-fhir-jpaserver-examples/hapi-fhir-jpaserver-example-postgres/pom.xml +++ b/hapi-fhir-jpaserver-examples/hapi-fhir-jpaserver-example-postgres/pom.xml @@ -138,8 +138,8 @@ test - com.phloc - phloc-schematron + com.helger + ph-schematron Saxon-HE diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index ac8c9cba079..806737dc495 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -55,8 +55,8 @@ - com.phloc - phloc-schematron + com.helger + ph-schematron Saxon-HE @@ -65,8 +65,8 @@ - com.phloc - phloc-commons + com.helger + ph-commons diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/FhirTesterConfig.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/FhirTesterConfig.java index 240c22eaad7..daee8943c41 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/FhirTesterConfig.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/FhirTesterConfig.java @@ -87,8 +87,8 @@ public class FhirTesterConfig { .addServer() .withId("spark2") .withFhirVersion(FhirVersionEnum.DSTU2) - .withBaseUrl("http://spark-dstu2.furore.com/fhir") - .withName("Spark - Furore (DSTU2 FHIR)"); + .withBaseUrl("http://vonk.furore.com/") + .withName("Vonk - Furore (STU3 FHIR)"); return retVal; } diff --git a/hapi-fhir-osgi-core/pom.xml b/hapi-fhir-osgi-core/pom.xml index 8500e137de7..35d30c0c7df 100644 --- a/hapi-fhir-osgi-core/pom.xml +++ b/hapi-fhir-osgi-core/pom.xml @@ -72,8 +72,8 @@ thymeleaf - com.phloc - phloc-schematron + com.helger + ph-schematron Saxon-HE @@ -82,8 +82,8 @@ - com.phloc - phloc-commons + com.helger + ph-commons @@ -197,8 +197,8 @@ com.ctc.wstx.api;version="4.4";resolution:=optional, com.ctc.wstx.*;version="4.4";resolution:=optional, com.google.*;resolution:=optional;-remove-attribute:=version, - com.phloc.commons;resolution:=optional;-remove-attribute:=version, - com.phloc.*;resolution:=optional;-remove-attribute:=version, + com.helger.commons;resolution:=optional;-remove-attribute:=version, + com.helger.*;resolution:=optional;-remove-attribute:=version, javassist;-remove-attribute:=version, javax.*;-remove-attribute:=version, net.sf.saxon;resolution:=optional, diff --git a/hapi-fhir-structures-dstu/pom.xml b/hapi-fhir-structures-dstu/pom.xml index cca4934514d..b14c2385fa7 100644 --- a/hapi-fhir-structures-dstu/pom.xml +++ b/hapi-fhir-structures-dstu/pom.xml @@ -68,13 +68,13 @@ test - com.phloc - phloc-schematron + com.helger + ph-schematron test - com.phloc - phloc-commons + com.helger + ph-commons test diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index f32d3461ae3..80be1513566 100644 --- a/hapi-fhir-structures-dstu2.1/pom.xml +++ b/hapi-fhir-structures-dstu2.1/pom.xml @@ -130,22 +130,6 @@ thymeleaf test - - com.phloc - phloc-schematron - test - - - - - com.phloc - phloc-commons - test - @@ -208,6 +192,16 @@ spring-web test + + com.helger + ph-schematron + test + + + com.helger + ph-commons + test + diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2_1Test.java index 7a7a7c81413..103de11e628 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2_1Test.java @@ -11,6 +11,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.*; +import com.helger.commons.io.stream.StringInputStream; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.ReaderInputStream; import org.apache.http.*; @@ -29,7 +30,6 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import com.google.common.base.Charsets; -import com.phloc.commons.io.streams.StringInputStream; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; @@ -83,7 +83,7 @@ public class GenericClientDstu2_1Test { return body; } - private ArgumentCaptor prepareClientForSearchResponse() throws IOException, ClientProtocolException { + private ArgumentCaptor prepareClientForSearchResponse() throws IOException { final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); @@ -92,7 +92,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -111,7 +111,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -149,7 +149,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -187,7 +187,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -225,7 +225,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -264,7 +264,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -303,7 +303,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -330,7 +330,7 @@ public class GenericClientDstu2_1Test { } @Test - public void testPatchInvalid() throws Exception { + public void testPatchInvalid() { IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); try { @@ -354,7 +354,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -398,7 +398,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -441,7 +441,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -505,7 +505,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -537,14 +537,14 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { @Override - public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + public Header[] answer(InvocationOnMock theInvocation) { return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; } }); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { if (myAnswerCount++ == 0) { return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); } else { @@ -586,14 +586,14 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { @Override - public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + public Header[] answer(InvocationOnMock theInvocation) { return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; } }); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { myAnswerCount++; return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); } @@ -625,7 +625,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -652,7 +652,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -697,7 +697,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -737,7 +737,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -773,7 +773,7 @@ public class GenericClientDstu2_1Test { private int myCount = 0; @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { final String respString; if (myCount == 1 || myCount == 2) { ourLog.info("Encoding patient"); @@ -822,7 +822,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { final String respString; if (myAnswerCount >= 1) { ourLog.info("Encoding patient"); @@ -872,7 +872,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { final String respString; if (myAnswerCount >= 1) { ourLog.info("Encoding patient"); @@ -912,7 +912,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public StringInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public StringInputStream answer(InvocationOnMock theInvocation) { return new StringInputStream("HELLO", Charsets.UTF_8); } }); @@ -937,7 +937,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public StringInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public StringInputStream answer(InvocationOnMock theInvocation) { return new StringInputStream("not implemented", Charsets.UTF_8); } }); @@ -968,7 +968,7 @@ public class GenericClientDstu2_1Test { .forResource(Patient.class) .where(Patient.FAMILY.matches().value((String) null)) .and(Patient.BIRTHDATE.exactly().day((Date) null)) - .and(Patient.GENDER.exactly().code((String) null)) + .and(Patient.GENDER.exactly().code(null)) .and(Patient.ORGANIZATION.hasId((String) null)) .returnBundle(Bundle.class) .execute(); @@ -1019,7 +1019,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(encoded), Charset.forName("UTF-8")); } }); @@ -1064,7 +1064,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(encoded), Charset.forName("UTF-8")); } }); @@ -1121,7 +1121,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(null); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1152,7 +1152,7 @@ public class GenericClientDstu2_1Test { // when(myHttpResponse.getEntity().getContentType()).thenReturn(null); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1179,7 +1179,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -1286,7 +1286,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -1405,7 +1405,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -1497,7 +1497,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -1564,7 +1564,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1601,7 +1601,7 @@ public class GenericClientDstu2_1Test { } @Test - public void testTransactionWithInvalidBody() throws Exception { + public void testTransactionWithInvalidBody() { IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); // Transaction @@ -1652,7 +1652,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1691,14 +1691,14 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { @Override - public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + public Header[] answer(InvocationOnMock theInvocation) { return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; } }); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { if (myAnswerCount++ == 0) { return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); } else { @@ -1739,14 +1739,14 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { @Override - public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + public Header[] answer(InvocationOnMock theInvocation) { return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; } }); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { myAnswerCount++; return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); } @@ -1784,7 +1784,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1821,7 +1821,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1842,7 +1842,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1870,14 +1870,14 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { @Override - public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + public Header[] answer(InvocationOnMock theInvocation) { return new Header[] {}; } }); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); } }); @@ -1910,7 +1910,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1948,7 +1948,7 @@ public class GenericClientDstu2_1Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index ffbbe2469a5..e10b307f786 100644 --- a/hapi-fhir-structures-dstu2/pom.xml +++ b/hapi-fhir-structures-dstu2/pom.xml @@ -103,13 +103,13 @@ test - com.phloc - phloc-schematron + com.helger + ph-schematron test - com.phloc - phloc-commons + com.helger + ph-commons test diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java index 2bea5344959..8888c4180d7 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java @@ -28,7 +28,7 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.UrlUtil; -import com.phloc.commons.collections.iterate.ArrayEnumeration; +import com.helger.collection.iterate.ArrayEnumeration; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; @@ -130,7 +130,7 @@ public class ResponseHighlightingInterceptorTest { when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer>() { @Override public Enumeration answer(InvocationOnMock theInvocation) throws Throwable { - return new ArrayEnumeration("text/html,application/xhtml+xml,application/xml;q=0.9"); + return new ArrayEnumeration<>("text/html,application/xhtml+xml,application/xml;q=0.9"); } }); when(req.getHeader(Constants.HEADER_ORIGIN)).thenAnswer(new Answer() { diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index 499350205a9..3a562cb840d 100644 --- a/hapi-fhir-structures-dstu3/pom.xml +++ b/hapi-fhir-structures-dstu3/pom.xml @@ -217,13 +217,13 @@ test - com.phloc - phloc-schematron + com.helger + ph-schematron test - com.phloc - phloc-commons + com.helger + ph-commons test diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/StructureMapUtilities.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/StructureMapUtilities.java index 7316f1bd6f4..55501fbf9d1 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/StructureMapUtilities.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/StructureMapUtilities.java @@ -1,2746 +1,2752 @@ -package org.hl7.fhir.dstu3.utils; - -// remember group resolution -// trace - account for which wasn't transformed in the source - -import java.io.IOException; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import org.hl7.fhir.dstu3.conformance.ProfileUtilities; -import org.hl7.fhir.dstu3.conformance.ProfileUtilities.ProfileKnowledgeProvider; -import org.hl7.fhir.dstu3.context.IWorkerContext; -import org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult; -import org.hl7.fhir.dstu3.elementmodel.Element; -import org.hl7.fhir.dstu3.elementmodel.Property; -import org.hl7.fhir.dstu3.model.Base; -import org.hl7.fhir.dstu3.model.BooleanType; -import org.hl7.fhir.dstu3.model.CodeType; -import org.hl7.fhir.dstu3.model.CodeableConcept; -import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.ConceptMap; -import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupComponent; -import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupUnmappedMode; -import org.hl7.fhir.dstu3.model.ConceptMap.SourceElementComponent; -import org.hl7.fhir.dstu3.model.ConceptMap.TargetElementComponent; -import org.hl7.fhir.dstu3.model.Constants; -import org.hl7.fhir.dstu3.model.ContactDetail; -import org.hl7.fhir.dstu3.model.ContactPoint; -import org.hl7.fhir.dstu3.model.DecimalType; -import org.hl7.fhir.dstu3.model.ElementDefinition; -import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionMappingComponent; -import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent; -import org.hl7.fhir.dstu3.model.Enumeration; -import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence; -import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus; -import org.hl7.fhir.dstu3.model.ExpressionNode; -import org.hl7.fhir.dstu3.model.ExpressionNode.CollectionStatus; -import org.hl7.fhir.dstu3.model.IdType; -import org.hl7.fhir.dstu3.model.IntegerType; -import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus; -import org.hl7.fhir.dstu3.model.PrimitiveType; -import org.hl7.fhir.dstu3.model.Reference; -import org.hl7.fhir.dstu3.model.Resource; -import org.hl7.fhir.dstu3.model.ResourceFactory; -import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionMappingComponent; -import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule; -import org.hl7.fhir.dstu3.model.StructureMap; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapContextType; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupComponent; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupInputComponent; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleComponent; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleDependentComponent; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleSourceComponent; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleTargetComponent; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleTargetParameterComponent; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupTypeMode; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapInputMode; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapSourceListMode; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapTargetListMode; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapModelMode; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapStructureComponent; -import org.hl7.fhir.dstu3.model.StructureMap.StructureMapTransform; -import org.hl7.fhir.dstu3.model.Type; -import org.hl7.fhir.dstu3.model.TypeDetails; -import org.hl7.fhir.dstu3.model.TypeDetails.ProfiledType; -import org.hl7.fhir.dstu3.model.UriType; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; -import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome; -import org.hl7.fhir.dstu3.utils.FHIRLexer.FHIRLexerException; -import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext; -import org.hl7.fhir.exceptions.DefinitionException; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.FHIRFormatError; -import org.hl7.fhir.exceptions.PathEngineException; -import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; -import org.hl7.fhir.utilities.TextFile; -import org.hl7.fhir.utilities.Utilities; -import org.hl7.fhir.utilities.xhtml.NodeType; -import org.hl7.fhir.utilities.xhtml.XhtmlNode; - -/** - * Services in this class: - * - * string render(map) - take a structure and convert it to text - * map parse(text) - take a text representation and parse it - * getTargetType(map) - return the definition for the type to create to hand in - * transform(appInfo, source, map, target) - transform from source to target following the map - * analyse(appInfo, map) - generate profiles and other analysis artifacts for the targets of the transform - * map generateMapFromMappings(StructureDefinition) - build a mapping from a structure definition with loigcal mappings - * - * @author Grahame Grieve - * - */ -public class StructureMapUtilities { - - public class ResolvedGroup { - public StructureMapGroupComponent target; - public StructureMap targetMap; - } - public static final String MAP_WHERE_CHECK = "map.where.check"; - public static final String MAP_WHERE_EXPRESSION = "map.where.expression"; - public static final String MAP_SEARCH_EXPRESSION = "map.search.expression"; - public static final String MAP_EXPRESSION = "map.transform.expression"; - private static final boolean RENDER_MULTIPLE_TARGETS_ONELINE = true; - private static final String AUTO_VAR_NAME = "vvv"; - - public interface ITransformerServices { - // public boolean validateByValueSet(Coding code, String valuesetId); - public void log(String message); // log internal progress - public Base createType(Object appInfo, String name) throws FHIRException; - public Base createResource(Object appInfo, Base res); // an already created resource is provided; this is to identify/store it - public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException; - // public Coding translate(Coding code) - // ValueSet validation operation - // Translation operation - // Lookup another tree of data - // Create an instance tree - // Return the correct string format to refer to a tree (input or output) - public Base resolveReference(Object appContext, String url); - public List performSearch(Object appContext, String url); - } - - private class FFHIRPathHostServices implements IEvaluationContext{ - - public Base resolveConstant(Object appContext, String name) throws PathEngineException { - Variables vars = (Variables) appContext; - Base res = vars.get(VariableMode.INPUT, name); - if (res == null) - res = vars.get(VariableMode.OUTPUT, name); - return res; - } - - @Override - public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { - if (!(appContext instanceof VariablesForProfiling)) - throw new Error("Internal Logic Error (wrong type '"+appContext.getClass().getName()+"' in resolveConstantType)"); - VariablesForProfiling vars = (VariablesForProfiling) appContext; - VariableForProfiling v = vars.get(null, name); - if (v == null) - throw new PathEngineException("Unknown variable '"+name+"' from variables "+vars.summary()); - return v.property.types; - } - - @Override - public boolean log(String argument, List focus) { - throw new Error("Not Implemented Yet"); - } - - @Override - public FunctionDetails resolveFunction(String functionName) { - return null; // throw new Error("Not Implemented Yet"); - } - - @Override - public TypeDetails checkFunction(Object appContext, String functionName, List parameters) throws PathEngineException { - throw new Error("Not Implemented Yet"); - } - - @Override - public List executeFunction(Object appContext, String functionName, List> parameters) { - throw new Error("Not Implemented Yet"); - } - - @Override - public Base resolveReference(Object appContext, String url) { - if (services == null) - return null; - return services.resolveReference(appContext, url); - } - - } - private IWorkerContext worker; - private FHIRPathEngine fpe; - private Map library; - private ITransformerServices services; - private ProfileKnowledgeProvider pkp; - private Map ids = new HashMap(); - - public StructureMapUtilities(IWorkerContext worker, Map library, ITransformerServices services, ProfileKnowledgeProvider pkp) { - super(); - this.worker = worker; - this.library = library; - this.services = services; - this.pkp = pkp; - fpe = new FHIRPathEngine(worker); - fpe.setHostServices(new FFHIRPathHostServices()); - } - - public StructureMapUtilities(IWorkerContext worker, Map library, ITransformerServices services) { - super(); - this.worker = worker; - this.library = library; - this.services = services; - fpe = new FHIRPathEngine(worker); - fpe.setHostServices(new FFHIRPathHostServices()); - } - - public StructureMapUtilities(IWorkerContext worker, Map library) { - super(); - this.worker = worker; - this.library = library; - fpe = new FHIRPathEngine(worker); - fpe.setHostServices(new FFHIRPathHostServices()); - } - - public StructureMapUtilities(IWorkerContext worker) { - super(); - this.worker = worker; - fpe = new FHIRPathEngine(worker); - fpe.setHostServices(new FFHIRPathHostServices()); - } - - public StructureMapUtilities(IWorkerContext worker, ITransformerServices services) { - super(); - this.worker = worker; - this.library = new HashMap(); - for (org.hl7.fhir.dstu3.model.MetadataResource bc : worker.allConformanceResources()) { - if (bc instanceof StructureMap) - library.put(bc.getUrl(), (StructureMap) bc); - } - this.services = services; - fpe = new FHIRPathEngine(worker); - fpe.setHostServices(new FFHIRPathHostServices()); - } - - public static String render(StructureMap map) { - StringBuilder b = new StringBuilder(); - b.append("map \""); - b.append(map.getUrl()); - b.append("\" = \""); - b.append(Utilities.escapeJava(map.getName())); - b.append("\"\r\n\r\n"); - - renderConceptMaps(b, map); - renderUses(b, map); - renderImports(b, map); - for (StructureMapGroupComponent g : map.getGroup()) - renderGroup(b, g); - return b.toString(); - } - - private static void renderConceptMaps(StringBuilder b, StructureMap map) { - for (Resource r : map.getContained()) { - if (r instanceof ConceptMap) { - produceConceptMap(b, (ConceptMap) r); - } - } - } - - private static void produceConceptMap(StringBuilder b, ConceptMap cm) { - b.append("conceptmap \""); - b.append(cm.getId()); - b.append("\" {\r\n"); - Map prefixesSrc = new HashMap(); - Map prefixesTgt = new HashMap(); - char prefix = 's'; - for (ConceptMapGroupComponent cg : cm.getGroup()) { - if (!prefixesSrc.containsKey(cg.getSource())) { - prefixesSrc.put(cg.getSource(), String.valueOf(prefix)); - b.append(" prefix "); - b.append(prefix); - b.append(" = \""); - b.append(cg.getSource()); - b.append("\"\r\n"); - prefix++; - } - if (!prefixesTgt.containsKey(cg.getTarget())) { - prefixesTgt.put(cg.getTarget(), String.valueOf(prefix)); - b.append(" prefix "); - b.append(prefix); - b.append(" = \""); - b.append(cg.getTarget()); - b.append("\"\r\n"); - prefix++; - } - } - b.append("\r\n"); - for (ConceptMapGroupComponent cg : cm.getGroup()) { - if (cg.hasUnmapped()) { - b.append(" unmapped for "); - b.append(prefix); - b.append(" = "); - b.append(cg.getUnmapped().getMode()); - b.append("\r\n"); - } - } - - for (ConceptMapGroupComponent cg : cm.getGroup()) { - for (SourceElementComponent ce : cg.getElement()) { - b.append(" "); - b.append(prefixesSrc.get(cg.getSource())); - b.append(":"); - b.append(ce.getCode()); - b.append(" "); - b.append(getChar(ce.getTargetFirstRep().getEquivalence())); - b.append(" "); - b.append(prefixesTgt.get(cg.getTarget())); - b.append(":"); - b.append(ce.getTargetFirstRep().getCode()); - b.append("\r\n"); - } - } - b.append("}\r\n\r\n"); - } - - private static Object getChar(ConceptMapEquivalence equivalence) { - switch (equivalence) { - case RELATEDTO: return "-"; - case EQUAL: return "="; - case EQUIVALENT: return "=="; - case DISJOINT: return "!="; - case UNMATCHED: return "--"; - case WIDER: return "<="; - case SUBSUMES: return "<-"; - case NARROWER: return ">="; - case SPECIALIZES: return ">-"; - case INEXACT: return "~"; - default: return "??"; - } - } - - private static void renderUses(StringBuilder b, StructureMap map) { - for (StructureMapStructureComponent s : map.getStructure()) { - b.append("uses \""); - b.append(s.getUrl()); - b.append("\" "); - if (s.hasAlias()) { - b.append("alias "); - b.append(s.getAlias()); - b.append(" "); - } - b.append("as "); - b.append(s.getMode().toCode()); - b.append("\r\n"); - renderDoco(b, s.getDocumentation()); - } - if (map.hasStructure()) - b.append("\r\n"); - } - - private static void renderImports(StringBuilder b, StructureMap map) { - for (UriType s : map.getImport()) { - b.append("imports \""); - b.append(s.getValue()); - b.append("\"\r\n"); - } - if (map.hasImport()) - b.append("\r\n"); - } - - public static String groupToString(StructureMapGroupComponent g) { - StringBuilder b = new StringBuilder(); - renderGroup(b, g); - return b.toString(); - } - - private static void renderGroup(StringBuilder b, StructureMapGroupComponent g) { - b.append("group "); - switch (g.getTypeMode()) { - case TYPES: b.append("for types"); - case TYPEANDTYPES: b.append("for type+types "); - default: // NONE, NULL - } - b.append("for types "); - b.append(g.getName()); - if (g.hasExtends()) { - b.append(" extends "); - b.append(g.getExtends()); - } - if (g.hasDocumentation()) - renderDoco(b, g.getDocumentation()); - b.append("\r\n"); - for (StructureMapGroupInputComponent gi : g.getInput()) { - b.append(" input "); - b.append(gi.getName()); - if (gi.hasType()) { - b.append(" : "); - b.append(gi.getType()); - } - b.append(" as "); - b.append(gi.getMode().toCode()); - b.append("\r\n"); - } - if (g.hasInput()) - b.append("\r\n"); - for (StructureMapGroupRuleComponent r : g.getRule()) { - renderRule(b, r, 2); - } - b.append("\r\nendgroup\r\n"); - } - - public static String ruleToString(StructureMapGroupRuleComponent r) { - StringBuilder b = new StringBuilder(); - renderRule(b, r, 0); - return b.toString(); - } - - private static void renderRule(StringBuilder b, StructureMapGroupRuleComponent r, int indent) { - for (int i = 0; i < indent; i++) - b.append(' '); - b.append(r.getName()); - b.append(" : for "); - boolean canBeAbbreviated = checkisSimple(r); - - boolean first = true; - for (StructureMapGroupRuleSourceComponent rs : r.getSource()) { - if (first) - first = false; - else - b.append(", "); - renderSource(b, rs, canBeAbbreviated); - } - if (r.getTarget().size() > 1) { - b.append(" make "); - first = true; - for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) { - if (first) - first = false; - else - b.append(", "); - if (RENDER_MULTIPLE_TARGETS_ONELINE) - b.append(' '); - else { - b.append("\r\n"); - for (int i = 0; i < indent+4; i++) - b.append(' '); - } - renderTarget(b, rt, false); - } - } else if (r.hasTarget()) { - b.append(" make "); - renderTarget(b, r.getTarget().get(0), canBeAbbreviated); - } - if (!canBeAbbreviated) { - if (r.hasRule()) { - b.append(" then {\r\n"); - renderDoco(b, r.getDocumentation()); - for (StructureMapGroupRuleComponent ir : r.getRule()) { - renderRule(b, ir, indent+2); - } - for (int i = 0; i < indent; i++) - b.append(' '); - b.append("}\r\n"); - } else { - if (r.hasDependent()) { - b.append(" then "); - first = true; - for (StructureMapGroupRuleDependentComponent rd : r.getDependent()) { - if (first) - first = false; - else - b.append(", "); - b.append(rd.getName()); - b.append("("); - boolean ifirst = true; - for (StringType rdp : rd.getVariable()) { - if (ifirst) - ifirst = false; - else - b.append(", "); - b.append(rdp.asStringValue()); - } - b.append(")"); - } - } - } - } - renderDoco(b, r.getDocumentation()); - b.append("\r\n"); - } - - private static boolean checkisSimple(StructureMapGroupRuleComponent r) { - return - (r.getSource().size() == 1 && r.getSourceFirstRep().hasElement() && r.getSourceFirstRep().hasVariable()) && - (r.getTarget().size() == 1 && r.getTargetFirstRep().hasVariable() && (r.getTargetFirstRep().getTransform() == null || r.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE) && r.getTargetFirstRep().getParameter().size() == 0) && - (r.getDependent().size() == 0); - } - - public static String sourceToString(StructureMapGroupRuleSourceComponent r) { - StringBuilder b = new StringBuilder(); - renderSource(b, r, false); - return b.toString(); - } - - private static void renderSource(StringBuilder b, StructureMapGroupRuleSourceComponent rs, boolean abbreviate) { - b.append(rs.getContext()); - if (rs.getContext().equals("@search")) { - b.append('('); - b.append(rs.getElement()); - b.append(')'); - } else if (rs.hasElement()) { - b.append('.'); - b.append(rs.getElement()); - } - if (rs.hasType()) { - b.append(" : "); - b.append(rs.getType()); - if (rs.hasMin()) { - b.append(" "); - b.append(rs.getMin()); - b.append(".."); - b.append(rs.getMax()); - } - } - - if (rs.hasListMode()) { - b.append(" "); - b.append(rs.getListMode().toCode()); - } - if (rs.hasDefaultValue()) { - b.append(" default "); - assert rs.getDefaultValue() instanceof StringType; - b.append("\""+Utilities.escapeJson(((StringType) rs.getDefaultValue()).asStringValue())+"\""); - } - if (!abbreviate && rs.hasVariable()) { - b.append(" as "); - b.append(rs.getVariable()); - } - if (rs.hasCondition()) { - b.append(" where "); - b.append(rs.getCondition()); - } - if (rs.hasCheck()) { - b.append(" check "); - b.append(rs.getCheck()); - } - } - - public static String targetToString(StructureMapGroupRuleTargetComponent rt) { - StringBuilder b = new StringBuilder(); - renderTarget(b, rt, false); - return b.toString(); - } - - private static void renderTarget(StringBuilder b, StructureMapGroupRuleTargetComponent rt, boolean abbreviate) { - if (rt.hasContext()) { - if (rt.getContextType() == StructureMapContextType.TYPE) - b.append("@"); - b.append(rt.getContext()); - if (rt.hasElement()) { - b.append('.'); - b.append(rt.getElement()); - } - } - if (!abbreviate && rt.hasTransform()) { - if (rt.hasContext()) - b.append(" = "); - if (rt.getTransform() == StructureMapTransform.COPY && rt.getParameter().size() == 1) { - renderTransformParam(b, rt.getParameter().get(0)); - } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 1) { - b.append("("); - b.append("\""+((StringType) rt.getParameter().get(0).getValue()).asStringValue()+"\""); - b.append(")"); - } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 2) { - b.append(rt.getTransform().toCode()); - b.append("("); - b.append(((IdType) rt.getParameter().get(0).getValue()).asStringValue()); - b.append("\""+((StringType) rt.getParameter().get(1).getValue()).asStringValue()+"\""); - b.append(")"); - } else { - b.append(rt.getTransform().toCode()); - b.append("("); - boolean first = true; - for (StructureMapGroupRuleTargetParameterComponent rtp : rt.getParameter()) { - if (first) - first = false; - else - b.append(", "); - renderTransformParam(b, rtp); - } - b.append(")"); - } - } - if (!abbreviate && rt.hasVariable()) { - b.append(" as "); - b.append(rt.getVariable()); - } - for (Enumeration lm : rt.getListMode()) { - b.append(" "); - b.append(lm.getValue().toCode()); - if (lm.getValue() == StructureMapTargetListMode.SHARE) { - b.append(" "); - b.append(rt.getListRuleId()); - } - } - } - - public static String paramToString(StructureMapGroupRuleTargetParameterComponent rtp) { - StringBuilder b = new StringBuilder(); - renderTransformParam(b, rtp); - return b.toString(); - } - - private static void renderTransformParam(StringBuilder b, StructureMapGroupRuleTargetParameterComponent rtp) { - try { - if (rtp.hasValueBooleanType()) - b.append(rtp.getValueBooleanType().asStringValue()); - else if (rtp.hasValueDecimalType()) - b.append(rtp.getValueDecimalType().asStringValue()); - else if (rtp.hasValueIdType()) - b.append(rtp.getValueIdType().asStringValue()); - else if (rtp.hasValueDecimalType()) - b.append(rtp.getValueDecimalType().asStringValue()); - else if (rtp.hasValueIntegerType()) - b.append(rtp.getValueIntegerType().asStringValue()); - else - b.append("\""+Utilities.escapeJava(rtp.getValueStringType().asStringValue())+"\""); - } catch (FHIRException e) { - e.printStackTrace(); - b.append("error!"); - } - } - - private static void renderDoco(StringBuilder b, String doco) { - if (Utilities.noString(doco)) - return; - b.append(" // "); - b.append(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")); - } - - public StructureMap parse(String text) throws FHIRException { - FHIRLexer lexer = new FHIRLexer(text); - if (lexer.done()) - throw lexer.error("Map Input cannot be empty"); - lexer.skipComments(); - lexer.token("map"); - StructureMap result = new StructureMap(); - result.setUrl(lexer.readConstant("url")); - lexer.token("="); - result.setName(lexer.readConstant("name")); - lexer.skipComments(); - - while (lexer.hasToken("conceptmap")) - parseConceptMap(result, lexer); - - while (lexer.hasToken("uses")) - parseUses(result, lexer); - while (lexer.hasToken("imports")) - parseImports(result, lexer); - - parseGroup(result, lexer); - - while (!lexer.done()) { - parseGroup(result, lexer); - } - - return result; - } - - private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException { - lexer.token("conceptmap"); - ConceptMap map = new ConceptMap(); - String id = lexer.readConstant("map id"); - if (!id.startsWith("#")) - lexer.error("Concept Map identifier must start with #"); - map.setId(id); - map.setStatus(PublicationStatus.DRAFT); // todo: how to add this to the text format - result.getContained().add(map); - lexer.token("{"); - lexer.skipComments(); - // lexer.token("source"); - // map.setSource(new UriType(lexer.readConstant("source"))); - // lexer.token("target"); - // map.setSource(new UriType(lexer.readConstant("target"))); - Map prefixes = new HashMap(); - while (lexer.hasToken("prefix")) { - lexer.token("prefix"); - String n = lexer.take(); - lexer.token("="); - String v = lexer.readConstant("prefix url"); - prefixes.put(n, v); - } - while (lexer.hasToken("unmapped")) { - lexer.token("unmapped"); - lexer.token("for"); - String n = readPrefix(prefixes, lexer); - ConceptMapGroupComponent g = getGroup(map, n, null); - lexer.token("="); - String v = lexer.take(); - if (v.equals("provided")) { - g.getUnmapped().setMode(ConceptMapGroupUnmappedMode.PROVIDED); - } else - lexer.error("Only unmapped mode PROVIDED is supported at this time"); - } - while (!lexer.hasToken("}")) { - String srcs = readPrefix(prefixes, lexer); - lexer.token(":"); - String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take(); - ConceptMapEquivalence eq = readEquivalence(lexer); - String tgts = (eq != ConceptMapEquivalence.UNMATCHED) ? readPrefix(prefixes, lexer) : ""; - ConceptMapGroupComponent g = getGroup(map, srcs, tgts); - SourceElementComponent e = g.addElement(); - e.setCode(sc); - if (e.getCode().startsWith("\"")) - e.setCode(lexer.processConstant(e.getCode())); - TargetElementComponent tgt = e.addTarget(); - if (eq != ConceptMapEquivalence.EQUIVALENT) - tgt.setEquivalence(eq); - if (tgt.getEquivalence() != ConceptMapEquivalence.UNMATCHED) { - lexer.token(":"); - tgt.setCode(lexer.take()); - if (tgt.getCode().startsWith("\"")) - tgt.setCode(lexer.processConstant(tgt.getCode())); - } - if (lexer.hasComment()) - tgt.setComment(lexer.take().substring(2).trim()); - } - lexer.token("}"); - } - - - private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) { - for (ConceptMapGroupComponent grp : map.getGroup()) { - if (grp.getSource().equals(srcs)) - if ((tgts == null && !grp.hasTarget()) || (tgts != null && tgts.equals(grp.getTarget()))) - return grp; - } - ConceptMapGroupComponent grp = map.addGroup(); - grp.setSource(srcs); - grp.setTarget(tgts); - return grp; - } - - - private String readPrefix(Map prefixes, FHIRLexer lexer) throws FHIRLexerException { - String prefix = lexer.take(); - if (!prefixes.containsKey(prefix)) - throw lexer.error("Unknown prefix '"+prefix+"'"); - return prefixes.get(prefix); - } - - - private ConceptMapEquivalence readEquivalence(FHIRLexer lexer) throws FHIRLexerException { - String token = lexer.take(); - if (token.equals("-")) - return ConceptMapEquivalence.RELATEDTO; - if (token.equals("=")) - return ConceptMapEquivalence.EQUAL; - if (token.equals("==")) - return ConceptMapEquivalence.EQUIVALENT; - if (token.equals("!=")) - return ConceptMapEquivalence.DISJOINT; - if (token.equals("--")) - return ConceptMapEquivalence.UNMATCHED; - if (token.equals("<=")) - return ConceptMapEquivalence.WIDER; - if (token.equals("<-")) - return ConceptMapEquivalence.SUBSUMES; - if (token.equals(">=")) - return ConceptMapEquivalence.NARROWER; - if (token.equals(">-")) - return ConceptMapEquivalence.SPECIALIZES; - if (token.equals("~")) - return ConceptMapEquivalence.INEXACT; - throw lexer.error("Unknown equivalence token '"+token+"'"); - } - - - private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException { - lexer.token("uses"); - StructureMapStructureComponent st = result.addStructure(); - st.setUrl(lexer.readConstant("url")); - if (lexer.hasToken("alias")) { - lexer.token("alias"); - st.setAlias(lexer.take()); - } - lexer.token("as"); - st.setMode(StructureMapModelMode.fromCode(lexer.take())); - lexer.skipToken(";"); - if (lexer.hasComment()) { - st.setDocumentation(lexer.take().substring(2).trim()); - } - lexer.skipComments(); - } - - private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException { - lexer.token("imports"); - result.addImport(lexer.readConstant("url")); - lexer.skipToken(";"); - if (lexer.hasComment()) { - lexer.next(); - } - lexer.skipComments(); - } - - private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException { - lexer.token("group"); - StructureMapGroupComponent group = result.addGroup(); - if (lexer.hasToken("for")) { - lexer.token("for"); - if ("type".equals(lexer.getCurrent())) { - lexer.token("type"); - lexer.token("+"); - lexer.token("types"); - group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES); - } else { - lexer.token("types"); - group.setTypeMode(StructureMapGroupTypeMode.TYPES); - } - } else - group.setTypeMode(StructureMapGroupTypeMode.NONE); - group.setName(lexer.take()); - if (lexer.hasToken("extends")) { - lexer.next(); - group.setExtends(lexer.take()); - } - lexer.skipComments(); - while (lexer.hasToken("input")) - parseInput(group, lexer); - while (!lexer.hasToken("endgroup")) { - if (lexer.done()) - throw lexer.error("premature termination expecting 'endgroup'"); - parseRule(result, group.getRule(), lexer); - } - lexer.next(); - lexer.skipComments(); - } - - private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer) throws FHIRException { - lexer.token("input"); - StructureMapGroupInputComponent input = group.addInput(); - input.setName(lexer.take()); - if (lexer.hasToken(":")) { - lexer.token(":"); - input.setType(lexer.take()); - } - lexer.token("as"); - input.setMode(StructureMapInputMode.fromCode(lexer.take())); - if (lexer.hasComment()) { - input.setDocumentation(lexer.take().substring(2).trim()); - } - lexer.skipToken(";"); - lexer.skipComments(); - } - - private void parseRule(StructureMap map, List list, FHIRLexer lexer) throws FHIRException { - StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent(); - list.add(rule); - rule.setName(lexer.takeDottedToken()); - lexer.token(":"); - lexer.token("for"); - boolean done = false; - while (!done) { - parseSource(rule, lexer); - done = !lexer.hasToken(","); - if (!done) - lexer.next(); - } - if (lexer.hasToken("make")) { - lexer.token("make"); - done = false; - while (!done) { - parseTarget(rule, lexer); - done = !lexer.hasToken(","); - if (!done) - lexer.next(); - } - } - if (lexer.hasToken("then")) { - lexer.token("then"); - if (lexer.hasToken("{")) { - lexer.token("{"); - if (lexer.hasComment()) { - rule.setDocumentation(lexer.take().substring(2).trim()); - } - lexer.skipComments(); - while (!lexer.hasToken("}")) { - if (lexer.done()) - throw lexer.error("premature termination expecting '}' in nested group"); - parseRule(map, rule.getRule(), lexer); - } - lexer.token("}"); - } else { - done = false; - while (!done) { - parseRuleReference(rule, lexer); - done = !lexer.hasToken(","); - if (!done) - lexer.next(); - } - } - } else if (lexer.hasComment()) { - rule.setDocumentation(lexer.take().substring(2).trim()); - } - if (isSimpleSyntax(rule)) { - rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME); - rule.getTargetFirstRep().setVariable(AUTO_VAR_NAME); - rule.getTargetFirstRep().setTransform(StructureMapTransform.CREATE); // with no parameter - e.g. imply what is to be created - // no dependencies - imply what is to be done based on types - } - lexer.skipComments(); - } - - private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) { - return - (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) && - (rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasContext() && rule.getTargetFirstRep().hasElement() && !rule.getTargetFirstRep().hasVariable() && !rule.getTargetFirstRep().hasParameter()) && - (rule.getDependent().size() == 0 && rule.getRule().size() == 0); - } - - private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException { - StructureMapGroupRuleDependentComponent ref = rule.addDependent(); - ref.setName(lexer.take()); - lexer.token("("); - boolean done = false; - while (!done) { - ref.addVariable(lexer.take()); - done = !lexer.hasToken(","); - if (!done) - lexer.next(); - } - lexer.token(")"); - } - - private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { - StructureMapGroupRuleSourceComponent source = rule.addSource(); - source.setContext(lexer.take()); - if (source.getContext().equals("search") && lexer.hasToken("(")) { - source.setContext("@search"); - lexer.take(); - ExpressionNode node = fpe.parse(lexer); - source.setUserData(MAP_SEARCH_EXPRESSION, node); - source.setElement(node.toString()); - lexer.token(")"); - } else if (lexer.hasToken(".")) { - lexer.token("."); - source.setElement(lexer.take()); - } - if (lexer.hasToken(":")) { - // type and cardinality - lexer.token(":"); - source.setType(lexer.takeDottedToken()); - if (!lexer.hasToken("as", "first", "last", "not_first", "not_last", "only_one", "default")) { - source.setMin(lexer.takeInt()); - lexer.token(".."); - source.setMax(lexer.take()); - } - } - if (lexer.hasToken("default")) { - lexer.token("default"); - source.setDefaultValue(new StringType(lexer.readConstant("default value"))); - } - if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one")) - source.setListMode(StructureMapSourceListMode.fromCode(lexer.take())); - - if (lexer.hasToken("as")) { - lexer.take(); - source.setVariable(lexer.take()); - } - if (lexer.hasToken("where")) { - lexer.take(); - ExpressionNode node = fpe.parse(lexer); - source.setUserData(MAP_WHERE_EXPRESSION, node); - source.setCondition(node.toString()); - } - if (lexer.hasToken("check")) { - lexer.take(); - ExpressionNode node = fpe.parse(lexer); - source.setUserData(MAP_WHERE_CHECK, node); - source.setCheck(node.toString()); - } - } - - private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { - StructureMapGroupRuleTargetComponent target = rule.addTarget(); - String start = lexer.take(); - if (lexer.hasToken(".")) { - target.setContext(start); - target.setContextType(StructureMapContextType.VARIABLE); - start = null; - lexer.token("."); - target.setElement(lexer.take()); - } - String name; - boolean isConstant = false; - if (lexer.hasToken("=")) { - if (start != null) - target.setContext(start); - lexer.token("="); - isConstant = lexer.isConstant(true); - name = lexer.take(); - } else - name = start; - - if ("(".equals(name)) { - // inline fluentpath expression - target.setTransform(StructureMapTransform.EVALUATE); - ExpressionNode node = fpe.parse(lexer); - target.setUserData(MAP_EXPRESSION, node); - target.addParameter().setValue(new StringType(node.toString())); - lexer.token(")"); - } else if (lexer.hasToken("(")) { - target.setTransform(StructureMapTransform.fromCode(name)); - lexer.token("("); - if (target.getTransform() == StructureMapTransform.EVALUATE) { - parseParameter(target, lexer); - lexer.token(","); - ExpressionNode node = fpe.parse(lexer); - target.setUserData(MAP_EXPRESSION, node); - target.addParameter().setValue(new StringType(node.toString())); - } else { - while (!lexer.hasToken(")")) { - parseParameter(target, lexer); - if (!lexer.hasToken(")")) - lexer.token(","); - } - } - lexer.token(")"); - } else if (name != null) { - target.setTransform(StructureMapTransform.COPY); - if (!isConstant) { - String id = name; - while (lexer.hasToken(".")) { - id = id + lexer.take() + lexer.take(); - } - target.addParameter().setValue(new IdType(id)); - } - else - target.addParameter().setValue(readConstant(name, lexer)); - } - if (lexer.hasToken("as")) { - lexer.take(); - target.setVariable(lexer.take()); - } - while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) { - if (lexer.getCurrent().equals("share")) { - target.addListMode(StructureMapTargetListMode.SHARE); - lexer.next(); - target.setListRuleId(lexer.take()); - } else if (lexer.getCurrent().equals("first")) - target.addListMode(StructureMapTargetListMode.FIRST); - else - target.addListMode(StructureMapTargetListMode.LAST); - lexer.next(); - } - } - - - private void parseParameter(StructureMapGroupRuleTargetComponent target, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError { - if (!lexer.isConstant(true)) { - target.addParameter().setValue(new IdType(lexer.take())); - } else if (lexer.isStringConstant()) - target.addParameter().setValue(new StringType(lexer.readConstant("??"))); - else { - target.addParameter().setValue(readConstant(lexer.take(), lexer)); - } - } - - private Type readConstant(String s, FHIRLexer lexer) throws FHIRLexerException { - if (Utilities.isInteger(s)) - return new IntegerType(s); - else if (Utilities.isDecimal(s)) - return new DecimalType(s); - else if (Utilities.existsInList(s, "true", "false")) - return new BooleanType(s.equals("true")); - else - return new StringType(lexer.processConstant(s)); - } - - public StructureDefinition getTargetType(StructureMap map) throws FHIRException { - boolean found = false; - StructureDefinition res = null; - for (StructureMapStructureComponent uses : map.getStructure()) { - if (uses.getMode() == StructureMapModelMode.TARGET) { - if (found) - throw new FHIRException("Multiple targets found in map "+map.getUrl()); - found = true; - res = worker.fetchResource(StructureDefinition.class, uses.getUrl()); - if (res == null) - throw new FHIRException("Unable to find "+uses.getUrl()+" referenced from map "+map.getUrl()); - } - } - if (res == null) - throw new FHIRException("No targets found in map "+map.getUrl()); - return res; - } - - public enum VariableMode { - INPUT, OUTPUT - } - - public class Variable { - private VariableMode mode; - private String name; - private Base object; - public Variable(VariableMode mode, String name, Base object) { - super(); - this.mode = mode; - this.name = name; - this.object = object; - } - public VariableMode getMode() { - return mode; - } - public String getName() { - return name; - } - public Base getObject() { - return object; - } - public String summary() { - return name+": "+object.fhirType(); - } - } - - public class Variables { - private List list = new ArrayList(); - - public void add(VariableMode mode, String name, Base object) { - Variable vv = null; - for (Variable v : list) - if ((v.mode == mode) && v.getName().equals(name)) - vv = v; - if (vv != null) - list.remove(vv); - list.add(new Variable(mode, name, object)); - } - - public Variables copy() { - Variables result = new Variables(); - result.list.addAll(list); - return result; - } - - public Base get(VariableMode mode, String name) { - for (Variable v : list) - if ((v.mode == mode) && v.getName().equals(name)) - return v.getObject(); - return null; - } - - public String summary() { - CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); - CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); - for (Variable v : list) - if (v.mode == VariableMode.INPUT) - s.append(v.summary()); - else - t.append(v.summary()); - return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]"; - } - } - - public class TransformContext { - private Object appInfo; - - public TransformContext(Object appInfo) { - super(); - this.appInfo = appInfo; - } - - public Object getAppInfo() { - return appInfo; - } - - } - - private void log(String cnt) { - if (services != null) - services.log(cnt); - } - - /** - * Given an item, return all the children that conform to the pattern described in name - * - * Possible patterns: - * - a simple name (which may be the base of a name with [] e.g. value[x]) - * - a name with a type replacement e.g. valueCodeableConcept - * - * which means all children - * - ** which means all descendents - * - * @param item - * @param name - * @param result - * @throws FHIRException - */ - protected void getChildrenByName(Base item, String name, List result) throws FHIRException { - for (Base v : item.listChildrenByName(name, true)) - if (v != null) - result.add(v); - } - - public void transform(Object appInfo, Base source, StructureMap map, Base target) throws FHIRException { - TransformContext context = new TransformContext(appInfo); - log("Start Transform "+map.getUrl()); - StructureMapGroupComponent g = map.getGroup().get(0); - - Variables vars = new Variables(); - vars.add(VariableMode.INPUT, getInputName(g, StructureMapInputMode.SOURCE, "source"), source); - vars.add(VariableMode.OUTPUT, getInputName(g, StructureMapInputMode.TARGET, "target"), target); - - executeGroup("", context, map, vars, g); - if (target instanceof Element) - ((Element) target).sort(); - } - - private String getInputName(StructureMapGroupComponent g, StructureMapInputMode mode, String def) throws DefinitionException { - String name = null; - for (StructureMapGroupInputComponent inp : g.getInput()) { - if (inp.getMode() == mode) - if (name != null) - throw new DefinitionException("This engine does not support multiple source inputs"); - else - name = inp.getName(); - } - return name == null ? def : name; - } - - private void executeGroup(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group) throws FHIRException { - log(indent+"Group : "+group.getName()); - // todo: check inputs - if (group.hasExtends()) { - ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends()); - executeGroup(indent+" ", context, rg.targetMap, vars, rg.target); - } - - for (StructureMapGroupRuleComponent r : group.getRule()) { - executeRule(indent+" ", context, map, vars, group, r); - } - } - - private void executeRule(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule) throws FHIRException { - log(indent+"rule : "+rule.getName()); - if (rule.getName().contains("CarePlan.participant-unlink")) - System.out.println("debug"); - Variables srcVars = vars.copy(); - if (rule.getSource().size() != 1) - throw new FHIRException("Rule \""+rule.getName()+"\": not handled yet"); - List source = processSource(rule.getName(), context, srcVars, rule.getSource().get(0)); - if (source != null) { - for (Variables v : source) { - for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { - processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null); - } - if (rule.hasRule()) { - for (StructureMapGroupRuleComponent childrule : rule.getRule()) { - executeRule(indent +" ", context, map, v, group, childrule); - } - } else if (rule.hasDependent()) { - for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { - executeDependency(indent+" ", context, map, v, group, dependent); - } - } else if (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasVariable() && rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasVariable() && rule.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE && !rule.getTargetFirstRep().hasParameter()) { - // simple inferred, map by type - Base src = v.get(VariableMode.INPUT, rule.getSourceFirstRep().getVariable()); - Base tgt = v.get(VariableMode.OUTPUT, rule.getTargetFirstRep().getVariable()); - String srcType = src.fhirType(); - String tgtType = tgt.fhirType(); - ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType); - Variables vdef = new Variables(); - vdef.add(VariableMode.INPUT, defGroup.target.getInput().get(0).getName(), src); - vdef.add(VariableMode.OUTPUT, defGroup.target.getInput().get(1).getName(), tgt); - executeGroup(indent+" ", context, defGroup.targetMap, vdef, defGroup.target); - } - } - } - } - - private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException { - ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName()); - - if (rg.target.getInput().size() != dependent.getVariable().size()) { - throw new FHIRException("Rule '"+dependent.getName()+"' has "+Integer.toString(rg.target.getInput().size())+" but the invocation has "+Integer.toString(dependent.getVariable().size())+" variables"); - } - Variables v = new Variables(); - for (int i = 0; i < rg.target.getInput().size(); i++) { - StructureMapGroupInputComponent input = rg.target.getInput().get(i); - StringType rdp = dependent.getVariable().get(i); - String var = rdp.asStringValue(); - VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT : VariableMode.OUTPUT; - Base vv = vin.get(mode, var); - if (vv == null && mode == VariableMode.INPUT) //* once source, always source. but target can be treated as source at user convenient - vv = vin.get(VariableMode.OUTPUT, var); - if (vv == null) - throw new FHIRException("Rule '"+dependent.getName()+"' "+mode.toString()+" variable '"+input.getName()+"' named as '"+var+"' has no value"); - v.add(mode, input.getName(), vv); - } - executeGroup(indent+" ", context, rg.targetMap, v, rg.target); - } - - private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException { - String type = base.fhirType(); - String kn = "type^"+type; - if (source.hasUserData(kn)) - return source.getUserString(kn); - - ResolvedGroup res = new ResolvedGroup(); - res.targetMap = null; - res.target = null; - for (StructureMapGroupComponent grp : map.getGroup()) { - if (matchesByType(map, grp, type)) { - if (res.targetMap == null) { - res.targetMap = map; - res.target = grp; - } else - throw new FHIRException("Multiple possible matches looking for default rule for '"+type+"'"); - } - } - if (res.targetMap != null) { - String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); - source.setUserData(kn, result); - return result; - } - - for (UriType imp : map.getImport()) { - List impMapList = findMatchingMaps(imp.getValue()); - if (impMapList.size() == 0) - throw new FHIRException("Unable to find map(s) for "+imp.getValue()); - for (StructureMap impMap : impMapList) { - if (!impMap.getUrl().equals(map.getUrl())) { - for (StructureMapGroupComponent grp : impMap.getGroup()) { - if (matchesByType(impMap, grp, type)) { - if (res.targetMap == null) { - res.targetMap = impMap; - res.target = grp; - } else - throw new FHIRException("Multiple possible matches for default rule for '"+type+"' in "+res.targetMap.getUrl()+" ("+res.target.getName()+") and "+impMap.getUrl()+" ("+grp.getName()+")"); - } - } - } - } - } - if (res.target == null) - throw new FHIRException("No matches found for default rule for '"+type+"' from "+map.getUrl()); - String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); // should be .getType, but R2... - source.setUserData(kn, result); - return result; - } - - private List findMatchingMaps(String value) { - List res = new ArrayList(); - if (value.contains("*")) { - for (StructureMap sm : library.values()) { - if (urlMatches(value, sm.getUrl())) { - res.add(sm); - } - } - } else { - StructureMap sm = library.get(value); - if (sm != null) - res.add(sm); - } - Set check = new HashSet(); - for (StructureMap sm : res) { - if (check.contains(sm.getUrl())) - throw new Error("duplicate"); - else - check.add(sm.getUrl()); - } - return res; - } - - private boolean urlMatches(String mask, String url) { - return url.length() > mask.length() && url.startsWith(mask.substring(0, mask.indexOf("*"))) && url.endsWith(mask.substring(mask.indexOf("*")+1)) ; - } - - private ResolvedGroup resolveGroupByTypes(StructureMap map, String ruleid, StructureMapGroupComponent source, String srcType, String tgtType) throws FHIRException { - String kn = "types^"+srcType+":"+tgtType; - if (source.hasUserData(kn)) - return (ResolvedGroup) source.getUserData(kn); - - ResolvedGroup res = new ResolvedGroup(); - res.targetMap = null; - res.target = null; - for (StructureMapGroupComponent grp : map.getGroup()) { - if (matchesByType(map, grp, srcType, tgtType)) { - if (res.targetMap == null) { - res.targetMap = map; - res.target = grp; - } else - throw new FHIRException("Multiple possible matches looking for rule for '"+srcType+"/"+tgtType+"', from rule '"+ruleid+"'"); - } - } - if (res.targetMap != null) { - source.setUserData(kn, res); - return res; - } - - for (UriType imp : map.getImport()) { - List impMapList = findMatchingMaps(imp.getValue()); - if (impMapList.size() == 0) - throw new FHIRException("Unable to find map(s) for "+imp.getValue()); - for (StructureMap impMap : impMapList) { - if (!impMap.getUrl().equals(map.getUrl())) { - for (StructureMapGroupComponent grp : impMap.getGroup()) { - if (matchesByType(impMap, grp, srcType, tgtType)) { - if (res.targetMap == null) { - res.targetMap = impMap; - res.target = grp; - } else - throw new FHIRException("Multiple possible matches for rule for '"+srcType+"/"+tgtType+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl()+", from rule '"+ruleid+"'"); - } - } - } - } - } - if (res.target == null) - throw new FHIRException("No matches found for rule for '"+srcType+"/"+tgtType+"' from "+map.getUrl()+", from rule '"+ruleid+"'"); - source.setUserData(kn, res); - return res; - } - - - private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String type) throws FHIRException { - if (grp.getTypeMode() != StructureMapGroupTypeMode.TYPEANDTYPES) - return false; - if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) - return false; - return matchesType(map, type, grp.getInput().get(0).getType()); - } - - private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String srcType, String tgtType) throws FHIRException { - if (grp.getTypeMode() == StructureMapGroupTypeMode.NONE) - return false; - if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) - return false; - if (!grp.getInput().get(0).hasType() || !grp.getInput().get(1).hasType()) - return false; - return matchesType(map, srcType, grp.getInput().get(0).getType()) && matchesType(map, tgtType, grp.getInput().get(1).getType()); - } - - private boolean matchesType(StructureMap map, String actualType, String statedType) throws FHIRException { - // check the aliases - for (StructureMapStructureComponent imp : map.getStructure()) { - if (imp.hasAlias() && statedType.equals(imp.getAlias())) { - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); - if (sd != null) - statedType = sd.getType(); - break; - } - } - - return actualType.equals(statedType); - } - - private String getActualType(StructureMap map, String statedType) throws FHIRException { - // check the aliases - for (StructureMapStructureComponent imp : map.getStructure()) { - if (imp.hasAlias() && statedType.equals(imp.getAlias())) { - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); - if (sd == null) - throw new FHIRException("Unable to resolve structure "+imp.getUrl()); - return sd.getId(); // should be sd.getType(), but R2... - } - } - return statedType; - } - - - private ResolvedGroup resolveGroupReference(StructureMap map, StructureMapGroupComponent source, String name) throws FHIRException { - String kn = "ref^"+name; - if (source.hasUserData(kn)) - return (ResolvedGroup) source.getUserData(kn); - - ResolvedGroup res = new ResolvedGroup(); - res.targetMap = null; - res.target = null; - for (StructureMapGroupComponent grp : map.getGroup()) { - if (grp.getName().equals(name)) { - if (res.targetMap == null) { - res.targetMap = map; - res.target = grp; - } else - throw new FHIRException("Multiple possible matches for rule '"+name+"'"); - } - } - if (res.targetMap != null) { - source.setUserData(kn, res); - return res; - } - - for (UriType imp : map.getImport()) { - List impMapList = findMatchingMaps(imp.getValue()); - if (impMapList.size() == 0) - throw new FHIRException("Unable to find map(s) for "+imp.getValue()); - for (StructureMap impMap : impMapList) { - if (!impMap.getUrl().equals(map.getUrl())) { - for (StructureMapGroupComponent grp : impMap.getGroup()) { - if (grp.getName().equals(name)) { - if (res.targetMap == null) { - res.targetMap = impMap; - res.target = grp; - } else - throw new FHIRException("Multiple possible matches for rule '"+name+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl()); - } - } - } - } - } - if (res.target == null) - throw new FHIRException("No matches found for rule '"+name+"'. Reference found in "+map.getUrl()); - source.setUserData(kn, res); - return res; - } - - private List processSource(String ruleId, TransformContext context, Variables vars, StructureMapGroupRuleSourceComponent src) throws FHIRException { - List items; - if (src.getContext().equals("@search")) { - ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_SEARCH_EXPRESSION); - if (expr == null) { - expr = fpe.parse(src.getElement()); - src.setUserData(MAP_SEARCH_EXPRESSION, expr); - } - String search = fpe.evaluateToString(vars, null, new StringType(), expr); // string is a holder of nothing to ensure that variables are processed correctly - items = services.performSearch(context.appInfo, search); - } else { - items = new ArrayList(); - Base b = vars.get(VariableMode.INPUT, src.getContext()); - if (b == null) - throw new FHIRException("Unknown input variable "+src.getContext()); - - if (!src.hasElement()) - items.add(b); - else { - getChildrenByName(b, src.getElement(), items); - if (items.size() == 0 && src.hasDefaultValue()) - items.add(src.getDefaultValue()); - } - } - - if (src.hasType()) { - List remove = new ArrayList(); - for (Base item : items) { - if (item != null && !isType(item, src.getType())) { - remove.add(item); - } - } - items.removeAll(remove); - } - - if (src.hasCondition()) { - ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_EXPRESSION); - if (expr == null) { - expr = fpe.parse(src.getCondition()); - // fpe.check(context.appInfo, ??, ??, expr) - src.setUserData(MAP_WHERE_EXPRESSION, expr); - } - List remove = new ArrayList(); - for (Base item : items) { - if (!fpe.evaluateToBoolean(vars, null, item, expr)) - remove.add(item); - } - items.removeAll(remove); - } - - if (src.hasCheck()) { - ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK); - if (expr == null) { - expr = fpe.parse(src.getCheck()); - // fpe.check(context.appInfo, ??, ??, expr) - src.setUserData(MAP_WHERE_CHECK, expr); - } - List remove = new ArrayList(); - for (Base item : items) { - if (!fpe.evaluateToBoolean(vars, null, item, expr)) - throw new FHIRException("Rule \""+ruleId+"\": Check condition failed"); - } - } - - - if (src.hasListMode() && !items.isEmpty()) { - switch (src.getListMode()) { - case FIRST: - Base bt = items.get(0); - items.clear(); - items.add(bt); - break; - case NOTFIRST: - if (items.size() > 0) - items.remove(0); - break; - case LAST: - bt = items.get(items.size()-1); - items.clear(); - items.add(bt); - break; - case NOTLAST: - if (items.size() > 0) - items.remove(items.size()-1); - break; - case ONLYONE: - if (items.size() > 1) - throw new FHIRException("Rule \""+ruleId+"\": Check condition failed: the collection has more than one item"); - break; - case NULL: - } - } - List result = new ArrayList(); - for (Base r : items) { - Variables v = vars.copy(); - if (src.hasVariable()) - v.add(VariableMode.INPUT, src.getVariable(), r); - result.add(v); - } - return result; - } - - - private boolean isType(Base item, String type) { - if (type.equals(item.fhirType())) - return true; - return false; - } - - private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar) throws FHIRException { - Base dest = null; - if (tgt.hasContext()) { - dest = vars.get(VariableMode.OUTPUT, tgt.getContext()); - if (dest == null) - throw new FHIRException("Rule \""+ruleId+"\": target context not known: "+tgt.getContext()); - if (!tgt.hasElement()) - throw new FHIRException("Rule \""+ruleId+"\": Not supported yet"); - } - Base v = null; - if (tgt.hasTransform()) { - v = runTransform(ruleId, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar); - if (v != null && dest != null) - v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value - } else if (dest != null) - v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); - if (tgt.hasVariable() && v != null) - vars.add(VariableMode.OUTPUT, tgt.getVariable(), v); - } - - private Base runTransform(String ruleId, TransformContext context, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, Variables vars, Base dest, String element, String srcVar) throws FHIRException { - try { - switch (tgt.getTransform()) { - case CREATE : - String tn; - if (tgt.getParameter().isEmpty()) { - // we have to work out the type. First, we see if there is a single type for the target. If there is, we use that - String[] types = dest.getTypesForProperty(element.hashCode(), element); - if (types.length == 1 && !"*".equals(types[0]) && !types[0].equals("Resource")) - tn = types[0]; - else if (srcVar != null) { - tn = determineTypeFromSourceType(map, group, vars.get(VariableMode.INPUT, srcVar), types); - } else - throw new Error("Cannot determine type implicitly because there is no single input variable"); - } else - tn = getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()); - Base res = services != null ? services.createType(context.getAppInfo(), tn) : ResourceFactory.createResourceOrType(tn); - if (res.isResource() && !res.fhirType().equals("Parameters")) { -// res.setIdBase(tgt.getParameter().size() > 1 ? getParamString(vars, tgt.getParameter().get(0)) : UUID.randomUUID().toString().toLowerCase()); - if (services != null) - res = services.createResource(context.getAppInfo(), res); - } - if (tgt.hasUserData("profile")) - res.setUserData("profile", tgt.getUserData("profile")); - return res; - case COPY : - return getParam(vars, tgt.getParameter().get(0)); - case EVALUATE : - ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); - if (expr == null) { - expr = fpe.parse(getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())); - tgt.setUserData(MAP_WHERE_EXPRESSION, expr); - } - List v = fpe.evaluate(vars, null, tgt.getParameter().size() == 2 ? getParam(vars, tgt.getParameter().get(0)) : new BooleanType(false), expr); - if (v.size() == 0) - return null; - else if (v.size() != 1) - throw new FHIRException("Rule \""+ruleId+"\": Evaluation of "+expr.toString()+" returned "+Integer.toString(v.size())+" objects"); - else - return v.get(0); - - case TRUNCATE : - String src = getParamString(vars, tgt.getParameter().get(0)); - String len = getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()); - if (Utilities.isInteger(len)) { - int l = Integer.parseInt(len); - if (src.length() > l) - src = src.substring(0, l); - } - return new StringType(src); - case ESCAPE : - throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); - case CAST : - throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); - case APPEND : - throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); - case TRANSLATE : - return translate(context, map, vars, tgt.getParameter()); - case REFERENCE : - Base b = getParam(vars, tgt.getParameter().get(0)); - if (b == null) - throw new FHIRException("Rule \""+ruleId+"\": Unable to find parameter "+((IdType) tgt.getParameter().get(0).getValue()).asStringValue()); - if (!b.isResource()) - throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType()); - else { - String id = b.getIdBase(); - if (id == null) { - id = UUID.randomUUID().toString().toLowerCase(); - b.setIdBase(id); - } - return new Reference().setReference(b.fhirType()+"/"+id); - } - case DATEOP : - throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); - case UUID : - return new IdType(UUID.randomUUID().toString()); - case POINTER : - b = getParam(vars, tgt.getParameter().get(0)); - if (b instanceof Resource) - return new UriType("urn:uuid:"+((Resource) b).getId()); - else - throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType()); - case CC: - CodeableConcept cc = new CodeableConcept(); - cc.addCoding(buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()))); - return cc; - case C: - Coding c = buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())); - return c; - default: - throw new Error("Rule \""+ruleId+"\": Transform Unknown: "+tgt.getTransform().toCode()); - } - } catch (Exception e) { - throw new FHIRException("Exception executing transform "+tgt.toString()+" on Rule \""+ruleId+"\": "+e.getMessage(), e); - } - } - - - private Coding buildCoding(String uri, String code) throws FHIRException { - // if we can get this as a valueSet, we will - String system = null; - String display = null; - ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri); - if (vs != null) { - ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false); - if (vse.getError() != null) - throw new FHIRException(vse.getError()); - CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); - for (ValueSetExpansionContainsComponent t : vse.getValueset().getExpansion().getContains()) { - if (t.hasCode()) - b.append(t.getCode()); - if (code.equals(t.getCode()) && t.hasSystem()) { - system = t.getSystem(); - display = t.getDisplay(); - break; - } - if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) { - system = t.getSystem(); - display = t.getDisplay(); - break; - } - } - if (system == null) - throw new FHIRException("The code '"+code+"' is not in the value set '"+uri+"' (valid codes: "+b.toString()+"; also checked displays)"); - } else - system = uri; - ValidationResult vr = worker.validateCode(system, code, null); - if (vr != null && vr.getDisplay() != null) - display = vr.getDisplay(); - return new Coding().setSystem(system).setCode(code).setDisplay(display); - } - - - private String getParamStringNoNull(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter, String message) throws FHIRException { - Base b = getParam(vars, parameter); - if (b == null) - throw new FHIRException("Unable to find a value for "+parameter.toString()+". Context: "+message); - if (!b.hasPrimitiveValue()) - throw new FHIRException("Found a value for "+parameter.toString()+", but it has a type of "+b.fhirType()+" and cannot be treated as a string. Context: "+message); - return b.primitiveValue(); - } - - private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { - Base b = getParam(vars, parameter); - if (b == null || !b.hasPrimitiveValue()) - return null; - return b.primitiveValue(); - } - - - private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { - Type p = parameter.getValue(); - if (!(p instanceof IdType)) - return p; - else { - String n = ((IdType) p).asStringValue(); - Base b = vars.get(VariableMode.INPUT, n); - if (b == null) - b = vars.get(VariableMode.OUTPUT, n); - if (b == null) - throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")"); - return b; - } - } - - - private Base translate(TransformContext context, StructureMap map, Variables vars, List parameter) throws FHIRException { - Base src = getParam(vars, parameter.get(0)); - String id = getParamString(vars, parameter.get(1)); - String fld = parameter.size() > 2 ? getParamString(vars, parameter.get(2)) : null; - return translate(context, map, src, id, fld); - } - - private class SourceElementComponentWrapper { - private ConceptMapGroupComponent group; - private SourceElementComponent comp; - public SourceElementComponentWrapper(ConceptMapGroupComponent group, SourceElementComponent comp) { - super(); - this.group = group; - this.comp = comp; - } - } - public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException { - Coding src = new Coding(); - if (source.isPrimitive()) { - src.setCode(source.primitiveValue()); - } else if ("Coding".equals(source.fhirType())) { - Base[] b = source.getProperty("system".hashCode(), "system", true); - if (b.length == 1) - src.setSystem(b[0].primitiveValue()); - b = source.getProperty("code".hashCode(), "code", true); - if (b.length == 1) - src.setCode(b[0].primitiveValue()); - } else if ("CE".equals(source.fhirType())) { - Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true); - if (b.length == 1) - src.setSystem(b[0].primitiveValue()); - b = source.getProperty("code".hashCode(), "code", true); - if (b.length == 1) - src.setCode(b[0].primitiveValue()); - } else - throw new FHIRException("Unable to translate source "+source.fhirType()); - - String su = conceptMapUrl; - if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) { - String uri = worker.oid2Uri(src.getCode()); - if (uri == null) - uri = "urn:oid:"+src.getCode(); - if ("uri".equals(fieldToReturn)) - return new UriType(uri); - else - throw new FHIRException("Error in return code"); - } else { - ConceptMap cmap = null; - if (conceptMapUrl.startsWith("#")) { - for (Resource r : map.getContained()) { - if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(conceptMapUrl.substring(1))) { - cmap = (ConceptMap) r; - su = map.getUrl()+conceptMapUrl; - } - } - if (cmap == null) - throw new FHIRException("Unable to translate - cannot find map "+conceptMapUrl); - } else - cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl); - Coding outcome = null; - boolean done = false; - String message = null; - if (cmap == null) { - if (services == null) - message = "No map found for "+conceptMapUrl; - else { - outcome = services.translate(context.appInfo, src, conceptMapUrl); - done = true; - } - } else { - List list = new ArrayList(); - for (ConceptMapGroupComponent g : cmap.getGroup()) { - for (SourceElementComponent e : g.getElement()) { - if (!src.hasSystem() && src.getCode().equals(e.getCode())) - list.add(new SourceElementComponentWrapper(g, e)); - else if (src.hasSystem() && src.getSystem().equals(g.getSource()) && src.getCode().equals(e.getCode())) - list.add(new SourceElementComponentWrapper(g, e)); - } - } - if (list.size() == 0) - done = true; - else if (list.get(0).comp.getTarget().size() == 0) - message = "Concept map "+su+" found no translation for "+src.getCode(); - else { - for (TargetElementComponent tgt : list.get(0).comp.getTarget()) { - if (tgt.getEquivalence() == null || EnumSet.of( ConceptMapEquivalence.EQUAL , ConceptMapEquivalence.RELATEDTO , ConceptMapEquivalence.EQUIVALENT, ConceptMapEquivalence.WIDER).contains(tgt.getEquivalence())) { - if (done) { - message = "Concept map "+su+" found multiple matches for "+src.getCode(); - done = false; - } else { - done = true; - outcome = new Coding().setCode(tgt.getCode()).setSystem(list.get(0).group.getTarget()); - } - } else if (tgt.getEquivalence() == ConceptMapEquivalence.UNMATCHED) { - done = true; - } - } - if (!done) - message = "Concept map "+su+" found no usable translation for "+src.getCode(); - } - } - if (!done) - throw new FHIRException(message); - if (outcome == null) - return null; - if ("code".equals(fieldToReturn)) - return new CodeType(outcome.getCode()); - else - return outcome; - } - } - - - public Map getLibrary() { - return library; - } - - public class PropertyWithType { - private String path; - private Property baseProperty; - private Property profileProperty; - private TypeDetails types; - public PropertyWithType(String path, Property baseProperty, Property profileProperty, TypeDetails types) { - super(); - this.baseProperty = baseProperty; - this.profileProperty = profileProperty; - this.path = path; - this.types = types; - } - - public TypeDetails getTypes() { - return types; - } - public String getPath() { - return path; - } - - public Property getBaseProperty() { - return baseProperty; - } - - public void setBaseProperty(Property baseProperty) { - this.baseProperty = baseProperty; - } - - public Property getProfileProperty() { - return profileProperty; - } - - public void setProfileProperty(Property profileProperty) { - this.profileProperty = profileProperty; - } - - public String summary() { - return path; - } - - } - - public class VariableForProfiling { - private VariableMode mode; - private String name; - private PropertyWithType property; - - public VariableForProfiling(VariableMode mode, String name, PropertyWithType property) { - super(); - this.mode = mode; - this.name = name; - this.property = property; - } - public VariableMode getMode() { - return mode; - } - public String getName() { - return name; - } - public PropertyWithType getProperty() { - return property; - } - public String summary() { - return name+": "+property.summary(); - } - } - - public class VariablesForProfiling { - private List list = new ArrayList(); - private boolean optional; - private boolean repeating; - - public VariablesForProfiling(boolean optional, boolean repeating) { - this.optional = optional; - this.repeating = repeating; - } - - public void add(VariableMode mode, String name, String path, Property property, TypeDetails types) { - add(mode, name, new PropertyWithType(path, property, null, types)); - } - - public void add(VariableMode mode, String name, String path, Property baseProperty, Property profileProperty, TypeDetails types) { - add(mode, name, new PropertyWithType(path, baseProperty, profileProperty, types)); - } - - public void add(VariableMode mode, String name, PropertyWithType property) { - VariableForProfiling vv = null; - for (VariableForProfiling v : list) - if ((v.mode == mode) && v.getName().equals(name)) - vv = v; - if (vv != null) - list.remove(vv); - list.add(new VariableForProfiling(mode, name, property)); - } - - public VariablesForProfiling copy(boolean optional, boolean repeating) { - VariablesForProfiling result = new VariablesForProfiling(optional, repeating); - result.list.addAll(list); - return result; - } - - public VariablesForProfiling copy() { - VariablesForProfiling result = new VariablesForProfiling(optional, repeating); - result.list.addAll(list); - return result; - } - - public VariableForProfiling get(VariableMode mode, String name) { - if (mode == null) { - for (VariableForProfiling v : list) - if ((v.mode == VariableMode.OUTPUT) && v.getName().equals(name)) - return v; - for (VariableForProfiling v : list) - if ((v.mode == VariableMode.INPUT) && v.getName().equals(name)) - return v; - } - for (VariableForProfiling v : list) - if ((v.mode == mode) && v.getName().equals(name)) - return v; - return null; - } - - public String summary() { - CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); - CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); - for (VariableForProfiling v : list) - if (v.mode == VariableMode.INPUT) - s.append(v.summary()); - else - t.append(v.summary()); - return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]"; - } - } - - public class StructureMapAnalysis { - private List profiles = new ArrayList(); - private XhtmlNode summary; - public List getProfiles() { - return profiles; - } - public XhtmlNode getSummary() { - return summary; - } - - } - - /** - * Given a structure map, return a set of analyses on it. - * - * Returned: - * - a list or profiles for what it will create. First profile is the target - * - a table with a summary (in xhtml) for easy human undertanding of the mapping - * - * - * @param appInfo - * @param map - * @return - * @throws Exception - */ - public StructureMapAnalysis analyse(Object appInfo, StructureMap map) throws Exception { - ids.clear(); - StructureMapAnalysis result = new StructureMapAnalysis(); - TransformContext context = new TransformContext(appInfo); - VariablesForProfiling vars = new VariablesForProfiling(false, false); - StructureMapGroupComponent start = map.getGroup().get(0); - for (StructureMapGroupInputComponent t : start.getInput()) { - PropertyWithType ti = resolveType(map, t.getType(), t.getMode()); - if (t.getMode() == StructureMapInputMode.SOURCE) - vars.add(VariableMode.INPUT, t.getName(), ti); - else - vars.add(VariableMode.OUTPUT, t.getName(), createProfile(map, result.profiles, ti, start.getName(), start)); - } - - result.summary = new XhtmlNode(NodeType.Element, "table").setAttribute("class", "grid"); - XhtmlNode tr = result.summary.addTag("tr"); - tr.addTag("td").addTag("b").addText("Source"); - tr.addTag("td").addTag("b").addText("Target"); - - log("Start Profiling Transform "+map.getUrl()); - analyseGroup("", context, map, vars, start, result); - ProfileUtilities pu = new ProfileUtilities(worker, null, pkp); - for (StructureDefinition sd : result.getProfiles()) - pu.cleanUpDifferential(sd); - return result; - } - - - private void analyseGroup(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapAnalysis result) throws Exception { - log(indent+"Analyse Group : "+group.getName()); - // todo: extends - // todo: check inputs - XhtmlNode tr = result.summary.addTag("tr").setAttribute("class", "diff-title"); - XhtmlNode xs = tr.addTag("td"); - XhtmlNode xt = tr.addTag("td"); - for (StructureMapGroupInputComponent inp : group.getInput()) { - if (inp.getMode() == StructureMapInputMode.SOURCE) - noteInput(vars, inp, VariableMode.INPUT, xs); - if (inp.getMode() == StructureMapInputMode.TARGET) - noteInput(vars, inp, VariableMode.OUTPUT, xt); - } - for (StructureMapGroupRuleComponent r : group.getRule()) { - analyseRule(indent+" ", context, map, vars, group, r, result); - } - } - - - private void noteInput(VariablesForProfiling vars, StructureMapGroupInputComponent inp, VariableMode mode, XhtmlNode xs) { - VariableForProfiling v = vars.get(mode, inp.getName()); - if (v != null) - xs.addText("Input: "+v.property.getPath()); - } - - private void analyseRule(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, StructureMapAnalysis result) throws Exception { - log(indent+"Analyse rule : "+rule.getName()); - XhtmlNode tr = result.summary.addTag("tr"); - XhtmlNode xs = tr.addTag("td"); - XhtmlNode xt = tr.addTag("td"); - - VariablesForProfiling srcVars = vars.copy(); - if (rule.getSource().size() != 1) - throw new Exception("Rule \""+rule.getName()+"\": not handled yet"); - VariablesForProfiling source = analyseSource(rule.getName(), context, srcVars, rule.getSourceFirstRep(), xs); - - TargetWriter tw = new TargetWriter(); - for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { - analyseTarget(rule.getName(), context, source, map, t, rule.getSourceFirstRep().getVariable(), tw, result.profiles, rule.getName()); - } - tw.commit(xt); - - for (StructureMapGroupRuleComponent childrule : rule.getRule()) { - analyseRule(indent+" ", context, map, source, group, childrule, result); - } -// for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { -// executeDependency(indent+" ", context, map, v, group, dependent); // do we need group here? -// } - } - - public class StringPair { - private String var; - private String desc; - public StringPair(String var, String desc) { - super(); - this.var = var; - this.desc = desc; - } - public String getVar() { - return var; - } - public String getDesc() { - return desc; - } - } - public class TargetWriter { - private Map newResources = new HashMap(); - private List assignments = new ArrayList(); - private List keyProps = new ArrayList(); - private CommaSeparatedStringBuilder txt = new CommaSeparatedStringBuilder(); - - public void newResource(String var, String name) { - newResources.put(var, name); - txt.append("new "+name); - } - - public void valueAssignment(String context, String desc) { - assignments.add(new StringPair(context, desc)); - txt.append(desc); - } - - public void keyAssignment(String context, String desc) { - keyProps.add(new StringPair(context, desc)); - txt.append(desc); - } - public void commit(XhtmlNode xt) { - if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 1 && newResources.containsKey(keyProps.get(0).getVar()) ) { - xt.addText("new "+assignments.get(0).desc+" ("+keyProps.get(0).desc.substring(keyProps.get(0).desc.indexOf(".")+1)+")"); - } else if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 0) { - xt.addText("new "+assignments.get(0).desc); - } else { - xt.addText(txt.toString()); - } - } - } - - private VariablesForProfiling analyseSource(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMapGroupRuleSourceComponent src, XhtmlNode td) throws Exception { - VariableForProfiling var = vars.get(VariableMode.INPUT, src.getContext()); - if (var == null) - throw new FHIRException("Rule \""+ruleId+"\": Unknown input variable "+src.getContext()); - PropertyWithType prop = var.getProperty(); - - boolean optional = false; - boolean repeating = false; - - if (src.hasCondition()) { - optional = true; - } - - if (src.hasElement()) { - Property element = prop.getBaseProperty().getChild(prop.types.getType(), src.getElement()); - if (element == null) - throw new Exception("Rule \""+ruleId+"\": Unknown element name "+src.getElement()); - if (element.getDefinition().getMin() == 0) - optional = true; - if (element.getDefinition().getMax().equals("*")) - repeating = true; - VariablesForProfiling result = vars.copy(optional, repeating); - TypeDetails type = new TypeDetails(CollectionStatus.SINGLETON); - for (TypeRefComponent tr : element.getDefinition().getType()) { - if (!tr.hasCode()) - throw new Error("Rule \""+ruleId+"\": Element has no type"); - ProfiledType pt = new ProfiledType(tr.getCode()); - if (tr.hasProfile()) - pt.addProfile(tr.getProfile()); - if (element.getDefinition().hasBinding()) - pt.addBinding(element.getDefinition().getBinding()); - type.addType(pt); - } - td.addText(prop.getPath()+"."+src.getElement()); - if (src.hasVariable()) - result.add(VariableMode.INPUT, src.getVariable(), new PropertyWithType(prop.getPath()+"."+src.getElement(), element, null, type)); - return result; - } else { - td.addText(prop.getPath()); // ditto! - return vars.copy(optional, repeating); - } - } - - - private void analyseTarget(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt, String tv, TargetWriter tw, List profiles, String sliceName) throws Exception { - VariableForProfiling var = null; - if (tgt.hasContext()) { - var = vars.get(VariableMode.OUTPUT, tgt.getContext()); - if (var == null) - throw new Exception("Rule \""+ruleId+"\": target context not known: "+tgt.getContext()); - if (!tgt.hasElement()) - throw new Exception("Rule \""+ruleId+"\": Not supported yet"); - } - - - TypeDetails type = null; - if (tgt.hasTransform()) { - type = analyseTransform(context, map, tgt, var, vars); - // profiling: dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); - } else { - Property vp = var.property.baseProperty.getChild(tgt.getElement(), tgt.getElement()); - if (vp == null) - throw new Exception("Unknown Property "+tgt.getElement()+" on "+var.property.path); - - type = new TypeDetails(CollectionStatus.SINGLETON, vp.getType(tgt.getElement())); - } - - if (tgt.getTransform() == StructureMapTransform.CREATE) { - String s = getParamString(vars, tgt.getParameter().get(0)); - if (worker.getResourceNames().contains(s)) - tw.newResource(tgt.getVariable(), s); - } else { - boolean mapsSrc = false; - for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { - Type pr = p.getValue(); - if (pr instanceof IdType && ((IdType) pr).asStringValue().equals(tv)) - mapsSrc = true; - } - if (mapsSrc) { - if (var == null) - throw new Error("Rule \""+ruleId+"\": Attempt to assign with no context"); - tw.valueAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+getTransformSuffix(tgt.getTransform())); - } else if (tgt.hasContext()) { - if (isSignificantElement(var.property, tgt.getElement())) { - String td = describeTransform(tgt); - if (td != null) - tw.keyAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+" = "+td); - } - } - } - Type fixed = generateFixedValue(tgt); - - PropertyWithType prop = updateProfile(var, tgt.getElement(), type, map, profiles, sliceName, fixed, tgt); - if (tgt.hasVariable()) - if (tgt.hasElement()) - vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); - else - vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); - } - - private Type generateFixedValue(StructureMapGroupRuleTargetComponent tgt) { - if (!allParametersFixed(tgt)) - return null; - if (!tgt.hasTransform()) - return null; - switch (tgt.getTransform()) { - case COPY: return tgt.getParameter().get(0).getValue(); - case TRUNCATE: return null; - //case ESCAPE: - //case CAST: - //case APPEND: - case TRANSLATE: return null; - //case DATEOP, - //case UUID, - //case POINTER, - //case EVALUATE, - case CC: - CodeableConcept cc = new CodeableConcept(); - cc.addCoding(buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue())); - return cc; - case C: - return buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue()); - case QTY: return null; - //case ID, - //case CP, - default: - return null; - } - } - - @SuppressWarnings("rawtypes") - private Coding buildCoding(Type value1, Type value2) { - return new Coding().setSystem(((PrimitiveType) value1).asStringValue()).setCode(((PrimitiveType) value2).asStringValue()) ; - } - - private boolean allParametersFixed(StructureMapGroupRuleTargetComponent tgt) { - for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { - Type pr = p.getValue(); - if (pr instanceof IdType) - return false; - } - return true; - } - - private String describeTransform(StructureMapGroupRuleTargetComponent tgt) throws FHIRException { - switch (tgt.getTransform()) { - case COPY: return null; - case TRUNCATE: return null; - //case ESCAPE: - //case CAST: - //case APPEND: - case TRANSLATE: return null; - //case DATEOP, - //case UUID, - //case POINTER, - //case EVALUATE, - case CC: return describeTransformCCorC(tgt); - case C: return describeTransformCCorC(tgt); - case QTY: return null; - //case ID, - //case CP, - default: - return null; - } - } - - @SuppressWarnings("rawtypes") - private String describeTransformCCorC(StructureMapGroupRuleTargetComponent tgt) throws FHIRException { - if (tgt.getParameter().size() < 2) - return null; - Type p1 = tgt.getParameter().get(0).getValue(); - Type p2 = tgt.getParameter().get(1).getValue(); - if (p1 instanceof IdType || p2 instanceof IdType) - return null; - if (!(p1 instanceof PrimitiveType) || !(p2 instanceof PrimitiveType)) - return null; - String uri = ((PrimitiveType) p1).asStringValue(); - String code = ((PrimitiveType) p2).asStringValue(); - if (Utilities.noString(uri)) - throw new FHIRException("Describe Transform, but the uri is blank"); - if (Utilities.noString(code)) - throw new FHIRException("Describe Transform, but the code is blank"); - Coding c = buildCoding(uri, code); - return NarrativeGenerator.describeSystem(c.getSystem())+"#"+c.getCode()+(c.hasDisplay() ? "("+c.getDisplay()+")" : ""); - } - - - private boolean isSignificantElement(PropertyWithType property, String element) { - if ("Observation".equals(property.getPath())) - return "code".equals(element); - else if ("Bundle".equals(property.getPath())) - return "type".equals(element); - else - return false; - } - - private String getTransformSuffix(StructureMapTransform transform) { - switch (transform) { - case COPY: return ""; - case TRUNCATE: return " (truncated)"; - //case ESCAPE: - //case CAST: - //case APPEND: - case TRANSLATE: return " (translated)"; - //case DATEOP, - //case UUID, - //case POINTER, - //case EVALUATE, - case CC: return " (--> CodeableConcept)"; - case C: return " (--> Coding)"; - case QTY: return " (--> Quantity)"; - //case ID, - //case CP, - default: - return " {??)"; - } - } - - private PropertyWithType updateProfile(VariableForProfiling var, String element, TypeDetails type, StructureMap map, List profiles, String sliceName, Type fixed, StructureMapGroupRuleTargetComponent tgt) throws FHIRException { - if (var == null) { - assert (Utilities.noString(element)); - // 1. start the new structure definition - StructureDefinition sdn = worker.fetchResource(StructureDefinition.class, type.getType()); - if (sdn == null) - throw new FHIRException("Unable to find definition for "+type.getType()); - ElementDefinition edn = sdn.getSnapshot().getElementFirstRep(); - PropertyWithType pn = createProfile(map, profiles, new PropertyWithType(sdn.getId(), new Property(worker, edn, sdn), null, type), sliceName, tgt); - -// // 2. hook it into the base bundle -// if (type.getType().startsWith("http://hl7.org/fhir/StructureDefinition/") && worker.getResourceNames().contains(type.getType().substring(40))) { -// StructureDefinition sd = var.getProperty().profileProperty.getStructure(); -// ElementDefinition ed = sd.getDifferential().addElement(); -// ed.setPath("Bundle.entry"); -// ed.setName(sliceName); -// ed.setMax("1"); // well, it is for now... -// ed = sd.getDifferential().addElement(); -// ed.setPath("Bundle.entry.fullUrl"); -// ed.setMin(1); -// ed = sd.getDifferential().addElement(); -// ed.setPath("Bundle.entry.resource"); -// ed.setMin(1); -// ed.addType().setCode(pn.getProfileProperty().getStructure().getType()).setProfile(pn.getProfileProperty().getStructure().getUrl()); -// } - return pn; - } else { - assert (!Utilities.noString(element)); - Property pvb = var.getProperty().getBaseProperty(); - Property pvd = var.getProperty().getProfileProperty(); - Property pc = pvb.getChild(element, var.property.types); - if (pc == null) - throw new DefinitionException("Unable to find a definition for "+pvb.getDefinition().getPath()+"."+element); - - // the profile structure definition (derived) - StructureDefinition sd = var.getProperty().profileProperty.getStructure(); - ElementDefinition ednew = sd.getDifferential().addElement(); - ednew.setPath(var.getProperty().profileProperty.getDefinition().getPath()+"."+pc.getName()); - ednew.setUserData("slice-name", sliceName); - ednew.setFixed(fixed); - for (ProfiledType pt : type.getProfiledTypes()) { - if (pt.hasBindings()) - ednew.setBinding(pt.getBindings().get(0)); - if (pt.getUri().startsWith("http://hl7.org/fhir/StructureDefinition/")) { - String t = pt.getUri().substring(40); - t = checkType(t, pc, pt.getProfiles()); - if (t != null) { - if (pt.hasProfiles()) { - for (String p : pt.getProfiles()) - if (t.equals("Reference")) - ednew.addType().setCode(t).setTargetProfile(p); - else - ednew.addType().setCode(t).setProfile(p); - } else - ednew.addType().setCode(t); - } - } - } - - return new PropertyWithType(var.property.path+"."+element, pc, new Property(worker, ednew, sd), type); - } - } - - - - private String checkType(String t, Property pvb, List profiles) throws FHIRException { - if (pvb.getDefinition().getType().size() == 1 && isCompatibleType(t, pvb.getDefinition().getType().get(0).getCode()) && profilesMatch(profiles, pvb.getDefinition().getType().get(0).getProfile())) - return null; - for (TypeRefComponent tr : pvb.getDefinition().getType()) { - if (isCompatibleType(t, tr.getCode())) - return tr.getCode(); // note what is returned - the base type, not the inferred mapping type - } - throw new FHIRException("The type "+t+" is not compatible with the allowed types for "+pvb.getDefinition().getPath()+" ("+pvb.getDefinition().typeSummary()+")"); - } - - private boolean profilesMatch(List profiles, String profile) { - return profiles == null || profiles.size() == 0 || (profiles.size() == 1 && profiles.get(0).equals(profile)); - } - - private boolean isCompatibleType(String t, String code) { - if (t.equals(code)) - return true; - if (t.equals("string")) { - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+code); - if (sd != null && sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/string")) - return true; - } - return false; - } - - private TypeDetails analyseTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, VariableForProfiling var, VariablesForProfiling vars) throws FHIRException { - switch (tgt.getTransform()) { - case CREATE : - String p = getParamString(vars, tgt.getParameter().get(0)); - return new TypeDetails(CollectionStatus.SINGLETON, p); - case COPY : - return getParam(vars, tgt.getParameter().get(0)); - case EVALUATE : - ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); - if (expr == null) { - expr = fpe.parse(getParamString(vars, tgt.getParameter().get(tgt.getParameter().size()-1))); - tgt.setUserData(MAP_WHERE_EXPRESSION, expr); - } - return fpe.check(vars, null, expr); - -////case TRUNCATE : -//// String src = getParamString(vars, tgt.getParameter().get(0)); -//// String len = getParamString(vars, tgt.getParameter().get(1)); -//// if (Utilities.isInteger(len)) { -//// int l = Integer.parseInt(len); -//// if (src.length() > l) -//// src = src.substring(0, l); -//// } -//// return new StringType(src); -////case ESCAPE : -//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); -////case CAST : -//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); -////case APPEND : -//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); - case TRANSLATE : - return new TypeDetails(CollectionStatus.SINGLETON, "CodeableConcept"); - case CC: - ProfiledType res = new ProfiledType("CodeableConcept"); - if (tgt.getParameter().size() >= 2 && isParamId(vars, tgt.getParameter().get(1))) { - TypeDetails td = vars.get(null, getParamId(vars, tgt.getParameter().get(1))).property.types; - if (td != null && td.hasBinding()) - // todo: do we need to check that there's no implicit translation her? I don't think we do... - res.addBinding(td.getBinding()); - } - return new TypeDetails(CollectionStatus.SINGLETON, res); - case C: - return new TypeDetails(CollectionStatus.SINGLETON, "Coding"); - case QTY: - return new TypeDetails(CollectionStatus.SINGLETON, "Quantity"); - case REFERENCE : - VariableForProfiling vrs = vars.get(VariableMode.OUTPUT, getParamId(vars, tgt.getParameterFirstRep())); - if (vrs == null) - throw new FHIRException("Unable to resolve variable \""+getParamId(vars, tgt.getParameterFirstRep())+"\""); - String profile = vrs.property.getProfileProperty().getStructure().getUrl(); - TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON); - td.addType("Reference", profile); - return td; -////case DATEOP : -//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); -////case UUID : -//// return new IdType(UUID.randomUUID().toString()); -////case POINTER : -//// Base b = getParam(vars, tgt.getParameter().get(0)); -//// if (b instanceof Resource) -//// return new UriType("urn:uuid:"+((Resource) b).getId()); -//// else -//// throw new FHIRException("Transform engine cannot point at an element of type "+b.fhirType()); - default: - throw new Error("Transform Unknown or not handled yet: "+tgt.getTransform().toCode()); - } - } - private String getParamString(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { - Type p = parameter.getValue(); - if (p == null || p instanceof IdType) - return null; - if (!p.hasPrimitiveValue()) - return null; - return p.primitiveValue(); - } - - private String getParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { - Type p = parameter.getValue(); - if (p == null || !(p instanceof IdType)) - return null; - return p.primitiveValue(); - } - - private boolean isParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { - Type p = parameter.getValue(); - if (p == null || !(p instanceof IdType)) - return false; - return vars.get(null, p.primitiveValue()) != null; - } - - private TypeDetails getParam(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { - Type p = parameter.getValue(); - if (!(p instanceof IdType)) - return new TypeDetails(CollectionStatus.SINGLETON, "http://hl7.org/fhir/StructureDefinition/"+p.fhirType()); - else { - String n = ((IdType) p).asStringValue(); - VariableForProfiling b = vars.get(VariableMode.INPUT, n); - if (b == null) - b = vars.get(VariableMode.OUTPUT, n); - if (b == null) - throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")"); - return b.getProperty().getTypes(); - } - } - - private PropertyWithType createProfile(StructureMap map, List profiles, PropertyWithType prop, String sliceName, Base ctxt) throws DefinitionException { - if (prop.getBaseProperty().getDefinition().getPath().contains(".")) - throw new DefinitionException("Unable to process entry point"); - - String type = prop.getBaseProperty().getDefinition().getPath(); - String suffix = ""; - if (ids.containsKey(type)) { - int id = ids.get(type); - id++; - ids.put(type, id); - suffix = "-"+Integer.toString(id); - } else - ids.put(type, 0); - - StructureDefinition profile = new StructureDefinition(); - profiles.add(profile); - profile.setDerivation(TypeDerivationRule.CONSTRAINT); - profile.setType(type); - profile.setBaseDefinition(prop.getBaseProperty().getStructure().getUrl()); - profile.setName("Profile for "+profile.getType()+" for "+sliceName); - profile.setUrl(map.getUrl().replace("StructureMap", "StructureDefinition")+"-"+profile.getType()+suffix); - ctxt.setUserData("profile", profile.getUrl()); // then we can easily assign this profile url for validation later when we actually transform - profile.setId(map.getId()+"-"+profile.getType()+suffix); - profile.setStatus(map.getStatus()); - profile.setExperimental(map.getExperimental()); - profile.setDescription("Generated automatically from the mapping by the Java Reference Implementation"); - for (ContactDetail c : map.getContact()) { - ContactDetail p = profile.addContact(); - p.setName(c.getName()); - for (ContactPoint cc : c.getTelecom()) - p.addTelecom(cc); - } - profile.setDate(map.getDate()); - profile.setCopyright(map.getCopyright()); - profile.setFhirVersion(Constants.VERSION); - profile.setKind(prop.getBaseProperty().getStructure().getKind()); - profile.setAbstract(false); - ElementDefinition ed = profile.getDifferential().addElement(); - ed.setPath(profile.getType()); - prop.profileProperty = new Property(worker, ed, profile); - return prop; - } - - private PropertyWithType resolveType(StructureMap map, String type, StructureMapInputMode mode) throws Exception { - for (StructureMapStructureComponent imp : map.getStructure()) { - if ((imp.getMode() == StructureMapModelMode.SOURCE && mode == StructureMapInputMode.SOURCE) || - (imp.getMode() == StructureMapModelMode.TARGET && mode == StructureMapInputMode.TARGET)) { - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); - if (sd == null) - throw new Exception("Import "+imp.getUrl()+" cannot be resolved"); - if (sd.getId().equals(type)) { - return new PropertyWithType(sd.getType(), new Property(worker, sd.getSnapshot().getElement().get(0), sd), null, new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl())); - } - } - } - throw new Exception("Unable to find structure definition for "+type+" in imports"); - } - - - public StructureMap generateMapFromMappings(StructureDefinition sd) throws IOException, FHIRException { - String id = getLogicalMappingId(sd); - if (id == null) - return null; - String prefix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_PREFIX); - String suffix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_SUFFIX); - if (prefix == null || suffix == null) - return null; - // we build this by text. Any element that has a mapping, we put it's mappings inside it.... - StringBuilder b = new StringBuilder(); - b.append(prefix); - - ElementDefinition root = sd.getSnapshot().getElementFirstRep(); - String m = getMapping(root, id); - if (m != null) - b.append(m+"\r\n"); - addChildMappings(b, id, "", sd, root, false); - b.append("\r\n"); - b.append(suffix); - b.append("\r\n"); - StructureMap map = parse(b.toString()); - map.setId(tail(map.getUrl())); - if (!map.hasStatus()) - map.setStatus(PublicationStatus.DRAFT); - map.getText().setStatus(NarrativeStatus.GENERATED); - map.getText().setDiv(new XhtmlNode(NodeType.Element, "div")); - map.getText().getDiv().addTag("pre").addText(render(map)); - return map; - } - - - private String tail(String url) { - return url.substring(url.lastIndexOf("/")+1); - } - - - private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException { - boolean first = true; - List children = ProfileUtilities.getChildMap(sd, ed); - for (ElementDefinition child : children) { - if (first && inner) { - b.append(" then {\r\n"); - first = false; - } - String map = getMapping(child, id); - if (map != null) { - b.append(indent+" "+child.getPath()+": "+map); - addChildMappings(b, id, indent+" ", sd, child, true); - b.append("\r\n"); - } - } - if (!first && inner) - b.append(indent+"}"); - - } - - - private String getMapping(ElementDefinition ed, String id) { - for (ElementDefinitionMappingComponent map : ed.getMapping()) - if (id.equals(map.getIdentity())) - return map.getMap(); - return null; - } - - - private String getLogicalMappingId(StructureDefinition sd) { - String id = null; - for (StructureDefinitionMappingComponent map : sd.getMapping()) { - if ("http://hl7.org/fhir/logical".equals(map.getUri())) - return map.getIdentity(); - } - return null; - } - -} +package org.hl7.fhir.dstu3.utils; + +// remember group resolution +// trace - account for which wasn't transformed in the source + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.hl7.fhir.dstu3.conformance.ProfileUtilities; +import org.hl7.fhir.dstu3.conformance.ProfileUtilities.ProfileKnowledgeProvider; +import org.hl7.fhir.dstu3.context.IWorkerContext; +import org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult; +import org.hl7.fhir.dstu3.elementmodel.Element; +import org.hl7.fhir.dstu3.elementmodel.Property; +import org.hl7.fhir.dstu3.model.Base; +import org.hl7.fhir.dstu3.model.BooleanType; +import org.hl7.fhir.dstu3.model.CodeType; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.ConceptMap; +import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupComponent; +import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupUnmappedMode; +import org.hl7.fhir.dstu3.model.ConceptMap.SourceElementComponent; +import org.hl7.fhir.dstu3.model.ConceptMap.TargetElementComponent; +import org.hl7.fhir.dstu3.model.Constants; +import org.hl7.fhir.dstu3.model.ContactDetail; +import org.hl7.fhir.dstu3.model.ContactPoint; +import org.hl7.fhir.dstu3.model.DecimalType; +import org.hl7.fhir.dstu3.model.ElementDefinition; +import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionMappingComponent; +import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.dstu3.model.Enumeration; +import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence; +import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus; +import org.hl7.fhir.dstu3.model.ExpressionNode; +import org.hl7.fhir.dstu3.model.ExpressionNode.CollectionStatus; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.IntegerType; +import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus; +import org.hl7.fhir.dstu3.model.PrimitiveType; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.dstu3.model.ResourceFactory; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.StructureDefinition; +import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionMappingComponent; +import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule; +import org.hl7.fhir.dstu3.model.StructureMap; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapContextType; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupComponent; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupInputComponent; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleComponent; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleDependentComponent; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleSourceComponent; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleTargetComponent; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleTargetParameterComponent; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupTypeMode; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapInputMode; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapSourceListMode; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapTargetListMode; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapModelMode; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapStructureComponent; +import org.hl7.fhir.dstu3.model.StructureMap.StructureMapTransform; +import org.hl7.fhir.dstu3.model.Type; +import org.hl7.fhir.dstu3.model.TypeDetails; +import org.hl7.fhir.dstu3.model.TypeDetails.ProfiledType; +import org.hl7.fhir.dstu3.model.UriType; +import org.hl7.fhir.dstu3.model.ValueSet; +import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; +import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome; +import org.hl7.fhir.dstu3.utils.FHIRLexer.FHIRLexerException; +import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext; +import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.exceptions.PathEngineException; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.xhtml.NodeType; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; + +/** + * Services in this class: + * + * string render(map) - take a structure and convert it to text + * map parse(text) - take a text representation and parse it + * getTargetType(map) - return the definition for the type to create to hand in + * transform(appInfo, source, map, target) - transform from source to target following the map + * analyse(appInfo, map) - generate profiles and other analysis artifacts for the targets of the transform + * map generateMapFromMappings(StructureDefinition) - build a mapping from a structure definition with loigcal mappings + * + * @author Grahame Grieve + * + */ +public class StructureMapUtilities { + + public class ResolvedGroup { + public StructureMapGroupComponent target; + public StructureMap targetMap; + } + public static final String MAP_WHERE_CHECK = "map.where.check"; + public static final String MAP_WHERE_EXPRESSION = "map.where.expression"; + public static final String MAP_SEARCH_EXPRESSION = "map.search.expression"; + public static final String MAP_EXPRESSION = "map.transform.expression"; + private static final boolean RENDER_MULTIPLE_TARGETS_ONELINE = true; + private static final String AUTO_VAR_NAME = "vvv"; + + public interface ITransformerServices { + // public boolean validateByValueSet(Coding code, String valuesetId); + public void log(String message); // log internal progress + public Base createType(Object appInfo, String name) throws FHIRException; + public Base createResource(Object appInfo, Base res); // an already created resource is provided; this is to identify/store it + public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException; + // public Coding translate(Coding code) + // ValueSet validation operation + // Translation operation + // Lookup another tree of data + // Create an instance tree + // Return the correct string format to refer to a tree (input or output) + public Base resolveReference(Object appContext, String url); + public List performSearch(Object appContext, String url); + } + + private class FFHIRPathHostServices implements IEvaluationContext{ + + public Base resolveConstant(Object appContext, String name) throws PathEngineException { + Variables vars = (Variables) appContext; + Base res = vars.get(VariableMode.INPUT, name); + if (res == null) + res = vars.get(VariableMode.OUTPUT, name); + return res; + } + + @Override + public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { + if (!(appContext instanceof VariablesForProfiling)) + throw new Error("Internal Logic Error (wrong type '"+appContext.getClass().getName()+"' in resolveConstantType)"); + VariablesForProfiling vars = (VariablesForProfiling) appContext; + VariableForProfiling v = vars.get(null, name); + if (v == null) + throw new PathEngineException("Unknown variable '"+name+"' from variables "+vars.summary()); + return v.property.types; + } + + @Override + public boolean log(String argument, List focus) { + throw new Error("Not Implemented Yet"); + } + + @Override + public FunctionDetails resolveFunction(String functionName) { + return null; // throw new Error("Not Implemented Yet"); + } + + @Override + public TypeDetails checkFunction(Object appContext, String functionName, List parameters) throws PathEngineException { + throw new Error("Not Implemented Yet"); + } + + @Override + public List executeFunction(Object appContext, String functionName, List> parameters) { + throw new Error("Not Implemented Yet"); + } + + @Override + public Base resolveReference(Object appContext, String url) { + if (services == null) + return null; + return services.resolveReference(appContext, url); + } + + } + private IWorkerContext worker; + private FHIRPathEngine fpe; + private Map library; + private ITransformerServices services; + private ProfileKnowledgeProvider pkp; + private Map ids = new HashMap(); + + public StructureMapUtilities(IWorkerContext worker, Map library, ITransformerServices services, ProfileKnowledgeProvider pkp) { + super(); + this.worker = worker; + this.library = library; + this.services = services; + this.pkp = pkp; + fpe = new FHIRPathEngine(worker); + fpe.setHostServices(new FFHIRPathHostServices()); + } + + public StructureMapUtilities(IWorkerContext worker, Map library, ITransformerServices services) { + super(); + this.worker = worker; + this.library = library; + this.services = services; + fpe = new FHIRPathEngine(worker); + fpe.setHostServices(new FFHIRPathHostServices()); + } + + public StructureMapUtilities(IWorkerContext worker, Map library) { + super(); + this.worker = worker; + this.library = library; + fpe = new FHIRPathEngine(worker); + fpe.setHostServices(new FFHIRPathHostServices()); + } + + public StructureMapUtilities(IWorkerContext worker) { + super(); + this.worker = worker; + fpe = new FHIRPathEngine(worker); + fpe.setHostServices(new FFHIRPathHostServices()); + } + + public StructureMapUtilities(IWorkerContext worker, ITransformerServices services) { + super(); + this.worker = worker; + this.library = new HashMap(); + for (org.hl7.fhir.dstu3.model.MetadataResource bc : worker.allConformanceResources()) { + if (bc instanceof StructureMap) + library.put(bc.getUrl(), (StructureMap) bc); + } + this.services = services; + fpe = new FHIRPathEngine(worker); + fpe.setHostServices(new FFHIRPathHostServices()); + } + + public static String render(StructureMap map) { + StringBuilder b = new StringBuilder(); + b.append("map \""); + b.append(map.getUrl()); + b.append("\" = \""); + b.append(Utilities.escapeJava(map.getName())); + b.append("\"\r\n\r\n"); + + renderConceptMaps(b, map); + renderUses(b, map); + renderImports(b, map); + for (StructureMapGroupComponent g : map.getGroup()) + renderGroup(b, g); + return b.toString(); + } + + private static void renderConceptMaps(StringBuilder b, StructureMap map) { + for (Resource r : map.getContained()) { + if (r instanceof ConceptMap) { + produceConceptMap(b, (ConceptMap) r); + } + } + } + + private static void produceConceptMap(StringBuilder b, ConceptMap cm) { + b.append("conceptmap \""); + b.append(cm.getId()); + b.append("\" {\r\n"); + Map prefixesSrc = new HashMap(); + Map prefixesTgt = new HashMap(); + char prefix = 's'; + for (ConceptMapGroupComponent cg : cm.getGroup()) { + if (!prefixesSrc.containsKey(cg.getSource())) { + prefixesSrc.put(cg.getSource(), String.valueOf(prefix)); + b.append(" prefix "); + b.append(prefix); + b.append(" = \""); + b.append(cg.getSource()); + b.append("\"\r\n"); + prefix++; + } + if (!prefixesTgt.containsKey(cg.getTarget())) { + prefixesTgt.put(cg.getTarget(), String.valueOf(prefix)); + b.append(" prefix "); + b.append(prefix); + b.append(" = \""); + b.append(cg.getTarget()); + b.append("\"\r\n"); + prefix++; + } + } + b.append("\r\n"); + for (ConceptMapGroupComponent cg : cm.getGroup()) { + if (cg.hasUnmapped()) { + b.append(" unmapped for "); + b.append(prefix); + b.append(" = "); + b.append(cg.getUnmapped().getMode()); + b.append("\r\n"); + } + } + + for (ConceptMapGroupComponent cg : cm.getGroup()) { + for (SourceElementComponent ce : cg.getElement()) { + b.append(" "); + b.append(prefixesSrc.get(cg.getSource())); + b.append(":"); + b.append(ce.getCode()); + b.append(" "); + b.append(getChar(ce.getTargetFirstRep().getEquivalence())); + b.append(" "); + b.append(prefixesTgt.get(cg.getTarget())); + b.append(":"); + b.append(ce.getTargetFirstRep().getCode()); + b.append("\r\n"); + } + } + b.append("}\r\n\r\n"); + } + + private static Object getChar(ConceptMapEquivalence equivalence) { + switch (equivalence) { + case RELATEDTO: return "-"; + case EQUAL: return "="; + case EQUIVALENT: return "=="; + case DISJOINT: return "!="; + case UNMATCHED: return "--"; + case WIDER: return "<="; + case SUBSUMES: return "<-"; + case NARROWER: return ">="; + case SPECIALIZES: return ">-"; + case INEXACT: return "~"; + default: return "??"; + } + } + + private static void renderUses(StringBuilder b, StructureMap map) { + for (StructureMapStructureComponent s : map.getStructure()) { + b.append("uses \""); + b.append(s.getUrl()); + b.append("\" "); + if (s.hasAlias()) { + b.append("alias "); + b.append(s.getAlias()); + b.append(" "); + } + b.append("as "); + b.append(s.getMode().toCode()); + b.append("\r\n"); + renderDoco(b, s.getDocumentation()); + } + if (map.hasStructure()) + b.append("\r\n"); + } + + private static void renderImports(StringBuilder b, StructureMap map) { + for (UriType s : map.getImport()) { + b.append("imports \""); + b.append(s.getValue()); + b.append("\"\r\n"); + } + if (map.hasImport()) + b.append("\r\n"); + } + + public static String groupToString(StructureMapGroupComponent g) { + StringBuilder b = new StringBuilder(); + renderGroup(b, g); + return b.toString(); + } + + private static void renderGroup(StringBuilder b, StructureMapGroupComponent g) { + b.append("group "); + switch (g.getTypeMode()) { + case TYPES: + b.append("for types"); + break; + case TYPEANDTYPES: + b.append("for type+types "); + break; + case NONE: + case NULL: + break; + } + b.append("for types "); + b.append(g.getName()); + if (g.hasExtends()) { + b.append(" extends "); + b.append(g.getExtends()); + } + if (g.hasDocumentation()) + renderDoco(b, g.getDocumentation()); + b.append("\r\n"); + for (StructureMapGroupInputComponent gi : g.getInput()) { + b.append(" input "); + b.append(gi.getName()); + if (gi.hasType()) { + b.append(" : "); + b.append(gi.getType()); + } + b.append(" as "); + b.append(gi.getMode().toCode()); + b.append("\r\n"); + } + if (g.hasInput()) + b.append("\r\n"); + for (StructureMapGroupRuleComponent r : g.getRule()) { + renderRule(b, r, 2); + } + b.append("\r\nendgroup\r\n"); + } + + public static String ruleToString(StructureMapGroupRuleComponent r) { + StringBuilder b = new StringBuilder(); + renderRule(b, r, 0); + return b.toString(); + } + + private static void renderRule(StringBuilder b, StructureMapGroupRuleComponent r, int indent) { + for (int i = 0; i < indent; i++) + b.append(' '); + b.append(r.getName()); + b.append(" : for "); + boolean canBeAbbreviated = checkisSimple(r); + + boolean first = true; + for (StructureMapGroupRuleSourceComponent rs : r.getSource()) { + if (first) + first = false; + else + b.append(", "); + renderSource(b, rs, canBeAbbreviated); + } + if (r.getTarget().size() > 1) { + b.append(" make "); + first = true; + for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) { + if (first) + first = false; + else + b.append(", "); + if (RENDER_MULTIPLE_TARGETS_ONELINE) + b.append(' '); + else { + b.append("\r\n"); + for (int i = 0; i < indent+4; i++) + b.append(' '); + } + renderTarget(b, rt, false); + } + } else if (r.hasTarget()) { + b.append(" make "); + renderTarget(b, r.getTarget().get(0), canBeAbbreviated); + } + if (!canBeAbbreviated) { + if (r.hasRule()) { + b.append(" then {\r\n"); + renderDoco(b, r.getDocumentation()); + for (StructureMapGroupRuleComponent ir : r.getRule()) { + renderRule(b, ir, indent+2); + } + for (int i = 0; i < indent; i++) + b.append(' '); + b.append("}\r\n"); + } else { + if (r.hasDependent()) { + b.append(" then "); + first = true; + for (StructureMapGroupRuleDependentComponent rd : r.getDependent()) { + if (first) + first = false; + else + b.append(", "); + b.append(rd.getName()); + b.append("("); + boolean ifirst = true; + for (StringType rdp : rd.getVariable()) { + if (ifirst) + ifirst = false; + else + b.append(", "); + b.append(rdp.asStringValue()); + } + b.append(")"); + } + } + } + } + renderDoco(b, r.getDocumentation()); + b.append("\r\n"); + } + + private static boolean checkisSimple(StructureMapGroupRuleComponent r) { + return + (r.getSource().size() == 1 && r.getSourceFirstRep().hasElement() && r.getSourceFirstRep().hasVariable()) && + (r.getTarget().size() == 1 && r.getTargetFirstRep().hasVariable() && (r.getTargetFirstRep().getTransform() == null || r.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE) && r.getTargetFirstRep().getParameter().size() == 0) && + (r.getDependent().size() == 0); + } + + public static String sourceToString(StructureMapGroupRuleSourceComponent r) { + StringBuilder b = new StringBuilder(); + renderSource(b, r, false); + return b.toString(); + } + + private static void renderSource(StringBuilder b, StructureMapGroupRuleSourceComponent rs, boolean abbreviate) { + b.append(rs.getContext()); + if (rs.getContext().equals("@search")) { + b.append('('); + b.append(rs.getElement()); + b.append(')'); + } else if (rs.hasElement()) { + b.append('.'); + b.append(rs.getElement()); + } + if (rs.hasType()) { + b.append(" : "); + b.append(rs.getType()); + if (rs.hasMin()) { + b.append(" "); + b.append(rs.getMin()); + b.append(".."); + b.append(rs.getMax()); + } + } + + if (rs.hasListMode()) { + b.append(" "); + b.append(rs.getListMode().toCode()); + } + if (rs.hasDefaultValue()) { + b.append(" default "); + assert rs.getDefaultValue() instanceof StringType; + b.append("\""+Utilities.escapeJson(((StringType) rs.getDefaultValue()).asStringValue())+"\""); + } + if (!abbreviate && rs.hasVariable()) { + b.append(" as "); + b.append(rs.getVariable()); + } + if (rs.hasCondition()) { + b.append(" where "); + b.append(rs.getCondition()); + } + if (rs.hasCheck()) { + b.append(" check "); + b.append(rs.getCheck()); + } + } + + public static String targetToString(StructureMapGroupRuleTargetComponent rt) { + StringBuilder b = new StringBuilder(); + renderTarget(b, rt, false); + return b.toString(); + } + + private static void renderTarget(StringBuilder b, StructureMapGroupRuleTargetComponent rt, boolean abbreviate) { + if (rt.hasContext()) { + if (rt.getContextType() == StructureMapContextType.TYPE) + b.append("@"); + b.append(rt.getContext()); + if (rt.hasElement()) { + b.append('.'); + b.append(rt.getElement()); + } + } + if (!abbreviate && rt.hasTransform()) { + if (rt.hasContext()) + b.append(" = "); + if (rt.getTransform() == StructureMapTransform.COPY && rt.getParameter().size() == 1) { + renderTransformParam(b, rt.getParameter().get(0)); + } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 1) { + b.append("("); + b.append("\""+((StringType) rt.getParameter().get(0).getValue()).asStringValue()+"\""); + b.append(")"); + } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 2) { + b.append(rt.getTransform().toCode()); + b.append("("); + b.append(((IdType) rt.getParameter().get(0).getValue()).asStringValue()); + b.append("\""+((StringType) rt.getParameter().get(1).getValue()).asStringValue()+"\""); + b.append(")"); + } else { + b.append(rt.getTransform().toCode()); + b.append("("); + boolean first = true; + for (StructureMapGroupRuleTargetParameterComponent rtp : rt.getParameter()) { + if (first) + first = false; + else + b.append(", "); + renderTransformParam(b, rtp); + } + b.append(")"); + } + } + if (!abbreviate && rt.hasVariable()) { + b.append(" as "); + b.append(rt.getVariable()); + } + for (Enumeration lm : rt.getListMode()) { + b.append(" "); + b.append(lm.getValue().toCode()); + if (lm.getValue() == StructureMapTargetListMode.SHARE) { + b.append(" "); + b.append(rt.getListRuleId()); + } + } + } + + public static String paramToString(StructureMapGroupRuleTargetParameterComponent rtp) { + StringBuilder b = new StringBuilder(); + renderTransformParam(b, rtp); + return b.toString(); + } + + private static void renderTransformParam(StringBuilder b, StructureMapGroupRuleTargetParameterComponent rtp) { + try { + if (rtp.hasValueBooleanType()) + b.append(rtp.getValueBooleanType().asStringValue()); + else if (rtp.hasValueDecimalType()) + b.append(rtp.getValueDecimalType().asStringValue()); + else if (rtp.hasValueIdType()) + b.append(rtp.getValueIdType().asStringValue()); + else if (rtp.hasValueDecimalType()) + b.append(rtp.getValueDecimalType().asStringValue()); + else if (rtp.hasValueIntegerType()) + b.append(rtp.getValueIntegerType().asStringValue()); + else + b.append("\""+Utilities.escapeJava(rtp.getValueStringType().asStringValue())+"\""); + } catch (FHIRException e) { + e.printStackTrace(); + b.append("error!"); + } + } + + private static void renderDoco(StringBuilder b, String doco) { + if (Utilities.noString(doco)) + return; + b.append(" // "); + b.append(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")); + } + + public StructureMap parse(String text) throws FHIRException { + FHIRLexer lexer = new FHIRLexer(text); + if (lexer.done()) + throw lexer.error("Map Input cannot be empty"); + lexer.skipComments(); + lexer.token("map"); + StructureMap result = new StructureMap(); + result.setUrl(lexer.readConstant("url")); + lexer.token("="); + result.setName(lexer.readConstant("name")); + lexer.skipComments(); + + while (lexer.hasToken("conceptmap")) + parseConceptMap(result, lexer); + + while (lexer.hasToken("uses")) + parseUses(result, lexer); + while (lexer.hasToken("imports")) + parseImports(result, lexer); + + parseGroup(result, lexer); + + while (!lexer.done()) { + parseGroup(result, lexer); + } + + return result; + } + + private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException { + lexer.token("conceptmap"); + ConceptMap map = new ConceptMap(); + String id = lexer.readConstant("map id"); + if (!id.startsWith("#")) + lexer.error("Concept Map identifier must start with #"); + map.setId(id); + map.setStatus(PublicationStatus.DRAFT); // todo: how to add this to the text format + result.getContained().add(map); + lexer.token("{"); + lexer.skipComments(); + // lexer.token("source"); + // map.setSource(new UriType(lexer.readConstant("source"))); + // lexer.token("target"); + // map.setSource(new UriType(lexer.readConstant("target"))); + Map prefixes = new HashMap(); + while (lexer.hasToken("prefix")) { + lexer.token("prefix"); + String n = lexer.take(); + lexer.token("="); + String v = lexer.readConstant("prefix url"); + prefixes.put(n, v); + } + while (lexer.hasToken("unmapped")) { + lexer.token("unmapped"); + lexer.token("for"); + String n = readPrefix(prefixes, lexer); + ConceptMapGroupComponent g = getGroup(map, n, null); + lexer.token("="); + String v = lexer.take(); + if (v.equals("provided")) { + g.getUnmapped().setMode(ConceptMapGroupUnmappedMode.PROVIDED); + } else + lexer.error("Only unmapped mode PROVIDED is supported at this time"); + } + while (!lexer.hasToken("}")) { + String srcs = readPrefix(prefixes, lexer); + lexer.token(":"); + String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take(); + ConceptMapEquivalence eq = readEquivalence(lexer); + String tgts = (eq != ConceptMapEquivalence.UNMATCHED) ? readPrefix(prefixes, lexer) : ""; + ConceptMapGroupComponent g = getGroup(map, srcs, tgts); + SourceElementComponent e = g.addElement(); + e.setCode(sc); + if (e.getCode().startsWith("\"")) + e.setCode(lexer.processConstant(e.getCode())); + TargetElementComponent tgt = e.addTarget(); + if (eq != ConceptMapEquivalence.EQUIVALENT) + tgt.setEquivalence(eq); + if (tgt.getEquivalence() != ConceptMapEquivalence.UNMATCHED) { + lexer.token(":"); + tgt.setCode(lexer.take()); + if (tgt.getCode().startsWith("\"")) + tgt.setCode(lexer.processConstant(tgt.getCode())); + } + if (lexer.hasComment()) + tgt.setComment(lexer.take().substring(2).trim()); + } + lexer.token("}"); + } + + + private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) { + for (ConceptMapGroupComponent grp : map.getGroup()) { + if (grp.getSource().equals(srcs)) + if ((tgts == null && !grp.hasTarget()) || (tgts != null && tgts.equals(grp.getTarget()))) + return grp; + } + ConceptMapGroupComponent grp = map.addGroup(); + grp.setSource(srcs); + grp.setTarget(tgts); + return grp; + } + + + private String readPrefix(Map prefixes, FHIRLexer lexer) throws FHIRLexerException { + String prefix = lexer.take(); + if (!prefixes.containsKey(prefix)) + throw lexer.error("Unknown prefix '"+prefix+"'"); + return prefixes.get(prefix); + } + + + private ConceptMapEquivalence readEquivalence(FHIRLexer lexer) throws FHIRLexerException { + String token = lexer.take(); + if (token.equals("-")) + return ConceptMapEquivalence.RELATEDTO; + if (token.equals("=")) + return ConceptMapEquivalence.EQUAL; + if (token.equals("==")) + return ConceptMapEquivalence.EQUIVALENT; + if (token.equals("!=")) + return ConceptMapEquivalence.DISJOINT; + if (token.equals("--")) + return ConceptMapEquivalence.UNMATCHED; + if (token.equals("<=")) + return ConceptMapEquivalence.WIDER; + if (token.equals("<-")) + return ConceptMapEquivalence.SUBSUMES; + if (token.equals(">=")) + return ConceptMapEquivalence.NARROWER; + if (token.equals(">-")) + return ConceptMapEquivalence.SPECIALIZES; + if (token.equals("~")) + return ConceptMapEquivalence.INEXACT; + throw lexer.error("Unknown equivalence token '"+token+"'"); + } + + + private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException { + lexer.token("uses"); + StructureMapStructureComponent st = result.addStructure(); + st.setUrl(lexer.readConstant("url")); + if (lexer.hasToken("alias")) { + lexer.token("alias"); + st.setAlias(lexer.take()); + } + lexer.token("as"); + st.setMode(StructureMapModelMode.fromCode(lexer.take())); + lexer.skipToken(";"); + if (lexer.hasComment()) { + st.setDocumentation(lexer.take().substring(2).trim()); + } + lexer.skipComments(); + } + + private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException { + lexer.token("imports"); + result.addImport(lexer.readConstant("url")); + lexer.skipToken(";"); + if (lexer.hasComment()) { + lexer.next(); + } + lexer.skipComments(); + } + + private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException { + lexer.token("group"); + StructureMapGroupComponent group = result.addGroup(); + if (lexer.hasToken("for")) { + lexer.token("for"); + if ("type".equals(lexer.getCurrent())) { + lexer.token("type"); + lexer.token("+"); + lexer.token("types"); + group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES); + } else { + lexer.token("types"); + group.setTypeMode(StructureMapGroupTypeMode.TYPES); + } + } else + group.setTypeMode(StructureMapGroupTypeMode.NONE); + group.setName(lexer.take()); + if (lexer.hasToken("extends")) { + lexer.next(); + group.setExtends(lexer.take()); + } + lexer.skipComments(); + while (lexer.hasToken("input")) + parseInput(group, lexer); + while (!lexer.hasToken("endgroup")) { + if (lexer.done()) + throw lexer.error("premature termination expecting 'endgroup'"); + parseRule(result, group.getRule(), lexer); + } + lexer.next(); + lexer.skipComments(); + } + + private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer) throws FHIRException { + lexer.token("input"); + StructureMapGroupInputComponent input = group.addInput(); + input.setName(lexer.take()); + if (lexer.hasToken(":")) { + lexer.token(":"); + input.setType(lexer.take()); + } + lexer.token("as"); + input.setMode(StructureMapInputMode.fromCode(lexer.take())); + if (lexer.hasComment()) { + input.setDocumentation(lexer.take().substring(2).trim()); + } + lexer.skipToken(";"); + lexer.skipComments(); + } + + private void parseRule(StructureMap map, List list, FHIRLexer lexer) throws FHIRException { + StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent(); + list.add(rule); + rule.setName(lexer.takeDottedToken()); + lexer.token(":"); + lexer.token("for"); + boolean done = false; + while (!done) { + parseSource(rule, lexer); + done = !lexer.hasToken(","); + if (!done) + lexer.next(); + } + if (lexer.hasToken("make")) { + lexer.token("make"); + done = false; + while (!done) { + parseTarget(rule, lexer); + done = !lexer.hasToken(","); + if (!done) + lexer.next(); + } + } + if (lexer.hasToken("then")) { + lexer.token("then"); + if (lexer.hasToken("{")) { + lexer.token("{"); + if (lexer.hasComment()) { + rule.setDocumentation(lexer.take().substring(2).trim()); + } + lexer.skipComments(); + while (!lexer.hasToken("}")) { + if (lexer.done()) + throw lexer.error("premature termination expecting '}' in nested group"); + parseRule(map, rule.getRule(), lexer); + } + lexer.token("}"); + } else { + done = false; + while (!done) { + parseRuleReference(rule, lexer); + done = !lexer.hasToken(","); + if (!done) + lexer.next(); + } + } + } else if (lexer.hasComment()) { + rule.setDocumentation(lexer.take().substring(2).trim()); + } + if (isSimpleSyntax(rule)) { + rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME); + rule.getTargetFirstRep().setVariable(AUTO_VAR_NAME); + rule.getTargetFirstRep().setTransform(StructureMapTransform.CREATE); // with no parameter - e.g. imply what is to be created + // no dependencies - imply what is to be done based on types + } + lexer.skipComments(); + } + + private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) { + return + (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) && + (rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasContext() && rule.getTargetFirstRep().hasElement() && !rule.getTargetFirstRep().hasVariable() && !rule.getTargetFirstRep().hasParameter()) && + (rule.getDependent().size() == 0 && rule.getRule().size() == 0); + } + + private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException { + StructureMapGroupRuleDependentComponent ref = rule.addDependent(); + ref.setName(lexer.take()); + lexer.token("("); + boolean done = false; + while (!done) { + ref.addVariable(lexer.take()); + done = !lexer.hasToken(","); + if (!done) + lexer.next(); + } + lexer.token(")"); + } + + private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { + StructureMapGroupRuleSourceComponent source = rule.addSource(); + source.setContext(lexer.take()); + if (source.getContext().equals("search") && lexer.hasToken("(")) { + source.setContext("@search"); + lexer.take(); + ExpressionNode node = fpe.parse(lexer); + source.setUserData(MAP_SEARCH_EXPRESSION, node); + source.setElement(node.toString()); + lexer.token(")"); + } else if (lexer.hasToken(".")) { + lexer.token("."); + source.setElement(lexer.take()); + } + if (lexer.hasToken(":")) { + // type and cardinality + lexer.token(":"); + source.setType(lexer.takeDottedToken()); + if (!lexer.hasToken("as", "first", "last", "not_first", "not_last", "only_one", "default")) { + source.setMin(lexer.takeInt()); + lexer.token(".."); + source.setMax(lexer.take()); + } + } + if (lexer.hasToken("default")) { + lexer.token("default"); + source.setDefaultValue(new StringType(lexer.readConstant("default value"))); + } + if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one")) + source.setListMode(StructureMapSourceListMode.fromCode(lexer.take())); + + if (lexer.hasToken("as")) { + lexer.take(); + source.setVariable(lexer.take()); + } + if (lexer.hasToken("where")) { + lexer.take(); + ExpressionNode node = fpe.parse(lexer); + source.setUserData(MAP_WHERE_EXPRESSION, node); + source.setCondition(node.toString()); + } + if (lexer.hasToken("check")) { + lexer.take(); + ExpressionNode node = fpe.parse(lexer); + source.setUserData(MAP_WHERE_CHECK, node); + source.setCheck(node.toString()); + } + } + + private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { + StructureMapGroupRuleTargetComponent target = rule.addTarget(); + String start = lexer.take(); + if (lexer.hasToken(".")) { + target.setContext(start); + target.setContextType(StructureMapContextType.VARIABLE); + start = null; + lexer.token("."); + target.setElement(lexer.take()); + } + String name; + boolean isConstant = false; + if (lexer.hasToken("=")) { + if (start != null) + target.setContext(start); + lexer.token("="); + isConstant = lexer.isConstant(true); + name = lexer.take(); + } else + name = start; + + if ("(".equals(name)) { + // inline fluentpath expression + target.setTransform(StructureMapTransform.EVALUATE); + ExpressionNode node = fpe.parse(lexer); + target.setUserData(MAP_EXPRESSION, node); + target.addParameter().setValue(new StringType(node.toString())); + lexer.token(")"); + } else if (lexer.hasToken("(")) { + target.setTransform(StructureMapTransform.fromCode(name)); + lexer.token("("); + if (target.getTransform() == StructureMapTransform.EVALUATE) { + parseParameter(target, lexer); + lexer.token(","); + ExpressionNode node = fpe.parse(lexer); + target.setUserData(MAP_EXPRESSION, node); + target.addParameter().setValue(new StringType(node.toString())); + } else { + while (!lexer.hasToken(")")) { + parseParameter(target, lexer); + if (!lexer.hasToken(")")) + lexer.token(","); + } + } + lexer.token(")"); + } else if (name != null) { + target.setTransform(StructureMapTransform.COPY); + if (!isConstant) { + String id = name; + while (lexer.hasToken(".")) { + id = id + lexer.take() + lexer.take(); + } + target.addParameter().setValue(new IdType(id)); + } + else + target.addParameter().setValue(readConstant(name, lexer)); + } + if (lexer.hasToken("as")) { + lexer.take(); + target.setVariable(lexer.take()); + } + while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) { + if (lexer.getCurrent().equals("share")) { + target.addListMode(StructureMapTargetListMode.SHARE); + lexer.next(); + target.setListRuleId(lexer.take()); + } else if (lexer.getCurrent().equals("first")) + target.addListMode(StructureMapTargetListMode.FIRST); + else + target.addListMode(StructureMapTargetListMode.LAST); + lexer.next(); + } + } + + + private void parseParameter(StructureMapGroupRuleTargetComponent target, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError { + if (!lexer.isConstant(true)) { + target.addParameter().setValue(new IdType(lexer.take())); + } else if (lexer.isStringConstant()) + target.addParameter().setValue(new StringType(lexer.readConstant("??"))); + else { + target.addParameter().setValue(readConstant(lexer.take(), lexer)); + } + } + + private Type readConstant(String s, FHIRLexer lexer) throws FHIRLexerException { + if (Utilities.isInteger(s)) + return new IntegerType(s); + else if (Utilities.isDecimal(s)) + return new DecimalType(s); + else if (Utilities.existsInList(s, "true", "false")) + return new BooleanType(s.equals("true")); + else + return new StringType(lexer.processConstant(s)); + } + + public StructureDefinition getTargetType(StructureMap map) throws FHIRException { + boolean found = false; + StructureDefinition res = null; + for (StructureMapStructureComponent uses : map.getStructure()) { + if (uses.getMode() == StructureMapModelMode.TARGET) { + if (found) + throw new FHIRException("Multiple targets found in map "+map.getUrl()); + found = true; + res = worker.fetchResource(StructureDefinition.class, uses.getUrl()); + if (res == null) + throw new FHIRException("Unable to find "+uses.getUrl()+" referenced from map "+map.getUrl()); + } + } + if (res == null) + throw new FHIRException("No targets found in map "+map.getUrl()); + return res; + } + + public enum VariableMode { + INPUT, OUTPUT + } + + public class Variable { + private VariableMode mode; + private String name; + private Base object; + public Variable(VariableMode mode, String name, Base object) { + super(); + this.mode = mode; + this.name = name; + this.object = object; + } + public VariableMode getMode() { + return mode; + } + public String getName() { + return name; + } + public Base getObject() { + return object; + } + public String summary() { + return name+": "+object.fhirType(); + } + } + + public class Variables { + private List list = new ArrayList(); + + public void add(VariableMode mode, String name, Base object) { + Variable vv = null; + for (Variable v : list) + if ((v.mode == mode) && v.getName().equals(name)) + vv = v; + if (vv != null) + list.remove(vv); + list.add(new Variable(mode, name, object)); + } + + public Variables copy() { + Variables result = new Variables(); + result.list.addAll(list); + return result; + } + + public Base get(VariableMode mode, String name) { + for (Variable v : list) + if ((v.mode == mode) && v.getName().equals(name)) + return v.getObject(); + return null; + } + + public String summary() { + CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); + CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); + for (Variable v : list) + if (v.mode == VariableMode.INPUT) + s.append(v.summary()); + else + t.append(v.summary()); + return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]"; + } + } + + public class TransformContext { + private Object appInfo; + + public TransformContext(Object appInfo) { + super(); + this.appInfo = appInfo; + } + + public Object getAppInfo() { + return appInfo; + } + + } + + private void log(String cnt) { + if (services != null) + services.log(cnt); + } + + /** + * Given an item, return all the children that conform to the pattern described in name + * + * Possible patterns: + * - a simple name (which may be the base of a name with [] e.g. value[x]) + * - a name with a type replacement e.g. valueCodeableConcept + * - * which means all children + * - ** which means all descendents + * + * @param item + * @param name + * @param result + * @throws FHIRException + */ + protected void getChildrenByName(Base item, String name, List result) throws FHIRException { + for (Base v : item.listChildrenByName(name, true)) + if (v != null) + result.add(v); + } + + public void transform(Object appInfo, Base source, StructureMap map, Base target) throws FHIRException { + TransformContext context = new TransformContext(appInfo); + log("Start Transform "+map.getUrl()); + StructureMapGroupComponent g = map.getGroup().get(0); + + Variables vars = new Variables(); + vars.add(VariableMode.INPUT, getInputName(g, StructureMapInputMode.SOURCE, "source"), source); + vars.add(VariableMode.OUTPUT, getInputName(g, StructureMapInputMode.TARGET, "target"), target); + + executeGroup("", context, map, vars, g); + if (target instanceof Element) + ((Element) target).sort(); + } + + private String getInputName(StructureMapGroupComponent g, StructureMapInputMode mode, String def) throws DefinitionException { + String name = null; + for (StructureMapGroupInputComponent inp : g.getInput()) { + if (inp.getMode() == mode) + if (name != null) + throw new DefinitionException("This engine does not support multiple source inputs"); + else + name = inp.getName(); + } + return name == null ? def : name; + } + + private void executeGroup(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group) throws FHIRException { + log(indent+"Group : "+group.getName()); + // todo: check inputs + if (group.hasExtends()) { + ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends()); + executeGroup(indent+" ", context, rg.targetMap, vars, rg.target); + } + + for (StructureMapGroupRuleComponent r : group.getRule()) { + executeRule(indent+" ", context, map, vars, group, r); + } + } + + private void executeRule(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule) throws FHIRException { + log(indent+"rule : "+rule.getName()); + if (rule.getName().contains("CarePlan.participant-unlink")) + System.out.println("debug"); + Variables srcVars = vars.copy(); + if (rule.getSource().size() != 1) + throw new FHIRException("Rule \""+rule.getName()+"\": not handled yet"); + List source = processSource(rule.getName(), context, srcVars, rule.getSource().get(0)); + if (source != null) { + for (Variables v : source) { + for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { + processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null); + } + if (rule.hasRule()) { + for (StructureMapGroupRuleComponent childrule : rule.getRule()) { + executeRule(indent +" ", context, map, v, group, childrule); + } + } else if (rule.hasDependent()) { + for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { + executeDependency(indent+" ", context, map, v, group, dependent); + } + } else if (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasVariable() && rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasVariable() && rule.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE && !rule.getTargetFirstRep().hasParameter()) { + // simple inferred, map by type + Base src = v.get(VariableMode.INPUT, rule.getSourceFirstRep().getVariable()); + Base tgt = v.get(VariableMode.OUTPUT, rule.getTargetFirstRep().getVariable()); + String srcType = src.fhirType(); + String tgtType = tgt.fhirType(); + ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType); + Variables vdef = new Variables(); + vdef.add(VariableMode.INPUT, defGroup.target.getInput().get(0).getName(), src); + vdef.add(VariableMode.OUTPUT, defGroup.target.getInput().get(1).getName(), tgt); + executeGroup(indent+" ", context, defGroup.targetMap, vdef, defGroup.target); + } + } + } + } + + private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException { + ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName()); + + if (rg.target.getInput().size() != dependent.getVariable().size()) { + throw new FHIRException("Rule '"+dependent.getName()+"' has "+Integer.toString(rg.target.getInput().size())+" but the invocation has "+Integer.toString(dependent.getVariable().size())+" variables"); + } + Variables v = new Variables(); + for (int i = 0; i < rg.target.getInput().size(); i++) { + StructureMapGroupInputComponent input = rg.target.getInput().get(i); + StringType rdp = dependent.getVariable().get(i); + String var = rdp.asStringValue(); + VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT : VariableMode.OUTPUT; + Base vv = vin.get(mode, var); + if (vv == null && mode == VariableMode.INPUT) //* once source, always source. but target can be treated as source at user convenient + vv = vin.get(VariableMode.OUTPUT, var); + if (vv == null) + throw new FHIRException("Rule '"+dependent.getName()+"' "+mode.toString()+" variable '"+input.getName()+"' named as '"+var+"' has no value"); + v.add(mode, input.getName(), vv); + } + executeGroup(indent+" ", context, rg.targetMap, v, rg.target); + } + + private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException { + String type = base.fhirType(); + String kn = "type^"+type; + if (source.hasUserData(kn)) + return source.getUserString(kn); + + ResolvedGroup res = new ResolvedGroup(); + res.targetMap = null; + res.target = null; + for (StructureMapGroupComponent grp : map.getGroup()) { + if (matchesByType(map, grp, type)) { + if (res.targetMap == null) { + res.targetMap = map; + res.target = grp; + } else + throw new FHIRException("Multiple possible matches looking for default rule for '"+type+"'"); + } + } + if (res.targetMap != null) { + String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); + source.setUserData(kn, result); + return result; + } + + for (UriType imp : map.getImport()) { + List impMapList = findMatchingMaps(imp.getValue()); + if (impMapList.size() == 0) + throw new FHIRException("Unable to find map(s) for "+imp.getValue()); + for (StructureMap impMap : impMapList) { + if (!impMap.getUrl().equals(map.getUrl())) { + for (StructureMapGroupComponent grp : impMap.getGroup()) { + if (matchesByType(impMap, grp, type)) { + if (res.targetMap == null) { + res.targetMap = impMap; + res.target = grp; + } else + throw new FHIRException("Multiple possible matches for default rule for '"+type+"' in "+res.targetMap.getUrl()+" ("+res.target.getName()+") and "+impMap.getUrl()+" ("+grp.getName()+")"); + } + } + } + } + } + if (res.target == null) + throw new FHIRException("No matches found for default rule for '"+type+"' from "+map.getUrl()); + String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); // should be .getType, but R2... + source.setUserData(kn, result); + return result; + } + + private List findMatchingMaps(String value) { + List res = new ArrayList(); + if (value.contains("*")) { + for (StructureMap sm : library.values()) { + if (urlMatches(value, sm.getUrl())) { + res.add(sm); + } + } + } else { + StructureMap sm = library.get(value); + if (sm != null) + res.add(sm); + } + Set check = new HashSet(); + for (StructureMap sm : res) { + if (check.contains(sm.getUrl())) + throw new Error("duplicate"); + else + check.add(sm.getUrl()); + } + return res; + } + + private boolean urlMatches(String mask, String url) { + return url.length() > mask.length() && url.startsWith(mask.substring(0, mask.indexOf("*"))) && url.endsWith(mask.substring(mask.indexOf("*")+1)) ; + } + + private ResolvedGroup resolveGroupByTypes(StructureMap map, String ruleid, StructureMapGroupComponent source, String srcType, String tgtType) throws FHIRException { + String kn = "types^"+srcType+":"+tgtType; + if (source.hasUserData(kn)) + return (ResolvedGroup) source.getUserData(kn); + + ResolvedGroup res = new ResolvedGroup(); + res.targetMap = null; + res.target = null; + for (StructureMapGroupComponent grp : map.getGroup()) { + if (matchesByType(map, grp, srcType, tgtType)) { + if (res.targetMap == null) { + res.targetMap = map; + res.target = grp; + } else + throw new FHIRException("Multiple possible matches looking for rule for '"+srcType+"/"+tgtType+"', from rule '"+ruleid+"'"); + } + } + if (res.targetMap != null) { + source.setUserData(kn, res); + return res; + } + + for (UriType imp : map.getImport()) { + List impMapList = findMatchingMaps(imp.getValue()); + if (impMapList.size() == 0) + throw new FHIRException("Unable to find map(s) for "+imp.getValue()); + for (StructureMap impMap : impMapList) { + if (!impMap.getUrl().equals(map.getUrl())) { + for (StructureMapGroupComponent grp : impMap.getGroup()) { + if (matchesByType(impMap, grp, srcType, tgtType)) { + if (res.targetMap == null) { + res.targetMap = impMap; + res.target = grp; + } else + throw new FHIRException("Multiple possible matches for rule for '"+srcType+"/"+tgtType+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl()+", from rule '"+ruleid+"'"); + } + } + } + } + } + if (res.target == null) + throw new FHIRException("No matches found for rule for '"+srcType+"/"+tgtType+"' from "+map.getUrl()+", from rule '"+ruleid+"'"); + source.setUserData(kn, res); + return res; + } + + + private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String type) throws FHIRException { + if (grp.getTypeMode() != StructureMapGroupTypeMode.TYPEANDTYPES) + return false; + if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) + return false; + return matchesType(map, type, grp.getInput().get(0).getType()); + } + + private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String srcType, String tgtType) throws FHIRException { + if (grp.getTypeMode() == StructureMapGroupTypeMode.NONE) + return false; + if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) + return false; + if (!grp.getInput().get(0).hasType() || !grp.getInput().get(1).hasType()) + return false; + return matchesType(map, srcType, grp.getInput().get(0).getType()) && matchesType(map, tgtType, grp.getInput().get(1).getType()); + } + + private boolean matchesType(StructureMap map, String actualType, String statedType) throws FHIRException { + // check the aliases + for (StructureMapStructureComponent imp : map.getStructure()) { + if (imp.hasAlias() && statedType.equals(imp.getAlias())) { + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); + if (sd != null) + statedType = sd.getType(); + break; + } + } + + return actualType.equals(statedType); + } + + private String getActualType(StructureMap map, String statedType) throws FHIRException { + // check the aliases + for (StructureMapStructureComponent imp : map.getStructure()) { + if (imp.hasAlias() && statedType.equals(imp.getAlias())) { + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); + if (sd == null) + throw new FHIRException("Unable to resolve structure "+imp.getUrl()); + return sd.getId(); // should be sd.getType(), but R2... + } + } + return statedType; + } + + + private ResolvedGroup resolveGroupReference(StructureMap map, StructureMapGroupComponent source, String name) throws FHIRException { + String kn = "ref^"+name; + if (source.hasUserData(kn)) + return (ResolvedGroup) source.getUserData(kn); + + ResolvedGroup res = new ResolvedGroup(); + res.targetMap = null; + res.target = null; + for (StructureMapGroupComponent grp : map.getGroup()) { + if (grp.getName().equals(name)) { + if (res.targetMap == null) { + res.targetMap = map; + res.target = grp; + } else + throw new FHIRException("Multiple possible matches for rule '"+name+"'"); + } + } + if (res.targetMap != null) { + source.setUserData(kn, res); + return res; + } + + for (UriType imp : map.getImport()) { + List impMapList = findMatchingMaps(imp.getValue()); + if (impMapList.size() == 0) + throw new FHIRException("Unable to find map(s) for "+imp.getValue()); + for (StructureMap impMap : impMapList) { + if (!impMap.getUrl().equals(map.getUrl())) { + for (StructureMapGroupComponent grp : impMap.getGroup()) { + if (grp.getName().equals(name)) { + if (res.targetMap == null) { + res.targetMap = impMap; + res.target = grp; + } else + throw new FHIRException("Multiple possible matches for rule '"+name+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl()); + } + } + } + } + } + if (res.target == null) + throw new FHIRException("No matches found for rule '"+name+"'. Reference found in "+map.getUrl()); + source.setUserData(kn, res); + return res; + } + + private List processSource(String ruleId, TransformContext context, Variables vars, StructureMapGroupRuleSourceComponent src) throws FHIRException { + List items; + if (src.getContext().equals("@search")) { + ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_SEARCH_EXPRESSION); + if (expr == null) { + expr = fpe.parse(src.getElement()); + src.setUserData(MAP_SEARCH_EXPRESSION, expr); + } + String search = fpe.evaluateToString(vars, null, new StringType(), expr); // string is a holder of nothing to ensure that variables are processed correctly + items = services.performSearch(context.appInfo, search); + } else { + items = new ArrayList(); + Base b = vars.get(VariableMode.INPUT, src.getContext()); + if (b == null) + throw new FHIRException("Unknown input variable "+src.getContext()); + + if (!src.hasElement()) + items.add(b); + else { + getChildrenByName(b, src.getElement(), items); + if (items.size() == 0 && src.hasDefaultValue()) + items.add(src.getDefaultValue()); + } + } + + if (src.hasType()) { + List remove = new ArrayList(); + for (Base item : items) { + if (item != null && !isType(item, src.getType())) { + remove.add(item); + } + } + items.removeAll(remove); + } + + if (src.hasCondition()) { + ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_EXPRESSION); + if (expr == null) { + expr = fpe.parse(src.getCondition()); + // fpe.check(context.appInfo, ??, ??, expr) + src.setUserData(MAP_WHERE_EXPRESSION, expr); + } + List remove = new ArrayList(); + for (Base item : items) { + if (!fpe.evaluateToBoolean(vars, null, item, expr)) + remove.add(item); + } + items.removeAll(remove); + } + + if (src.hasCheck()) { + ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK); + if (expr == null) { + expr = fpe.parse(src.getCheck()); + // fpe.check(context.appInfo, ??, ??, expr) + src.setUserData(MAP_WHERE_CHECK, expr); + } + List remove = new ArrayList(); + for (Base item : items) { + if (!fpe.evaluateToBoolean(vars, null, item, expr)) + throw new FHIRException("Rule \""+ruleId+"\": Check condition failed"); + } + } + + + if (src.hasListMode() && !items.isEmpty()) { + switch (src.getListMode()) { + case FIRST: + Base bt = items.get(0); + items.clear(); + items.add(bt); + break; + case NOTFIRST: + if (items.size() > 0) + items.remove(0); + break; + case LAST: + bt = items.get(items.size()-1); + items.clear(); + items.add(bt); + break; + case NOTLAST: + if (items.size() > 0) + items.remove(items.size()-1); + break; + case ONLYONE: + if (items.size() > 1) + throw new FHIRException("Rule \""+ruleId+"\": Check condition failed: the collection has more than one item"); + break; + case NULL: + } + } + List result = new ArrayList(); + for (Base r : items) { + Variables v = vars.copy(); + if (src.hasVariable()) + v.add(VariableMode.INPUT, src.getVariable(), r); + result.add(v); + } + return result; + } + + + private boolean isType(Base item, String type) { + if (type.equals(item.fhirType())) + return true; + return false; + } + + private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar) throws FHIRException { + Base dest = null; + if (tgt.hasContext()) { + dest = vars.get(VariableMode.OUTPUT, tgt.getContext()); + if (dest == null) + throw new FHIRException("Rule \""+ruleId+"\": target context not known: "+tgt.getContext()); + if (!tgt.hasElement()) + throw new FHIRException("Rule \""+ruleId+"\": Not supported yet"); + } + Base v = null; + if (tgt.hasTransform()) { + v = runTransform(ruleId, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar); + if (v != null && dest != null) + v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value + } else if (dest != null) + v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); + if (tgt.hasVariable() && v != null) + vars.add(VariableMode.OUTPUT, tgt.getVariable(), v); + } + + private Base runTransform(String ruleId, TransformContext context, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, Variables vars, Base dest, String element, String srcVar) throws FHIRException { + try { + switch (tgt.getTransform()) { + case CREATE : + String tn; + if (tgt.getParameter().isEmpty()) { + // we have to work out the type. First, we see if there is a single type for the target. If there is, we use that + String[] types = dest.getTypesForProperty(element.hashCode(), element); + if (types.length == 1 && !"*".equals(types[0]) && !types[0].equals("Resource")) + tn = types[0]; + else if (srcVar != null) { + tn = determineTypeFromSourceType(map, group, vars.get(VariableMode.INPUT, srcVar), types); + } else + throw new Error("Cannot determine type implicitly because there is no single input variable"); + } else + tn = getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()); + Base res = services != null ? services.createType(context.getAppInfo(), tn) : ResourceFactory.createResourceOrType(tn); + if (res.isResource() && !res.fhirType().equals("Parameters")) { +// res.setIdBase(tgt.getParameter().size() > 1 ? getParamString(vars, tgt.getParameter().get(0)) : UUID.randomUUID().toString().toLowerCase()); + if (services != null) + res = services.createResource(context.getAppInfo(), res); + } + if (tgt.hasUserData("profile")) + res.setUserData("profile", tgt.getUserData("profile")); + return res; + case COPY : + return getParam(vars, tgt.getParameter().get(0)); + case EVALUATE : + ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); + if (expr == null) { + expr = fpe.parse(getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())); + tgt.setUserData(MAP_WHERE_EXPRESSION, expr); + } + List v = fpe.evaluate(vars, null, tgt.getParameter().size() == 2 ? getParam(vars, tgt.getParameter().get(0)) : new BooleanType(false), expr); + if (v.size() == 0) + return null; + else if (v.size() != 1) + throw new FHIRException("Rule \""+ruleId+"\": Evaluation of "+expr.toString()+" returned "+Integer.toString(v.size())+" objects"); + else + return v.get(0); + + case TRUNCATE : + String src = getParamString(vars, tgt.getParameter().get(0)); + String len = getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()); + if (Utilities.isInteger(len)) { + int l = Integer.parseInt(len); + if (src.length() > l) + src = src.substring(0, l); + } + return new StringType(src); + case ESCAPE : + throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); + case CAST : + throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); + case APPEND : + throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); + case TRANSLATE : + return translate(context, map, vars, tgt.getParameter()); + case REFERENCE : + Base b = getParam(vars, tgt.getParameter().get(0)); + if (b == null) + throw new FHIRException("Rule \""+ruleId+"\": Unable to find parameter "+((IdType) tgt.getParameter().get(0).getValue()).asStringValue()); + if (!b.isResource()) + throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType()); + else { + String id = b.getIdBase(); + if (id == null) { + id = UUID.randomUUID().toString().toLowerCase(); + b.setIdBase(id); + } + return new Reference().setReference(b.fhirType()+"/"+id); + } + case DATEOP : + throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); + case UUID : + return new IdType(UUID.randomUUID().toString()); + case POINTER : + b = getParam(vars, tgt.getParameter().get(0)); + if (b instanceof Resource) + return new UriType("urn:uuid:"+((Resource) b).getId()); + else + throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType()); + case CC: + CodeableConcept cc = new CodeableConcept(); + cc.addCoding(buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()))); + return cc; + case C: + Coding c = buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())); + return c; + default: + throw new Error("Rule \""+ruleId+"\": Transform Unknown: "+tgt.getTransform().toCode()); + } + } catch (Exception e) { + throw new FHIRException("Exception executing transform "+tgt.toString()+" on Rule \""+ruleId+"\": "+e.getMessage(), e); + } + } + + + private Coding buildCoding(String uri, String code) throws FHIRException { + // if we can get this as a valueSet, we will + String system = null; + String display = null; + ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri); + if (vs != null) { + ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false); + if (vse.getError() != null) + throw new FHIRException(vse.getError()); + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); + for (ValueSetExpansionContainsComponent t : vse.getValueset().getExpansion().getContains()) { + if (t.hasCode()) + b.append(t.getCode()); + if (code.equals(t.getCode()) && t.hasSystem()) { + system = t.getSystem(); + display = t.getDisplay(); + break; + } + if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) { + system = t.getSystem(); + display = t.getDisplay(); + break; + } + } + if (system == null) + throw new FHIRException("The code '"+code+"' is not in the value set '"+uri+"' (valid codes: "+b.toString()+"; also checked displays)"); + } else + system = uri; + ValidationResult vr = worker.validateCode(system, code, null); + if (vr != null && vr.getDisplay() != null) + display = vr.getDisplay(); + return new Coding().setSystem(system).setCode(code).setDisplay(display); + } + + + private String getParamStringNoNull(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter, String message) throws FHIRException { + Base b = getParam(vars, parameter); + if (b == null) + throw new FHIRException("Unable to find a value for "+parameter.toString()+". Context: "+message); + if (!b.hasPrimitiveValue()) + throw new FHIRException("Found a value for "+parameter.toString()+", but it has a type of "+b.fhirType()+" and cannot be treated as a string. Context: "+message); + return b.primitiveValue(); + } + + private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { + Base b = getParam(vars, parameter); + if (b == null || !b.hasPrimitiveValue()) + return null; + return b.primitiveValue(); + } + + + private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { + Type p = parameter.getValue(); + if (!(p instanceof IdType)) + return p; + else { + String n = ((IdType) p).asStringValue(); + Base b = vars.get(VariableMode.INPUT, n); + if (b == null) + b = vars.get(VariableMode.OUTPUT, n); + if (b == null) + throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")"); + return b; + } + } + + + private Base translate(TransformContext context, StructureMap map, Variables vars, List parameter) throws FHIRException { + Base src = getParam(vars, parameter.get(0)); + String id = getParamString(vars, parameter.get(1)); + String fld = parameter.size() > 2 ? getParamString(vars, parameter.get(2)) : null; + return translate(context, map, src, id, fld); + } + + private class SourceElementComponentWrapper { + private ConceptMapGroupComponent group; + private SourceElementComponent comp; + public SourceElementComponentWrapper(ConceptMapGroupComponent group, SourceElementComponent comp) { + super(); + this.group = group; + this.comp = comp; + } + } + public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException { + Coding src = new Coding(); + if (source.isPrimitive()) { + src.setCode(source.primitiveValue()); + } else if ("Coding".equals(source.fhirType())) { + Base[] b = source.getProperty("system".hashCode(), "system", true); + if (b.length == 1) + src.setSystem(b[0].primitiveValue()); + b = source.getProperty("code".hashCode(), "code", true); + if (b.length == 1) + src.setCode(b[0].primitiveValue()); + } else if ("CE".equals(source.fhirType())) { + Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true); + if (b.length == 1) + src.setSystem(b[0].primitiveValue()); + b = source.getProperty("code".hashCode(), "code", true); + if (b.length == 1) + src.setCode(b[0].primitiveValue()); + } else + throw new FHIRException("Unable to translate source "+source.fhirType()); + + String su = conceptMapUrl; + if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) { + String uri = worker.oid2Uri(src.getCode()); + if (uri == null) + uri = "urn:oid:"+src.getCode(); + if ("uri".equals(fieldToReturn)) + return new UriType(uri); + else + throw new FHIRException("Error in return code"); + } else { + ConceptMap cmap = null; + if (conceptMapUrl.startsWith("#")) { + for (Resource r : map.getContained()) { + if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(conceptMapUrl.substring(1))) { + cmap = (ConceptMap) r; + su = map.getUrl()+conceptMapUrl; + } + } + if (cmap == null) + throw new FHIRException("Unable to translate - cannot find map "+conceptMapUrl); + } else + cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl); + Coding outcome = null; + boolean done = false; + String message = null; + if (cmap == null) { + if (services == null) + message = "No map found for "+conceptMapUrl; + else { + outcome = services.translate(context.appInfo, src, conceptMapUrl); + done = true; + } + } else { + List list = new ArrayList(); + for (ConceptMapGroupComponent g : cmap.getGroup()) { + for (SourceElementComponent e : g.getElement()) { + if (!src.hasSystem() && src.getCode().equals(e.getCode())) + list.add(new SourceElementComponentWrapper(g, e)); + else if (src.hasSystem() && src.getSystem().equals(g.getSource()) && src.getCode().equals(e.getCode())) + list.add(new SourceElementComponentWrapper(g, e)); + } + } + if (list.size() == 0) + done = true; + else if (list.get(0).comp.getTarget().size() == 0) + message = "Concept map "+su+" found no translation for "+src.getCode(); + else { + for (TargetElementComponent tgt : list.get(0).comp.getTarget()) { + if (tgt.getEquivalence() == null || EnumSet.of( ConceptMapEquivalence.EQUAL , ConceptMapEquivalence.RELATEDTO , ConceptMapEquivalence.EQUIVALENT, ConceptMapEquivalence.WIDER).contains(tgt.getEquivalence())) { + if (done) { + message = "Concept map "+su+" found multiple matches for "+src.getCode(); + done = false; + } else { + done = true; + outcome = new Coding().setCode(tgt.getCode()).setSystem(list.get(0).group.getTarget()); + } + } else if (tgt.getEquivalence() == ConceptMapEquivalence.UNMATCHED) { + done = true; + } + } + if (!done) + message = "Concept map "+su+" found no usable translation for "+src.getCode(); + } + } + if (!done) + throw new FHIRException(message); + if (outcome == null) + return null; + if ("code".equals(fieldToReturn)) + return new CodeType(outcome.getCode()); + else + return outcome; + } + } + + + public Map getLibrary() { + return library; + } + + public class PropertyWithType { + private String path; + private Property baseProperty; + private Property profileProperty; + private TypeDetails types; + public PropertyWithType(String path, Property baseProperty, Property profileProperty, TypeDetails types) { + super(); + this.baseProperty = baseProperty; + this.profileProperty = profileProperty; + this.path = path; + this.types = types; + } + + public TypeDetails getTypes() { + return types; + } + public String getPath() { + return path; + } + + public Property getBaseProperty() { + return baseProperty; + } + + public void setBaseProperty(Property baseProperty) { + this.baseProperty = baseProperty; + } + + public Property getProfileProperty() { + return profileProperty; + } + + public void setProfileProperty(Property profileProperty) { + this.profileProperty = profileProperty; + } + + public String summary() { + return path; + } + + } + + public class VariableForProfiling { + private VariableMode mode; + private String name; + private PropertyWithType property; + + public VariableForProfiling(VariableMode mode, String name, PropertyWithType property) { + super(); + this.mode = mode; + this.name = name; + this.property = property; + } + public VariableMode getMode() { + return mode; + } + public String getName() { + return name; + } + public PropertyWithType getProperty() { + return property; + } + public String summary() { + return name+": "+property.summary(); + } + } + + public class VariablesForProfiling { + private List list = new ArrayList(); + private boolean optional; + private boolean repeating; + + public VariablesForProfiling(boolean optional, boolean repeating) { + this.optional = optional; + this.repeating = repeating; + } + + public void add(VariableMode mode, String name, String path, Property property, TypeDetails types) { + add(mode, name, new PropertyWithType(path, property, null, types)); + } + + public void add(VariableMode mode, String name, String path, Property baseProperty, Property profileProperty, TypeDetails types) { + add(mode, name, new PropertyWithType(path, baseProperty, profileProperty, types)); + } + + public void add(VariableMode mode, String name, PropertyWithType property) { + VariableForProfiling vv = null; + for (VariableForProfiling v : list) + if ((v.mode == mode) && v.getName().equals(name)) + vv = v; + if (vv != null) + list.remove(vv); + list.add(new VariableForProfiling(mode, name, property)); + } + + public VariablesForProfiling copy(boolean optional, boolean repeating) { + VariablesForProfiling result = new VariablesForProfiling(optional, repeating); + result.list.addAll(list); + return result; + } + + public VariablesForProfiling copy() { + VariablesForProfiling result = new VariablesForProfiling(optional, repeating); + result.list.addAll(list); + return result; + } + + public VariableForProfiling get(VariableMode mode, String name) { + if (mode == null) { + for (VariableForProfiling v : list) + if ((v.mode == VariableMode.OUTPUT) && v.getName().equals(name)) + return v; + for (VariableForProfiling v : list) + if ((v.mode == VariableMode.INPUT) && v.getName().equals(name)) + return v; + } + for (VariableForProfiling v : list) + if ((v.mode == mode) && v.getName().equals(name)) + return v; + return null; + } + + public String summary() { + CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); + CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); + for (VariableForProfiling v : list) + if (v.mode == VariableMode.INPUT) + s.append(v.summary()); + else + t.append(v.summary()); + return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]"; + } + } + + public class StructureMapAnalysis { + private List profiles = new ArrayList(); + private XhtmlNode summary; + public List getProfiles() { + return profiles; + } + public XhtmlNode getSummary() { + return summary; + } + + } + + /** + * Given a structure map, return a set of analyses on it. + * + * Returned: + * - a list or profiles for what it will create. First profile is the target + * - a table with a summary (in xhtml) for easy human undertanding of the mapping + * + * + * @param appInfo + * @param map + * @return + * @throws Exception + */ + public StructureMapAnalysis analyse(Object appInfo, StructureMap map) throws Exception { + ids.clear(); + StructureMapAnalysis result = new StructureMapAnalysis(); + TransformContext context = new TransformContext(appInfo); + VariablesForProfiling vars = new VariablesForProfiling(false, false); + StructureMapGroupComponent start = map.getGroup().get(0); + for (StructureMapGroupInputComponent t : start.getInput()) { + PropertyWithType ti = resolveType(map, t.getType(), t.getMode()); + if (t.getMode() == StructureMapInputMode.SOURCE) + vars.add(VariableMode.INPUT, t.getName(), ti); + else + vars.add(VariableMode.OUTPUT, t.getName(), createProfile(map, result.profiles, ti, start.getName(), start)); + } + + result.summary = new XhtmlNode(NodeType.Element, "table").setAttribute("class", "grid"); + XhtmlNode tr = result.summary.addTag("tr"); + tr.addTag("td").addTag("b").addText("Source"); + tr.addTag("td").addTag("b").addText("Target"); + + log("Start Profiling Transform "+map.getUrl()); + analyseGroup("", context, map, vars, start, result); + ProfileUtilities pu = new ProfileUtilities(worker, null, pkp); + for (StructureDefinition sd : result.getProfiles()) + pu.cleanUpDifferential(sd); + return result; + } + + + private void analyseGroup(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapAnalysis result) throws Exception { + log(indent+"Analyse Group : "+group.getName()); + // todo: extends + // todo: check inputs + XhtmlNode tr = result.summary.addTag("tr").setAttribute("class", "diff-title"); + XhtmlNode xs = tr.addTag("td"); + XhtmlNode xt = tr.addTag("td"); + for (StructureMapGroupInputComponent inp : group.getInput()) { + if (inp.getMode() == StructureMapInputMode.SOURCE) + noteInput(vars, inp, VariableMode.INPUT, xs); + if (inp.getMode() == StructureMapInputMode.TARGET) + noteInput(vars, inp, VariableMode.OUTPUT, xt); + } + for (StructureMapGroupRuleComponent r : group.getRule()) { + analyseRule(indent+" ", context, map, vars, group, r, result); + } + } + + + private void noteInput(VariablesForProfiling vars, StructureMapGroupInputComponent inp, VariableMode mode, XhtmlNode xs) { + VariableForProfiling v = vars.get(mode, inp.getName()); + if (v != null) + xs.addText("Input: "+v.property.getPath()); + } + + private void analyseRule(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, StructureMapAnalysis result) throws Exception { + log(indent+"Analyse rule : "+rule.getName()); + XhtmlNode tr = result.summary.addTag("tr"); + XhtmlNode xs = tr.addTag("td"); + XhtmlNode xt = tr.addTag("td"); + + VariablesForProfiling srcVars = vars.copy(); + if (rule.getSource().size() != 1) + throw new Exception("Rule \""+rule.getName()+"\": not handled yet"); + VariablesForProfiling source = analyseSource(rule.getName(), context, srcVars, rule.getSourceFirstRep(), xs); + + TargetWriter tw = new TargetWriter(); + for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { + analyseTarget(rule.getName(), context, source, map, t, rule.getSourceFirstRep().getVariable(), tw, result.profiles, rule.getName()); + } + tw.commit(xt); + + for (StructureMapGroupRuleComponent childrule : rule.getRule()) { + analyseRule(indent+" ", context, map, source, group, childrule, result); + } +// for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { +// executeDependency(indent+" ", context, map, v, group, dependent); // do we need group here? +// } + } + + public class StringPair { + private String var; + private String desc; + public StringPair(String var, String desc) { + super(); + this.var = var; + this.desc = desc; + } + public String getVar() { + return var; + } + public String getDesc() { + return desc; + } + } + public class TargetWriter { + private Map newResources = new HashMap(); + private List assignments = new ArrayList(); + private List keyProps = new ArrayList(); + private CommaSeparatedStringBuilder txt = new CommaSeparatedStringBuilder(); + + public void newResource(String var, String name) { + newResources.put(var, name); + txt.append("new "+name); + } + + public void valueAssignment(String context, String desc) { + assignments.add(new StringPair(context, desc)); + txt.append(desc); + } + + public void keyAssignment(String context, String desc) { + keyProps.add(new StringPair(context, desc)); + txt.append(desc); + } + public void commit(XhtmlNode xt) { + if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 1 && newResources.containsKey(keyProps.get(0).getVar()) ) { + xt.addText("new "+assignments.get(0).desc+" ("+keyProps.get(0).desc.substring(keyProps.get(0).desc.indexOf(".")+1)+")"); + } else if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 0) { + xt.addText("new "+assignments.get(0).desc); + } else { + xt.addText(txt.toString()); + } + } + } + + private VariablesForProfiling analyseSource(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMapGroupRuleSourceComponent src, XhtmlNode td) throws Exception { + VariableForProfiling var = vars.get(VariableMode.INPUT, src.getContext()); + if (var == null) + throw new FHIRException("Rule \""+ruleId+"\": Unknown input variable "+src.getContext()); + PropertyWithType prop = var.getProperty(); + + boolean optional = false; + boolean repeating = false; + + if (src.hasCondition()) { + optional = true; + } + + if (src.hasElement()) { + Property element = prop.getBaseProperty().getChild(prop.types.getType(), src.getElement()); + if (element == null) + throw new Exception("Rule \""+ruleId+"\": Unknown element name "+src.getElement()); + if (element.getDefinition().getMin() == 0) + optional = true; + if (element.getDefinition().getMax().equals("*")) + repeating = true; + VariablesForProfiling result = vars.copy(optional, repeating); + TypeDetails type = new TypeDetails(CollectionStatus.SINGLETON); + for (TypeRefComponent tr : element.getDefinition().getType()) { + if (!tr.hasCode()) + throw new Error("Rule \""+ruleId+"\": Element has no type"); + ProfiledType pt = new ProfiledType(tr.getCode()); + if (tr.hasProfile()) + pt.addProfile(tr.getProfile()); + if (element.getDefinition().hasBinding()) + pt.addBinding(element.getDefinition().getBinding()); + type.addType(pt); + } + td.addText(prop.getPath()+"."+src.getElement()); + if (src.hasVariable()) + result.add(VariableMode.INPUT, src.getVariable(), new PropertyWithType(prop.getPath()+"."+src.getElement(), element, null, type)); + return result; + } else { + td.addText(prop.getPath()); // ditto! + return vars.copy(optional, repeating); + } + } + + + private void analyseTarget(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt, String tv, TargetWriter tw, List profiles, String sliceName) throws Exception { + VariableForProfiling var = null; + if (tgt.hasContext()) { + var = vars.get(VariableMode.OUTPUT, tgt.getContext()); + if (var == null) + throw new Exception("Rule \""+ruleId+"\": target context not known: "+tgt.getContext()); + if (!tgt.hasElement()) + throw new Exception("Rule \""+ruleId+"\": Not supported yet"); + } + + + TypeDetails type = null; + if (tgt.hasTransform()) { + type = analyseTransform(context, map, tgt, var, vars); + // profiling: dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); + } else { + Property vp = var.property.baseProperty.getChild(tgt.getElement(), tgt.getElement()); + if (vp == null) + throw new Exception("Unknown Property "+tgt.getElement()+" on "+var.property.path); + + type = new TypeDetails(CollectionStatus.SINGLETON, vp.getType(tgt.getElement())); + } + + if (tgt.getTransform() == StructureMapTransform.CREATE) { + String s = getParamString(vars, tgt.getParameter().get(0)); + if (worker.getResourceNames().contains(s)) + tw.newResource(tgt.getVariable(), s); + } else { + boolean mapsSrc = false; + for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { + Type pr = p.getValue(); + if (pr instanceof IdType && ((IdType) pr).asStringValue().equals(tv)) + mapsSrc = true; + } + if (mapsSrc) { + if (var == null) + throw new Error("Rule \""+ruleId+"\": Attempt to assign with no context"); + tw.valueAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+getTransformSuffix(tgt.getTransform())); + } else if (tgt.hasContext()) { + if (isSignificantElement(var.property, tgt.getElement())) { + String td = describeTransform(tgt); + if (td != null) + tw.keyAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+" = "+td); + } + } + } + Type fixed = generateFixedValue(tgt); + + PropertyWithType prop = updateProfile(var, tgt.getElement(), type, map, profiles, sliceName, fixed, tgt); + if (tgt.hasVariable()) + if (tgt.hasElement()) + vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); + else + vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); + } + + private Type generateFixedValue(StructureMapGroupRuleTargetComponent tgt) { + if (!allParametersFixed(tgt)) + return null; + if (!tgt.hasTransform()) + return null; + switch (tgt.getTransform()) { + case COPY: return tgt.getParameter().get(0).getValue(); + case TRUNCATE: return null; + //case ESCAPE: + //case CAST: + //case APPEND: + case TRANSLATE: return null; + //case DATEOP, + //case UUID, + //case POINTER, + //case EVALUATE, + case CC: + CodeableConcept cc = new CodeableConcept(); + cc.addCoding(buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue())); + return cc; + case C: + return buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue()); + case QTY: return null; + //case ID, + //case CP, + default: + return null; + } + } + + @SuppressWarnings("rawtypes") + private Coding buildCoding(Type value1, Type value2) { + return new Coding().setSystem(((PrimitiveType) value1).asStringValue()).setCode(((PrimitiveType) value2).asStringValue()) ; + } + + private boolean allParametersFixed(StructureMapGroupRuleTargetComponent tgt) { + for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { + Type pr = p.getValue(); + if (pr instanceof IdType) + return false; + } + return true; + } + + private String describeTransform(StructureMapGroupRuleTargetComponent tgt) throws FHIRException { + switch (tgt.getTransform()) { + case COPY: return null; + case TRUNCATE: return null; + //case ESCAPE: + //case CAST: + //case APPEND: + case TRANSLATE: return null; + //case DATEOP, + //case UUID, + //case POINTER, + //case EVALUATE, + case CC: return describeTransformCCorC(tgt); + case C: return describeTransformCCorC(tgt); + case QTY: return null; + //case ID, + //case CP, + default: + return null; + } + } + + @SuppressWarnings("rawtypes") + private String describeTransformCCorC(StructureMapGroupRuleTargetComponent tgt) throws FHIRException { + if (tgt.getParameter().size() < 2) + return null; + Type p1 = tgt.getParameter().get(0).getValue(); + Type p2 = tgt.getParameter().get(1).getValue(); + if (p1 instanceof IdType || p2 instanceof IdType) + return null; + if (!(p1 instanceof PrimitiveType) || !(p2 instanceof PrimitiveType)) + return null; + String uri = ((PrimitiveType) p1).asStringValue(); + String code = ((PrimitiveType) p2).asStringValue(); + if (Utilities.noString(uri)) + throw new FHIRException("Describe Transform, but the uri is blank"); + if (Utilities.noString(code)) + throw new FHIRException("Describe Transform, but the code is blank"); + Coding c = buildCoding(uri, code); + return NarrativeGenerator.describeSystem(c.getSystem())+"#"+c.getCode()+(c.hasDisplay() ? "("+c.getDisplay()+")" : ""); + } + + + private boolean isSignificantElement(PropertyWithType property, String element) { + if ("Observation".equals(property.getPath())) + return "code".equals(element); + else if ("Bundle".equals(property.getPath())) + return "type".equals(element); + else + return false; + } + + private String getTransformSuffix(StructureMapTransform transform) { + switch (transform) { + case COPY: return ""; + case TRUNCATE: return " (truncated)"; + //case ESCAPE: + //case CAST: + //case APPEND: + case TRANSLATE: return " (translated)"; + //case DATEOP, + //case UUID, + //case POINTER, + //case EVALUATE, + case CC: return " (--> CodeableConcept)"; + case C: return " (--> Coding)"; + case QTY: return " (--> Quantity)"; + //case ID, + //case CP, + default: + return " {??)"; + } + } + + private PropertyWithType updateProfile(VariableForProfiling var, String element, TypeDetails type, StructureMap map, List profiles, String sliceName, Type fixed, StructureMapGroupRuleTargetComponent tgt) throws FHIRException { + if (var == null) { + assert (Utilities.noString(element)); + // 1. start the new structure definition + StructureDefinition sdn = worker.fetchResource(StructureDefinition.class, type.getType()); + if (sdn == null) + throw new FHIRException("Unable to find definition for "+type.getType()); + ElementDefinition edn = sdn.getSnapshot().getElementFirstRep(); + PropertyWithType pn = createProfile(map, profiles, new PropertyWithType(sdn.getId(), new Property(worker, edn, sdn), null, type), sliceName, tgt); + +// // 2. hook it into the base bundle +// if (type.getType().startsWith("http://hl7.org/fhir/StructureDefinition/") && worker.getResourceNames().contains(type.getType().substring(40))) { +// StructureDefinition sd = var.getProperty().profileProperty.getStructure(); +// ElementDefinition ed = sd.getDifferential().addElement(); +// ed.setPath("Bundle.entry"); +// ed.setName(sliceName); +// ed.setMax("1"); // well, it is for now... +// ed = sd.getDifferential().addElement(); +// ed.setPath("Bundle.entry.fullUrl"); +// ed.setMin(1); +// ed = sd.getDifferential().addElement(); +// ed.setPath("Bundle.entry.resource"); +// ed.setMin(1); +// ed.addType().setCode(pn.getProfileProperty().getStructure().getType()).setProfile(pn.getProfileProperty().getStructure().getUrl()); +// } + return pn; + } else { + assert (!Utilities.noString(element)); + Property pvb = var.getProperty().getBaseProperty(); + Property pvd = var.getProperty().getProfileProperty(); + Property pc = pvb.getChild(element, var.property.types); + if (pc == null) + throw new DefinitionException("Unable to find a definition for "+pvb.getDefinition().getPath()+"."+element); + + // the profile structure definition (derived) + StructureDefinition sd = var.getProperty().profileProperty.getStructure(); + ElementDefinition ednew = sd.getDifferential().addElement(); + ednew.setPath(var.getProperty().profileProperty.getDefinition().getPath()+"."+pc.getName()); + ednew.setUserData("slice-name", sliceName); + ednew.setFixed(fixed); + for (ProfiledType pt : type.getProfiledTypes()) { + if (pt.hasBindings()) + ednew.setBinding(pt.getBindings().get(0)); + if (pt.getUri().startsWith("http://hl7.org/fhir/StructureDefinition/")) { + String t = pt.getUri().substring(40); + t = checkType(t, pc, pt.getProfiles()); + if (t != null) { + if (pt.hasProfiles()) { + for (String p : pt.getProfiles()) + if (t.equals("Reference")) + ednew.addType().setCode(t).setTargetProfile(p); + else + ednew.addType().setCode(t).setProfile(p); + } else + ednew.addType().setCode(t); + } + } + } + + return new PropertyWithType(var.property.path+"."+element, pc, new Property(worker, ednew, sd), type); + } + } + + + + private String checkType(String t, Property pvb, List profiles) throws FHIRException { + if (pvb.getDefinition().getType().size() == 1 && isCompatibleType(t, pvb.getDefinition().getType().get(0).getCode()) && profilesMatch(profiles, pvb.getDefinition().getType().get(0).getProfile())) + return null; + for (TypeRefComponent tr : pvb.getDefinition().getType()) { + if (isCompatibleType(t, tr.getCode())) + return tr.getCode(); // note what is returned - the base type, not the inferred mapping type + } + throw new FHIRException("The type "+t+" is not compatible with the allowed types for "+pvb.getDefinition().getPath()+" ("+pvb.getDefinition().typeSummary()+")"); + } + + private boolean profilesMatch(List profiles, String profile) { + return profiles == null || profiles.size() == 0 || (profiles.size() == 1 && profiles.get(0).equals(profile)); + } + + private boolean isCompatibleType(String t, String code) { + if (t.equals(code)) + return true; + if (t.equals("string")) { + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+code); + if (sd != null && sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/string")) + return true; + } + return false; + } + + private TypeDetails analyseTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, VariableForProfiling var, VariablesForProfiling vars) throws FHIRException { + switch (tgt.getTransform()) { + case CREATE : + String p = getParamString(vars, tgt.getParameter().get(0)); + return new TypeDetails(CollectionStatus.SINGLETON, p); + case COPY : + return getParam(vars, tgt.getParameter().get(0)); + case EVALUATE : + ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); + if (expr == null) { + expr = fpe.parse(getParamString(vars, tgt.getParameter().get(tgt.getParameter().size()-1))); + tgt.setUserData(MAP_WHERE_EXPRESSION, expr); + } + return fpe.check(vars, null, expr); + +////case TRUNCATE : +//// String src = getParamString(vars, tgt.getParameter().get(0)); +//// String len = getParamString(vars, tgt.getParameter().get(1)); +//// if (Utilities.isInteger(len)) { +//// int l = Integer.parseInt(len); +//// if (src.length() > l) +//// src = src.substring(0, l); +//// } +//// return new StringType(src); +////case ESCAPE : +//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); +////case CAST : +//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); +////case APPEND : +//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); + case TRANSLATE : + return new TypeDetails(CollectionStatus.SINGLETON, "CodeableConcept"); + case CC: + ProfiledType res = new ProfiledType("CodeableConcept"); + if (tgt.getParameter().size() >= 2 && isParamId(vars, tgt.getParameter().get(1))) { + TypeDetails td = vars.get(null, getParamId(vars, tgt.getParameter().get(1))).property.types; + if (td != null && td.hasBinding()) + // todo: do we need to check that there's no implicit translation her? I don't think we do... + res.addBinding(td.getBinding()); + } + return new TypeDetails(CollectionStatus.SINGLETON, res); + case C: + return new TypeDetails(CollectionStatus.SINGLETON, "Coding"); + case QTY: + return new TypeDetails(CollectionStatus.SINGLETON, "Quantity"); + case REFERENCE : + VariableForProfiling vrs = vars.get(VariableMode.OUTPUT, getParamId(vars, tgt.getParameterFirstRep())); + if (vrs == null) + throw new FHIRException("Unable to resolve variable \""+getParamId(vars, tgt.getParameterFirstRep())+"\""); + String profile = vrs.property.getProfileProperty().getStructure().getUrl(); + TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON); + td.addType("Reference", profile); + return td; +////case DATEOP : +//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); +////case UUID : +//// return new IdType(UUID.randomUUID().toString()); +////case POINTER : +//// Base b = getParam(vars, tgt.getParameter().get(0)); +//// if (b instanceof Resource) +//// return new UriType("urn:uuid:"+((Resource) b).getId()); +//// else +//// throw new FHIRException("Transform engine cannot point at an element of type "+b.fhirType()); + default: + throw new Error("Transform Unknown or not handled yet: "+tgt.getTransform().toCode()); + } + } + private String getParamString(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { + Type p = parameter.getValue(); + if (p == null || p instanceof IdType) + return null; + if (!p.hasPrimitiveValue()) + return null; + return p.primitiveValue(); + } + + private String getParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { + Type p = parameter.getValue(); + if (p == null || !(p instanceof IdType)) + return null; + return p.primitiveValue(); + } + + private boolean isParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { + Type p = parameter.getValue(); + if (p == null || !(p instanceof IdType)) + return false; + return vars.get(null, p.primitiveValue()) != null; + } + + private TypeDetails getParam(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { + Type p = parameter.getValue(); + if (!(p instanceof IdType)) + return new TypeDetails(CollectionStatus.SINGLETON, "http://hl7.org/fhir/StructureDefinition/"+p.fhirType()); + else { + String n = ((IdType) p).asStringValue(); + VariableForProfiling b = vars.get(VariableMode.INPUT, n); + if (b == null) + b = vars.get(VariableMode.OUTPUT, n); + if (b == null) + throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")"); + return b.getProperty().getTypes(); + } + } + + private PropertyWithType createProfile(StructureMap map, List profiles, PropertyWithType prop, String sliceName, Base ctxt) throws DefinitionException { + if (prop.getBaseProperty().getDefinition().getPath().contains(".")) + throw new DefinitionException("Unable to process entry point"); + + String type = prop.getBaseProperty().getDefinition().getPath(); + String suffix = ""; + if (ids.containsKey(type)) { + int id = ids.get(type); + id++; + ids.put(type, id); + suffix = "-"+Integer.toString(id); + } else + ids.put(type, 0); + + StructureDefinition profile = new StructureDefinition(); + profiles.add(profile); + profile.setDerivation(TypeDerivationRule.CONSTRAINT); + profile.setType(type); + profile.setBaseDefinition(prop.getBaseProperty().getStructure().getUrl()); + profile.setName("Profile for "+profile.getType()+" for "+sliceName); + profile.setUrl(map.getUrl().replace("StructureMap", "StructureDefinition")+"-"+profile.getType()+suffix); + ctxt.setUserData("profile", profile.getUrl()); // then we can easily assign this profile url for validation later when we actually transform + profile.setId(map.getId()+"-"+profile.getType()+suffix); + profile.setStatus(map.getStatus()); + profile.setExperimental(map.getExperimental()); + profile.setDescription("Generated automatically from the mapping by the Java Reference Implementation"); + for (ContactDetail c : map.getContact()) { + ContactDetail p = profile.addContact(); + p.setName(c.getName()); + for (ContactPoint cc : c.getTelecom()) + p.addTelecom(cc); + } + profile.setDate(map.getDate()); + profile.setCopyright(map.getCopyright()); + profile.setFhirVersion(Constants.VERSION); + profile.setKind(prop.getBaseProperty().getStructure().getKind()); + profile.setAbstract(false); + ElementDefinition ed = profile.getDifferential().addElement(); + ed.setPath(profile.getType()); + prop.profileProperty = new Property(worker, ed, profile); + return prop; + } + + private PropertyWithType resolveType(StructureMap map, String type, StructureMapInputMode mode) throws Exception { + for (StructureMapStructureComponent imp : map.getStructure()) { + if ((imp.getMode() == StructureMapModelMode.SOURCE && mode == StructureMapInputMode.SOURCE) || + (imp.getMode() == StructureMapModelMode.TARGET && mode == StructureMapInputMode.TARGET)) { + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); + if (sd == null) + throw new Exception("Import "+imp.getUrl()+" cannot be resolved"); + if (sd.getId().equals(type)) { + return new PropertyWithType(sd.getType(), new Property(worker, sd.getSnapshot().getElement().get(0), sd), null, new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl())); + } + } + } + throw new Exception("Unable to find structure definition for "+type+" in imports"); + } + + + public StructureMap generateMapFromMappings(StructureDefinition sd) throws IOException, FHIRException { + String id = getLogicalMappingId(sd); + if (id == null) + return null; + String prefix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_PREFIX); + String suffix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_SUFFIX); + if (prefix == null || suffix == null) + return null; + // we build this by text. Any element that has a mapping, we put it's mappings inside it.... + StringBuilder b = new StringBuilder(); + b.append(prefix); + + ElementDefinition root = sd.getSnapshot().getElementFirstRep(); + String m = getMapping(root, id); + if (m != null) + b.append(m+"\r\n"); + addChildMappings(b, id, "", sd, root, false); + b.append("\r\n"); + b.append(suffix); + b.append("\r\n"); + StructureMap map = parse(b.toString()); + map.setId(tail(map.getUrl())); + if (!map.hasStatus()) + map.setStatus(PublicationStatus.DRAFT); + map.getText().setStatus(NarrativeStatus.GENERATED); + map.getText().setDiv(new XhtmlNode(NodeType.Element, "div")); + map.getText().getDiv().addTag("pre").addText(render(map)); + return map; + } + + + private String tail(String url) { + return url.substring(url.lastIndexOf("/")+1); + } + + + private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException { + boolean first = true; + List children = ProfileUtilities.getChildMap(sd, ed); + for (ElementDefinition child : children) { + if (first && inner) { + b.append(" then {\r\n"); + first = false; + } + String map = getMapping(child, id); + if (map != null) { + b.append(indent+" "+child.getPath()+": "+map); + addChildMappings(b, id, indent+" ", sd, child, true); + b.append("\r\n"); + } + } + if (!first && inner) + b.append(indent+"}"); + + } + + + private String getMapping(ElementDefinition ed, String id) { + for (ElementDefinitionMappingComponent map : ed.getMapping()) + if (id.equals(map.getIdentity())) + return map.getMap(); + return null; + } + + + private String getLogicalMappingId(StructureDefinition sd) { + String id = null; + for (StructureDefinitionMappingComponent map : sd.getMapping()) { + if ("http://hl7.org/fhir/logical".equals(map.getUri())) + return map.getIdentity(); + } + return null; + } + +} diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java index 8fd30f66f53..59265c38c73 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java @@ -11,6 +11,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.*; +import com.helger.commons.io.stream.StringInputStream; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.ReaderInputStream; import org.apache.http.*; @@ -29,7 +30,6 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import com.google.common.base.Charsets; -import com.phloc.commons.io.streams.StringInputStream; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml index b4175479294..0c0a87fdbb1 100644 --- a/hapi-fhir-structures-hl7org-dstu2/pom.xml +++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml @@ -122,8 +122,8 @@ test - com.phloc - phloc-schematron + com.helger + ph-schematron test @@ -133,8 +133,8 @@ - com.phloc - phloc-commons + com.helger + ph-commons test diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index 3d0e3e37049..e6e9eca0d9f 100644 --- a/hapi-fhir-structures-r4/pom.xml +++ b/hapi-fhir-structures-r4/pom.xml @@ -125,13 +125,13 @@ test - com.phloc - phloc-schematron + com.helger + ph-schematron test - com.phloc - phloc-commons + com.helger + ph-commons test @@ -192,11 +192,6 @@ spring-web test - - org.springframework - spring-context - test - diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/context/BaseWorkerContext.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/context/BaseWorkerContext.java index feee569839c..1ed5df4b552 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/context/BaseWorkerContext.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/context/BaseWorkerContext.java @@ -6,13 +6,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import org.apache.commons.codec.Charsets; import org.apache.commons.lang3.StringUtils; @@ -250,7 +244,7 @@ public abstract class BaseWorkerContext implements IWorkerContext { return laterVersion(newParts[i], oldParts[i]); } // This should never happen - throw new Error("Delimited versions have exact match for delimiter '"+delimiter+"' : "+newParts+" vs "+oldParts); + throw new Error("Delimited versions have exact match for delimiter '"+delimiter+"' : "+ Arrays.asList(newParts)+" vs "+Arrays.asList(oldParts)); } protected void seeMetadataResource(T r, Map map, boolean addId) throws FHIRException { @@ -1045,6 +1039,8 @@ public abstract class BaseWorkerContext implements IWorkerContext { return (T) maps.get(uri); if (transforms.containsKey(uri)) return (T) transforms.get(uri); + if (questionnaires.containsKey(uri)) + return (T) questionnaires.get(uri); return null; } else if (class_ == StructureDefinition.class) { return (T) structures.get(uri); @@ -1052,6 +1048,8 @@ public abstract class BaseWorkerContext implements IWorkerContext { return (T) valueSets.get(uri); } else if (class_ == CodeSystem.class) { return (T) codeSystems.get(uri); + } else if (class_ == ConceptMap.class) { + return (T) maps.get(uri); } else if (class_ == OperationDefinition.class) { OperationDefinition od = operations.get(uri); return (T) od; @@ -1069,7 +1067,7 @@ public abstract class BaseWorkerContext implements IWorkerContext { } } if (class_ == Questionnaire.class) - return null; + return (T) questionnaires.get(uri); if (class_ == null) { if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) return null; diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java index ef4f05d3e69..80e2f7400bd 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java @@ -1,10 +1,11 @@ package org.hl7.fhir.r4.utils; //import ca.uhn.fhir.model.api.TemporalPrecisionEnum; - import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.util.ElementUtil; + import org.apache.commons.lang3.NotImplementedException; +import org.apache.http.protocol.ExecutionContext; import org.fhir.ucum.Decimal; import org.fhir.ucum.Pair; import org.fhir.ucum.UcumException; @@ -24,8 +25,13 @@ import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails; import org.hl7.fhir.utilities.Utilities; import java.math.BigDecimal; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.*; +import static org.apache.commons.lang3.StringUtils.length; + /** * * @author Grahame Grieve @@ -731,7 +737,7 @@ public class FHIRPathEngine { String s = lexer.take(); if (s.equals("year") || s.equals("years")) ucum = "a"; - else if (s.equals("month") || s.equals("month")) + else if (s.equals("month") || s.equals("months")) ucum = "mo"; else if (s.equals("week") || s.equals("weeks")) ucum = "wk"; @@ -2072,8 +2078,9 @@ public class FHIRPathEngine { result.add(item); } else getChildrenByName(item, exp.getName(), result); - // todo: GG 1st April 201 - why do this? if (result.size() == 0 && atEntry && context.appInfo != null) { + // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context. + // (if the name does match, and the user wants to get the constant value, they'll have to try harder... Base temp = hostServices.resolveConstant(context.appInfo, exp.getName()); if (temp != null) { result.add(temp); @@ -3280,7 +3287,7 @@ public class FHIRPathEngine { return Quantity.fromUcum(v, s.substring(1, s.length()-1)); if (s.equals("year") || s.equals("years")) return Quantity.fromUcum(v, "a"); - else if (s.equals("month") || s.equals("month")) + else if (s.equals("month") || s.equals("months")) return Quantity.fromUcum(v, "mo"); else if (s.equals("week") || s.equals("weeks")) return Quantity.fromUcum(v, "wk"); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java index 7a4b587456b..18876a64970 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java @@ -17,6 +17,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.*; +import com.helger.commons.io.stream.StringInputStream; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.ReaderInputStream; import org.apache.http.*; @@ -36,7 +37,6 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import com.google.common.base.Charsets; -import com.phloc.commons.io.streams.StringInputStream; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; @@ -93,7 +93,7 @@ public class GenericClientR4Test { return body; } - private ArgumentCaptor prepareClientForSearchResponse() throws IOException, ClientProtocolException { + private ArgumentCaptor prepareClientForSearchResponse() throws IOException { final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); @@ -102,7 +102,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -143,7 +143,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -187,7 +187,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -234,7 +234,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -278,7 +278,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -322,7 +322,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -366,7 +366,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -408,7 +408,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -472,7 +472,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -504,14 +504,14 @@ public class GenericClientR4Test { when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { @Override - public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + public Header[] answer(InvocationOnMock theInvocation) { return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; } }); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { if (myAnswerCount++ == 0) { return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); } else { @@ -553,14 +553,14 @@ public class GenericClientR4Test { when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { @Override - public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + public Header[] answer(InvocationOnMock theInvocation) { return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; } }); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { myAnswerCount++; return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); } @@ -592,7 +592,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -619,7 +619,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -664,7 +664,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -704,7 +704,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -740,7 +740,7 @@ public class GenericClientR4Test { private int myCount = 0; @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { final String respString; if (myCount == 1 || myCount == 2) { ourLog.info("Encoding patient"); @@ -788,7 +788,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { final String respString; if (myAnswerCount >= 1) { ourLog.info("Encoding patient"); @@ -828,7 +828,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public StringInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public StringInputStream answer(InvocationOnMock theInvocation) { return new StringInputStream("HELLO", Charsets.UTF_8); } }); @@ -853,7 +853,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public StringInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public StringInputStream answer(InvocationOnMock theInvocation) { return new StringInputStream("not implemented", Charsets.UTF_8); } }); @@ -884,7 +884,7 @@ public class GenericClientR4Test { .forResource(Patient.class) .where(Patient.FAMILY.matches().value((String) null)) .and(Patient.BIRTHDATE.exactly().day((Date) null)) - .and(Patient.GENDER.exactly().code((String) null)) + .and(Patient.GENDER.exactly().code(null)) .and(Patient.ORGANIZATION.hasId((String) null)) .returnBundle(Bundle.class) .execute(); @@ -915,7 +915,7 @@ public class GenericClientR4Test { } @Test - public void testPatchInvalid() throws Exception { + public void testPatchInvalid() { IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); try { @@ -941,7 +941,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -980,7 +980,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1019,7 +1019,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1057,7 +1057,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1095,7 +1095,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1133,7 +1133,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1180,7 +1180,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(encoded), Charset.forName("UTF-8")); } }); @@ -1225,7 +1225,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(encoded), Charset.forName("UTF-8")); } }); @@ -1282,7 +1282,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(null); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1313,7 +1313,7 @@ public class GenericClientR4Test { // when(myHttpResponse.getEntity().getContentType()).thenReturn(null); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1331,7 +1331,7 @@ public class GenericClientR4Test { } @Test - public void testRevIncludeRecursive() throws ClientProtocolException, IOException { + public void testRevIncludeRecursive() throws IOException { ArgumentCaptor capt = prepareClientForSearchResponse(); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); @@ -1361,7 +1361,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -1468,7 +1468,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -1578,7 +1578,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -1670,7 +1670,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -1756,7 +1756,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).then(new Answer() { @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + public InputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); } }); @@ -1793,7 +1793,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1830,7 +1830,7 @@ public class GenericClientR4Test { } @Test - public void testTransactionWithInvalidBody() throws Exception { + public void testTransactionWithInvalidBody() { IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); // Transaction @@ -1881,7 +1881,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -1920,14 +1920,14 @@ public class GenericClientR4Test { when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { @Override - public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + public Header[] answer(InvocationOnMock theInvocation) { return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; } }); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { if (myAnswerCount++ == 0) { return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); } else { @@ -1968,14 +1968,14 @@ public class GenericClientR4Test { when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { @Override - public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + public Header[] answer(InvocationOnMock theInvocation) { return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; } }); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { myAnswerCount++; return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); } @@ -2013,7 +2013,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -2050,7 +2050,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -2071,7 +2071,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -2099,14 +2099,14 @@ public class GenericClientR4Test { when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { @Override - public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + public Header[] answer(InvocationOnMock theInvocation) { return new Header[] {}; } }); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); } }); @@ -2139,7 +2139,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); @@ -2177,7 +2177,7 @@ public class GenericClientR4Test { when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + public ReaderInputStream answer(InvocationOnMock theInvocation) { return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); } }); diff --git a/hapi-fhir-tutorial/skeleton-project/pom.xml b/hapi-fhir-tutorial/skeleton-project/pom.xml index 050113b9d87..164f5cc4200 100644 --- a/hapi-fhir-tutorial/skeleton-project/pom.xml +++ b/hapi-fhir-tutorial/skeleton-project/pom.xml @@ -65,8 +65,8 @@ Schematron resource validation. Otherwise, they may be omitted. --> - com.phloc - phloc-schematron + com.helger + ph-schematron Saxon-HE @@ -75,8 +75,8 @@ - com.phloc - phloc-commons + com.helger + ph-commons 4.3.3 @@ -123,4 +123,4 @@ - \ No newline at end of file + diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index bc8351450de..01d6f30eb26 100644 --- a/hapi-fhir-validation/pom.xml +++ b/hapi-fhir-validation/pom.xml @@ -173,13 +173,13 @@ test - com.phloc - phloc-schematron + com.helger + ph-schematron test - com.phloc - phloc-commons + com.helger + ph-commons test diff --git a/osgi/hapi-fhir-karaf-features/src/main/resources/features.xml b/osgi/hapi-fhir-karaf-features/src/main/resources/features.xml index 6c576fe2b1c..449ad9db4f7 100644 --- a/osgi/hapi-fhir-karaf-features/src/main/resources/features.xml +++ b/osgi/hapi-fhir-karaf-features/src/main/resources/features.xml @@ -89,9 +89,14 @@ mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.xmlresolver/${servicemix_xmlresolver_version} + mvn:org.apache.aries.spifly/org.apache.aries.spifly.dynamic.bundle/${aries_spifly_version} mvn:com.google.code.findbugs/jsr305/${jsr305_version} - mvn:com.phloc/phloc-schematron/${phloc_schematron_version} - mvn:com.phloc/phloc-commons/${phloc_commons_version} + mvn:com.helger/ph-commons/${ph_commons_version} + mvn:com.helger/ph-collection/${ph_commons_version} + mvn:com.helger/ph-xml/${ph_commons_version} + mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.jaxb-impl/${jaxb_bundle_version} + mvn:com.helger/ph-jaxb/${ph_commons_version} + mvn:com.helger/ph-schematron/${ph_schematron_version} diff --git a/osgi/hapi-fhir-karaf-integration-tests/pom.xml b/osgi/hapi-fhir-karaf-integration-tests/pom.xml index b569623d79c..7cbd6f1ddc8 100644 --- a/osgi/hapi-fhir-karaf-integration-tests/pom.xml +++ b/osgi/hapi-fhir-karaf-integration-tests/pom.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - + 4.0.0 @@ -98,6 +98,7 @@ ${project.version} + org.ops4j.pax.exam @@ -122,6 +123,12 @@ ${apache_karaf_version} test tar.gz + + + org.ops4j.pax.logging + pax-logging-api + + org.apache.karaf.shell diff --git a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/ValidationConstants.java b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/ValidationConstants.java new file mode 100644 index 00000000000..7278e4d80bd --- /dev/null +++ b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/ValidationConstants.java @@ -0,0 +1,7 @@ +package ca.uhn.fhir.tests.integration.karaf; + +public class ValidationConstants { + + public static final boolean SCHEMATRON_ENABLED = true; + +} diff --git a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu2/Dstu2ResourceValidatorDstu2Test.java b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu2/Dstu2ResourceValidatorDstu2Test.java index ff2cc52a836..6b296b30bb5 100644 --- a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu2/Dstu2ResourceValidatorDstu2Test.java +++ b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu2/Dstu2ResourceValidatorDstu2Test.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; +import ca.uhn.fhir.tests.integration.karaf.ValidationConstants; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.SchemaBaseValidator; import ca.uhn.fhir.validation.ValidationFailureException; @@ -85,7 +86,7 @@ public class Dstu2ResourceValidatorDstu2Test { private FhirValidator createFhirValidator() { FhirValidator val = ourCtx.newValidator(); val.setValidateAgainstStandardSchema(true); - val.setValidateAgainstStandardSchematron(true); + val.setValidateAgainstStandardSchematron(ValidationConstants.SCHEMATRON_ENABLED); return val; } @@ -156,7 +157,9 @@ public class Dstu2ResourceValidatorDstu2Test { FhirValidator val = ourCtx.newValidator(); val.setValidateAgainstStandardSchema(true); - val.setValidateAgainstStandardSchematron(false); + if (ValidationConstants.SCHEMATRON_ENABLED) { + val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + } val.validate(p); @@ -246,7 +249,9 @@ public class Dstu2ResourceValidatorDstu2Test { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); - val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + if (ValidationConstants.SCHEMATRON_ENABLED) { + val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + } ValidationResult result = val.validateWithResult(messageString); @@ -299,7 +304,9 @@ public class Dstu2ResourceValidatorDstu2Test { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); - val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + if (ValidationConstants.SCHEMATRON_ENABLED) { + val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + } ValidationResult result = val.validateWithResult(messageString); diff --git a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu3/ResourceValidatorDstu3FeatureTest.java b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu3/ResourceValidatorDstu3FeatureTest.java index d0918aa628f..356ef1b1d3a 100644 --- a/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu3/ResourceValidatorDstu3FeatureTest.java +++ b/osgi/hapi-fhir-karaf-integration-tests/src/test/java/ca/uhn/fhir/tests/integration/karaf/dstu3/ResourceValidatorDstu3FeatureTest.java @@ -6,19 +6,12 @@ import ca.uhn.fhir.model.api.annotation.ResourceDef; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; +import ca.uhn.fhir.tests.integration.karaf.ValidationConstants; import ca.uhn.fhir.validation.*; import ca.uhn.fhir.validation.schematron.SchematronBaseValidator; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.Validate; import org.hamcrest.core.StringContains; -import org.hl7.fhir.dstu3.conformance.ProfileUtilities; -import org.hl7.fhir.dstu3.context.IWorkerContext; -import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport; import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.utilities.validation.ValidationMessage; import org.junit.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.Configuration; @@ -33,14 +26,10 @@ import java.util.Arrays; import java.util.Date; import java.util.List; -import static ca.uhn.fhir.tests.integration.karaf.PaxExamOptions.HAPI_FHIR_VALIDATION_DSTU3; -import static ca.uhn.fhir.tests.integration.karaf.PaxExamOptions.KARAF; -import static ca.uhn.fhir.tests.integration.karaf.PaxExamOptions.WRAP; +import static ca.uhn.fhir.tests.integration.karaf.PaxExamOptions.*; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; -import static org.ops4j.pax.exam.CoreOptions.mavenBundle; -import static org.ops4j.pax.exam.CoreOptions.options; -import static org.ops4j.pax.exam.CoreOptions.when; +import static org.ops4j.pax.exam.CoreOptions.*; import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.debugConfiguration; /** @@ -145,7 +134,9 @@ public class ResourceValidatorDstu3FeatureTest { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); - val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + if (ValidationConstants.SCHEMATRON_ENABLED) { + val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + } val.registerValidatorModule(new FhirInstanceValidator()); ValidationResult output = val.validateWithResult(p); @@ -162,7 +153,9 @@ public class ResourceValidatorDstu3FeatureTest { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); - val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + if (ValidationConstants.SCHEMATRON_ENABLED) { + val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + } val.registerValidatorModule(new FhirInstanceValidator()); ValidationResult output = val.validateWithResult(p); @@ -232,7 +225,9 @@ public class ResourceValidatorDstu3FeatureTest { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); - val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + if (ValidationConstants.SCHEMATRON_ENABLED) { + val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + } val.registerValidatorModule(new FhirInstanceValidator()); ValidationResult result = val.validateWithResult(q); @@ -273,7 +268,9 @@ public class ResourceValidatorDstu3FeatureTest { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); - val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + if (ValidationConstants.SCHEMATRON_ENABLED) { + val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + } val.registerValidatorModule(new FhirInstanceValidator()); ValidationResult result = val.validateWithResult(encoded); @@ -321,7 +318,9 @@ public class ResourceValidatorDstu3FeatureTest { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); - val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + if (ValidationConstants.SCHEMATRON_ENABLED) { + val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + } val.registerValidatorModule(new FhirInstanceValidator()); ValidationResult result = val.validateWithResult(messageString); @@ -374,7 +373,9 @@ public class ResourceValidatorDstu3FeatureTest { FhirValidator val = ourCtx.newValidator(); val.registerValidatorModule(new SchemaBaseValidator(ourCtx)); - val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + if (ValidationConstants.SCHEMATRON_ENABLED) { + val.registerValidatorModule(new SchematronBaseValidator(ourCtx)); + } val.registerValidatorModule(new FhirInstanceValidator()); ValidationResult result = val.validateWithResult(messageString); diff --git a/pom.xml b/pom.xml index 17638a99c7b..584b36a5aeb 100644 --- a/pom.xml +++ b/pom.xml @@ -51,7 +51,20 @@ jitpack.io https://jitpack.io + + false + + @@ -444,16 +457,17 @@ 4.1.4 + 1.0.10 2.6.2 1.10 2.5 3.6 - 10.14.1.0 + 10.14.2.0 2.0.18 23.0 2.8.1 2.3.0 - 2.3.0 + 2.3.0 2.25.1 9.4.8.v20171121 3.0.2 @@ -464,11 +478,12 @@ 5.7.1.Final 4.4.6 4.5.3 + 2.2.11_1 5.5.4 2.5.3 1.8 - 2.7.1 - 4.4.11 + 5.0.4 + 9.1.1 9.5.1-5_1 1.2_5 5.0.3.RELEASE @@ -572,14 +587,14 @@ 23.0 - com.phloc - phloc-schematron - ${phloc_schematron_version} + com.helger + ph-schematron + ${ph_schematron_version} - com.phloc - phloc-commons - ${phloc_commons_version} + com.helger + ph-commons + ${ph_commons_version} com.squareup.okhttp3 @@ -601,6 +616,11 @@ commons-codec ${commons_codec_version} + + org.apache.commons + commons-collections4 + 4.1 + org.apache.commons commons-compress @@ -658,16 +678,6 @@ gson ${gson_version} - - com.sun.xml.bind - jaxb-core - ${jaxb_core_version} - - - com.sun.xml.bind - jaxb-impl - ${jaxb_core_version} - javax.mail javax.mail-api @@ -952,6 +962,11 @@ javax.json 1.0.4 + + org.glassfish.jaxb + jaxb-runtime + ${jaxb_runtime_version} + org.glassfish.jersey.core jersey-server @@ -1182,6 +1197,11 @@ maven-antrun-plugin 1.8 + + org.apache.maven.plugins + maven-clean-plugin + 3.1.0 + org.apache.maven.plugins maven-compiler-plugin @@ -1251,7 +1271,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 2.19.1 + 2.20 org.apache.maven.plugins @@ -1278,7 +1298,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.20.1 + 2.21.0 true random @@ -1509,24 +1529,29 @@ org.apache.maven.plugins maven-enforcer-plugin + 1.4.1 enforce-java enforce - - - - [1.8,) - - The hapi-fhir Maven build requires JDK version 1.8 or higher. - - - - + + + + 3.5 + + + + 1.8 + + The hapi-fhir Maven build requires JDK version 1.8.x. + + + + org.codehaus.mojo diff --git a/src/changes/changes.xml b/src/changes/changes.xml index b63c37f738b..d6bf91ee1c1 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -162,6 +162,11 @@ an unspecified error was thrown by the JPA server. An HTTP 409 (Conflict) with an informative error message is now thrown. + + A bug in the JPA server's DSTU2 transaction processing routine caused it + to occasionally consume two database connections, which could lead to deadlocks + under heavy load. This has been fixed. + diff --git a/src/site/xdoc/doc_cli.xml b/src/site/xdoc/doc_cli.xml index 8eccb4afe17..fb1a811bc3f 100644 --- a/src/site/xdoc/doc_cli.xml +++ b/src/site/xdoc/doc_cli.xml @@ -1,135 +1,146 @@ - - - - - Command Line Tool - James Agnew - - - - -
-

- hapi-fhir-cli is the HAPI FHIR Command Line tool. It features a number of HAPI's - built-in features as easy to use command line options. -

- - -

- You can get the tool by downloading it from our - GitHub Releases page - (look for the archive named hapi-fhir-[version]-cli.tar.bz2 on OSX/Linux or hapi-fhir-[version]-cli.zip on Windows). -

-

- When you have downloaded the archive (either ZIP or tar.bz2), expand it into a directory - where you will keep it, and add this directory to your path. -

-

- You can now try the tool out by executing the following command: hapi-fhir-cli -

-

- This command should show a help screen, as shown in the screeenshot below. -

- Basic screen shot -
- - -

- Note on Java version support: The HAPI library is designed to - work in Java 6+, but the Command Line Tool required a minimum of Java 8. This - is because the Jetty Web Server that is used within the tool has this requirement. -

-

- The tool should work correctly on any system that has Java 8 (or newer) installed. If - it is not working correctly, first try the following command to test if Java is installed:
- $ java -version -

-

- If this command does not produce output similar to the following, you should install/reinstall - Java.
-

-

-

- If this does not help, please post a question on our - Google Group. -

-
- -
- -
-

- The CLI tool can be used to start a local, fully functional FHIR server which you can use - for testing. To start this server, simply issue the command hapi-fhir-cli run-server - as shown in the example below: -

- Run Server -

- Once the server has started, you can access the testing webpage by pointing your - browser at http://localhost:8080/. The FHIR - server base URL will be http://localhost:8080/baseDstu2/. -

-

- Note that by default this server will not be populated with any resources at all. You can - easily populate it with the FHIR example resources by leaving it running and opening - a second terminal window, then using the hapi-fhir-cli upload-examples command - (see the section below). -

-

- The server uses a local Derby database instance for storage. You may want to execute - this command in an empty directory, which you can clear if you want to reset the server. -

-
- -
-

- The upload-examples command downloads the complete set of FHIR example resources from - the HL7 website, and uploads them to a server of your choice. This can be useful to - populate a server with test data. -

-

- To execute this command, uploading test resources to a local CLI server, issue - the following: hapi-fhir-cli upload-examples -t http://localhost:8080/baseDstu2 -

-

- Note that this command may take a surprisingly long time to complete because of the - large number of examples. -

-
- -
- -

- The HAPI FHIR JPA server has a terminology server, and has the ability to - be populated with "external" code systems. These code systems are systems - that contain large numbers of codes, so the codes are not stored directly - inside the resource body. -

-

- HAPI has methods for uploading several popular code systems into its tables - using the distribution files produced by the respective code systems. This - is done using the upload-terminology command. The following - examples show how to do this for several popular code systems. -

-

- Note that the path and exact filename of the terminology files will likely - need to be adjusted for your local disk structure. -

-

- SNOMED CT -

-
./hapi-fhir-cli upload-terminology -d Downloads/SnomedCT_RF2Release_INT_20160131.zip -f dstu3 -t http://localhost:8080/baseDstu3 -u http://snomed.info/sct
- -

LOINC

-
./hapi-fhir-cli upload-terminology -d Downloads/LOINC_2.54_MULTI-AXIAL_HIERARCHY.zip -d Downloads/LOINC_2.54_Text.zip -f dstu3 -t http://localhost:8080/baseDstu3 -u http://loinc.org
- - - -
- - - -
+ + + + + Command Line Tool + James Agnew + + + + +
+

+ hapi-fhir-cli is the HAPI FHIR Command Line tool. It features a number of HAPI's + built-in features as easy to use command line options. +

+ + +

+ You can get the tool by downloading it from our + GitHub Releases page + (look for the archive named hapi-fhir-[version]-cli.tar.bz2 on OSX/Linux or hapi-fhir-[version]-cli.zip on Windows). +

+

+ When you have downloaded the archive (either ZIP or tar.bz2), expand it into a directory + where you will keep it, and add this directory to your path. +

+

+ You can now try the tool out by executing the following command: hapi-fhir-cli +

+

+ This command should show a help screen, as shown in the screeenshot below. +

+ Basic screen shot +
+ + + +

+ hapi-fhir-cli is available as a Homebrew package + for Mac. It can be installed using the following command: +

+ brew install hapi-fhir-cli + + +
+ + +

+ Note on Java version support: The HAPI library is designed to + work in Java 6+, but the Command Line Tool required a minimum of Java 8. This + is because the Jetty Web Server that is used within the tool has this requirement. +

+

+ The tool should work correctly on any system that has Java 8 (or newer) installed. If + it is not working correctly, first try the following command to test if Java is installed:
+ $ java -version +

+

+ If this command does not produce output similar to the following, you should install/reinstall + Java.
+

+

+

+ If this does not help, please post a question on our + Google Group. +

+
+ +
+ +
+

+ The CLI tool can be used to start a local, fully functional FHIR server which you can use + for testing. To start this server, simply issue the command hapi-fhir-cli run-server + as shown in the example below: +

+ Run Server +

+ Once the server has started, you can access the testing webpage by pointing your + browser at http://localhost:8080/. The FHIR + server base URL will be http://localhost:8080/baseDstu2/. +

+

+ Note that by default this server will not be populated with any resources at all. You can + easily populate it with the FHIR example resources by leaving it running and opening + a second terminal window, then using the hapi-fhir-cli upload-examples command + (see the section below). +

+

+ The server uses a local Derby database instance for storage. You may want to execute + this command in an empty directory, which you can clear if you want to reset the server. +

+
+ +
+

+ The upload-examples command downloads the complete set of FHIR example resources from + the HL7 website, and uploads them to a server of your choice. This can be useful to + populate a server with test data. +

+

+ To execute this command, uploading test resources to a local CLI server, issue + the following: hapi-fhir-cli upload-examples -t http://localhost:8080/baseDstu2 +

+

+ Note that this command may take a surprisingly long time to complete because of the + large number of examples. +

+
+ +
+ +

+ The HAPI FHIR JPA server has a terminology server, and has the ability to + be populated with "external" code systems. These code systems are systems + that contain large numbers of codes, so the codes are not stored directly + inside the resource body. +

+

+ HAPI has methods for uploading several popular code systems into its tables + using the distribution files produced by the respective code systems. This + is done using the upload-terminology command. The following + examples show how to do this for several popular code systems. +

+

+ Note that the path and exact filename of the terminology files will likely + need to be adjusted for your local disk structure. +

+

+ SNOMED CT +

+
./hapi-fhir-cli upload-terminology -d Downloads/SnomedCT_RF2Release_INT_20160131.zip -f dstu3 -t http://localhost:8080/baseDstu3 -u http://snomed.info/sct
+ +

LOINC

+
./hapi-fhir-cli upload-terminology -d Downloads/LOINC_2.54_MULTI-AXIAL_HIERARCHY.zip -d Downloads/LOINC_2.54_Text.zip -f dstu3 -t http://localhost:8080/baseDstu3 -u http://loinc.org
+ + + +
+ + + +
diff --git a/src/site/xdoc/doc_validation.xml b/src/site/xdoc/doc_validation.xml index 25b4d58fae8..0fda5a4cdab 100644 --- a/src/site/xdoc/doc_validation.xml +++ b/src/site/xdoc/doc_validation.xml @@ -1,288 +1,288 @@ - - - - - Validation - James Agnew - - - -
- -

- HAPI supportes two types of validation, both of which are described in the - sections below. -

-
    -
  • - Parser Validation - is validation at runtime during the parsing - of a resource. It can be used to catch input data that is impossible to - fit into the HAPI data model. For - example, it can be used to throw exceptions - or display error messages if a resource being parsed contains elements for which - there are no appropriate fields in a HAPI data structure. This is useful in order to ensure - that no data is being lost during parsing, but is less comprehensive than resource validation - against raw text data. -
  • -
  • - Resource Validation - is validation of the raw or parsed resource against - the official FHIR validation rules (e.g. Schema/Schematron/Profile/StructureDefinition/ValueSet) - as well as against custom profiles which have been developed. -
  • -
- -
- - -
- -

- Parser validation is controlled by calling - setParserErrorHandler(IParserErrorHandler) - on - either the FhirContext or on individual parser instances. This method - takes an - IParserErrorHandler - , which is a callback that - will be invoked any time a parse issue is detected. -

-

- There are two implementations of - IParserErrorHandler - worth - mentioning. You can also supply your own implementation if you want. -

-
    -
  • - LenientErrorHandler - logs any errors but does not abort parsing. By default this handler is used, and it - logs errors at "warning" level. It can also be configured to silently - ignore issues. -
  • -
  • - StrictErrorHandler - throws a - DataFormatException - if any errors are detected. -
  • -
- -

- The following example shows how to configure a parser to use strict validation. -

- - - - - -

- You can also configure the error handler at the FhirContext level, which is useful - for clients. -

- - - - - -

- FhirContext level validators can also be useful on servers. -

- - - - - -
- - - -
- -

- HAPI provides a built-in and configurable mechanism for validating resources. - This mechanism is called the - Resource Validator. -

-

- The resource validator is an extendible and modular system, and you - can configure it in a number of ways in order to get the specific - type of validation you want to achieve. -

- -

- The validator can be manually invoked at any time by creating a - validator and configuring it with one or more - IValidatorModule - instances. -

- - - - - - -
- -
- -

- FHIR resource definitions are distributed with a set of XML schema files (XSD) - as well as a set of XML Schematron (SCH) files. These two sets of files are - complimentary to each other, meaning that in order to claim compliance to the - FHIR specification, your resources must validate against both sets. -

-

- The two sets of files are included with HAPI, and it uses them to perform - validation. -

- - - -

- In order to use HAPI's Schematron support, a libaray called - Phloc-Schematron - is used, so this library must be added to your classpath (or Maven POM file, Gradle - file, etc.) - Note that this library is specified as an optional dependency - by HAPI FHIR - so you need to explicitly include it if you want to use this - functionality. -

-

- See - Downloads - for more information on how - to add it. -

-
- - - -

- To validate a resource instance, a new validator instance is requested - from the FHIR Context. This validator is then applied against - a specific resource - instance, as shown in the example below. -

- - - - - -
- - - -

- The following example shows how to load a set of resources from files - on disk and validate each one. -

- - - - - -
- - -
- -
- -

- HAPI also supports validation against StructureDefinition - resources. This functionality uses the HL7 "InstanceValidator", which is able - to check a resource for conformance to FHIR profiles - (StructureDefinitions, ValueSets, and CodeSystems), - including validating fields, extensions, and codes for conformance to their given ValueSets. -

-

- StructureDefinition validation can be used to validate a resource against the - official structure definitions (produced by HL7) as well as against custom - definitions provided either by HL7 or by the user. -

- -

- The instance validator is experimental in the DSTU2 mode, but has become very stable - and full-featured in DSTU3 mode. Use with caution when validating DSTU2 resources using - instance validator. -

- - - -

- To use this functionality, you must add the following two dependencies - to your classpath (or Maven POM file, Gradle file, etc.): -

-
    -
  • - - hapi-fhir-structures-hl7org-dstu2 - : This file contains the "reference implementation" - structures and tooling. You need to include it even if you are not using the RI model - (the StructureDefinition validation will work against HAPI structures as well) -
  • -
  • - hapi-fhir-validation-resources-dstu2 - : This file contains the official FHIR - StructureDefinition files, and the ValueSets needed to support them. -
  • -
-

- See the - download page - for more information. -

- - - - - -

- To execute the validator, you simply create an instance of - FhirInstanceValidator - and register it to new validator, as shown in the example below. -

- -

- Note that the example below uses the official FHIR StructureDefintions and ValueSets - to validate the resource. It will not work unless you include the - hapi-fhir-validation-resources-[version].jar to your classpath. -

- - - - - - -
- - - -

- The FhirInstanceValidator relies on the - IValidationSupport - interface to load StructureDefinitions, and validate codes. -

-

- By default, the - DefaultProfileValidationSupport - implementation is used. This implementation loads the FHIR profiles from the - validator resources JAR. If you want to use your own profiles, you may wish to - supply your own implementation. -

- - - - - - -
- -
- - - -
+ + + + + Validation + James Agnew + + + +
+ +

+ HAPI supportes two types of validation, both of which are described in the + sections below. +

+
    +
  • + Parser Validation + is validation at runtime during the parsing + of a resource. It can be used to catch input data that is impossible to + fit into the HAPI data model. For + example, it can be used to throw exceptions + or display error messages if a resource being parsed contains elements for which + there are no appropriate fields in a HAPI data structure. This is useful in order to ensure + that no data is being lost during parsing, but is less comprehensive than resource validation + against raw text data. +
  • +
  • + Resource Validation + is validation of the raw or parsed resource against + the official FHIR validation rules (e.g. Schema/Schematron/Profile/StructureDefinition/ValueSet) + as well as against custom profiles which have been developed. +
  • +
+ +
+ + +
+ +

+ Parser validation is controlled by calling + setParserErrorHandler(IParserErrorHandler) + on + either the FhirContext or on individual parser instances. This method + takes an + IParserErrorHandler + , which is a callback that + will be invoked any time a parse issue is detected. +

+

+ There are two implementations of + IParserErrorHandler + worth + mentioning. You can also supply your own implementation if you want. +

+
    +
  • + LenientErrorHandler + logs any errors but does not abort parsing. By default this handler is used, and it + logs errors at "warning" level. It can also be configured to silently + ignore issues. +
  • +
  • + StrictErrorHandler + throws a + DataFormatException + if any errors are detected. +
  • +
+ +

+ The following example shows how to configure a parser to use strict validation. +

+ + + + + +

+ You can also configure the error handler at the FhirContext level, which is useful + for clients. +

+ + + + + +

+ FhirContext level validators can also be useful on servers. +

+ + + + + +
+ + + +
+ +

+ HAPI provides a built-in and configurable mechanism for validating resources. + This mechanism is called the + Resource Validator. +

+

+ The resource validator is an extendible and modular system, and you + can configure it in a number of ways in order to get the specific + type of validation you want to achieve. +

+ +

+ The validator can be manually invoked at any time by creating a + validator and configuring it with one or more + IValidatorModule + instances. +

+ + + + + + +
+ +
+ +

+ FHIR resource definitions are distributed with a set of XML schema files (XSD) + as well as a set of XML Schematron (SCH) files. These two sets of files are + complimentary to each other, meaning that in order to claim compliance to the + FHIR specification, your resources must validate against both sets. +

+

+ The two sets of files are included with HAPI, and it uses them to perform + validation. +

+ + + +

+ In order to use HAPI's Schematron support, a libaray called + Ph-Schematron + is used, so this library must be added to your classpath (or Maven POM file, Gradle + file, etc.) + Note that this library is specified as an optional dependency + by HAPI FHIR + so you need to explicitly include it if you want to use this + functionality. +

+

+ See + Downloads + for more information on how + to add it. +

+
+ + + +

+ To validate a resource instance, a new validator instance is requested + from the FHIR Context. This validator is then applied against + a specific resource + instance, as shown in the example below. +

+ + + + + +
+ + + +

+ The following example shows how to load a set of resources from files + on disk and validate each one. +

+ + + + + +
+ + +
+ +
+ +

+ HAPI also supports validation against StructureDefinition + resources. This functionality uses the HL7 "InstanceValidator", which is able + to check a resource for conformance to FHIR profiles + (StructureDefinitions, ValueSets, and CodeSystems), + including validating fields, extensions, and codes for conformance to their given ValueSets. +

+

+ StructureDefinition validation can be used to validate a resource against the + official structure definitions (produced by HL7) as well as against custom + definitions provided either by HL7 or by the user. +

+ +

+ The instance validator is experimental in the DSTU2 mode, but has become very stable + and full-featured in DSTU3 mode. Use with caution when validating DSTU2 resources using + instance validator. +

+ + + +

+ To use this functionality, you must add the following two dependencies + to your classpath (or Maven POM file, Gradle file, etc.): +

+
    +
  • + + hapi-fhir-structures-hl7org-dstu2 + : This file contains the "reference implementation" + structures and tooling. You need to include it even if you are not using the RI model + (the StructureDefinition validation will work against HAPI structures as well) +
  • +
  • + hapi-fhir-validation-resources-dstu2 + : This file contains the official FHIR + StructureDefinition files, and the ValueSets needed to support them. +
  • +
+

+ See the + download page + for more information. +

+ + + + + +

+ To execute the validator, you simply create an instance of + FhirInstanceValidator + and register it to new validator, as shown in the example below. +

+ +

+ Note that the example below uses the official FHIR StructureDefintions and ValueSets + to validate the resource. It will not work unless you include the + hapi-fhir-validation-resources-[version].jar to your classpath. +

+ + + + + + +
+ + + +

+ The FhirInstanceValidator relies on the + IValidationSupport + interface to load StructureDefinitions, and validate codes. +

+

+ By default, the + DefaultProfileValidationSupport + implementation is used. This implementation loads the FHIR profiles from the + validator resources JAR. If you want to use your own profiles, you may wish to + supply your own implementation. +

+ + + + + + +
+ +
+ + + +
diff --git a/src/site/xdoc/download.xml.vm b/src/site/xdoc/download.xml.vm index 27bb4a04b2d..ed9fb7f20e4 100644 --- a/src/site/xdoc/download.xml.vm +++ b/src/site/xdoc/download.xml.vm @@ -541,13 +541,14 @@ System.setProperty("javax.xml.stream.XMLEventFactory", "com.ctc.wstx.stax.WstxEv - +

If you are using the Schematron Validatioon - module, you will also need to include the Phloc library on your - classpath. + module, you will also need to include the Ph-Schematron library on your + classpath. (Note that prior to HAPI FHIR 3.4.0 we used Phloc-Schamtron + instead, but that lirary has been discontinued)

If you are using Maven, this library is not added by default (it is @@ -556,16 +557,15 @@ System.setProperty("javax.xml.stream.XMLEventFactory", "com.ctc.wstx.stax.WstxEv dependencies to your project POM.xml

- com.phloc - phloc-schematron - ${phloc_schematron_version} + com.helger + ph-schematron + ${ph_schematron_version}
- com.phloc - phloc-commons - ${phloc_commons_version} + com.helger + ph-commons + ${ph_commons_version} ]]> - diff --git a/tests/hapi-fhir-base-test-mindeps-server/src/test/java/ca/uhn/fhir/testmindeps/ValidatorTest.java b/tests/hapi-fhir-base-test-mindeps-server/src/test/java/ca/uhn/fhir/testmindeps/ValidatorTest.java index 17248c1a681..387aac67e46 100644 --- a/tests/hapi-fhir-base-test-mindeps-server/src/test/java/ca/uhn/fhir/testmindeps/ValidatorTest.java +++ b/tests/hapi-fhir-base-test-mindeps-server/src/test/java/ca/uhn/fhir/testmindeps/ValidatorTest.java @@ -26,7 +26,7 @@ public class ValidatorTest { assertEquals("This parser is for FHIR version DSTU2 - Can not encode a structure for version DSTU3", e.getMessage()); } - // Phloc is not onthe classpath + // Ph-Schematron is not onthe classpath assertTrue(val.isValidateAgainstStandardSchema()); assertFalse(val.isValidateAgainstStandardSchematron()); @@ -34,7 +34,7 @@ public class ValidatorTest { val.setValidateAgainstStandardSchematron(true); fail(); } catch (IllegalArgumentException e) { - assertEquals("Phloc-schematron library not found on classpath, can not enable perform schematron validation", e.getMessage()); + assertEquals("Ph-schematron library not found on classpath, can not enable perform schematron validation", e.getMessage()); } }