Performance improvements

This commit is contained in:
jamesagnew 2014-08-27 08:55:00 -04:00
parent 794db6a141
commit bfd6329303
32 changed files with 545 additions and 144 deletions

View File

@ -6,7 +6,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.ContactSystemEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.validation.ResourceValidator;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationFailureException;
public class ValidatorExamples {
@ -24,7 +24,7 @@ public class ValidatorExamples {
p.addTelecom().setSystem(ContactSystemEnum.PHONE).setValue("416 123-4567");
// Request a validator and apply it
ResourceValidator val = ctx.newValidator();
FhirValidator val = ctx.newValidator();
try {
val.validate(p);

View File

@ -608,7 +608,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.5.3</version>
<version>3.0.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -118,6 +118,9 @@
Resource of type "List" failed to parse from a bundle correctly. Thanks to David Hay of Orion Health
for reporting!
</action>
<action type="fix">
QuantityParam correctly encodes approximate (~) prefix to values
</action>
</release>
<release version="0.5" date="2014-Jul-30">
<action type="add">

View File

@ -45,7 +45,7 @@ import ca.uhn.fhir.rest.client.RestfulClientFactory;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.validation.ResourceValidator;
import ca.uhn.fhir.validation.FhirValidator;
/**
* The FHIR context is the central starting point for the use of the HAPI FHIR API. It should be created once, and then
@ -251,8 +251,8 @@ public class FhirContext {
return new FhirTerser(this);
}
public ResourceValidator newValidator() {
return new ResourceValidator(this);
public FhirValidator newValidator() {
return new FhirValidator(this);
}
public ViewGenerator newViewGenerator() {

View File

@ -703,6 +703,9 @@ class ParserState<T> {
myStack = theState;
}
/**
* @param theData The string value
*/
public void string(String theData) {
// ignore by default
}
@ -711,6 +714,9 @@ class ParserState<T> {
// allow an implementor to override
}
/**
* @param theNextEvent The XML event
*/
public void xmlEvent(XMLEvent theNextEvent) {
// ignore
}
@ -1198,7 +1204,7 @@ class ParserState<T> {
BaseRuntimeElementDefinition<?> definition;
if (myResourceType == null) {
definition = myContext.getResourceDefinition(theLocalPart);
if (!(definition instanceof RuntimeResourceDefinition)) {
if ((definition == null)) {
throw new DataFormatException("Element '" + theLocalPart + "' is not a resource, expected a resource at this position");
}
} else {
@ -1258,16 +1264,16 @@ class ParserState<T> {
}
myContext.newTerser().visit(myInstance, new IModelVisitor() {
@Override
public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, ExtensionDt theNextExt) {
acceptElement(theNextExt.getValue(), null, null);
}
@Override
public void acceptElement(IElement theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
if (theElement instanceof ResourceReferenceDt) {
ResourceReferenceDt nextRef = (ResourceReferenceDt)theElement;
ResourceReferenceDt nextRef = (ResourceReferenceDt) theElement;
String ref = nextRef.getReference().getValue();
if (isNotBlank(ref)) {
if (ref.startsWith("#")) {
@ -1282,7 +1288,7 @@ class ParserState<T> {
}
}
});
}
}

View File

@ -948,7 +948,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
private class SortInternal implements ISort {
private static class SortInternal implements ISort {
private SearchInternal myFor;
private String myParamName;

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.rest.method;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.model.api.IQueryParameterType;
@ -30,36 +30,36 @@ import ca.uhn.fhir.rest.param.CompositeParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
class BaseBinder<T> {
private Class<? extends IQueryParameterType>[] myCompositeTypes;
private List<Class<? extends IQueryParameterType>> myCompositeTypes;
private Constructor<? extends T> myConstructor;
private final Class<? extends T> myType;
public BaseBinder(Class<? extends T> theType, Class<? extends IQueryParameterType>[] theCompositeTypes) {
public BaseBinder(Class<? extends T> theType, List<Class<? extends IQueryParameterType>> theCompositeTypes) {
myType = theType;
myCompositeTypes = theCompositeTypes;
if (myType.equals(CompositeParam.class)) {
if (myCompositeTypes.length != 2) {
throw new ConfigurationException("Search parameter of type " + myType.getName() + " must have 2 composite types declared in parameter annotation, found " + theCompositeTypes.length);
if (myCompositeTypes.size() != 2) {
throw new ConfigurationException("Search parameter of type " + myType.getName() + " must have 2 composite types declared in parameter annotation, found " + theCompositeTypes.size());
}
}
try {
Class<?>[] types = new Class<?>[myCompositeTypes.length];
for (int i = 0; i < myCompositeTypes.length; i++) {
types[i] = myCompositeTypes[i].getClass();
Class<?>[] types = new Class<?>[myCompositeTypes.size()];
for (int i = 0; i < myCompositeTypes.size(); i++) {
types[i] = myCompositeTypes.get(i).getClass();
}
myConstructor = myType.getConstructor(types);
} catch (NoSuchMethodException e) {
throw new ConfigurationException("Query parameter type " + theType.getName() + " has no constructor with types " + Arrays.asList(theCompositeTypes));
throw new ConfigurationException("Query parameter type " + theType.getName() + " has no constructor with types " + theCompositeTypes);
}
}
public T newInstance() {
try {
final Object[] args = new Object[myCompositeTypes.length];
for (int i = 0; i < myCompositeTypes.length;i++) {
args[i] = myCompositeTypes[i];//.newInstance();
final Object[] args = new Object[myCompositeTypes.size()];
for (int i = 0; i < myCompositeTypes.size();i++) {
args[i] = myCompositeTypes.get(i);//.newInstance();
}
T dt = myConstructor.newInstance(args);

View File

@ -163,7 +163,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
throw new InternalErrorException("Method " + getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName()
+ " returned null, which is not allowed for create operation");
}
if (response.getCreated() == null || response.getCreated() == Boolean.TRUE) {
if (response.getCreated() == null || Boolean.TRUE.equals(response.getCreated())) {
servletResponse.setStatus(Constants.STATUS_HTTP_201_CREATED);
} else {
servletResponse.setStatus(Constants.STATUS_HTTP_200_OK);
@ -172,7 +172,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
break;
case UPDATE:
if (response.getCreated() == null || response.getCreated() == Boolean.FALSE) {
if (response.getCreated() == null || Boolean.FALSE.equals(response.getCreated())) {
servletResponse.setStatus(Constants.STATUS_HTTP_200_OK);
} else {
servletResponse.setStatus(Constants.STATUS_HTTP_201_CREATED);

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.method;
/*
* #%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%
*/
public enum OtherOperationTypeEnum {
METADATA("metadata"),

View File

@ -31,7 +31,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
final class QueryParameterAndBinder extends BaseBinder<IQueryParameterAnd<?>> implements IParamBinder {
QueryParameterAndBinder(Class<? extends IQueryParameterAnd<?>> theType, Class<? extends IQueryParameterType>[] theCompositeTypes) {
QueryParameterAndBinder(Class<? extends IQueryParameterAnd<?>> theType, List<Class<? extends IQueryParameterType>> theCompositeTypes) {
super(theType, theCompositeTypes);
}

View File

@ -31,7 +31,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
final class QueryParameterOrBinder extends BaseBinder<IQueryParameterOr<?>> implements IParamBinder {
QueryParameterOrBinder(Class<? extends IQueryParameterOr<?>> theType, Class<? extends IQueryParameterType>[] theCompositeTypes) {
QueryParameterOrBinder(Class<? extends IQueryParameterOr<?>> theType, List<Class<? extends IQueryParameterType>> theCompositeTypes) {
super(theType, theCompositeTypes);
}

View File

@ -33,7 +33,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
final class QueryParameterTypeBinder extends BaseBinder<IQueryParameterType> implements IParamBinder {
QueryParameterTypeBinder(Class<? extends IQueryParameterType> theType, Class<? extends IQueryParameterType>[] theCompositeTypes) {
QueryParameterTypeBinder(Class<? extends IQueryParameterType> theType, List<Class<? extends IQueryParameterType>> theCompositeTypes) {
super(theType, theCompositeTypes);
}

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.method;
/*
* #%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.Map;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;

View File

@ -21,7 +21,9 @@ package ca.uhn.fhir.rest.method;
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -105,8 +107,8 @@ public class SearchParameter extends BaseQueryParameter {
ourParamTypes.put(CompositeOrListParam.class, SearchParamTypeEnum.COMPOSITE);
ourParamTypes.put(CompositeAndListParam.class, SearchParamTypeEnum.COMPOSITE);
}
private Class<? extends IQueryParameterType>[] myCompositeTypes;
private Class<? extends IResource>[] myDeclaredTypes;
private List<Class<? extends IQueryParameterType>> myCompositeTypes;
private List<Class<? extends IResource>> myDeclaredTypes;
private String myDescription;
private String myName;
private IParamBinder myParamBinder;
@ -140,8 +142,8 @@ public class SearchParameter extends BaseQueryParameter {
return retVal;
}
public Class<? extends IResource>[] getDeclaredTypes() {
return myDeclaredTypes;
public List<Class<? extends IResource>> getDeclaredTypes() {
return Collections.unmodifiableList(myDeclaredTypes);
}
public String getDescription() {
@ -188,11 +190,11 @@ public class SearchParameter extends BaseQueryParameter {
}
public void setCompositeTypes(Class<? extends IQueryParameterType>[] theCompositeTypes) {
myCompositeTypes = theCompositeTypes;
myCompositeTypes = Arrays.asList(theCompositeTypes);
}
public void setDeclaredTypes(Class<? extends IResource>[] theTypes) {
myDeclaredTypes = theTypes;
myDeclaredTypes = Arrays.asList(theTypes);
}
public void setDescription(String theDescription) {

View File

@ -184,7 +184,7 @@ public class ParameterUtil {
*/
public static String escape(String theValue) {
if (theValue == null) {
return theValue;
return null;
}
StringBuilder b = new StringBuilder();

View File

@ -22,7 +22,8 @@ package ca.uhn.fhir.rest.param;
import java.math.BigDecimal;
import java.util.List;
import static ca.uhn.fhir.rest.param.ParameterUtil.*;
import static org.apache.commons.lang3.StringUtils.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@ -163,27 +164,27 @@ public class QuantityParam extends BaseParam implements IQueryParameterType {
@Override
public String getValueAsQueryToken() {
if (super.getMissing()!=null) {
if (super.getMissing() != null) {
return super.getValueAsQueryToken();
}
StringBuilder b = new StringBuilder();
if (myQuantity.getComparator() != null) {
b.append(ParameterUtil.escape(myQuantity.getComparator().getValue()));
} else if (myApproximate) {
if (myApproximate) {
b.append('~');
} else {
b.append(defaultString(escape(myQuantity.getComparator().getValue())));
}
if (!myQuantity.getValue().isEmpty()) {
b.append(ParameterUtil.escape(myQuantity.getValue().getValueAsString()));
b.append(defaultString(escape(myQuantity.getValue().getValueAsString())));
}
b.append('|');
if (!myQuantity.getSystem().isEmpty()) {
b.append(ParameterUtil.escape(myQuantity.getSystem().getValueAsString()));
b.append(defaultString(escape(myQuantity.getSystem().getValueAsString())));
}
b.append('|');
if (!myQuantity.getUnits().isEmpty()) {
b.append(ParameterUtil.escape(myQuantity.getUnits().getValueAsString()));
b.append(defaultString(escape(myQuantity.getUnits().getValueAsString())));
}
return b.toString();
@ -195,7 +196,9 @@ public class QuantityParam extends BaseParam implements IQueryParameterType {
public void setApproximate(boolean theApproximate) {
myApproximate = theApproximate;
myQuantity.setComparator((QuantityCompararatorEnum) null);
if (theApproximate) {
myQuantity.setComparator((QuantityCompararatorEnum) null);
}
}
public QuantityParam setComparator(QuantityCompararatorEnum theComparator) {
@ -254,14 +257,14 @@ public class QuantityParam extends BaseParam implements IQueryParameterType {
clear();
super.setValueAsQueryToken(theQualifier, theValue);
if (getMissing()!=null) {
if (getMissing() != null) {
return;
}
if (theValue == null) {
return;
}
List<String> parts = ParameterUtil.splitParameterString(theValue, true);
List<String> parts = ParameterUtil.splitParameterString(theValue, '|', true);
if (parts.size() > 0 && StringUtils.isNotBlank(parts.get(0))) {
if (parts.get(0).startsWith("~")) {
@ -273,7 +276,8 @@ public class QuantityParam extends BaseParam implements IQueryParameterType {
myQuantity.setValue(new BigDecimal(parts.get(0).substring(2)));
} else if (parts.get(0).startsWith("<")) {
myQuantity.setComparator(QuantityCompararatorEnum.LESSTHAN);
myQuantity.setValue(new BigDecimal(parts.get(0).substring(1)));
String valStr = parts.get(0).substring(1);
myQuantity.setValue(new BigDecimal(valStr));
} else if (parts.get(0).startsWith(">=")) {
myQuantity.setComparator(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS);
myQuantity.setValue(new BigDecimal(parts.get(0).substring(2)));
@ -300,7 +304,7 @@ public class QuantityParam extends BaseParam implements IQueryParameterType {
b.append("value", myQuantity.getValue().getValueAsString());
b.append("system", myQuantity.getSystem().getValueAsString());
b.append("units", myQuantity.getUnits().getValueAsString());
if (getMissing()!=null) {
if (getMissing() != null) {
b.append("missing", getMissing());
}
return b.toString();

View File

@ -41,4 +41,4 @@ enum AddProfileTagEnum {
* it is an instance of a class that extends a built in type, but adds or constrains it)
*/
ONLY_FOR_CUSTOM
}
}

View File

@ -54,12 +54,12 @@ public class FifoMemoryPagingProvider implements IPagingProvider {
return myBundleProviders.get(theId);
}
public synchronized void setDefaultPageSize(int theDefaultPageSize) {
public void setDefaultPageSize(int theDefaultPageSize) {
Validate.isTrue(theDefaultPageSize > 0, "size must be greater than 0");
myDefaultPageSize = theDefaultPageSize;
}
public synchronized void setMaximumPageSize(int theMaximumPageSize) {
public void setMaximumPageSize(int theMaximumPageSize) {
Validate.isTrue(theMaximumPageSize > 0, "size must be greater than 0");
myMaximumPageSize = theMaximumPageSize;
}

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.server.interceptor;
/*
* #%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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.server.interceptor;
/*
* #%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.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map.Entry;
@ -90,7 +110,7 @@ public class LoggingInterceptor extends InterceptorAdapter {
myMessageFormat = theMessageFormat;
}
private final class MyLookup extends StrLookup<String> {
private static final class MyLookup extends StrLookup<String> {
private final HttpServletRequest myRequest;
private final RequestDetails myRequestDetails;

View File

@ -0,0 +1,52 @@
package ca.uhn.fhir.rest.server.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.Validate;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.validation.FhirValidator;
public class ResponseValidatingInterceptor extends InterceptorAdapter {
private FhirValidator myValidator;
/**
* Returns the validator used ny this interceptor
*/
public FhirValidator getValidator() {
return myValidator;
}
/**
* Sets the validator instance to use. Must not be null.
*/
public void setValidator(FhirValidator theValidator) {
Validate.notNull(theValidator, "Validator must not be null");
myValidator = theValidator;
}
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
return true;
}
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, Bundle theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
return true;
}
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, IResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
return true;
}
}

View File

@ -145,11 +145,11 @@ public class FhirTerser {
@SuppressWarnings("unchecked")
@Override
public void acceptElement(IElement theElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
if (theElement.isEmpty()) {
if (theElement == null || theElement.isEmpty()) {
return;
}
if (theElement != null && theType.isAssignableFrom(theElement.getClass())) {
if (theType.isAssignableFrom(theElement.getClass())) {
retVal.add((T) theElement);
}
}

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.validation;
/*
* #%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.Iterator;
import java.util.List;
@ -18,18 +38,18 @@ import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
* To obtain a resource validator, call {@link FhirContext#newValidator()}
* </p>
*/
public class ResourceValidator {
public class FhirValidator {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceValidator.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirValidator.class);
private FhirContext myContext;
private List<IValidator> myValidators = new ArrayList<IValidator>();
/**
* Constructor (this should not be called directly, but rather {@link FhirContext#newValidator()} should be called
* to obtain an instance of {@link ResourceValidator})
* to obtain an instance of {@link FhirValidator})
*/
public ResourceValidator(FhirContext theFhirContext) {
public FhirValidator(FhirContext theFhirContext) {
myContext = theFhirContext;
setValidateBaseSchema(true);
setValidateBaseSchematron(true);

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.validation;
/*
* #%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%
*/
interface IValidator {

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.validation;
/*
* #%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.io.InputStream;
import java.io.Reader;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.validation;
/*
* #%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%
*/
public class Problem {
private String myDescription;

View File

@ -1,11 +1,33 @@
package ca.uhn.fhir.validation;
/*
* #%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.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.transform.Source;
@ -21,74 +43,42 @@ import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome.Issue;
import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum;
class SchemaBaseValidator implements IValidator {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SchemaBaseValidator.class);
private Map<Class<? extends IResource>, Schema> myClassToSchema = new HashMap<Class<? extends IResource>, Schema>();
private Schema loadSchema(final IResource theResource, ValidationContext theValidationCtx) {
Source baseSource = loadXml(theValidationCtx, theResource, null, "fhir-single.xsd");
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
schemaFactory.setResourceResolver(new LSResourceResolver() {
@Override
public LSInput resolveResource(String theType, String theNamespaceURI, String thePublicId, String theSystemId, String theBaseURI) {
if ("xml.xsd".equals(theSystemId) || "xhtml1-strict.xsd".equals(theSystemId)) {
LSInputImpl input = new LSInputImpl();
input.setPublicId(thePublicId);
input.setSystemId(theSystemId);
input.setBaseURI(theBaseURI);
String pathToBase = theResource.getClass().getPackage().getName().replace('.', '/') + '/' + theSystemId;
InputStream baseIs = ResourceValidator.class.getClassLoader().getResourceAsStream(pathToBase);
if (baseIs == null) {
throw new ValidationFailureException("No FHIR-BASE schema found");
}
ourLog.debug("Loading schema: {}", theSystemId);
byte[] schema;
try {
schema = IOUtils.toByteArray(new InputStreamReader(baseIs, "UTF-8"));
} catch (IOException e) {
throw new ValidationFailureException("Failed to load schema " + theSystemId, e);
}
// Account for BOM in UTF-8 text (this seems to choke Java 6's built in XML reader)
int offset = 0;
if (schema[0] == (byte) 0xEF && schema[1] == (byte) 0xBB && schema[2] == (byte) 0xBF) {
offset = 3;
}
try {
input.setCharacterStream(new InputStreamReader(new ByteArrayInputStream(schema, offset, schema.length - offset), "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new ValidationFailureException("Failed to load schema " + theSystemId, e);
}
return input;
}
throw new ConfigurationException("Unknown schema: " + theBaseURI);
private Schema loadSchema(final Class<? extends IResource> theClass, ValidationContext theValidationCtx) {
synchronized (myClassToSchema) {
Schema schema = myClassToSchema.get(theClass);
if (schema != null) {
return schema;
}
});
Schema schema;
try {
schema = schemaFactory.newSchema(new Source[] { baseSource });
} catch (SAXException e) {
throw new ConfigurationException("Could not load/parse schema file", e);
Source baseSource = loadXml(theValidationCtx, theClass, null, "fhir-single.xsd");
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
schemaFactory.setResourceResolver(new MyResourceResolver(theClass));
try {
schema = schemaFactory.newSchema(new Source[] { baseSource });
} catch (SAXException e) {
throw new ConfigurationException("Could not load/parse schema file", e);
}
myClassToSchema.put(theClass, schema);
return schema;
}
return schema;
}
private Source loadXml(ValidationContext theCtx, IResource theResource, String theSystemId, String theSchemaName) {
Class<? extends IResource> baseResourceClass = theCtx.getFhirContext().getResourceDefinition(theResource).getBaseDefinition().getImplementingClass();
private Source loadXml(ValidationContext theCtx, Class<? extends IResource> theClass, String theSystemId, String theSchemaName) {
Class<? extends IResource> baseResourceClass = theCtx.getFhirContext().getResourceDefinition(theClass).getBaseDefinition().getImplementingClass();
Package pack = baseResourceClass.getPackage();
String pathToBase = pack.getName().replace('.', '/') + '/' + theSchemaName;
InputStream baseIs = ResourceValidator.class.getClassLoader().getResourceAsStream(pathToBase);
InputStream baseIs = FhirValidator.class.getClassLoader().getResourceAsStream(pathToBase);
if (baseIs == null) {
throw new ValidationFailureException("No FHIR-BASE schema found");
}
@ -98,10 +88,12 @@ class SchemaBaseValidator implements IValidator {
@Override
public void validate(ValidationContext theContext) {
Schema schema = loadSchema(theContext.getOperationOutcome(), theContext);
OperationOutcome outcome = theContext.getOperationOutcome();
Schema schema = loadSchema(outcome.getClass(), theContext);
try {
Validator validator = schema.newValidator();
ErrorHandler handler = new ErrorHandler(theContext);
MyErrorHandler handler = new MyErrorHandler(theContext);
validator.setErrorHandler(handler);
validator.validate(new StreamSource(new StringReader(theContext.getXmlEncodedResource())));
} catch (SAXException e) {
@ -112,11 +104,59 @@ class SchemaBaseValidator implements IValidator {
}
}
public class ErrorHandler implements org.xml.sax.ErrorHandler {
private static final class MyResourceResolver implements LSResourceResolver {
private final Class<? extends IResource> myClass;
private MyResourceResolver(Class<? extends IResource> theClass) {
myClass = theClass;
}
@Override
public LSInput resolveResource(String theType, String theNamespaceURI, String thePublicId, String theSystemId, String theBaseURI) {
if ("xml.xsd".equals(theSystemId) || "xhtml1-strict.xsd".equals(theSystemId)) {
LSInputImpl input = new LSInputImpl();
input.setPublicId(thePublicId);
input.setSystemId(theSystemId);
input.setBaseURI(theBaseURI);
String pathToBase = myClass.getPackage().getName().replace('.', '/') + '/' + theSystemId;
InputStream baseIs = FhirValidator.class.getClassLoader().getResourceAsStream(pathToBase);
if (baseIs == null) {
throw new ValidationFailureException("No FHIR-BASE schema found");
}
ourLog.debug("Loading schema: {}", theSystemId);
byte[] schema;
try {
schema = IOUtils.toByteArray(new InputStreamReader(baseIs, "UTF-8"));
} catch (IOException e) {
throw new ValidationFailureException("Failed to load schema " + theSystemId, e);
}
// Account for BOM in UTF-8 text (this seems to choke Java 6's built in XML reader)
int offset = 0;
if (schema[0] == (byte) 0xEF && schema[1] == (byte) 0xBB && schema[2] == (byte) 0xBF) {
offset = 3;
}
try {
input.setCharacterStream(new InputStreamReader(new ByteArrayInputStream(schema, offset, schema.length - offset), "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new ValidationFailureException("Failed to load schema " + theSystemId, e);
}
return input;
}
throw new ConfigurationException("Unknown schema: " + theBaseURI);
}
}
private static class MyErrorHandler implements org.xml.sax.ErrorHandler {
private ValidationContext myContext;
public ErrorHandler(ValidationContext theContext) {
public MyErrorHandler(ValidationContext theContext) {
myContext = theContext;
}

View File

@ -1,14 +1,35 @@
package ca.uhn.fhir.validation;
/*
* #%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.io.InputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.xml.transform.stream.StreamSource;
import org.oclc.purl.dsdl.svrl.SchematronOutputType;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome.Issue;
import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum;
@ -21,37 +42,28 @@ import com.phloc.schematron.xslt.SchematronResourceSCH;
public class SchematronBaseValidator implements IValidator {
private Map<Class<? extends IResource>, ISchematronResource> myClassToSchematron = new HashMap<Class<? extends IResource>, ISchematronResource>();
@Override
public void validate(ValidationContext theCtx) {
IResource resource = theCtx.getResource();
RuntimeResourceDefinition baseDef = theCtx.getFhirContext().getResourceDefinition(resource).getBaseDefinition();
Class<? extends IResource> baseResourceClass = baseDef.getImplementingClass();
Package pack = baseResourceClass.getPackage();
String pathToBase = pack.getName().replace('.', '/') + '/' + baseDef.getName().toLowerCase() + ".sch";
InputStream baseIs = ResourceValidator.class.getClassLoader().getResourceAsStream(pathToBase);
if (baseIs == null) {
throw new ValidationFailureException("No schematron found for resource type: " + baseDef.getImplementingClass().getCanonicalName());
}
ISchematronResource sch = SchematronResourceSCH.fromClassPath(pathToBase);
ISchematronResource sch = getSchematron(theCtx);
StreamSource source = new StreamSource(new StringReader(theCtx.getXmlEncodedResource()));
SchematronOutputType results = SchematronHelper.applySchematron(sch, source);
if (results == null) {
return;
}
IResourceErrorGroup errors = SchematronHelper.convertToResourceErrorGroup(results, baseDef.getName());
IResourceErrorGroup errors = SchematronHelper.convertToResourceErrorGroup(results, theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName());
if (errors.getAllErrors().containsOnlySuccess()) {
return;
}
for (IResourceError next : errors.getAllErrors().getAllResourceErrors()) {
Issue issue = theCtx.getOperationOutcome().addIssue();
switch(next.getErrorLevel()) {
switch (next.getErrorLevel()) {
case ERROR:
issue.setSeverity(IssueSeverityEnum.ERROR);
break;
@ -65,13 +77,37 @@ public class SchematronBaseValidator implements IValidator {
case SUCCESS:
continue;
}
issue.getDetails().setValue(next.getAsString(Locale.getDefault()));
}
}
private ISchematronResource getSchematron(ValidationContext theCtx) {
Class<? extends IResource> resource = theCtx.getResource().getClass();
Class<? extends IResource> baseResourceClass = theCtx.getFhirContext().getResourceDefinition(resource).getBaseDefinition().getImplementingClass();
return getSchematronAndCache(theCtx, baseResourceClass);
}
private ISchematronResource getSchematronAndCache(ValidationContext theCtx, Class<? extends IResource> theClass) {
synchronized (myClassToSchematron) {
ISchematronResource retVal = myClassToSchematron.get(theClass);
if (retVal != null) {
return retVal;
}
Package pack = theClass.getPackage();
String pathToBase = pack.getName().replace('.', '/') + '/' + theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getName().toLowerCase() + ".sch";
InputStream baseIs = FhirValidator.class.getClassLoader().getResourceAsStream(pathToBase);
if (baseIs == null) {
throw new ValidationFailureException("No schematron found for resource type: " + theCtx.getFhirContext().getResourceDefinition(theCtx.getResource()).getBaseDefinition().getImplementingClass().getCanonicalName());
}
retVal = SchematronResourceSCH.fromClassPath(pathToBase);
myClassToSchematron.put(theClass, retVal);
return retVal;
}
}
}

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.validation;
/*
* #%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 ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.validation;
/*
* #%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 ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum;

View File

@ -0,0 +1,58 @@
package ca.uhn.fhir.rest.param;
import static org.junit.Assert.*;
import org.junit.Test;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
public class QuantityParamTest {
@Test
public void testFull() {
QuantityParam p = new QuantityParam();
p.setValueAsQueryToken(null, "<5.4|http://unitsofmeasure.org|mg");
assertEquals(QuantityCompararatorEnum.LESSTHAN,p.getComparator());
assertEquals("5.4", p.getValue().getValueAsString());
assertEquals("http://unitsofmeasure.org", p.getSystem().getValueAsString());
assertEquals("mg", p.getUnits());
assertEquals("<5.4|http://unitsofmeasure.org|mg", p.getValueAsQueryToken());
}
@Test
public void testApproximate() {
QuantityParam p = new QuantityParam();
p.setValueAsQueryToken(null, "~5.4|http://unitsofmeasure.org|mg");
assertEquals(null,p.getComparator());
assertEquals(true, p.isApproximate());
assertEquals("5.4", p.getValue().getValueAsString());
assertEquals("http://unitsofmeasure.org", p.getSystem().getValueAsString());
assertEquals("mg", p.getUnits());
assertEquals("~5.4|http://unitsofmeasure.org|mg", p.getValueAsQueryToken());
}
@Test
public void testNoQualifier() {
QuantityParam p = new QuantityParam();
p.setValueAsQueryToken(null, "5.4|http://unitsofmeasure.org|mg");
assertEquals(null, p.getComparator());
assertEquals("5.4", p.getValue().getValueAsString());
assertEquals("http://unitsofmeasure.org", p.getSystem().getValueAsString());
assertEquals("mg", p.getUnits());
assertEquals("5.4|http://unitsofmeasure.org|mg", p.getValueAsQueryToken());
}
@Test
public void testNoUnits() {
QuantityParam p = new QuantityParam();
p.setValueAsQueryToken(null, "5.4");
assertEquals(null, p.getComparator());
assertEquals("5.4", p.getValue().getValueAsString());
assertEquals(null, p.getSystem().getValueAsString());
assertEquals(null, p.getUnits());
assertEquals("5.4||", p.getValueAsQueryToken());
}
}

View File

@ -22,7 +22,7 @@ public class ResourceValidatorTest {
String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("patient-example-dicom.xml"));
Patient p = ourCtx.newXmlParser().parseResource(Patient.class, res);
ResourceValidator val = ourCtx.newValidator();
FhirValidator val = ourCtx.newValidator();
val.setValidateBaseSchema(true);
val.setValidateBaseSchematron(false);
@ -48,7 +48,7 @@ public class ResourceValidatorTest {
String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("patient-example-dicom.xml"));
Patient p = ourCtx.newXmlParser().parseResource(Patient.class, res);
ResourceValidator val = ourCtx.newValidator();
FhirValidator val = ourCtx.newValidator();
val.setValidateBaseSchema(false);
val.setValidateBaseSchematron(true);