Add interceptor framework

This commit is contained in:
James Agnew 2014-07-18 17:49:14 -04:00
parent 2285d3812e
commit d15dbd4317
43 changed files with 1633 additions and 475 deletions

View File

@ -6,6 +6,7 @@
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin" path="src/site/example/java"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>

View File

@ -2,5 +2,6 @@
<wb-module deploy-name="hapi-fhir-base">
<wb-resource deploy-path="/" source-path="/src/main/java"/>
<wb-resource deploy-path="/" source-path="/src/main/resources"/>
<wb-resource deploy-path="/" source-path="/src/site/example/java"/>
</wb-module>
</project-modules>

View File

@ -76,7 +76,7 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.6</version>
<version>${slf4j_version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>

View File

@ -14,6 +14,19 @@
Search parameters are not properly escaped and unescaped. E.g. for a token parameter such as
"&amp;identifier=system|codepart1\|codepart2"
</action>
<action type="add">
Add support for OPTIONS verb (which returns the server conformance statement)
</action>
<action type="add">
Add support for CORS headers in server
</action>
<action type="add">
Bump SLF4j dependency to latest version (1.7.7)
</action>
<action type="add">
Add interceptor framework for clients (annotation based and generic), and add interceptors
for configurable logging, capturing requests and responses, and HTTP basic auth.
</action>
</release>
<release version="0.4" date="2014-Jul-13">
<action type="add">

View File

@ -127,7 +127,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
*/
@Override
protected boolean isBaseEmpty() {
return super.isBaseEmpty() && ElementUtil.isEmpty(myLanguage, myText);
return super.isBaseEmpty() && ElementUtil.isEmpty(myLanguage, myText, myId);
}
}

View File

