More work on terminology service

This commit is contained in:
jamesagnew 2016-06-01 09:10:47 -04:00
parent a2954ef181
commit 1b8843aa5e
10 changed files with 365 additions and 170 deletions

View File

@ -22,15 +22,12 @@ package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.annotation.GetPage;
import ca.uhn.fhir.util.CoverageIgnore;
public class PageProvider {
// @GetPage(dstu1=true)
// public Bundle getPageDstu1() {
// return null;
// }
@GetPage()
@CoverageIgnore
public IResource getPage() {
return null;
}

View File

@ -124,8 +124,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
private boolean myUseBrowserFriendlyContentTypes;
/**
* Constructor. Note that if no {@link FhirContext} is passed in to the server (either through the constructor, or through {@link #setFhirContext(FhirContext)}) the server will determine which
* version of FHIR to support through classpath scanning. This is brittle, and it is highly recommended to explicitly specify a FHIR version.
* Constructor. Note that if no {@link FhirContext} is passed in to the server (either through the constructor, or
* through {@link #setFhirContext(FhirContext)}) the server will determine which
* version of FHIR to support through classpath scanning. This is brittle, and it is highly recommended to explicitly
* specify a FHIR version.
*/
public RestfulServer() {
this(null);
@ -148,7 +150,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
/**
* This method is called prior to sending a response to incoming requests. It is used to add custom headers.
* <p>
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid inadvertantly disabling functionality.
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid
* inadvertantly disabling functionality.
* </p>
*/
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
@ -383,7 +386,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* @deprecated As of HAPI FHIR 1.5, this property has been moved to {@link FhirContext#setAddProfileTagWhenEncoding(AddProfileTagEnum)}
* @deprecated As of HAPI FHIR 1.5, this property has been moved to
* {@link FhirContext#setAddProfileTagWhenEncoding(AddProfileTagEnum)}
*/
@Override
@Deprecated
@ -397,7 +401,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Returns the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with the <code>_format</code> URL parameter, or with an <code>Accept</code> header
* Returns the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either
* with the <code>_format</code> URL parameter, or with an <code>Accept</code> header
* in the request. The default is {@link EncodingEnum#XML}. Will not return null.
*/
@Override
@ -411,7 +416,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* providers should generally use this context if one is needed, as opposed to
* creating their own.
*/
@Override
@ -449,7 +455,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path implementation
* Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path
* implementation
*
* @param requestFullPath
* the full request path
@ -475,7 +482,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public IServerAddressStrategy getServerAddressStrategy() {
return myServerAddressStrategy;
@ -495,7 +503,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Returns the method bindings for this server which are not specific to any particular resource type. This method is internal to HAPI and developers generally do not need to interact with it. Use
* Returns the method bindings for this server which are not specific to any particular resource type. This method is
* internal to HAPI and developers generally do not need to interact with it. Use
* with caution, as it may change.
*/
public List<BaseMethodBinding<?>> getServerBindings() {
@ -503,9 +512,11 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement if one has been explicitly defined.
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement if one has been explicitly defined.
* <p>
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or set to <code>null</code> to use the appropriate one for the given FHIR version.
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or
* set to <code>null</code> to use the appropriate one for the given FHIR version.
* </p>
*/
public Object getServerConformanceProvider() {
@ -513,7 +524,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
*
* @see RestfulServer#setServerName(String)
*/
@ -526,7 +538,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
*/
public String getServerVersion() {
return myServerVersion;
@ -696,10 +709,12 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
} catch (Throwable e) {
/*
* We have caught an exception during request processing. This might be because a handling method threw something they wanted to throw (e.g. UnprocessableEntityException because the request
* We have caught an exception during request processing. This might be because a handling method threw
* something they wanted to throw (e.g. UnprocessableEntityException because the request
* had business requirement problems) or it could be due to bugs (e.g. NullPointerException).
*
* First we let the interceptors have a crack at converting the exception into something HAPI can use (BaseServerResponseException)
* First we let the interceptors have a crack at converting the exception into something HAPI can use
* (BaseServerResponseException)
*/
BaseServerResponseException exception = null;
for (int i = getInterceptors().size() - 1; i >= 0; i--) {
@ -712,7 +727,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/*
* If none of the interceptors converted the exception, default behaviour is to keep the exception as-is if it extends BaseServerResponseException, otherwise wrap it in an
* If none of the interceptors converted the exception, default behaviour is to keep the exception as-is if it
* extends BaseServerResponseException, otherwise wrap it in an
* InternalErrorException.
*/
if (exception == null) {
@ -745,7 +761,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations, but subclasses may put initialization code in {@link #initialize()}, which is
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
* but subclasses may put initialization code in {@link #initialize()}, which is
* called immediately before beginning initialization of the restful server's internal init.
*/
@Override
@ -780,11 +797,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
// findSystemMethods(confProvider);
findResourceMethods(confProvider);
} catch (Exception ex) {
ourLog.error("An error occurred while loading request handlers!", ex);
throw new ServletException("Failed to initialize FHIR Restful server", ex);
}
ourLog.trace("Invoking provider initialize methods");
if (getResourceProviders() != null) {
for (IResourceProvider iResourceProvider : getResourceProviders()) {
@ -800,8 +812,14 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
}
try {
/*
* This is a bit odd, but we have a placeholder @GetPage method for now
* that gets the server to bind for the paging request. At some point
* it would be nice to set things up so that client code could provide
* an alternate implementation, but this isn't currently possible..
*/
findResourceMethods(new PageProvider());
} catch (Exception ex) {
ourLog.error("An error occurred while loading request handlers!", ex);
throw new ServletException("Failed to initialize FHIR Restful server", ex);
@ -815,10 +833,12 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used.
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
* server being used.
*
* @throws ServletException
* If the initialization failed. Note that you should consider throwing {@link UnavailableException} (which extends {@link ServletException}), as this is a flag to the servlet container
* If the initialization failed. Note that you should consider throwing {@link UnavailableException}
* (which extends {@link ServletException}), as this is a flag to the servlet container
* that the servlet is not usable.
*/
protected void initialize() throws ServletException {
@ -876,7 +896,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Should the server "pretty print" responses by default (requesting clients can always override this default by supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
* Should the server "pretty print" responses by default (requesting clients can always override this default by
* supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
* parameter in the request URL.
* <p>
* The default is <code>false</code>
@ -904,7 +925,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Should the server attempt to decompress incoming request contents (default is <code>true</code>). Typically this should be set to <code>true</code> unless the server has other configuration to
* Should the server attempt to decompress incoming request contents (default is <code>true</code>). Typically this
* should be set to <code>true</code> unless the server has other configuration to
* deal with decompressing request bodies (e.g. a filter applied to the whole server).
*/
public boolean isUncompressIncomingContents() {
@ -912,7 +934,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor} instead as an interceptor on your server and it will provide more useful syntax
* @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor}
* instead as an interceptor on your server and it will provide more useful syntax
* highlighting. Deprocated in 1.4
*/
@Deprecated
@ -995,7 +1018,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Register a single provider. This could be a Resource Provider or a "plain" provider not associated with any resource.
* Register a single provider. This could be a Resource Provider or a "plain" provider not associated with any
* resource.
*
* @param provider
* @throws Exception
@ -1053,8 +1077,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
if (myTypeToProvider.containsKey(resourceName)) {
throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + myTypeToProvider.get(resourceName).getClass().getCanonicalName()
+ "] and Second[" + rsrcProvider.getClass().getCanonicalName() + "]");
throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + myTypeToProvider.get(resourceName).getClass().getCanonicalName() + "] and Second[" + rsrcProvider.getClass().getCanonicalName() + "]");
}
if (!inInit) {
myResourceProviders.add(rsrcProvider);
@ -1167,12 +1190,14 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER} (which is the default), the server will automatically add a profile tag based on
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER}
* (which is the default), the server will automatically add a profile tag based on
* the class of the resource(s) being returned.
*
* @param theAddProfileTag
* The behaviour enum (must not be null)
* @deprecated As of HAPI FHIR 1.5, this property has been moved to {@link FhirContext#setAddProfileTagWhenEncoding(AddProfileTagEnum)}
* @deprecated As of HAPI FHIR 1.5, this property has been moved to
* {@link FhirContext#setAddProfileTagWhenEncoding(AddProfileTagEnum)}
*/
@Deprecated
@CoverageIgnore
@ -1192,7 +1217,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Should the server "pretty print" responses by default (requesting clients can always override this default by supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
* Should the server "pretty print" responses by default (requesting clients can always override this default by
* supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
* parameter in the request URL.
* <p>
* The default is <code>false</code>
@ -1206,10 +1232,12 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with the <code>_format</code> URL parameter, or with an <code>Accept</code> header in
* Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with
* the <code>_format</code> URL parameter, or with an <code>Accept</code> header in
* the request. The default is {@link EncodingEnum#XML}.
* <p>
* Note when testing this feature: Some browsers will include "application/xml" in their Accept header, which means that the
* Note when testing this feature: Some browsers will include "application/xml" in their Accept header, which means
* that the
* </p>
*/
public void setDefaultResponseEncoding(EncodingEnum theDefaultResponseEncoding) {
@ -1218,7 +1246,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is {@link #DEFAULT_ETAG_SUPPORT}
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is
* {@link #DEFAULT_ETAG_SUPPORT}
*
* @param theETagSupport
* The ETag support mode
@ -1340,7 +1369,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
@ -1348,15 +1378,18 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement.
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement.
* <p>
* By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be changed, or set to <code>null</code> if you do not wish to export a conformance
* By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be
* changed, or set to <code>null</code> if you do not wish to export a conformance
* statement.
* </p>
* Note that this method can only be called before the server is initialized.
*
* @throws IllegalStateException
* Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
* Note that this method can only be called prior to {@link #init() initialization} and will throw an
* {@link IllegalStateException} if called after that.
*/
public void setServerConformanceProvider(Object theServerConformanceProvider) {
if (myStarted) {
@ -1379,21 +1412,24 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
*/
public void setServerName(String theServerName) {
myServerName = theServerName;
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
*/
public void setServerVersion(String theServerVersion) {
myServerVersion = theServerVersion;
}
/**
* Should the server attempt to decompress incoming request contents (default is <code>true</code>). Typically this should be set to <code>true</code> unless the server has other configuration to
* Should the server attempt to decompress incoming request contents (default is <code>true</code>). Typically this
* should be set to <code>true</code> unless the server has other configuration to
* deal with decompressing request bodies (e.g. a filter applied to the whole server).
*/
public void setUncompressIncomingContents(boolean theUncompressIncomingContents) {
@ -1401,7 +1437,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/**
* @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor} instead as an interceptor on your server and it will provide more useful syntax
* @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor}
* instead as an interceptor on your server and it will provide more useful syntax
* highlighting. Deprocated in 1.4
*/
@Deprecated

View File

@ -25,68 +25,43 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.commons.codec.binary.StringUtils;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.HapiWorkerContext;
import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.util.LogicUtil;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3<ValueSet> implements IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> {
@Autowired
private IJpaValidationSupportDstu3 myJpaValidationSupport;
private ValidationSupportChain myValidationSupport;
private DefaultProfileValidationSupport myDefaultProfileValidationSupport;
@Qualifier("myJpaValidationSupportChainDstu3")
private IValidationSupport myValidationSupport;
@Autowired
private IFhirResourceDaoCodeSystem<CodeSystem, CodeableConcept, Coding> myCodeSystemDao;
@Override
@PostConstruct
public void postConstruct() {
super.postConstruct();
myDefaultProfileValidationSupport = new DefaultProfileValidationSupport();
myValidationSupport = new ValidationSupportChain(myDefaultProfileValidationSupport, myJpaValidationSupport);
}
@Override
@PreDestroy
public void purgeCaches() {
myDefaultProfileValidationSupport.flush();
}
@Override
public ValueSet expand(IIdType theId, String theFilter) {
ValueSet source = myValidationSupport.fetchResource(getContext(), ValueSet.class, theId.getValue());
@ -301,4 +276,9 @@ public class FhirResourceDaoValueSetDstu3 extends FhirResourceDaoDstu3<ValueSet>
return null;
}
@Override
public void purgeCaches() {
// nothing
}
}

View File

@ -119,7 +119,11 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
@TokenFilterDef(factory = LowerCaseFilterFactory.class),
}) // Def
}),
@AnalyzerDef(name = "exactAnalyzer",
tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
})
}
)
//@formatter:on

View File

@ -26,6 +26,7 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
@ -40,6 +41,7 @@ import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import org.apache.commons.lang3.Validate;
@ -47,13 +49,22 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Analyzer;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Fields;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
//@formatter:off
@Entity
@Indexed()
@Table(name="TRM_CONCEPT", uniqueConstraints= {
@UniqueConstraint(name="IDX_CONCEPT_CS_CODE", columnNames= {"CODESYSTEM_PID", "CODE"})
})
//@formatter:on
public class TermConcept implements Serializable {
private static final int MAX_DESC_LENGTH = 400;
@ -63,23 +74,46 @@ public class TermConcept implements Serializable {
private Collection<TermConceptParentChildLink> myChildren;
@Column(name="CODE", length=100, nullable=false)
@Fields({
@Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")),
})
private String myCode;
@ManyToOne()
@JoinColumn(name="CODESYSTEM_PID", referencedColumnName="PID", foreignKey=@ForeignKey(name="FK_CONCEPT_PID_CS_PID"))
private TermCodeSystemVersion myCodeSystem;
//@formatter:off
@Column(name="DISPLAY", length=MAX_DESC_LENGTH, nullable=true)
@Fields({
@Field(name = "myDisplay", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")),
@Field(name = "myDisplayEdgeNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteEdgeAnalyzer")),
@Field(name = "myDisplayNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteNGramAnalyzer")),
@Field(name = "myDisplayPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer"))
})
private String myDisplay;
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="myChild")
private Collection<TermConceptParentChildLink> myParents;
//@formatter:on
@Id()
@SequenceGenerator(name="SEQ_CONCEPT_PID", sequenceName="SEQ_CONCEPT_PID")
@GeneratedValue(strategy=GenerationType.AUTO, generator="SEQ_CONCEPT_PID")
@Column(name="PID")
private Long myPid;
private Long myId;
@Transient
@Fields({
@Field(name = "myParentPids", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "standardAnalyzer")),
})
private String myParentPids;
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="myChild")
private Collection<TermConceptParentChildLink> myParents;
@Column(name="CODESYSTEM_PID", insertable=false, updatable=false)
@Fields({
@Field(name="myCodeSystemVersionPid")
})
private long myCodeSystemVersionPid;
public TermConcept() {
super();
@ -100,6 +134,12 @@ public class TermConcept implements Serializable {
return this;
}
public void addChildren(List<TermConcept> theChildren, RelationshipTypeEnum theRelationshipType) {
for (TermConcept next : theChildren) {
addChild(next, theRelationshipType);
}
}
@Override
public boolean equals(Object theObj) {
if (!(theObj instanceof TermConcept)) {
@ -110,20 +150,15 @@ public class TermConcept implements Serializable {
}
TermConcept obj = (TermConcept)theObj;
if (obj.myPid == null) {
if (obj.myId == null) {
return false;
}
EqualsBuilder b = new EqualsBuilder();
b.append(myPid, obj.myPid);
b.append(myId, obj.myId);
return b.isEquals();
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("code", myCode).append("display", myDisplay).build();
}
public Collection<TermConceptParentChildLink> getChildren() {
if (myChildren == null) {
myChildren = new ArrayList<TermConceptParentChildLink>();
@ -143,6 +178,10 @@ public class TermConcept implements Serializable {
return myDisplay;
}
public Long getId() {
return myId;
}
public Collection<TermConceptParentChildLink> getParents() {
if (myParents == null) {
myParents = new ArrayList<TermConceptParentChildLink>();
@ -153,7 +192,7 @@ public class TermConcept implements Serializable {
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
b.append(myPid);
b.append(myId);
return b.toHashCode();
}
@ -163,6 +202,9 @@ public class TermConcept implements Serializable {
public void setCodeSystem(TermCodeSystemVersion theCodeSystem) {
myCodeSystem = theCodeSystem;
if (theCodeSystem.getPid() != null) {
myCodeSystemVersionPid = theCodeSystem.getPid();
}
}
public void setDisplay(String theDisplay) {
@ -172,10 +214,21 @@ public class TermConcept implements Serializable {
}
}
public void addChildren(List<TermConcept> theChildren, RelationshipTypeEnum theRelationshipType) {
for (TermConcept next : theChildren) {
addChild(next, theRelationshipType);
public void setParentPids(Set<Long> theParentPids) {
StringBuilder b = new StringBuilder();
for (Long next : theParentPids) {
if (b.length() > 0) {
b.append(' ');
}
b.append(next);
}
myParentPids = b.toString();
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("code", myCode).append("display", myDisplay).build();
}
}

View File

@ -28,6 +28,10 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@ -71,6 +75,9 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
@Autowired
protected FhirContext myContext;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
private void fetchChildren(TermConcept theConcept, Set<TermConcept> theSetToPopulate) {
for (TermConceptParentChildLink nextChildLink : theConcept.getChildren()) {
TermConcept nextChild = nextChildLink.getChild();
@ -170,7 +177,7 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
return retVal;
}
private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack) {
private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack, HashSet<Long> thePidsInHierarchy) {
if (theConceptsStack.put(theConcept, PLACEHOLDER_OBJECT) != null) {
return;
}
@ -179,23 +186,30 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
ourLog.info("Have saved {} concepts", theConceptsStack.size());
}
for (TermConceptParentChildLink next : theConcept.getChildren()) {
persistChildren(next.getChild(), theCodeSystem, theConceptsStack);
}
theConcept.setParentPids(thePidsInHierarchy);
theConcept.setCodeSystem(theCodeSystem);
myConceptDao.save(theConcept);
TermConcept flushedConcept = myConceptDao.saveAndFlush(theConcept);
thePidsInHierarchy.add(flushedConcept.getId());
try {
for (TermConceptParentChildLink next : theConcept.getChildren()) {
persistChildren(next.getChild(), theCodeSystem, theConceptsStack, thePidsInHierarchy);
}
for (TermConceptParentChildLink next : theConcept.getChildren()) {
myConceptParentChildLinkDao.save(next);
}
} finally {
thePidsInHierarchy.remove(flushedConcept.getId());
}
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void storeNewCodeSystemVersion(Long theCodeSystemResourcePid, String theSystemUri, TermCodeSystemVersion theCodeSystem) {
public void storeNewCodeSystemVersion(Long theCodeSystemResourcePid, String theSystemUri, TermCodeSystemVersion theCodeSystemVersion) {
ourLog.info("Storing code system");
ValidateUtil.isNotNullOrThrowInvalidRequest(theCodeSystem.getResource() != null, "No resource supplied");
ValidateUtil.isNotNullOrThrowInvalidRequest(theCodeSystemVersion.getResource() != null, "No resource supplied");
ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystemUri, "No system URI supplied");
// Grab the existing versions so we can delete them later
@ -207,11 +221,11 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
if (codeSystem == null) {
codeSystem = new TermCodeSystem();
}
codeSystem.setResource(theCodeSystem.getResource());
codeSystem.setResource(theCodeSystemVersion.getResource());
codeSystem.setCodeSystemUri(theSystemUri);
myCodeSystemDao.save(codeSystem);
} else {
if (!ObjectUtil.equals(codeSystem.getResource().getId(), theCodeSystem.getResource().getId())) {
if (!ObjectUtil.equals(codeSystem.getResource().getId(), theCodeSystemVersion.getResource().getId())) {
String msg = myContext.getLocalizer().getMessage(BaseHapiTerminologySvc.class, "cannotCreateDuplicateCodeSystemUri", theSystemUri, codeSystem.getResource().getIdDt().toUnqualifiedVersionless().getValue());
throw new UnprocessableEntityException(msg);
}
@ -222,24 +236,24 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
// Validate the code system
IdentityHashMap<TermConcept, Object> conceptsStack = new IdentityHashMap<TermConcept, Object>();
int totalCodeCount = 0;
for (TermConcept next : theCodeSystem.getConcepts()) {
totalCodeCount += validateConceptForStorage(next, theCodeSystem, conceptsStack);
for (TermConcept next : theCodeSystemVersion.getConcepts()) {
totalCodeCount += validateConceptForStorage(next, theCodeSystemVersion, conceptsStack);
}
ourLog.info("Saving version");
myCodeSystemVersionDao.save(theCodeSystem);
TermCodeSystemVersion codeSystemVersion = myCodeSystemVersionDao.saveAndFlush(theCodeSystemVersion);
ourLog.info("Saving code system");
codeSystem.setCurrentVersion(theCodeSystem);
myCodeSystemDao.save(codeSystem);
codeSystem.setCurrentVersion(theCodeSystemVersion);
codeSystem = myCodeSystemDao.saveAndFlush(codeSystem);
ourLog.info("Saving {} concepts...", totalCodeCount);
conceptsStack = new IdentityHashMap<TermConcept, Object>();
for (TermConcept next : theCodeSystem.getConcepts()) {
persistChildren(next, theCodeSystem, conceptsStack);
for (TermConcept next : theCodeSystemVersion.getConcepts()) {
persistChildren(next, codeSystemVersion, conceptsStack, new HashSet<Long>());
}
/*
@ -280,7 +294,6 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
ValidateUtil.isNotNullOrThrowInvalidRequest(theConcept.getCodeSystem() == theCodeSystem, "Codesystem contains a code which does not reference the codesystem");
ValidateUtil.isNotBlankOrThrowInvalidRequest(theConcept.getCode(), "Codesystem contains a code which does not reference the codesystem");
if (theConceptsStack.put(theConcept, PLACEHOLDER_OBJECT) != null) {
throw new InvalidRequestException("CodeSystem contains circular reference around code " + theConcept.getCode());
}
@ -306,5 +319,4 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
return myConceptDao.findByCodeSystemAndCode(csv, theCode);
}
}

View File

@ -24,8 +24,18 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BooleanQuery.Builder;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.Query;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode;
@ -38,6 +48,7 @@ import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpander;
@ -54,6 +65,7 @@ import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.UrlUtil;
@ -109,24 +121,50 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvc implements I
TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(system);
TermCodeSystemVersion csv = cs.getCurrentVersion();
ValueSetExpansionComponent retVal = new ValueSetExpansionComponent();
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);
QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(TermConcept.class).get();
BooleanJunction<?> bool = qb.bool();
boolean haveSpecificWantedCode = false;
bool.must(qb.keyword().onField("myCodeSystemVersionPid").matching(csv.getPid()).createQuery());
Set<String> wantCodes = new HashSet<String>();
for (ConceptReferenceComponent next : theInclude.getConcept()) {
String nextCode = next.getCode();
if (isNotBlank(nextCode)) {
haveSpecificWantedCode = true;
TermConcept termCode = myConceptDao.findByCodeSystemAndCode(csv, nextCode);
if (termCode != null) {
addCodeIfFilterMatches(retVal, termCode, theInclude.getFilter(), nextCode);
}
wantCodes.add(nextCode);
bool.should(qb.keyword().onField("myCode").matching(nextCode).createQuery());
}
}
if (!haveSpecificWantedCode) {
for(TermConcept next : myConceptDao.findByCodeSystemVersion(csv)) {
addCodeIfFilterMatches(retVal, next, theInclude.getFilter(), system);
for (ConceptSetFilterComponent nextFilter : theInclude.getFilter()) {
if (nextFilter.getProperty().equals("display") && nextFilter.getOp() == FilterOperator.EQUAL) {
if (isNotBlank(nextFilter.getValue())) {
bool.must(qb.phrase().onField("myDisplay").sentence(nextFilter.getValue()).createQuery());
}
} else if (nextFilter.getOp() == FilterOperator.ISA) {
if (isNotBlank(nextFilter.getValue())) {
TermConcept code = super.findCode(system, nextFilter.getValue());
bool.must(qb.keyword().onField("myParentPids").matching(code.getId()).createQuery());
}
} else {
throw new InvalidRequestException("Unknown filter property[" + nextFilter + "] + op[" + nextFilter.getOpElement().getValueAsString() + "]");
}
}
ValueSetExpansionComponent retVal = new ValueSetExpansionComponent();
Query luceneQuery = bool.createQuery();
FullTextQuery jpaQuery = em.createFullTextQuery(luceneQuery, TermConcept.class);
@SuppressWarnings("unchecked")
List<TermConcept> result = jpaQuery.getResultList();
for (TermConcept nextConcept : result) {
if (!wantCodes.isEmpty() && !wantCodes.contains(nextConcept.getCode())) {
continue;
}
ValueSetExpansionContainsComponent contains = retVal.addContains();
contains.setCode(nextConcept.getCode());
contains.setSystem(system);
contains.setDisplay(nextConcept.getDisplay());
}
return retVal;

View File

@ -6,7 +6,10 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.hl7.fhir.dstu3.model.AuditEvent;
import org.hl7.fhir.dstu3.model.CodeSystem;
@ -15,6 +18,9 @@ import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.FilterOperator;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Before;
@ -264,6 +270,74 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
}
}
@Test
public void testExpandWithSystemAndCodesInLocalValueSet() {
createLocalCsAndVs();
ValueSet vs = new ValueSet();
ConceptSetComponent include = vs.getCompose().addInclude();
include.setSystem(URL_MY_CODE_SYSTEM);
include.addConcept().setCode("A");
include.addConcept().setCode("AA");
include.addConcept().setCode("AAA");
include.addConcept().setCode("AB");
ValueSet result = myValueSetDao.expand(vs, null);
String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result);
ourLog.info(encoded);
ArrayList<String> codes = toCodesContains(result.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("A", "AA", "AAA", "AB"));
int idx = codes.indexOf("AAA");
assertEquals("AAA", result.getExpansion().getContains().get(idx).getCode());
assertEquals("Code AAA", result.getExpansion().getContains().get(idx).getDisplay());
assertEquals(URL_MY_CODE_SYSTEM, result.getExpansion().getContains().get(idx).getSystem());
// ValueSet expansion = myValueSetDao.expandByIdentifier(URL_MY_VALUE_SET, "cervical");
// ValueSet expansion = myValueSetDao.expandByIdentifier(URL_MY_VALUE_SET, "cervical");
//
}
@Test
public void testExpandWithSystemAndCodesAndFilterInLocalValueSet() {
createLocalCsAndVs();
ValueSet vs = new ValueSet();
ConceptSetComponent include = vs.getCompose().addInclude();
include.setSystem(URL_MY_CODE_SYSTEM);
include.addConcept().setCode("A");
include.addConcept().setCode("AA");
include.addConcept().setCode("AAA");
include.addConcept().setCode("AB");
include.addFilter().setProperty("display").setOp(FilterOperator.EQUAL).setValue("Code AAA");
ValueSet result = myValueSetDao.expand(vs, null);
String encoded = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result);
ourLog.info(encoded);
ArrayList<String> codes = toCodesContains(result.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("AAA"));
assertEquals("AAA", result.getExpansion().getContains().get(0).getCode());
assertEquals("Code AAA", result.getExpansion().getContains().get(0).getDisplay());
assertEquals(URL_MY_CODE_SYSTEM, result.getExpansion().getContains().get(0).getSystem());
// ValueSet expansion = myValueSetDao.expandByIdentifier(URL_MY_VALUE_SET, "cervical");
// ValueSet expansion = myValueSetDao.expandByIdentifier(URL_MY_VALUE_SET, "cervical");
//
}
private ArrayList<String> toCodesContains(List<ValueSetExpansionContainsComponent> theContains) {
ArrayList<String> retVal = new ArrayList<String>();
for (ValueSetExpansionContainsComponent next : theContains) {
retVal.add(next.getCode());
}
return retVal;
}
@Test
public void testSearchCodeAboveLocalCodesystem() {
createLocalCsAndVs();