From d170c1e40831073d8f4529bb8eb8abe60c166434 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Fri, 24 Oct 2014 16:10:54 -0400 Subject: [PATCH] More work on DEV --- .../java/ca/uhn/fhir/context/FhirContext.java | 3 +- .../ca/uhn/fhir/context/FhirVersionEnum.java | 1 - .../java/ca/uhn/fhir/model/dstu/FhirDev.java | 44 +++ .../provider/ServerConformanceProvider.java | 359 ++++++++++++++++++ .../provider/ServerProfileProvider.java | 79 ++++ 5 files changed, 483 insertions(+), 3 deletions(-) create mode 100644 hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dstu/FhirDev.java create mode 100644 hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java create mode 100644 hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/rest/server/provider/ServerProfileProvider.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java index a3192a190b5..6b92adc15c5 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java @@ -34,7 +34,6 @@ import ca.uhn.fhir.i18n.HapiLocalizer; import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IFhirVersion; import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu.valueset.FHIRDefinedTypeEnum; import ca.uhn.fhir.model.view.ViewGenerator; import ca.uhn.fhir.narrative.INarrativeGenerator; import ca.uhn.fhir.parser.DataFormatException; @@ -97,7 +96,7 @@ public class FhirContext { if (FhirVersionEnum.DSTU1.isPresentOnClasspath()) { myVersion = FhirVersionEnum.DSTU1.getVersionImplementation(); } else { - throw new IllegalStateException("Could not find any HAPI-FHIR structure JARs on the classpath. Note that as of HAPI version 0.7, a separate FHIR strcture JAR must be added to your classpath or project pom.xml"); + throw new IllegalStateException("Could not find any HAPI-FHIR structure JARs on the classpath. Note that as of HAPI-FHIR v0.8, a separate FHIR strcture JAR must be added to your classpath or project pom.xml"); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java index eeb9b1204f4..d5a3f5f2591 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java @@ -2,7 +2,6 @@ package ca.uhn.fhir.context; import ca.uhn.fhir.model.api.IFhirVersion; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.validation.FhirValidator; public enum FhirVersionEnum { diff --git a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dstu/FhirDev.java b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dstu/FhirDev.java new file mode 100644 index 00000000000..51856c58c5d --- /dev/null +++ b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/model/dstu/FhirDev.java @@ -0,0 +1,44 @@ +package ca.uhn.fhir.model.dstu; + +import org.apache.commons.lang3.StringUtils; + +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.model.api.IFhirVersion; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dev.resource.Profile; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.provider.ServerConformanceProvider; +import ca.uhn.fhir.rest.server.provider.ServerProfileProvider; + +public class FhirDev implements IFhirVersion { + + private String myId; + + @Override + public Object createServerConformanceProvider(RestfulServer theServer) { + return new ServerConformanceProvider(theServer); + } + + @Override + public IResource generateProfile(RuntimeResourceDefinition theRuntimeResourceDefinition) { + Profile retVal = new Profile(); + + RuntimeResourceDefinition def = theRuntimeResourceDefinition; + + myId = def.getId(); + if (StringUtils.isBlank(myId)) { + myId = theRuntimeResourceDefinition.getName().toLowerCase(); + } + + retVal.setId(new IdDt(myId)); + return retVal; + } + + @Override + public IResourceProvider createServerProfilesProvider(RestfulServer theRestfulServer) { + return new ServerProfileProvider(theRestfulServer.getFhirContext()); + } + +} diff --git a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java new file mode 100644 index 00000000000..16800bdf8b9 --- /dev/null +++ b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java @@ -0,0 +1,359 @@ +package ca.uhn.fhir.rest.server.provider; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.lang3.StringUtils; + +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dev.resource.Conformance; +import ca.uhn.fhir.model.dev.resource.OperationDefinition; +import ca.uhn.fhir.model.dev.resource.Conformance.Rest; +import ca.uhn.fhir.model.dev.resource.Conformance.RestOperation; +import ca.uhn.fhir.model.dev.resource.Conformance.RestResource; +import ca.uhn.fhir.model.dev.resource.Conformance.RestResourceSearchParam; +import ca.uhn.fhir.model.dev.resource.OperationDefinition.Parameter; +import ca.uhn.fhir.model.dev.valueset.RestfulConformanceModeEnum; +import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; +import ca.uhn.fhir.model.primitive.BooleanDt; +import ca.uhn.fhir.model.primitive.CodeDt; +import ca.uhn.fhir.model.primitive.DateTimeDt; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.annotation.Metadata; +import ca.uhn.fhir.rest.method.BaseMethodBinding; +import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding; +import ca.uhn.fhir.rest.method.IParameter; +import ca.uhn.fhir.rest.method.SearchMethodBinding; +import ca.uhn.fhir.rest.method.SearchParameter; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.ResourceBinding; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.util.ExtensionConstants; + +/** + * Server FHIR Provider which serves the conformance statement for a RESTful server implementation + * + *

+ * Note: This class is safe to extend, but it is important to note that the same instance of {@link Conformance} is + * always returned unless {@link #setCache(boolean)} is called with a value of false. This means that if + * you are adding anything to the returned conformance instance on each call you should call + * setCache(false) in your provider constructor. + *

+ */ +public class ServerConformanceProvider { + + private boolean myCache = true; + private volatile Conformance myConformance; + private String myPublisher = "Not provided"; + private final RestfulServer myRestfulServer; + + public ServerConformanceProvider(RestfulServer theRestfulServer) { + myRestfulServer = theRestfulServer; + } + + /** + * Gets the value of the "publisher" that will be placed in the generated conformance statement. As this + * is a mandatory element, the value should not be null (although this is not enforced). The value defaults + * to "Not provided" but may be set to null, which will cause this element to be omitted. + */ + public String getPublisher() { + return myPublisher; + } + + /** + * Actually create and return the conformance statement + * + * See the class documentation for an important note if you are extending this class + */ + @Metadata + public Conformance getServerConformance() { + if (myConformance != null && myCache) { + return myConformance; + } + + Conformance retVal = new Conformance(); + + retVal.setPublisher(myPublisher); + retVal.setDate(DateTimeDt.withCurrentTime()); + retVal.setFhirVersion("0.80"); // TODO: pull from model + retVal.setAcceptUnknown(false); // TODO: make this configurable - this is a fairly big effort since the parser needs to be modified to actually allow it + + retVal.getImplementation().setDescription(myRestfulServer.getImplementationDescription()); + retVal.getSoftware().setName(myRestfulServer.getServerName()); + retVal.getSoftware().setVersion(myRestfulServer.getServerVersion()); + retVal.addFormat(Constants.CT_FHIR_XML); + retVal.addFormat(Constants.CT_FHIR_JSON); + + Rest rest = retVal.addRest(); + rest.setMode(RestfulConformanceModeEnum.SERVER); + +// Set systemOps = new HashSet(); +// +// List bindings = new ArrayList(myRestfulServer.getResourceBindings()); +// Collections.sort(bindings, new Comparator() { +// @Override +// public int compare(ResourceBinding theArg0, ResourceBinding theArg1) { +// return theArg0.getResourceName().compareToIgnoreCase(theArg1.getResourceName()); +// } +// }); +// +// for (ResourceBinding next : bindings) { +// +// Set resourceOps = new HashSet(); +// RestResource resource = rest.addResource(); +// +// String resourceName = next.getResourceName(); +// RuntimeResourceDefinition def = myRestfulServer.getFhirContext().getResourceDefinition(resourceName); +// resource.getType().setValue(def.getName()); +// resource.getProfile().setReference(new IdDt(def.getResourceProfile())); +// +// TreeSet includes = new TreeSet(); +// +// // Map nameToSearchParam = new HashMap(); +// for (BaseMethodBinding nextMethodBinding : next.getMethodBindings()) { +// RestfulOperationTypeEnum resOp = nextMethodBinding.getResourceOperationType(); +// if (resOp != null) { +// if (resourceOps.contains(resOp) == false) { +// resourceOps.add(resOp); +// resource.addOperation().setCode(resOp); +// } +// } +// +// RestfulOperationSystemEnum sysOp = nextMethodBinding.getSystemOperationType(); +// if (sysOp != null) { +// if (systemOps.contains(sysOp) == false) { +// systemOps.add(sysOp); +// rest.addOperation().setCode(sysOp); +// } +// } +// +// if (nextMethodBinding instanceof SearchMethodBinding) { +// handleSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding); +// } else if (nextMethodBinding instanceof DynamicSearchMethodBinding) { +// handleDynamicSearchMethodBinding(resource, def, includes, (DynamicSearchMethodBinding) nextMethodBinding); +// } +// +// Collections.sort(resource.getOperation(), new Comparator() { +// @Override +// public int compare(RestResourceOperation theO1, RestResourceOperation theO2) { +// RestfulOperationTypeEnum o1 = theO1.getCode().getValueAsEnum(); +// RestfulOperationTypeEnum o2 = theO2.getCode().getValueAsEnum(); +// if (o1 == null && o2 == null) { +// return 0; +// } +// if (o1 == null) { +// return 1; +// } +// if (o2 == null) { +// return -1; +// } +// return o1.ordinal() - o2.ordinal(); +// } +// }); +// +// } +// +// for (String nextInclude : includes) { +// resource.addSearchInclude(nextInclude); +// } +// +// } + + myConformance = retVal; + return retVal; + } + + private void handleDynamicSearchMethodBinding(RestResource resource, RuntimeResourceDefinition def, TreeSet includes, DynamicSearchMethodBinding searchMethodBinding) { + includes.addAll(searchMethodBinding.getIncludes()); + + List searchParameters = new ArrayList(); + searchParameters.addAll(searchMethodBinding.getSearchParams()); + sortRuntimeSearchParameters(searchParameters); + + if (!searchParameters.isEmpty()) { + + for (RuntimeSearchParam nextParameter : searchParameters) { + + String nextParamName = nextParameter.getName(); + + // String chain = null; + String nextParamUnchainedName = nextParamName; + if (nextParamName.contains(".")) { + // chain = nextParamName.substring(nextParamName.indexOf('.') + 1); + nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.')); + } + + String nextParamDescription = nextParameter.getDescription(); + + /* + * If the parameter has no description, default to the one from the resource + */ + if (StringUtils.isBlank(nextParamDescription)) { + RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName); + if (paramDef != null) { + nextParamDescription = paramDef.getDescription(); + } + } + + RestResourceSearchParam param; + param = resource.addSearchParam(); + + param.setName(nextParamName); + // if (StringUtils.isNotBlank(chain)) { + // param.addChain(chain); + // } + param.setDocumentation(nextParamDescription); +// param.setType(nextParameter.getParamType()); + } + } + } + + private void handleSearchMethodBinding(Rest rest, RestResource resource, String resourceName, RuntimeResourceDefinition def, TreeSet includes, SearchMethodBinding searchMethodBinding) { + includes.addAll(searchMethodBinding.getIncludes()); + + List params = searchMethodBinding.getParameters(); + List searchParameters = new ArrayList(); + for (IParameter nextParameter : params) { + if ((nextParameter instanceof SearchParameter)) { + searchParameters.add((SearchParameter) nextParameter); + } + } + sortSearchParameters(searchParameters); + if (!searchParameters.isEmpty()) { + boolean allOptional = searchParameters.get(0).isRequired() == false; + + OperationDefinition query = null; + if (!allOptional) { + RestOperation operation = rest.addOperation(); + query = new OperationDefinition(); + operation.setDefinition(new ResourceReferenceDt(query)); + query.getDescription().setValue(searchMethodBinding.getDescription()); + query.addUndeclaredExtension(false, ExtensionConstants.QUERY_RETURN_TYPE, new CodeDt(resourceName)); + for (String nextInclude : searchMethodBinding.getIncludes()) { + query.addUndeclaredExtension(false, ExtensionConstants.QUERY_ALLOWED_INCLUDE, new StringDt(nextInclude)); + } + } + + for (SearchParameter nextParameter : searchParameters) { + + String nextParamName = nextParameter.getName(); + + // String chain = null; + String nextParamUnchainedName = nextParamName; + if (nextParamName.contains(".")) { + // chain = nextParamName.substring(nextParamName.indexOf('.') + 1); + nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.')); + } + + String nextParamDescription = nextParameter.getDescription(); + + /* + * If the parameter has no description, default to the one from the resource + */ + if (StringUtils.isBlank(nextParamDescription)) { + RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName); + if (paramDef != null) { + nextParamDescription = paramDef.getDescription(); + } + } + + Parameter param; + if (query == null) { +// param = resource.addSearchParam(); + } else { + param = query.addParameter(); + param.addUndeclaredExtension(false, ExtensionConstants.PARAM_IS_REQUIRED, new BooleanDt(nextParameter.isRequired())); + } + +// param.setName(nextParamName); + // if (StringUtils.isNotBlank(chain)) { + // param.addChain(chain); + // } +// param.setDocumentation(nextParamDescription); +// param.setType(nextParameter.getParamType()); + for (Class nextTarget : nextParameter.getDeclaredTypes()) { + RuntimeResourceDefinition targetDef = myRestfulServer.getFhirContext().getResourceDefinition(nextTarget); + if (targetDef != null) { +// ResourceTypeEnum code = ResourceTypeEnum.VALUESET_BINDER.fromCodeString(targetDef.getName()); +// if (code != null) { +// param.addTarget(code); +// } + } + } + } + } + } + + /** + * Sets the cache property (default is true). If set to true, the same response will be returned for each + * invocation. + *

+ * See the class documentation for an important note if you are extending this class + *

+ */ + public void setCache(boolean theCache) { + myCache = theCache; + } + + /** + * Sets the value of the "publisher" that will be placed in the generated conformance statement. As this + * is a mandatory element, the value should not be null (although this is not enforced). The value defaults + * to "Not provided" but may be set to null, which will cause this element to be omitted. + */ + public void setPublisher(String thePublisher) { + myPublisher = thePublisher; + } + + private void sortRuntimeSearchParameters(List searchParameters) { + Collections.sort(searchParameters, new Comparator() { + @Override + public int compare(RuntimeSearchParam theO1, RuntimeSearchParam theO2) { + return theO1.getName().compareTo(theO2.getName()); + } + }); + } + + private void sortSearchParameters(List searchParameters) { + Collections.sort(searchParameters, new Comparator() { + @Override + public int compare(SearchParameter theO1, SearchParameter theO2) { + if (theO1.isRequired() == theO2.isRequired()) { + return theO1.getName().compareTo(theO2.getName()); + } + if (theO1.isRequired()) { + return -1; + } + return 1; + } + }); + } +} diff --git a/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/rest/server/provider/ServerProfileProvider.java b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/rest/server/provider/ServerProfileProvider.java new file mode 100644 index 00000000000..2db9bb386e0 --- /dev/null +++ b/hapi-fhir-structures-dev/src/main/java/ca/uhn/fhir/rest/server/provider/ServerProfileProvider.java @@ -0,0 +1,79 @@ +package ca.uhn.fhir.rest.server.provider; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dev.resource.Profile; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.server.IResourceProvider; + +public class ServerProfileProvider implements IResourceProvider { + + private FhirContext myContext; + + public ServerProfileProvider(FhirContext theCtx) { + myContext = theCtx; + } + + @Override + public Class getResourceType() { + return Profile.class; + } + + @Read() + public Profile getProfileById(@IdParam IdDt theId) { + RuntimeResourceDefinition retVal = myContext.getResourceDefinitionById(theId.getValue()); + if (retVal==null) { + return null; + } + return (Profile) retVal.toProfile(); + } + + @Search() + public List getAllProfiles() { + List defs = new ArrayList(myContext.getResourceDefinitions()); + Collections.sort(defs, new Comparator() { + @Override + public int compare(RuntimeResourceDefinition theO1, RuntimeResourceDefinition theO2) { + int cmp = theO1.getName().compareTo(theO2.getName()); + if (cmp==0) { + cmp=theO1.getResourceProfile().compareTo(theO2.getResourceProfile()); + } + return cmp; + }}); + ArrayList retVal = new ArrayList(); + for (RuntimeResourceDefinition next : defs) { + retVal.add((Profile) next.toProfile()); + } + return retVal; + } + +}