@ -179,7 +179,8 @@ public class JsonParser extends BaseParser implements IParser {
for (BundleEntry nextEntry : theBundle.getEntries()) {
eventWriter.writeStartObject();
if (nextEntry.getDeletedAt() !=null&&nextEntry.getDeletedAt().isEmpty()==false) {
boolean deleted = nextEntry.getDeletedAt() !=null&&nextEntry.getDeletedAt().isEmpty()==false;
if (deleted) {
writeTagWithTextNode(eventWriter, "deleted", nextEntry.getDeletedAt());
}
writeTagWithTextNode(eventWriter, "title", nextEntry.getTitle());
@ -210,7 +211,7 @@ public class JsonParser extends BaseParser implements IParser {
writeAuthor(nextEntry, eventWriter);
IResource resource = nextEntry.getResource();
if (resource != null && !resource.isEmpty()) {
if (resource != null && !resource.isEmpty() && !deleted) {
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(resource);
encodeResourceToJsonStreamWriter(resDef, resource, eventWriter, "content", false);
}

View File

@ -199,7 +199,7 @@ public class XmlParser extends BaseParser implements IParser {
}
IResource resource = nextEntry.getResource();
if (resource != null && !resource.isEmpty()) {
if (resource != null && !resource.isEmpty() && !deleted) {
eventWriter.writeStartElement("content");
eventWriter.writeAttribute("type", "text/xml");
encodeResourceToXmlStreamWriter(resource, eventWriter, false);

View File

@ -27,6 +27,7 @@ import java.util.Map;
import org.apache.http.client.HttpClient;
import ch.qos.logback.core.pattern.util.RegularEscapeUtil;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
@ -51,6 +52,8 @@ public class ClientInvocationHandler extends BaseClient implements InvocationHan
myMethodToLambda.put(theClientType.getMethod("setEncoding", EncodingEnum.class), new SetEncodingLambda());
myMethodToLambda.put(theClientType.getMethod("setPrettyPrint", boolean.class), new SetPrettyPrintLambda());
myMethodToLambda.put(theClientType.getMethod("registerInterceptor", IClientInterceptor.class), new RegisterInterceptorLambda());
myMethodToLambda.put(theClientType.getMethod("unregisterInterceptor", IClientInterceptor.class), new UnregisterInterceptorLambda());
} catch (NoSuchMethodException e) {
throw new ConfigurationException("Failed to find methods on client. This is a HAPI bug!", e);
@ -105,5 +108,22 @@ public class ClientInvocationHandler extends BaseClient implements InvocationHan
return null;
}
}
private class UnregisterInterceptorLambda implements ILambda {
@Override
public Object handle(Object[] theArgs) {
IClientInterceptor interceptor = (IClientInterceptor) theArgs[0];
unregisterInterceptor(interceptor);
return null;
}
}
private class RegisterInterceptorLambda implements ILambda {
@Override
public Object handle(Object[] theArgs) {
IClientInterceptor interceptor = (IClientInterceptor) theArgs[0];
registerInterceptor(interceptor);
return null;
}
}
}

View File

@ -26,12 +26,14 @@ import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpRequestBase;
@ -49,6 +51,8 @@ import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.gclient.IClientExecutable;
import ca.uhn.fhir.rest.gclient.ICreate;
import ca.uhn.fhir.rest.gclient.ICreateTyped;
import ca.uhn.fhir.rest.gclient.ICriterion;
import ca.uhn.fhir.rest.gclient.ICriterionInternal;
import ca.uhn.fhir.rest.gclient.IGetPage;
@ -77,6 +81,7 @@ import ca.uhn.fhir.rest.method.ValidateMethodBinding;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class GenericClient extends BaseClient implements IGenericClient {
@ -106,6 +111,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
return resp;
}
@Override
public ICreate create() {
return new CreateInternal();
}
@Override
public MethodOutcome create(IResource theResource) {
BaseHttpClientInvocation invocation = CreateMethodBinding.createCreateInvocation(theResource, myContext);
@ -150,11 +160,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
return new GetTagsInternal();
}
@Override
public ITransaction transaction() {
return new TransactionInternal();
}
@Override
public <T extends IResource> Bundle history(final Class<T> theType, IdDt theIdDt, DateTimeDt theSince, Integer theLimit) {
String resourceName = theType != null ? toResourceName(theType) : null;
@ -179,12 +184,17 @@ public class GenericClient extends BaseClient implements IGenericClient {
return myLogRequestAndResponse;
}
@Override
public IGetPage loadPage() {
return new LoadPageInternal();
}
@Override
public <T extends IResource> T read(final Class<T> theType, IdDt theId) {
if (theId == null || theId.hasIdPart() == false) {
throw new IllegalArgumentException("theId does not contain a valid ID, is: " + theId);
}
HttpGetClientInvocation invocation = ReadMethodBinding.createReadInvocation(theId, toResourceName(theType));
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
@ -241,6 +251,15 @@ public class GenericClient extends BaseClient implements IGenericClient {
myLogRequestAndResponse = theLogRequestAndResponse;
}
private String toResourceName(Class<? extends IResource> theType) {
return myContext.getResourceDefinition(theType).getName();
}
@Override
public ITransaction transaction() {
return new TransactionInternal();
}
@Override
public List<IResource> transaction(List<IResource> theResources) {
BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(theResources, myContext);
@ -305,15 +324,18 @@ public class GenericClient extends BaseClient implements IGenericClient {
return vread(theType, new IdDt(theId), new IdDt(theVersionId));
}
private String toResourceName(Class<? extends IResource> theType) {
return myContext.getResourceDefinition(theType).getName();
}
private abstract class BaseClientExecutable<T extends IClientExecutable<?, ?>, Y> implements IClientExecutable<T, Y> {
private EncodingEnum myParamEncoding;
private Boolean myPrettyPrint;
private boolean myQueryLogRequestAndResponse;
protected void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) {
if (!params.containsKey(parameterName)) {
params.put(parameterName, new ArrayList<String>());
}
params.get(parameterName).add(parameterValue);
}
@SuppressWarnings("unchecked")
@Override
public T andLogRequestAndResponse(boolean theLogRequestAndResponse) {
@ -328,26 +350,13 @@ public class GenericClient extends BaseClient implements IGenericClient {
return (T) this;
}
@SuppressWarnings("unchecked")
@Override
public T encodedXml() {
myParamEncoding = EncodingEnum.XML;
return (T) this;
}
@SuppressWarnings("unchecked")
@Override
public T prettyPrint() {
myPrettyPrint = true;
return (T) this;
}
protected void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) {
if (!params.containsKey(parameterName)) {
params.put(parameterName, new ArrayList<String>());
}
params.get(parameterName).add(parameterValue);
}
protected <Z> Z invoke(Map<String, List<String>> theParams, IClientResponseHandler<Z> theHandler, BaseHttpClientInvocation theInvocation) {
if (myParamEncoding != null) {
theParams.put(Constants.PARAM_FORMAT, Collections.singletonList(myParamEncoding.getFormatContentType()));
@ -365,6 +374,13 @@ public class GenericClient extends BaseClient implements IGenericClient {
return resp;
}
@SuppressWarnings("unchecked")
@Override
public T prettyPrint() {
myPrettyPrint = true;
return (T) this;
}
}
private final class BundleResponseHandler implements IClientResponseHandler<Bundle> {
@ -376,7 +392,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
@ -385,19 +402,71 @@ public class GenericClient extends BaseClient implements IGenericClient {
return parser.parseBundle(myType, theResponseReader);
}
}
private final class ResourceListResponseHandler implements IClientResponseHandler<List<IResource>> {
private Class<? extends IResource> myType;
private class CreateInternal extends BaseClientExecutable<ICreateTyped, MethodOutcome> implements ICreate, ICreateTyped {
private String myId;
private IResource myResource;
private String myResourceBody;
@Override
public MethodOutcome execute() {
if (myResource == null) {
EncodingEnum encoding = null;
for (int i = 0; i < myResourceBody.length() && encoding == null; i++) {
switch (myResourceBody.charAt(i)) {
case '<':
encoding = EncodingEnum.XML;
break;
case '{':
encoding = EncodingEnum.JSON;
break;
}
}
if (encoding == null) {
throw new InvalidRequestException("FHIR client can't determine resource encoding");
}
myResource = encoding.newParser(myContext).parseResource(myResourceBody);
}
BaseHttpClientInvocation invocation = CreateMethodBinding.createCreateInvocation(myResource,myResourceBody, myId, myContext);
RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource);
final String resourceName = def.getName();
OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName);
Map<String, List<String>> params = new HashMap<String, List<String>>();
return invoke(params, binding, invocation);
public ResourceListResponseHandler(Class<? extends IResource> theType) {
myType = theType;
}
@Override
public List<IResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
return new BundleResponseHandler(myType).invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders).toListOfResources();
public ICreateTyped resource(IResource theResource) {
Validate.notNull(theResource, "Resource can not be null");
myResource = theResource;
return this;
}
@Override
public ICreateTyped resource(String theResourceBody) {
Validate.notBlank(theResourceBody, "Body can not be null or blank");
myResourceBody = theResourceBody;
return this;
}
@Override
public CreateInternal withId(IdDt theId) {
myId = theId.getIdPart();
return this;
}
@Override
public CreateInternal withId(String theId) {
myId = theId;
return this;
}
}
private class ForInternal extends BaseClientExecutable<IQuery, Bundle> implements IQuery {
@ -496,10 +565,31 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
private class GetPageInternal extends BaseClientExecutable<IGetPageTyped, Bundle> implements IGetPageTyped {
private String myUrl;
public GetPageInternal(String theUrl) {
myUrl = theUrl;
}
@Override
public Bundle execute() {
BundleResponseHandler binding = new BundleResponseHandler(null);
HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myUrl);
Map<String, List<String>> params = null;
return invoke(params, binding, invocation);
}
}
private class GetTagsInternal extends BaseClientExecutable<IGetTags, TagList> implements IGetTags {
private String myResourceName;
private String myId;
private String myResourceName;
private String myVersionId;
@Override
@ -537,14 +627,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
return this;
}
private void setResourceClass(Class<? extends IResource> theClass) {
if (theClass != null) {
myResourceName = myContext.getResourceDefinition(theClass).getName();
} else {
myResourceName = null;
}
}
@Override
public IGetTags forResource(Class<? extends IResource> theClass, String theId) {
setResourceClass(theClass);
@ -560,6 +642,33 @@ public class GenericClient extends BaseClient implements IGenericClient {
return this;
}
private void setResourceClass(Class<? extends IResource> theClass) {
if (theClass != null) {
myResourceName = myContext.getResourceDefinition(theClass).getName();
} else {
myResourceName = null;
}
}
}
private final class LoadPageInternal implements IGetPage {
@Override
public IGetPageTyped next(Bundle theBundle) {
return new GetPageInternal(theBundle.getLinkNext().getValue());
}
@Override
public IGetPageTyped previous(Bundle theBundle) {
return new GetPageInternal(theBundle.getLinkPrevious().getValue());
}
@Override
public IGetPageTyped url(String thePageUrl) {
return new GetPageInternal(thePageUrl);
}
}
private final class OutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> {
@ -570,7 +679,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
MethodOutcome response = BaseOutcomeReturningMethodBinding.process2xxResponse(myContext, myResourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
return response;
}
@ -595,14 +705,29 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
private final class ResourceListResponseHandler implements IClientResponseHandler<List<IResource>> {
private Class<? extends IResource> myType;
public ResourceListResponseHandler(Class<? extends IResource> theType) {
myType = theType;
}
@Override
public List<IResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
return new BundleResponseHandler(myType).invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders).toListOfResources();
}
}
private final class ResourceResponseHandler<T extends IResource> implements IClientResponseHandler<T> {
private Class<T> myType;
private IdDt myId;
private Class<T> myType;
public ResourceResponseHandler(Class<T> theType, IdDt theId) {
myType = theType;
myId=theId;
myId = theId;
}
@Override
@ -613,11 +738,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
IParser parser = respType.newParser(myContext);
T retVal = parser.parseResource(myType, theResponseReader);
if (myId != null) {
retVal.setId(myId);
}
return retVal;
}
}
@ -666,7 +791,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
private final class TagListResponseHandler implements IClientResponseHandler<TagList> {
@Override
public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
@ -676,94 +802,47 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
}
@Override
public IGetPage loadPage() {
return new LoadPageInternal();
}
private final class TransactionExecutable<T> extends BaseClientExecutable<ITransactionTyped<T>, T> implements ITransactionTyped<T> {
private final class LoadPageInternal implements IGetPage {
private Bundle myBundle;
private List<IResource> myResources;
@Override
public IGetPageTyped previous(Bundle theBundle) {
return new GetPageInternal(theBundle.getLinkPrevious().getValue());
public TransactionExecutable(Bundle theResources) {
myBundle = theResources;
}
@Override
public IGetPageTyped next(Bundle theBundle) {
return new GetPageInternal(theBundle.getLinkNext().getValue());
public TransactionExecutable(List<IResource> theResources) {
myResources = theResources;
}
@SuppressWarnings("unchecked")
@Override
public IGetPageTyped url(String thePageUrl) {
return new GetPageInternal(thePageUrl);
public T execute() {
if (myResources != null) {
ResourceListResponseHandler binding = new ResourceListResponseHandler(null);
BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myResources, myContext);
Map<String, List<String>> params = null;
return (T) invoke(params, binding, invocation);
} else {
BundleResponseHandler binding = new BundleResponseHandler(null);
BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBundle, myContext);
Map<String, List<String>> params = null;
return (T) invoke(params, binding, invocation);
}
}
}
private final class TransactionInternal implements ITransaction {
@Override
public ITransactionTyped<List<IResource>> withResources(List<IResource> theResources) {
return new TransactionExecutable<List<IResource>>(theResources);
}
@Override
public ITransactionTyped<Bundle> withBundle(Bundle theResources) {
return new TransactionExecutable<Bundle>(theResources);
}
}
private final class TransactionExecutable<T> extends BaseClientExecutable<ITransactionTyped<T>, T> implements ITransactionTyped<T>{
private List<IResource> myResources;
private Bundle myBundle;
public TransactionExecutable(List<IResource> theResources) {
myResources=theResources;
}
public TransactionExecutable(Bundle theResources) {
myBundle=theResources;
}
@SuppressWarnings("unchecked")
@Override
public T execute() {
if (myResources!=null) {
ResourceListResponseHandler binding = new ResourceListResponseHandler(null);
BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myResources, myContext);
Map<String, List<String>> params = null;
return (T) invoke(params, binding, invocation);
}else {
BundleResponseHandler binding = new BundleResponseHandler(null);
BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBundle, myContext);
Map<String, List<String>> params = null;
return (T) invoke(params, binding, invocation);
}
}
}
private class GetPageInternal extends BaseClientExecutable<IGetPageTyped, Bundle> implements IGetPageTyped {
private String myUrl;
public GetPageInternal(String theUrl) {
myUrl = theUrl;
}
@Override
public Bundle execute() {
BundleResponseHandler binding = new BundleResponseHandler(null);
HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myUrl);
Map<String, List<String>> params = null;
return invoke(params, binding, invocation);
public ITransactionTyped<List<IResource>> withResources(List<IResource> theResources) {
return new TransactionExecutable<List<IResource>>(theResources);
}
}

View File

@ -1,5 +1,19 @@
package ca.uhn.fhir.rest.client;
import java.io.IOException;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.protocol.HttpContext;
import ca.uhn.fhir.rest.client.api.IBasicClient;
/*
* #%L
* HAPI FHIR Library
@ -20,27 +34,11 @@ package ca.uhn.fhir.rest.client;
* #L%
*/
import java.io.IOException;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.protocol.HttpContext;
/**
* HTTP interceptor to be used for adding HTTP basic auth username/password tokens
* to requests
* <p>
* See the <a href="http://hl7api.sourceforge.net/hapi-fhir/doc_rest_client.html#HTTP_Basic_Authorization">HAPI Documentation</a>
* for information on how to use this class.
* </p>
* @deprecated Use {@link ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor} instead. Note that that class is a HAPI client interceptor instead of being a commons-httpclient interceptor, so you register it to your client instance once it's created using {@link IGenericClient#registerInterceptor(IClientInterceptor)} or {@link IBasicClient#registerInterceptor(IClientInterceptor)} instead
*/
public class HttpBasicAuthInterceptor implements HttpRequestInterceptor {
public class HttpBasicAuthInterceptor implements HttpRequestInterceptor {
private String myUsername;
private String myPassword;

View File

@ -20,13 +20,21 @@ package ca.uhn.fhir.rest.client;
* #L%
*/
import java.io.IOException;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
public interface IClientInterceptor {
/**
* Fired by the client just before invoking the HTTP client request
*/
void interceptRequest(HttpRequestBase theRequest);
void interceptResponse(HttpResponse theRequest);
/**
* Fired by the client upon receiving an HTTP response, prior to processing that response
*/
void interceptResponse(HttpResponse theResponse) throws IOException;
}

View File

@ -30,13 +30,25 @@ import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.gclient.ICreate;
import ca.uhn.fhir.rest.gclient.IGetPage;
import ca.uhn.fhir.rest.gclient.IGetTags;
import ca.uhn.fhir.rest.gclient.ITransaction;
import ca.uhn.fhir.rest.gclient.IUntypedQuery;
public interface IGenericClient {
/**
* Register a new interceptor for this client. An interceptor can be used to add additional
* logging, or add security headers, or pre-process responses, etc.
*/
void registerInterceptor(IClientInterceptor theInterceptor);
/**
* Remove an intercaptor that was previously registered using {@link IRestfulClient#registerInterceptor(IClientInterceptor)}
*/
void unregisterInterceptor(IClientInterceptor theInterceptor);
/**
* Retrieves and returns the server conformance statement
*/
@ -243,4 +255,9 @@ public interface IGenericClient {
*/
ITransaction transaction();
/**
* Fluent method for the "create" operation, which creates a new resource instance on the server
*/
ICreate create();
}

View File

@ -21,9 +21,11 @@ package ca.uhn.fhir.rest.client.api;
*/
import org.apache.commons.lang3.Validate;
import org.apache.http.client.HttpClient;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.IClientInterceptor;
import ca.uhn.fhir.rest.server.EncodingEnum;
public interface IRestfulClient {
@ -64,6 +66,15 @@ public interface IRestfulClient {
*/
String getServerBase();
/**
* Register a new interceptor for this client. An interceptor can be used to add additional
* logging, or add security headers, or pre-process responses, etc.
*/
void registerInterceptor(IClientInterceptor theInterceptor);
/**
* Remove an intercaptor that was previously registered using {@link IRestfulClient#registerInterceptor(IClientInterceptor)}
*/
void unregisterInterceptor(IClientInterceptor theInterceptor);
}

View File

@ -0,0 +1,73 @@
package ca.uhn.fhir.rest.client.interceptor;
/*
* #%L
* HAPI FHIR 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.IOException;
import java.io.UnsupportedEncodingException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import ca.uhn.fhir.rest.client.IClientInterceptor;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
/**
* HTTP interceptor to be used for adding HTTP basic auth username/password tokens
* to requests
* <p>
* See the <a href="http://hl7api.sourceforge.net/hapi-fhir/doc_rest_client.html#HTTP_Basic_Authorization">HAPI Documentation</a>
* for information on how to use this class.
* </p>
*/
public class BasicAuthInterceptor implements IClientInterceptor {
private String myUsername;
private String myPassword;
public BasicAuthInterceptor(String theUsername, String thePassword) {
super();
myUsername = theUsername;
myPassword = thePassword;
}
@Override
public void interceptRequest(HttpRequestBase theRequest) {
String authorizationUnescaped = StringUtils.defaultString(myUsername) + ":" + StringUtils.defaultString(myPassword);
String encoded;
try {
encoded = Base64.encodeBase64String(authorizationUnescaped.getBytes("ISO-8859-1"));
} catch (UnsupportedEncodingException e) {
throw new InternalErrorException("Could not find US-ASCII encoding. This shouldn't happen!");
}
theRequest.addHeader(Constants.HEADER_AUTHORIZATION, ("Basic " + encoded));
}
@Override
public void interceptResponse(HttpResponse theResponse) throws IOException {
// nothing
}
}

View File

@ -0,0 +1,43 @@
package ca.uhn.fhir.rest.client.interceptor;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import ca.uhn.fhir.rest.client.IClientInterceptor;
/**
* Client interceptor which simply captures request and response objects and stored them so that they can be inspected after a client
* call has returned
*/
public class CapturingInterceptor implements IClientInterceptor {
private HttpRequestBase myLastRequest;
private HttpResponse myLastResponse;
/**
* Clear the last request and response values
*/
public void clear() {
myLastRequest = null;
myLastResponse = null;
}
public HttpRequestBase getLastRequest() {
return myLastRequest;
}
public HttpResponse getLastResponse() {
return myLastResponse;
}
@Override
public void interceptRequest(HttpRequestBase theRequest) {
myLastRequest = theRequest;
}
@Override
public void interceptResponse(HttpResponse theRequest) {
myLastResponse = theRequest;
}
}

View File

@ -0,0 +1,184 @@
package ca.uhn.fhir.rest.client.interceptor;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.HttpEntityWrapper;
import ca.uhn.fhir.rest.client.IClientInterceptor;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class LoggingInterceptor implements IClientInterceptor {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoggingInterceptor.class);
private boolean myLogRequestSummary = true;
private boolean myLogResponseSummary = true;
private boolean myLogResponseBody = false;
private boolean myLogRequestBody = false;
private boolean myLogResponseHeaders=false;
private boolean myLogRequestHeaders=false;
/**
* Constructor
*/
public LoggingInterceptor() {
super();
}
/**
* Constructor
*
* @param theVerbose If set to true, all logging is enabled
*/
public LoggingInterceptor(boolean theVerbose) {
if (theVerbose) {
setLogRequestBody(true);
setLogRequestSummary(true);
setLogResponseBody(true);
setLogResponseSummary(true);
setLogRequestHeaders(true);
setLogResponseHeaders(true);
}
}
@Override
public void interceptRequest(HttpRequestBase theRequest) {
if (myLogRequestSummary) {
ourLog.info("Client request: {}", theRequest);
}
if (myLogRequestHeaders) {
StringBuilder b = new StringBuilder();
for (int i = 0;i < theRequest.getAllHeaders().length;i++) {
Header next = theRequest.getAllHeaders()[i];
b.append(next.getName() + ": " + next.getValue());
if(i+1 < theRequest.getAllHeaders().length) {
b.append('\n');
}
}
ourLog.info("Client request headers:\n{}", b.toString());
}
if (myLogRequestBody) {
if (theRequest instanceof HttpEntityEnclosingRequest) {
HttpEntity entity = ((HttpEntityEnclosingRequest) theRequest).getEntity();
if (entity.isRepeatable()) {
try {
String content = IOUtils.toString(entity.getContent());
ourLog.info("Client request body:\n{}", content);
} catch (IllegalStateException e) {
ourLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e);
} catch (IOException e) {
ourLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e);
}
}
}
}
}
@Override
public void interceptResponse(HttpResponse theResponse) throws IOException {
if (myLogResponseSummary) {
String message = "HTTP " + theResponse.getStatusLine().getStatusCode() + " " + theResponse.getStatusLine().getReasonPhrase();
ourLog.info("Client response: {}", message);
}
if (myLogRequestHeaders) {
StringBuilder b = new StringBuilder();
for (int i = 0;i < theResponse.getAllHeaders().length;i++) {
Header next = theResponse.getAllHeaders()[i];
b.append(next.getName() + ": " + next.getValue());
if(i+1 < theResponse.getAllHeaders().length) {
b.append('\n');
}
}
ourLog.info("Client response headers:\n{}", b.toString());
}
if (myLogResponseBody) {
HttpEntity respEntity = theResponse.getEntity();
final byte[] bytes;
try {
bytes = IOUtils.toByteArray(respEntity.getContent());
} catch (IllegalStateException e) {
throw new InternalErrorException(e);
}
ourLog.info("Client response body:\n{}", new String(bytes));
theResponse.setEntity(new MyEntityWrapper(respEntity, bytes));
}
}
private static class MyEntityWrapper extends HttpEntityWrapper {
private byte[] myBytes;
public MyEntityWrapper(HttpEntity theWrappedEntity, byte[] theBytes) {
super(theWrappedEntity);
myBytes = theBytes;
}
@Override
public InputStream getContent() throws IOException {
return new ByteArrayInputStream(myBytes);
}
@Override
public void writeTo(OutputStream theOutstream) throws IOException {
theOutstream.write(myBytes);
}
}
/**
* Should a summary (one line) for each request be logged, containing the URL and other information
*/
public void setLogRequestSummary(boolean theValue) {
myLogRequestSummary = theValue;
}
/**
* Should a summary (one line) for each request be logged, containing the URL and other information
*/
public void setLogResponseSummary(boolean theValue) {
myLogResponseSummary = theValue;
}
/**
* Should headers for each request be logged, containing the URL and other information
*/
public void setLogRequestHeaders(boolean theValue) {
myLogRequestHeaders = theValue;
}
/**
* Should headers for each request be logged, containing the URL and other information
*/
public void setLogResponseHeaders(boolean theValue) {
myLogResponseHeaders = theValue;
}
/**
* Should a summary (one line) for each request be logged, containing the URL and other information
*/
public void setLogRequestBody(boolean theValue) {
myLogRequestBody = theValue;
}
/**
* Should a summary (one line) for each request be logged, containing the URL and other information
*/
public void setLogResponseBody(boolean theValue) {
myLogResponseBody = theValue;
}
}

View File

@ -0,0 +1,31 @@
package ca.uhn.fhir.rest.gclient;
/*
* #%L
* HAPI FHIR 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.api.IResource;
public interface ICreate {
ICreateTyped resource(IResource theResource);
ICreateTyped resource(String theResourceAsText);
}

View File

@ -0,0 +1,40 @@
package ca.uhn.fhir.rest.gclient;
/*
* #%L
* HAPI FHIR 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.primitive.IdDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
public interface ICreateTyped extends IClientExecutable<ICreateTyped, MethodOutcome> {
/**
* If you want the explicitly state an ID for your created resource, put that ID here. You generally do not
* need to invoke this method, so that the server will assign the ID itself.
*/
ICreateTyped withId(String theId);
/**
* If you want the explicitly state an ID for your created resource, put that ID here. You generally do not
* need to invoke this method, so that the server will assign the ID itself.
*/
ICreateTyped withId(IdDt theId);
}

View File

@ -50,7 +50,8 @@ public abstract class BaseHttpClientInvocationWithContents extends BaseHttpClien
private final TagList myTagList;
private final List<IResource> myResources;
private final Bundle myBundle;
private final String myContents;
public BaseHttpClientInvocationWithContents(FhirContext theContext, IResource theResource, String theUrlExtension) {
super();
myContext = theContext;
@ -59,6 +60,7 @@ public abstract class BaseHttpClientInvocationWithContents extends BaseHttpClien
myTagList = null;
myResources = null;
myBundle = null;
myContents = null;
}
public BaseHttpClientInvocationWithContents(FhirContext theContext, TagList theTagList, String... theUrlExtension) {
@ -72,6 +74,7 @@ public abstract class BaseHttpClientInvocationWithContents extends BaseHttpClien
myTagList = theTagList;
myResources = null;
myBundle = null;
myContents = null;
myUrlExtension = StringUtils.join(theUrlExtension, '/');
}
@ -83,6 +86,7 @@ public abstract class BaseHttpClientInvocationWithContents extends BaseHttpClien
myUrlExtension = null;
myResources = theResources;
myBundle = null;
myContents = null;
}
public BaseHttpClientInvocationWithContents(FhirContext theContext, Bundle theBundle) {
@ -92,8 +96,20 @@ public abstract class BaseHttpClientInvocationWithContents extends BaseHttpClien
myUrlExtension = null;
myResources = null;
myBundle = theBundle;
myContents = null;
}
public BaseHttpClientInvocationWithContents(FhirContext theContext, String theContents, String theUrlExtension) {
myContext = theContext;
myResource = null;
myTagList = null;
myUrlExtension = theUrlExtension;
myResources = null;
myBundle = null;
myContents = theContents;
}
@Override
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding) throws DataFormatException {
StringBuilder b = new StringBuilder();
@ -131,6 +147,8 @@ public abstract class BaseHttpClientInvocationWithContents extends BaseHttpClien
} else if (myResources != null) {
Bundle bundle = RestfulServer.createBundleFromResourceList(myContext, "", myResources, "", "", myResources.size());
contents = parser.encodeBundleToString(bundle);
} else if (myContents != null) {
contents = myContents;
} else {
contents = parser.encodeResourceToString(myResource);
}

View File

@ -20,10 +20,13 @@ package ca.uhn.fhir.rest.method;
* #L%
*/
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IResource;
@ -55,12 +58,24 @@ public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
return Collections.singleton(RequestType.POST);
}
@Override
protected IResource parseIncomingServerResource(Request theRequest) throws IOException {
IResource retVal = super.parseIncomingServerResource(theRequest);
if (theRequest.getId() != null && theRequest.getId().hasIdPart()) {
retVal.setId(theRequest.getId());
}
return retVal;
}
@Override
protected BaseHttpClientInvocation createClientInvocation(Object[] theArgs, IResource theResource) {
FhirContext context = getContext();
BaseHttpClientInvocation retVal = createCreateInvocation(theResource, context);
if (theArgs != null) {
for (int idx = 0; idx < theArgs.length; idx++) {
IParameter nextParam = getParameters().get(idx);
@ -72,16 +87,28 @@ public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
}
public static HttpPostClientInvocation createCreateInvocation(IResource theResource, FhirContext theContext) {
return createCreateInvocation(theResource, null,null, theContext);
}
public static HttpPostClientInvocation createCreateInvocation(IResource theResource, String theResourceBody, String theId, FhirContext theContext) {
RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource);
String resourceName = def.getName();
StringBuilder urlExtension = new StringBuilder();
urlExtension.append(resourceName);
if (StringUtils.isNotBlank(theId)) {
urlExtension.append('/');
urlExtension.append(theId);
}
HttpPostClientInvocation retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString());
HttpPostClientInvocation retVal;
if (StringUtils.isBlank(theResourceBody)) {
retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString());
}else {
retVal = new HttpPostClientInvocation(theContext, theResourceBody, urlExtension.toString());
}
addTagsToPostOrPut(theResource, retVal);
return retVal;
}

View File

@ -51,6 +51,10 @@ public class HttpPostClientInvocation extends BaseHttpClientInvocationWithConten
super(theContext,theBundle);
}
public HttpPostClientInvocation(FhirContext theContext, String theContents, String theUrlExtension) {
super(theContext,theContents, theUrlExtension);
}
@Override
protected HttpPost createRequest(String url, AbstractHttpEntity theEntity) {

View File

@ -28,6 +28,10 @@ import java.util.Set;
public class Constants {
public static final String HEADER_CORS_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
public static final String HEADER_CORS_ALLOW_METHODS = "Access-Control-Allow-Methods";
public static final String HEADER_CORS_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
public static final String CHARSET_UTF_8 = "UTF-8";
public static final String CT_ATOM_XML = "application/atom+xml";
public static final String CT_FHIR_JSON = "application/json+fhir";
@ -88,6 +92,8 @@ public class Constants {
public static final String ENCODING_GZIP = "gzip";
public static final String HEADER_LOCATION = "Location";
public static final String HEADER_LOCATION_LC = HEADER_LOCATION.toLowerCase();
public static final String HEADERVALUE_CORS_ALLOW_METHODS_ALL = "GET, POST, PUT, DELETE";
public static final String HEADER_AUTHORIZATION = "Authorization";
static {

View File

@ -79,6 +79,7 @@ import ca.uhn.fhir.util.VersionUtil;
public class RestfulServer extends HttpServlet {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
private static final long serialVersionUID = 1L;
@ -99,6 +100,20 @@ public class RestfulServer extends HttpServlet {
private String myServerVersion = VersionUtil.getVersion();
private boolean myStarted;
private boolean myUseBrowserFriendlyContentTypes;
private String myCorsAllowDomain;
public String getCorsAllowDomain() {
return myCorsAllowDomain;
}
/**
* If set to anything other than <code>null</code> (which is the default), the server will return CORS (Cross Origin Resource Sharing) headers with the given domain string.
* <p>
* A value of "*" indicates that the server allows access to all domains (which may be appropriate in development situations but is generally not appropriate in production)
*/
public void setCorsAllowDomain(String theCorsAllowDomain) {
myCorsAllowDomain = theCorsAllowDomain;
}
/**
* Constructor
@ -120,6 +135,13 @@ public class RestfulServer extends HttpServlet {
*/
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
theHttpResponse.addHeader("X-Powered-By", "HAPI FHIR " + VersionUtil.getVersion() + " RESTful Server");
if (isNotBlank(myCorsAllowDomain)) {
theHttpResponse.addHeader(Constants.HEADER_CORS_ALLOW_ORIGIN, myCorsAllowDomain);
theHttpResponse.addHeader(Constants.HEADER_CORS_ALLOW_METHODS, Constants.HEADERVALUE_CORS_ALLOW_METHODS_ALL);
theHttpResponse.addHeader(Constants.HEADER_CORS_EXPOSE_HEADERS, Constants.HEADER_CONTENT_LOCATION);
}
}
private void assertProviderIsValid(Object theNext) throws ConfigurationException {
@ -352,7 +374,7 @@ public class RestfulServer extends HttpServlet {
int start = Math.min(offsetI, resultList.size() - 1);
EncodingEnum responseEncoding = determineRequestEncoding(theRequest);
EncodingEnum responseEncoding = determineResponseEncoding(theRequest.getServletRequest());
boolean prettyPrint = prettyPrintResponse(theRequest);
boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest());
NarrativeModeEnum narrativeMode = determineNarrativeMode(theRequest);
@ -418,7 +440,7 @@ public class RestfulServer extends HttpServlet {
ResourceBinding resourceBinding = null;
BaseMethodBinding<?> resourceMethod = null;
if ("metadata".equals(resourceName)) {
if ("metadata".equals(resourceName) || theRequestType == RequestType.OPTIONS) {
resourceMethod = myServerConformanceMethod;
} else if (resourceName == null) {
resourceBinding = myNullResourceBinding;

View File

@ -3,10 +3,11 @@ package example;
import org.apache.http.impl.client.HttpClientBuilder;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.HttpBasicAuthInterceptor;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.IRestfulClientFactory;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.EncodingEnum;
public class ClientExamples {
@ -17,29 +18,50 @@ public class ClientExamples {
@SuppressWarnings("unused")
public void createSecurity() {
{
//START SNIPPET: security
// Create a context and get the client factory so it can be configured
FhirContext ctx = new FhirContext();
IRestfulClientFactory clientFactory = ctx.getRestfulClientFactory();
// Create an HTTP Client Builder
HttpClientBuilder builder = HttpClientBuilder.create();
// This interceptor adds HTTP username/password to every request
//Create an HTTP basic auth interceptor
String username = "foobar";
String password = "boobear";
builder.addInterceptorFirst(new HttpBasicAuthInterceptor(username, password));
BasicAuthInterceptor authInterceptor = new BasicAuthInterceptor(username, password);
// Use the new HTTP client builder
clientFactory.setHttpClient(builder.build());
// This factory is applied to both styles of client
// Register the interceptor with your client (either style)
IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/fhir");
annotationClient.registerInterceptor(authInterceptor);
IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir");
annotationClient.registerInterceptor(authInterceptor);
//END SNIPPET: security
}
@SuppressWarnings("unused")
public void createLogging() {
{
//START SNIPPET: logging
//Create a context and get the client factory so it can be configured
FhirContext ctx = new FhirContext();
IRestfulClientFactory clientFactory = ctx.getRestfulClientFactory();
//Create a logging interceptor
LoggingInterceptor loggingInterceptor = new LoggingInterceptor();
// Optionally you may configure the interceptor (by default only summary info is logged)
loggingInterceptor.setLogRequestSummary(true);
loggingInterceptor.setLogRequestBody(true);
//Register the interceptor with your client (either style)
IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/fhir");
annotationClient.registerInterceptor(loggingInterceptor);
IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir");
annotationClient.registerInterceptor(loggingInterceptor);
//END SNIPPET: logging
}
/******************************/
{

View File

@ -165,6 +165,28 @@
</subsection>
<subsection name="Configuring Encoding (JSON/XML)">
<p>
Restful client interfaces that you create will also extend
the interface
<a href="./apidocs/ca/uhn/fhir/rest/client/api/IRestfulClient.html">IRestfulClient</a>,
which comes with some helpful methods for configuring the way that
the client will interact with the server.
</p>
<p>
The following snippet shows how to configure the cliet to explicitly
request JSON or XML responses, and how to request "pretty printed" responses
on servers that support this (HAPI based servers currently).
</p>
<macro name="snippet">
<param name="id" value="clientConfig" />
<param name="file" value="src/site/example/java/example/ClientExamples.java" />
</macro>
</subsection>
<subsection name="A Complete Example">
<p>
@ -205,6 +227,21 @@
on the RestfulClientFactory.
</p>
<subsection name="Interceptors">
<p>
Both generic clients and annotation-driven clients support
<a href="./apidocs/ca/uhn/fhir/rest/client/IClientInterceptor.html">Client Interceptors</a>,
which may be registered in order to provide specific behaviour to each
client request.
</p>
<p>
The following section shows some sample interceptors which may be used.
</p>
</subsection>
<subsection name="HTTP Basic Authorization">
<p>
@ -219,23 +256,15 @@
</subsection>
<subsection name="Configuring Encoding (JSON/XML)">
<subsection name="Logging Requests and Responses">
<p>
Restful client interfaces that you create will also extend
the interface
<a href="./apidocs/ca/uhn/fhir/rest/client/api/IRestfulClient.html">IRestfulClient</a>,
which comes with some helpful methods for configuring the way that
the client will interact with the server.
The following example shows how to configure your client to
use a specific username and password in every request.
</p>
<p>
The following snippet shows how to configure the cliet to explicitly
request JSON or XML responses, and how to request "pretty printed" responses
on servers that support this (HAPI based servers currently).
</p>
<macro name="snippet">
<param name="id" value="clientConfig" />
<param name="id" value="logging" />
<param name="file" value="src/site/example/java/example/ClientExamples.java" />
</macro>

View File

@ -22,6 +22,7 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
import org.hamcrest.core.StringContains;
import org.hamcrest.core.StringEndsWith;
@ -57,6 +58,8 @@ import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.param.CodingListParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.QualifiedDateParam;
@ -73,8 +76,6 @@ public class ClientTest {
private HttpClient httpClient;
private HttpResponse httpResponse;
// atom-document-large.xml
@Before
public void before() {
ctx = new FhirContext(Patient.class, Conformance.class);
@ -85,6 +86,36 @@ public class ClientTest {
httpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
}
// atom-document-large.xml
private String getPatientFeedWithOneResult() {
//@formatter:off
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n" +
"<title/>\n" +
"<id>d039f91a-cc3c-4013-988e-af4d8d0614bd</id>\n" +
"<os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">1</os:totalResults>\n" +
"<published>2014-03-11T16:35:07-04:00</published>\n" +
"<author>\n" +
"<name>ca.uhn.fhir.rest.server.DummyRestfulServer</name>\n" +
"</author>\n" +
"<entry>\n" +
"<content type=\"text/xml\">"
+ "<Patient xmlns=\"http://hl7.org/fhir\">"
+ "<text><status value=\"generated\" /><div xmlns=\"http://www.w3.org/1999/xhtml\">John Cardinal: 444333333 </div></text>"
+ "<identifier><label value=\"SSN\" /><system value=\"http://orionhealth.com/mrn\" /><value value=\"PRP1660\" /></identifier>"
+ "<name><use value=\"official\" /><family value=\"Cardinal\" /><given value=\"John\" /></name>"
+ "<name><family value=\"Kramer\" /><given value=\"Doe\" /></name>"
+ "<telecom><system value=\"phone\" /><value value=\"555-555-2004\" /><use value=\"work\" /></telecom>"
+ "<gender><coding><system value=\"http://hl7.org/fhir/v3/AdministrativeGender\" /><code value=\"M\" /></coding></gender>"
+ "<address><use value=\"home\" /><line value=\"2222 Home Street\" /></address><active value=\"true\" />"
+ "</Patient>"
+ "</content>\n"
+ " </entry>\n"
+ "</feed>";
//@formatter:on
return msg;
}
@Test
public void testCreate() throws Exception {
@ -99,8 +130,13 @@ public class ClientTest {
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location", "http://example.com/fhir/Patient/100/_history/200"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
CapturingInterceptor interceptor = new CapturingInterceptor();
client.registerInterceptor(interceptor);
MethodOutcome response = client.createPatient(patient);
assertEquals(interceptor.getLastRequest().getURI().toASCIIString(), "http://foo/Patient");
assertEquals(HttpPost.class, capt.getValue().getClass());
HttpPost post = (HttpPost) capt.getValue();
assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient"));
@ -129,8 +165,7 @@ public class ClientTest {
}
/**
* Some servers (older ones?) return the resourcde you created instead of an
* OperationOutcome. We just need to ignore it.
* Some servers (older ones?) return the resourcde you created instead of an OperationOutcome. We just need to ignore it.
*/
@Test
public void testCreateWithResourceResponse() throws Exception {
@ -562,6 +597,29 @@ public class ClientTest {
}
@Test
public void testReadFailureInternalError() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, "INTERNAL"));
Header[] headers = new Header[1];
headers[0] = new BasicHeader(Constants.HEADER_LAST_MODIFIED, "2011-01-02T22:01:02");
when(httpResponse.getAllHeaders()).thenReturn(headers);
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("Internal Failure"), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
try {
client.getPatientById(new IdDt("111"));
fail();
} catch (InternalErrorException e) {
assertThat(e.getMessage(), containsString("INTERNAL"));
assertThat(e.getResponseBody(), containsString("Internal Failure"));
}
}
@Test
public void testReadFailureNoCharset() throws Exception {
@ -588,30 +646,6 @@ public class ClientTest {
}
@Test
public void testReadFailureInternalError() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, "INTERNAL"));
Header[] headers = new Header[1];
headers[0] = new BasicHeader(Constants.HEADER_LAST_MODIFIED, "2011-01-02T22:01:02");
when(httpResponse.getAllHeaders()).thenReturn(headers);
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("Internal Failure"), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
try {
client.getPatientById(new IdDt("111"));
fail();
} catch (InternalErrorException e) {
assertThat(e.getMessage(), containsString("INTERNAL"));
assertThat(e.getResponseBody(), containsString("Internal Failure"));
}
}
@Test
public void testReadNoCharset() throws Exception {
@ -676,6 +710,28 @@ public class ClientTest {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
// httpResponse = new BasicHttpResponse(statusline, catalog, locale)
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
List<Patient> response = client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"));
assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.get(0).getIdentifier().get(0).getValue().getValue());
}
@Test
public void testSearchByQuantity() throws Exception {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
@ -683,10 +739,10 @@ public class ClientTest {
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
List<Patient> response = client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"));
Patient response = client.findPatientQuantity(new QuantityDt(QuantityCompararatorEnum.GREATERTHAN, 123L, "foo", "bar"));
assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.get(0).getIdentifier().get(0).getValue().getValue());
assertEquals("http://foo/Patient?quantityParam=%3E123%7Cfoo%7Cbar", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue());
}
@ -709,25 +765,6 @@ public class ClientTest {
}
@Test
public void testSearchByQuantity() throws Exception {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
Patient response = client.findPatientQuantity(new QuantityDt(QuantityCompararatorEnum.GREATERTHAN,123L,"foo","bar"));
assertEquals("http://foo/Patient?quantityParam=%3E123%7Cfoo%7Cbar", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue());
}
@Test
public void testSearchComposite() throws Exception {
@ -946,8 +983,7 @@ public class ClientTest {
}
/**
* Return a FHIR content type, but no content and make sure we handle this
* without crashing
* Return a FHIR content type, but no content and make sure we handle this without crashing
*/
@Test
public void testUpdateWithEmptyResponse() throws Exception {
@ -1063,38 +1099,14 @@ public class ClientTest {
}
private String getPatientFeedWithOneResult() {
//@formatter:off
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n" +
"<title/>\n" +
"<id>d039f91a-cc3c-4013-988e-af4d8d0614bd</id>\n" +
"<os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">1</os:totalResults>\n" +
"<published>2014-03-11T16:35:07-04:00</published>\n" +
"<author>\n" +
"<name>ca.uhn.fhir.rest.server.DummyRestfulServer</name>\n" +
"</author>\n" +
"<entry>\n" +
"<content type=\"text/xml\">"
+ "<Patient xmlns=\"http://hl7.org/fhir\">"
+ "<text><status value=\"generated\" /><div xmlns=\"http://www.w3.org/1999/xhtml\">John Cardinal: 444333333 </div></text>"
+ "<identifier><label value=\"SSN\" /><system value=\"http://orionhealth.com/mrn\" /><value value=\"PRP1660\" /></identifier>"
+ "<name><use value=\"official\" /><family value=\"Cardinal\" /><given value=\"John\" /></name>"
+ "<name><family value=\"Kramer\" /><given value=\"Doe\" /></name>"
+ "<telecom><system value=\"phone\" /><value value=\"555-555-2004\" /><use value=\"work\" /></telecom>"
+ "<gender><coding><system value=\"http://hl7.org/fhir/v3/AdministrativeGender\" /><code value=\"M\" /></coding></gender>"
+ "<address><use value=\"home\" /><line value=\"2222 Home Street\" /></address><active value=\"true\" />"
+ "</Patient>"
+ "</content>\n"
+ " </entry>\n"
+ "</feed>";
//@formatter:on
return msg;
}
private Header[] toHeaderArray(String theName, String theValue) {
return new Header[] { new BasicHeader(theName, theValue) };
}
private interface ClientWithoutAnnotation extends IBasicClient {
Patient read(@IdParam IdDt theId);
}
@ResourceDef(name = "Patient")
public static class CustomPatient extends Patient {
// nothing
@ -1114,8 +1126,4 @@ public class ClientTest {
@Search()
public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString, @IncludeParam String theInclude);
}
private interface ClientWithoutAnnotation extends IBasicClient {
Patient read(@IdParam IdDt theId);
}
}

View File

@ -13,6 +13,7 @@ import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
@ -59,7 +60,7 @@ public class GenericClientTest {
}
@Test
public void testCreateWithTag() throws Exception {
public void testCreateWithTagNonFluent() throws Exception {
Patient p1 = new Patient();
p1.addIdentifier("foo:bar", "12345");
@ -88,6 +89,52 @@ public class GenericClientTest {
assertEquals("urn:happytag; label=\"This is a happy resource\"; scheme=\"http://hl7.org/fhir/tag\"", catH.getValue());
}
@Test
public void testCreateWithTag() throws Exception {
Patient p1 = new Patient();
p1.addIdentifier("foo:bar", "12345");
p1.addName().addFamily("Smith").addGiven("John");
TagList list = new TagList();
list.addTag("http://hl7.org/fhir/tag", "urn:happytag", "This is a happy resource");
ResourceMetadataKeyEnum.TAG_LIST.put(p1, list);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
MethodOutcome outcome = client.create().resource(p1).execute();
assertEquals("44", outcome.getId().getIdPart());
assertEquals("22", outcome.getId().getVersionIdPart());
assertEquals("http://example.com/fhir/Patient", capt.getValue().getURI().toString());
assertEquals("POST", capt.getValue().getMethod());
Header catH = capt.getValue().getFirstHeader("Category");
assertNotNull(Arrays.asList(capt.getValue().getAllHeaders()).toString(), catH);
assertEquals("urn:happytag; label=\"This is a happy resource\"; scheme=\"http://hl7.org/fhir/tag\"", catH.getValue());
/*
* Try fluent options
*/
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
client.create().resource(p1).withId("123").execute();
assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(1).getURI().toString());
String resourceText = "<Patient xmlns=\"http://hl7.org/fhir\"> </Patient>";
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
client.create().resource(resourceText).withId("123").execute();
assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(2).getURI().toString());
assertEquals(resourceText, IOUtils.toString(((HttpPost)capt.getAllValues().get(2)).getEntity().getContent()));
}
private String getPatientFeedWithOneResult() {
//@formatter:off
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n" +

View File

@ -0,0 +1,83 @@
package ca.uhn.fhir.rest.client;
import static org.junit.Assert.assertFalse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Patient;
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.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class InterceptorTest {
private static int ourPort;
private static Server ourServer;
private static FhirContext ourCtx;
@Test
public void testLogger() throws Exception {
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
client.registerInterceptor(new LoggingInterceptor(true));
Patient patient = client.read(Patient.class, "1");
assertFalse(patient.getIdentifierFirstRep().isEmpty());
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(ourPort);
DummyProvider patientProvider = new DummyProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
ourCtx = servlet.getFhirContext();
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyProvider implements IResourceProvider {
@Read(version = true)
public Patient findPatient(@IdParam IdDt theId) {
Patient patient = new Patient();
patient.addIdentifier(theId.getIdPart(), theId.getVersionIdPart());
patient.setId("Patient/1/_history/1");
return patient;
}
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
}
}

View File

@ -0,0 +1,267 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.resource.AdverseReaction;
import ca.uhn.fhir.model.dstu.resource.DiagnosticOrder;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class CreateTest {
private static CloseableHttpClient ourClient;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CreateTest.class);
private static int ourPort;
private static DiagnosticReportProvider ourReportProvider;
private static Server ourServer;
@Test
public void testCreate() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(201, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("location").getValue());
}
@Test
public void testCreateById() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/1234");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(201, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/Patient/1234/_history/002", status.getFirstHeader("location").getValue());
}
@Test
public void testCreateJson() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity(new FhirContext().newJsonParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(201, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("location").getValue());
}
@Test
public void testCreateWithUnprocessableEntity() throws Exception {
DiagnosticReport report = new DiagnosticReport();
report.getIdentifier().setValue("001");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/DiagnosticReport");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(report), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(422, status.getStatusLine().getStatusCode());
OperationOutcome outcome = new FhirContext().newXmlParser().parseResource(OperationOutcome.class, new StringReader(responseContent));
assertEquals("FOOBAR", outcome.getIssueFirstRep().getDetails().getValue());
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(ourPort);
PatientProvider patientProvider = new PatientProvider();
ourReportProvider = new DiagnosticReportProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
servlet.setResourceProviders(patientProvider, ourReportProvider, new DummyAdverseReactionResourceProvider());
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class DiagnosticReportProvider implements IResourceProvider {
private TagList myLastTags;
private IdDt myLastVersion;
public TagList getLastTags() {
return myLastTags;
}
@Override
public Class<? extends IResource> getResourceType() {
return DiagnosticReport.class;
}
@Create()
public MethodOutcome createDiagnosticReport(@ResourceParam DiagnosticReport thePatient) {
OperationOutcome outcome = new OperationOutcome();
outcome.addIssue().setDetails("FOOBAR");
throw new UnprocessableEntityException(outcome);
}
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyAdverseReactionResourceProvider implements IResourceProvider {
/*
* *********************
* NO NEW METHODS *********************
*/
@Create()
public MethodOutcome create(@ResourceParam AdverseReaction thePatient) {
IdDt id = new IdDt(thePatient.getIdentifier().get(0).getValue().getValue());
IdDt version = new IdDt(thePatient.getIdentifier().get(1).getValue().getValue());
return new MethodOutcome(id, version);
}
@Search()
public Collection<AdverseReaction> getAllResources() {
ArrayList<AdverseReaction> retVal = new ArrayList<AdverseReaction>();
AdverseReaction ar1 = new AdverseReaction();
ar1.setId("1");
retVal.add(ar1);
AdverseReaction ar2 = new AdverseReaction();
ar2.setId("2");
retVal.add(ar2);
return retVal;
}
@Override
public Class<? extends IResource> getResourceType() {
return AdverseReaction.class;
}
}
public static class PatientProvider implements IResourceProvider {
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
@Create()
public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
IdDt id = new IdDt(thePatient.getIdentifier().get(0).getValue().getValue());
if (thePatient.getId().isEmpty()==false) {
id=thePatient.getId();
}
return new MethodOutcome(id.withVersion("002"));
}
}
}

View File

@ -52,35 +52,38 @@ public class PagingTest {
String link;
String base = "http://localhost:" + ourPort;
{
HttpGet httpGet = new HttpGet(base+ "/Patient?_format=xml&_pretty=true");
HttpGet httpGet = new HttpGet(base + "/Patient?_format=xml&_pretty=true");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourContext.newXmlParser().parseBundle(responseContent);
assertEquals(5, bundle.getEntries().size());
assertEquals("0", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("4", bundle.getEntries().get(4).getId().getIdPart());
assertEquals(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=5&" + Constants.PARAM_COUNT + "=5&_format=xml&_pretty=true", bundle.getLinkNext().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=5&" + Constants.PARAM_COUNT + "=5&_format=xml&_pretty=true", bundle.getLinkNext()
.getValue());
assertNull(bundle.getLinkPrevious().getValue());
link=bundle.getLinkNext().getValue();
link = bundle.getLinkNext().getValue();
}
{
HttpGet httpGet = new HttpGet(link);
HttpGet httpGet = new HttpGet(link.replace("=xml", "=json"));
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourContext.newXmlParser().parseBundle(responseContent);
Bundle bundle = ourContext.newJsonParser().parseBundle(responseContent);
assertEquals(5, bundle.getEntries().size());
assertEquals("5", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("9", bundle.getEntries().get(4).getId().getIdPart());
assertNull(bundle.getLinkNext().getValue());
assertEquals(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=0&" + Constants.PARAM_COUNT + "=5&_format=xml&_pretty=true", bundle.getLinkPrevious().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=0&" + Constants.PARAM_COUNT + "=5&_format=json&_pretty=true", bundle.getLinkPrevious()
.getValue());
}
}
@Test
public void testSearchInexactOffset() throws Exception {
when(myPagingProvider.getDefaultPageSize()).thenReturn(5);
@ -90,9 +93,10 @@ public class PagingTest {
String base = "http://localhost:" + ourPort;
{
HttpGet httpGet = new HttpGet(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=8&" + Constants.PARAM_COUNT + "=5&_format=xml&_pretty=true");
HttpGet httpGet = new HttpGet(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=8&" + Constants.PARAM_COUNT + "=5&_format=xml&_pretty=true");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourContext.newXmlParser().parseBundle(responseContent);
@ -100,7 +104,8 @@ public class PagingTest {
assertEquals("8", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("9", bundle.getEntries().get(1).getId().getIdPart());
assertNull(bundle.getLinkNext().getValue());
assertEquals(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=3&" + Constants.PARAM_COUNT + "=5&_format=xml&_pretty=true", bundle.getLinkPrevious().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=3&" + Constants.PARAM_COUNT + "=5&_format=xml&_pretty=true", bundle.getLinkPrevious()
.getValue());
}
}
@ -115,37 +120,38 @@ public class PagingTest {
String link;
String base = "http://localhost:" + ourPort;
{
HttpGet httpGet = new HttpGet(base+ "/Patient?_count=2");
HttpGet httpGet = new HttpGet(base + "/Patient?_count=2");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourContext.newXmlParser().parseBundle(responseContent);
assertEquals(2, bundle.getEntries().size());
assertEquals("0", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("1", bundle.getEntries().get(1).getId().getIdPart());
assertEquals(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=2&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkNext().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=2&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkNext().getValue());
assertNull(bundle.getLinkPrevious().getValue());
link=bundle.getLinkNext().getValue();
link = bundle.getLinkNext().getValue();
}
{
HttpGet httpGet = new HttpGet(link);
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourContext.newXmlParser().parseBundle(responseContent);
assertEquals(2, bundle.getEntries().size());
assertEquals("2", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("3", bundle.getEntries().get(1).getId().getIdPart());
assertEquals(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=4&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkNext().getValue());
assertEquals(base + '/'+'?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=2&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkSelf().getValue());
assertEquals(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=0&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkPrevious().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=4&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkNext().getValue());
assertEquals(base + '/' + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=2&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkSelf().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=0&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkPrevious().getValue());
}
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();

View File

@ -109,71 +109,6 @@ public class ResfulServerMethodTest {
assertThat(responseContent, StringContains.containsString("AAAABBBB"));
}
@Test
public void testCreate() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(201, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("location").getValue());
}
@Test
public void testCreateJson() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity(new FhirContext().newJsonParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(201, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("location").getValue());
}
@Test
public void testCreateWithUnprocessableEntity() throws Exception {
DiagnosticReport report = new DiagnosticReport();
report.getIdentifier().setValue("001");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/DiagnosticReport");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(report), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(422, status.getStatusLine().getStatusCode());
OperationOutcome outcome = new FhirContext().newXmlParser().parseResource(OperationOutcome.class, new StringReader(responseContent));
assertEquals("FOOBAR", outcome.getIssueFirstRep().getDetails().getValue());
}
@Test
public void testDateRangeParam() throws Exception {
@ -1102,18 +1037,6 @@ public class ResfulServerMethodTest {
*/
public static class DummyAdverseReactionResourceProvider implements IResourceProvider {
/*
* *********************
* NO NEW METHODS *********************
*/
@Create()
public MethodOutcome create(@ResourceParam AdverseReaction thePatient) {
IdDt id = new IdDt(thePatient.getIdentifier().get(0).getValue().getValue());
IdDt version = new IdDt(thePatient.getIdentifier().get(1).getValue().getValue());
return new MethodOutcome(id, version);
}
@Search()
public Collection<AdverseReaction> getAllResources() {
ArrayList<AdverseReaction> retVal = new ArrayList<AdverseReaction>();
@ -1148,12 +1071,6 @@ public class ResfulServerMethodTest {
throw new ResourceNotFoundException("AAAABBBB");
}
@Create()
public MethodOutcome createDiagnosticReport(@ResourceParam DiagnosticReport thePatient) {
OperationOutcome outcome = new OperationOutcome();
outcome.addIssue().setDetails("FOOBAR");
throw new UnprocessableEntityException(outcome);
}
@Delete()
public void deleteDiagnosticReport(@IdParam IdDt theId) {
@ -1176,12 +1093,6 @@ public class ResfulServerMethodTest {
*/
public static class DummyPatientResourceProvider implements IResourceProvider {
@Create()
public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
IdDt id = new IdDt(thePatient.getIdentifier().get(0).getValue().getValue());
IdDt version = new IdDt(thePatient.getIdentifier().get(1).getValue().getValue());
return new MethodOutcome(id, version);
}
@Delete()
public MethodOutcome deletePatient(@IdParam IdDt theId) {

View File

@ -10,9 +10,11 @@ import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
@ -114,6 +116,39 @@ public class ServerFeaturesTest {
}
@Test
public void testCors() throws Exception {
servlet.setCorsAllowDomain("http://foo.com");
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
httpGet.addHeader("Accept", Constants.CT_FHIR_XML);
HttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
Header origin = status.getFirstHeader(Constants.HEADER_CORS_ALLOW_ORIGIN);
assertEquals("http://foo.com", origin.getValue());
}
@Test
public void testOptions() throws Exception {
servlet.setCorsAllowDomain("http://foo.com");
HttpOptions httpGet = new HttpOptions("http://localhost:" + ourPort + "/");
httpGet.addHeader("Accept", Constants.CT_FHIR_XML);
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
Header origin = status.getFirstHeader(Constants.HEADER_CORS_ALLOW_ORIGIN);
assertEquals("http://foo.com", origin.getValue());
assertThat(responseContent,StringContains.containsString("<Conformance"));
}
@Test
public void testAcceptHeaderWithMultiple() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
@ -235,8 +270,10 @@ public class ServerFeaturesTest {
@Before
public void before() {
servlet.setServerAddressStrategy(new IncomingRequestAddressStrategy());
servlet.setCorsAllowDomain(null);
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = RandomServerPortProvider.findFreePort();

View File

@ -1053,6 +1053,11 @@ public abstract class BaseFhirDao implements IDao {
if (entity.getId() == null) {
myEntityManager.persist(entity);
if (entity.getForcedId() != null) {
myEntityManager.persist(entity.getForcedId());
}
} else {
entity = myEntityManager.merge(entity);
}

View File

@ -24,6 +24,7 @@ import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.validation.ConstraintViolationException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -42,6 +43,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.entity.BaseHasResource;
import ca.uhn.fhir.jpa.entity.BaseTag;
import ca.uhn.fhir.jpa.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber;
@ -403,7 +405,8 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed ("
+ ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
}
String likeExpression = normalizeString(rawSearchTerm);
@ -461,10 +464,12 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system);
throw new InvalidRequestException("Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed ("
+ ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system);
}
if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) {
throw new InvalidRequestException("Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code);
throw new InvalidRequestException("Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH
+ "): " + code);
}
ArrayList<Predicate> singleCodePredicates = (new ArrayList<Predicate>());
@ -533,9 +538,20 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
if (theResource.getId().isEmpty() == false) {
if (isValidPid(theResource.getId())) {
throw new UnprocessableEntityException("This server cannot create an entity with a numeric ID - Numeric IDs are server assigned");
throw new UnprocessableEntityException(
"This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID");
}
createForcedIdIfNeeded(entity, theResource.getId());
if (entity.getForcedId() != null) {
try {
translateForcedIdToPid(theResource.getId());
throw new UnprocessableEntityException("Can not create entity with ID[" + theResource.getId().getValue() + "], constraint violation occurred");
} catch (ResourceNotFoundException e) {
// good, this ID doesn't exist so we can create it
}
}
}
updateEntity(theResource, entity, false, false);
@ -608,7 +624,8 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
final T current = currentTmp;
String querySring = "SELECT count(h) FROM ResourceHistoryTable h " + "WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE" + " AND h.myUpdated < :END" + (theSince != null ? " AND h.myUpdated >= :SINCE" : "");
String querySring = "SELECT count(h) FROM ResourceHistoryTable h " + "WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE" + " AND h.myUpdated < :END"
+ (theSince != null ? " AND h.myUpdated >= :SINCE" : "");
TypedQuery<Long> countQuery = myEntityManager.createQuery(querySring, Long.class);
countQuery.setParameter("PID", theId.getIdPartAsLong());
countQuery.setParameter("RESTYPE", resourceType);
@ -646,8 +663,9 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
retVal.add(current);
}
TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery("SELECT h FROM ResourceHistoryTable h WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE AND h.myUpdated < :END " + (theSince != null ? " AND h.myUpdated >= :SINCE" : "")
+ " ORDER BY h.myUpdated ASC", ResourceHistoryTable.class);
TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery(
"SELECT h FROM ResourceHistoryTable h WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE AND h.myUpdated < :END "
+ (theSince != null ? " AND h.myUpdated >= :SINCE" : "") + " ORDER BY h.myUpdated ASC", ResourceHistoryTable.class);
q.setParameter("PID", theId.getIdPartAsLong());
q.setParameter("RESTYPE", resourceType);
q.setParameter("END", end.getValue(), TemporalType.TIMESTAMP);
@ -716,7 +734,8 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]");
}
if (sp.getParamType() != SearchParamTypeEnum.TOKEN) {
throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "] is not a token type, only token is supported");
throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName
+ "] is not a token type, only token is supported");
}
}
@ -743,7 +762,8 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
private void validateResourceType(BaseHasResource entity) {
if (!myResourceName.equals(entity.getResourceType())) {
throw new ResourceNotFoundException("Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type " + entity.getResourceType());
throw new ResourceNotFoundException("Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type "
+ entity.getResourceType());
}
}
@ -767,7 +787,8 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
if (entity == null) {
if (theId.hasVersionIdPart()) {
TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery("SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class);
TypedQuery<ResourceHistoryTable> q = myEntityManager.createQuery(
"SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class);
q.setParameter("RID", theId.getIdPartAsLong());
q.setParameter("RTYP", myResourceName);
q.setParameter("RVER", theId.getVersionIdPartAsLong());
@ -1048,8 +1069,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
/**
* If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to
* share the same value.
* If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to share the same value.
*/
public void setSecondaryPrimaryKeyParamName(String theSecondaryPrimaryKeyParamName) {
mySecondaryPrimaryKeyParamName = theSecondaryPrimaryKeyParamName;

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.entity;
import java.util.Collection;
import java.util.Date;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
@ -14,6 +15,8 @@ import javax.persistence.OneToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.validator.cfg.context.Cascadable;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;

View File

@ -18,8 +18,10 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.JpaConformanceProvider;
import ca.uhn.fhir.jpa.provider.JpaSystemProvider;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.Conformance;
@ -36,6 +38,7 @@ import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ExtensionConstants;
import ca.uhn.test.jpasrv.ObservationResourceProvider;
import ca.uhn.test.jpasrv.OrganizationResourceProvider;
@ -51,7 +54,8 @@ public class CompleteResourceProviderTest {
private static IFhirResourceDao<Questionnaire> questionnaireDao;
private static IGenericClient ourClient;
private static IFhirResourceDao<Observation> observationDao;
// private static JpaConformanceProvider ourConfProvider;
// private static JpaConformanceProvider ourConfProvider;
// @Test
// public void test01UploadTestResources() throws Exception {
@ -92,20 +96,46 @@ public class CompleteResourceProviderTest {
}
@Test
public void testCreateWithId() {
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testCreateWithId01");
IdDt p1Id = ourClient.create().resource(p1).withId("testCreateWithId").execute().getId();
assertThat(p1Id.getValue(), containsString("Patient/testCreateWithId/_history"));
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testCreateWithId01")).encodedJson().prettyPrint().execute();
assertEquals(1, actual.size());
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
/*
* ensure that trying to create the same ID again fails appropriately
*/
try {
ourClient.create().resource(p1).withId("testCreateWithId").execute().getId();
fail();
} catch (UnprocessableEntityException e) {
// good
}
Bundle history = ourClient.history(null, (String)null, null, null);
assertEquals(p1Id.getIdPart(), history.getEntries().get(0).getId().getIdPart());
assertNotNull(history.getEntries().get(0).getResource());
}
@Test
public void testSearchByIdentifierWithoutSystem() {
Patient p1 = new Patient();
p1.addIdentifier().setValue("testSearchByIdentifierWithoutSystem01");
IdDt p1Id = ourClient.create(p1).getId();
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")).encodedJson().prettyPrint().execute();
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")).encodedJson().prettyPrint()
.execute();
assertEquals(1, actual.size());
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
}
@Test
public void testSearchByResourceChain() {
Organization o1 = new Organization();
@ -137,48 +167,48 @@ public class CompleteResourceProviderTest {
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
}
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CompleteResourceProviderTest.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CompleteResourceProviderTest.class);
@Test
public void testInsertUpdatesConformance() {
// Conformance conf = ourConfProvider.getServerConformance();
// ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conf));
//
// RestResource res=null;
// for (Rest nextRest : conf.getRest()) {
// for (RestResource nextRes : nextRest.getResource()) {
// if (nextRes.getType().getValueAsEnum()==ResourceTypeEnum.PATIENT) {
// res = nextRes;
// }
// }
// }
// List<ExtensionDt> resCounts = res.getUndeclaredExtensionsByUrl(ExtensionConstants.CONF_RESOURCE_COUNT);
//
// int initial = 0;
// Conformance conf = ourConfProvider.getServerConformance();
// ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conf));
//
// RestResource res=null;
// for (Rest nextRest : conf.getRest()) {
// for (RestResource nextRes : nextRest.getResource()) {
// if (nextRes.getType().getValueAsEnum()==ResourceTypeEnum.PATIENT) {
// res = nextRes;
// }
// }
// }
// List<ExtensionDt> resCounts = res.getUndeclaredExtensionsByUrl(ExtensionConstants.CONF_RESOURCE_COUNT);
//
// int initial = 0;
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01");
p1.addName().addFamily("testSearchByResourceChainFamily01").addGiven("testSearchByResourceChainGiven01");
ourClient.create(p1).getId();
// conf = ourConfProvider.getServerConformance();
// res=null;
// for (Rest nextRest : conf.getRest()) {
// for (RestResource nextRes : nextRest.getResource()) {
// if (nextRes.getType().getValueAsEnum()==ResourceTypeEnum.PATIENT) {
// res = nextRes;
// }
// }
// }
// resCounts = res.getUndeclaredExtensionsByUrl(ExtensionConstants.CONF_RESOURCE_COUNT);
// assertNotNull(resCounts);
// assertEquals(1, resCounts.size());
// DecimalDt number = (DecimalDt) resCounts.get(0).getValue();
// assertEquals(initial+1, number.getValueAsInteger());
// conf = ourConfProvider.getServerConformance();
// res=null;
// for (Rest nextRest : conf.getRest()) {
// for (RestResource nextRes : nextRest.getResource()) {
// if (nextRes.getType().getValueAsEnum()==ResourceTypeEnum.PATIENT) {
// res = nextRes;
// }
// }
// }
// resCounts = res.getUndeclaredExtensionsByUrl(ExtensionConstants.CONF_RESOURCE_COUNT);
// assertNotNull(resCounts);
// assertEquals(1, resCounts.size());
// DecimalDt number = (DecimalDt) resCounts.get(0).getValue();
// assertEquals(initial+1, number.getValueAsInteger());
}
@Test
public void testInsertBadReference() {
Patient p1 = new Patient();
@ -195,7 +225,6 @@ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger
}
@Test
public void testSaveAndRetrieveExistingNarrative() {
Patient p1 = new Patient();
@ -218,7 +247,7 @@ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger
Patient actual = ourClient.read(Patient.class, newId);
assertThat(actual.getText().getDiv().getValueAsString(), containsString("<td>Identifier</td><td>testSearchByResourceChain01</td>"));
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
@ -251,9 +280,11 @@ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger
restServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
IFhirSystemDao systemDao = (IFhirSystemDao) ourAppCtx.getBean("mySystemDao", IFhirSystemDao.class);
// ourConfProvider = new JpaConformanceProvider(restServer, systemDao, Collections.singletonList((IFhirResourceDao)patientDao));
JpaSystemProvider systemProv = new JpaSystemProvider(systemDao);
restServer.setPlainProviders(systemProv);
// ourConfProvider = new JpaConformanceProvider(restServer, systemDao, Collections.singletonList((IFhirResourceDao)patientDao));
int myPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(myPort);

View File

@ -6,7 +6,13 @@
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
<dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-tester-overlay?includes=**/**&amp;excludes=META-INF/MANIFEST.MF">
<dependent-module archiveName="hapi-fhir-jpaserver-base-0.5-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-jpaserver-base/hapi-fhir-jpaserver-base">
<dependency-type>uses</dependency-type>
</dependent-module>
<dependent-module archiveName="hapi-fhir-base-0.5-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
<dependency-type>uses</dependency-type>
</dependent-module>
<dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-testpage-overlay?includes=**/**&amp;excludes=META-INF/MANIFEST.MF">
<dependency-type>consumes</dependency-type>
</dependent-module>
<dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&amp;excludes=META-INF/MANIFEST.MF">

View File

@ -77,7 +77,12 @@
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j_version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j_version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

View File

@ -9,7 +9,6 @@ import org.springframework.context.ApplicationContext;
import org.springframework.web.context.ContextLoaderListener;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.JpaConformanceProvider;
import ca.uhn.fhir.jpa.provider.JpaSystemProvider;
@ -69,6 +68,7 @@ public class TestRestfulServer extends RestfulServer {
setServerConformanceProvider(confProvider);
setUseBrowserFriendlyContentTypes(true);
setCorsAllowDomain("*");
String baseUrl = System.getProperty("fhir.baseurl");
if (StringUtils.isBlank(baseUrl)) {

View File

@ -0,0 +1,26 @@
package ca.uhn.fhirtest;
import ca.uhn.fhir.model.dstu.resource.Profile;
import ca.uhn.fhir.model.dstu.resource.Profile.ExtensionDefn;
import ca.uhn.fhir.model.dstu.valueset.DataTypeEnum;
import ca.uhn.fhir.model.dstu.valueset.ExtensionContextEnum;
public class PopulateProfiles {
public static void main(String[] args) {
Profile hapiExtensions = new Profile();
ExtensionDefn ext = hapiExtensions.addExtensionDefn();
ext.addContext("Conformance.rest.resource");
ext.getCode().setValue("resourceCount");
ext.getContextType().setValueAsEnum(ExtensionContextEnum.RESOURCE);
ext.getDisplay().setValue("Resource count on server");
ext.getDefinition().addType().setCode(DataTypeEnum.DECIMAL);
}
}

View File

@ -53,6 +53,7 @@ import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.client.GenericClient;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.gclient.ICreateTyped;
import ca.uhn.fhir.rest.gclient.IQuery;
import ca.uhn.fhir.rest.gclient.IUntypedQuery;
import ca.uhn.fhir.rest.gclient.StringParam;
@ -91,10 +92,6 @@ public class Controller {
return "about";
}
private String logPrefix(ModelMap theModel) {
return "[server=" + theModel.get("serverId") + "] - ";
}
@RequestMapping(value = { "/conformance" })
public String actionConformance(final HomeRequest theRequest, final BindingResult theBindingResult, final ModelMap theModel) {
addCommonParams(theRequest, theModel);
@ -160,18 +157,6 @@ public class Controller {
return "result";
}
private ResultType handleClientException(GenericClient theClient, Exception e, ModelMap theModel) {
ResultType returnsResource;
returnsResource = ResultType.NONE;
ourLog.warn("Failed to invoke server", e);
if (theClient.getLastResponse() == null) {
theModel.put("errorMsg", "Error: " + e.getMessage());
}
return returnsResource;
}
@RequestMapping(value = { "/get-tags" })
public String actionGetTags(HttpServletRequest theReq, HomeRequest theRequest, BindingResult theBindingResult, ModelMap theModel) {
addCommonParams(theRequest, theModel);
@ -527,6 +512,12 @@ public class Controller {
return "result";
}
@RequestMapping(value = { "/update" })
public String actionUpdate(final HttpServletRequest theReq, final HomeRequest theRequest, final BindingResult theBindingResult, final ModelMap theModel) {
doActionCreateOrValidate(theReq, theRequest, theBindingResult, theModel, "update");
return "result";
}
@RequestMapping(value = { "/validate" })
public String actionValidate(final HttpServletRequest theReq, final HomeRequest theRequest, final BindingResult theBindingResult, final ModelMap theModel) {
doActionCreateOrValidate(theReq, theRequest, theBindingResult, theModel, "validate");
@ -614,13 +605,17 @@ public class Controller {
client.validate(resource);
} else {
String id = theReq.getParameter("resource-create-id");
if (isNotBlank(id)) {
if ("update".equals(theMethod)) {
outcomeDescription = "Update Resource";
client.update(id, resource);
update = true;
} else {
outcomeDescription = "Create Resource";
client.create(resource);
ICreateTyped create = client.create().resource(body);
if (isNotBlank(id)) {
create.withId(id);
}
create.execute();
}
}
} catch (Exception e) {
@ -802,19 +797,19 @@ public class Controller {
}
try {
str=URLDecoder.decode(str, "UTF-8");
str = URLDecoder.decode(str, "UTF-8");
} catch (UnsupportedEncodingException e) {
ourLog.error("Should not happen",e);
ourLog.error("Should not happen", e);
}
StringBuilder b = new StringBuilder();
b.append("<span class='hlUrlBase'>");
boolean inParams = false;
for (int i = 0; i < str.length(); i++) {
char nextChar = str.charAt(i);
// char nextChar2 = i < str.length()-2 ? str.charAt(i+1):' ';
// char nextChar3 = i < str.length()-2 ? str.charAt(i+2):' ';
// char nextChar2 = i < str.length()-2 ? str.charAt(i+1):' ';
// char nextChar3 = i < str.length()-2 ? str.charAt(i+2):' ';
if (!inParams) {
if (nextChar == '?') {
inParams = true;
@ -830,8 +825,8 @@ public class Controller {
b.append("</span><wbr /><span class='hlControl'>&amp;</span><span class='hlTagName'>");
} else if (nextChar == '=') {
b.append("</span><span class='hlControl'>=</span><span class='hlAttr'>");
// }else if (nextChar=='%' && Character.isLetterOrDigit(nextChar2)&& Character.isLetterOrDigit(nextChar3)) {
// URLDecoder.decode(s, enc)
// }else if (nextChar=='%' && Character.isLetterOrDigit(nextChar2)&& Character.isLetterOrDigit(nextChar3)) {
// URLDecoder.decode(s, enc)
} else {
b.append(nextChar);
}
@ -853,6 +848,18 @@ public class Controller {
return def;
}
private ResultType handleClientException(GenericClient theClient, Exception e, ModelMap theModel) {
ResultType returnsResource;
returnsResource = ResultType.NONE;
ourLog.warn("Failed to invoke server", e);
if (theClient.getLastResponse() == null) {
theModel.put("errorMsg", "Error: " + e.getMessage());
}
return returnsResource;
}
private boolean handleSearchParam(String paramIdxString, HttpServletRequest theReq, IQuery theQuery, JsonGenerator theClientCodeJsonWriter) {
String nextName = theReq.getParameter("param." + paramIdxString + ".name");
if (isBlank(nextName)) {
@ -970,6 +977,10 @@ public class Controller {
return conformance;
}
private String logPrefix(ModelMap theModel) {
return "[server=" + theModel.get("serverId") + "] - ";
}
private String parseNarrative(EncodingEnum theCtEnum, String theResultBody) {
try {
IResource resource = theCtEnum.newParser(myCtx).parseResource(theResultBody);

View File

@ -337,12 +337,11 @@
</div>
<br clear="all"/>
<!-- Create/Update -->
<!-- Create -->
<div class="row-fluid">
<b>Create/Update</b> an instance of the resource. If no ID is specified,
a new resource will be created. If an ID is specified, the existing
resource with that ID will be updated.
<b>Create</b> an instance of the resource. Generally you do not need to specify an ID
but you may force the server to use a specific ID by including one.
</div>
<div class="row-fluid top-buffer">
<div class="col-sm-2">
@ -358,7 +357,7 @@
<div class="input-group-addon">
ID
</div>
<input type="text" class="form-control" id="resource-create-id" placeholder="(add for update)" th:value="${updateResourceId}"/>
<input type="text" class="form-control" id="resource-create-id" placeholder="(optional)"/>
</div>
</div>
</div>
@ -370,37 +369,16 @@
<span class="loadingStar">*</span>
</div>
<textarea class="form-control" id="resource-create-body" style="white-space: nowrap; overflow: auto;" placeholder="(place resource body here)" rows="1">
<th:block th:if="${updateResource} != null" th:text="${updateResource}"/>
</textarea>
</div>
</div>
</div>
<script type="text/javascript">
var buttonChanger = function() {
var val = $('#resource-create-id').val();
if (val != "") {
//$('#resource-create-btn').text("Update");
$("#resource-create-btn").fadeOut(function() {
$(this).html('<i class="fa fa-pencil"></i> Update').fadeIn();
});
} else {
$("#resource-create-btn").fadeOut(function() {
$(this).html('<i class="fa fa-send"></i> Create').fadeIn();
});
//$('#resource-create-btn').text("Create");
}
};
var textAreaChanger = function() {
createBodyOriginalHeight = $('#resource-create-body').height();
$('#resource-create-body').animate({height: "200px"}, 500);
}
$('#resource-create-id').change(buttonChanger);
$('#resource-create-id').keyup(buttonChanger);
$('#resource-create-body').focus(textAreaChanger);
/*$('#resource-create-body').blur(
function() {
$('#resource-create-body').animate({height: "34px"}, 500);
});*/
$('#resource-create-btn').click(
function() {
var btn = $(this);
@ -422,6 +400,72 @@
</div>
<br clear="all"/>
<!-- Update -->
<div class="row-fluid">
<b>Update</b> an existing instance of the resource by ID.
</div>
<div class="row-fluid top-buffer">
<div class="col-sm-2">
<button type="button" id="resource-update-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<i class="fa fa-send"></i>
Update
</button>
</div>
<div class='col-sm-3'>
<div class="form-group">
<div class='input-group date'>
<div class="input-group-addon">
ID
<span class="loadingStar">*</span>
</div>
<input type="text" class="form-control" id="resource-update-id" placeholder="(resource ID)" th:value="${updateResourceId}"/>
</div>
</div>
</div>
<div class='col-sm-7'>
<div class="form-group">
<div class='input-group date'>
<div class="input-group-addon">
Contents
<span class="loadingStar">*</span>
</div>
<textarea class="form-control" id="resource-update-body" style="white-space: nowrap; overflow: auto;" placeholder="(place resource body here)" rows="1">
<th:block th:if="${updateResource} != null" th:text="${updateResource}"/>
</textarea>
</div>
</div>
</div>
<script type="text/javascript">
var textAreaChanger = function() {
updateBodyOriginalHeight = $('#resource-update-body').height();
$('#resource-update-body').animate({height: "200px"}, 500);
}
$('#resource-update-body').focus(textAreaChanger);
$('#resource-update-btn').click(
function() {
var btn = $(this);
btn.button('loading');
var id = $('#resource-update-id').val();
// Note we're using resource-create-id even though this is an update because
// the controller expects that...
if (id != null) btn.append($('<input />', { type: 'hidden', name: 'resource-create-id', value: id }));
var body = $('#resource-update-body').val();
btn.append($('<input />', { type: 'hidden', name: 'resource-create-body', value: body }));
$("#outerForm").attr("action", "update").submit();
});
$( document ).ready(function() {
if ($('#resource-update-id').val() != "") {
buttonChanger();
textAreaChanger();
$('#resource-update-body').focus();
}
});
</script>
</div>
<br clear="all"/>
<!-- Validate -->
<div class="row-fluid">

View File

@ -81,7 +81,7 @@
<maven_site_plugin_version>3.3</maven_site_plugin_version>
<mockito_version>1.9.5</mockito_version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<slf4j_version>1.7.2</slf4j_version>
<slf4j_version>1.7.7</slf4j_version>
<spring_version>4.0.1.RELEASE</spring_version>
<thymeleaf-version>2.1.3.RELEASE</thymeleaf-version>
</properties>