diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Transaction.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Transaction.java new file mode 100644 index 00000000000..ad4ae41d052 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Transaction.java @@ -0,0 +1,41 @@ +package ca.uhn.fhir.rest.annotation; + +/* + * #%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.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import ca.uhn.fhir.model.api.Bundle; + +/** + * RESTful method annotation to be used for the FHIR transaction method. + * + *

+ * This method should have a parameter of type {@link Bundle} annotated with the {@link TransactionParam} annotation. + *

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Transaction { + // nothing +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/TransactionParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/TransactionParam.java new file mode 100644 index 00000000000..df98f675344 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/TransactionParam.java @@ -0,0 +1,32 @@ +package ca.uhn.fhir.rest.annotation; + +/* + * #%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.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(value=ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface TransactionParam { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java index c1f30afe1f7..bdb66a9f3b4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java @@ -56,6 +56,7 @@ import ca.uhn.fhir.rest.annotation.History; import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.annotation.Transaction; import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.annotation.Validate; import ca.uhn.fhir.rest.api.MethodOutcome; @@ -172,8 +173,9 @@ public abstract class BaseMethodBinding implements IClientResponseHandler GetTags getTags = theMethod.getAnnotation(GetTags.class); AddTags addTags = theMethod.getAnnotation(AddTags.class); DeleteTags deleteTags = theMethod.getAnnotation(DeleteTags.class); + Transaction transaction = theMethod.getAnnotation(Transaction.class); // ** if you add another annotation above, also add it to the next line: - if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate, getTags, addTags, deleteTags)) { + if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate, getTags, addTags, deleteTags,transaction)) { return null; } @@ -283,6 +285,8 @@ public abstract class BaseMethodBinding implements IClientResponseHandler return new AddTagsMethodBinding(theMethod, theContext, theProvider, addTags); } else if (deleteTags != null) { return new DeleteTagsMethodBinding(theMethod, theContext, theProvider, deleteTags); + } else if (transaction != null) { + return new TransactionMethodBinding(theMethod, theContext, theProvider); } else { throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName()); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java index 5ba620f465a..f6548337c08 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java @@ -209,13 +209,15 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding theResourceMetadata, ResourceMetadataKeyEnum theKey) { Object retValObj = theResourceMetadata.get(theKey); if (retValObj == null) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java index 94dd786fb60..fb66cf76ae6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java @@ -1,45 +1,45 @@ package ca.uhn.fhir.rest.method; -import java.io.IOException; -import java.io.Reader; +import static org.apache.commons.lang3.StringUtils.*; + import java.lang.reflect.Method; import java.util.List; -import java.util.Map; - -import javax.servlet.http.HttpServletResponse; +import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.annotation.TransactionParam; import ca.uhn.fhir.rest.client.BaseClientInvocation; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; +import ca.uhn.fhir.rest.param.TransactionParameter; +import ca.uhn.fhir.rest.param.IParameter; +import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class TransactionMethodBinding extends BaseResourceReturningMethodBinding { + private int myResourceParameterIndex; public TransactionMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) { super(null, theMethod, theConetxt, theProvider); - } + + myResourceParameterIndex = -1; + int index=0; + for (IParameter next : getParameters()) { + if (next instanceof TransactionParameter) { + myResourceParameterIndex = index; + } + index++; + } - @Override - public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException { - // TODO Auto-generated method stub - return null; - } - - @Override - public String getResourceName() { - return null; - } - - @Override - public RestfulOperationTypeEnum getResourceOperationType() { - return null; + if (myResourceParameterIndex==-1) { + throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " does not have a parameter annotated with the @" + TransactionParam.class + " annotation"); + } } @Override @@ -47,22 +47,19 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding return RestfulOperationSystemEnum.TRANSACTION; } - @Override - public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { - // TODO Auto-generated method stub - return null; - } - - @Override - public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException { - // TODO Auto-generated method stub - - } @Override public boolean incomingServerRequestMatchesMethod(Request theRequest) { - // TODO Auto-generated method stub - return false; + if (theRequest.getRequestType() != RequestType.POST) { + return false; + } + if (isNotBlank(theRequest.getOperation())) { + return false; + } + if (isNotBlank(theRequest.getResourceName())) { + return false; + } + return true; } @Override @@ -72,6 +69,26 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding @Override public List invokeServer(Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { + @SuppressWarnings("unchecked") + List retVal=(List) invokeServerMethod(theMethodParams); + return retVal; + } + + @Override + protected Object parseRequestObject(Request theRequest) { + EncodingEnum encoding = determineResponseEncoding(theRequest); + IParser parser = encoding.newParser(getContext()); + Bundle bundle = parser.parseBundle(theRequest.getInputReader()); + return bundle; + } + + @Override + public RestfulOperationTypeEnum getResourceOperationType() { + return null; + } + + @Override + public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { // TODO Auto-generated method stub return null; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java index 80d9af19da5..580e2986f4c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java @@ -50,6 +50,7 @@ import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.api.annotation.TagListParam; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.IntegerDt; +import ca.uhn.fhir.rest.annotation.TransactionParam; import ca.uhn.fhir.rest.annotation.Count; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IncludeParam; @@ -225,6 +226,8 @@ public class ParameterUtil { param = new CountParameter(); } else if (nextAnnotation instanceof Sort) { param = new SortParameter(); + } else if (nextAnnotation instanceof TransactionParam) { + param = new TransactionParameter(); } else { continue; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TransactionParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TransactionParameter.java new file mode 100644 index 00000000000..7a119891232 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TransactionParameter.java @@ -0,0 +1,68 @@ +package ca.uhn.fhir.rest.param; + +/* + * #%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.lang.reflect.Method; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.rest.annotation.TransactionParam; +import ca.uhn.fhir.rest.client.BaseClientInvocation; +import ca.uhn.fhir.rest.method.Request; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +public class TransactionParameter implements IParameter { + + public TransactionParameter() { + } + + @Override + public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map> theTargetQueryArguments, BaseClientInvocation theClientInvocation) throws InternalErrorException { + // TODO Auto-generated method stub + + } + + @Override + public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException { + Bundle resource = (Bundle) theRequestContents; + return resource; + } + + @Override + public void initializeTypes(Method theMethod, Class> theOuterCollectionType, Class> theInnerCollectionType, Class theParameterType) { + if (theOuterCollectionType != null) { + throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + TransactionParam.class.getName() + " but can not be a collection of collections"); + } + if (theInnerCollectionType.equals(List.class)==false) { + throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + TransactionParam.class.getName() + " but is not of type List<" + IResource.class.getCanonicalName()+">"); + } + if (theParameterType.equals(IResource.class)==false) { + throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + TransactionParam.class.getName() + " but is not of type List<" + IResource.class.getCanonicalName()+">"); + } + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index c3ffd9cf8ef..154dac73b2f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -89,12 +89,10 @@ public class RestfulServer extends HttpServlet { } /** - * This method is called prior to sending a response to incoming requests. - * It is used to add custom headers. + * This method is called prior to sending a response to incoming requests. It is used to add custom headers. *

- * Use caution if overriding this method: it is recommended to call - * super.addHeadersToResponse to avoid inadvertantly disabling - * functionality. + * Use caution if overriding this method: it is recommended to call super.addHeadersToResponse to avoid + * inadvertantly disabling functionality. *

*/ public void addHeadersToResponse(HttpServletResponse theHttpResponse) { @@ -102,17 +100,15 @@ public class RestfulServer extends HttpServlet { } /** - * Gets the {@link FhirContext} associated with this server. For efficient - * processing, resource providers and plain providers should generally use - * this context if one is needed, as opposed to creating their own. + * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain + * providers should generally use this context if one is needed, as opposed to creating their own. */ public FhirContext getFhirContext() { return myFhirContext; } /** - * Provides the non-resource specific providers which implement method calls - * on this server + * Provides the non-resource specific providers which implement method calls on this server * * @see #getResourceProviders() */ @@ -139,12 +135,11 @@ public class RestfulServer extends HttpServlet { } /** - * Returns the server conformance provider, which is the provider that is - * used to generate the server's conformance (metadata) statement. + * Returns the server conformance provider, which is the provider that is used to generate the server's conformance + * (metadata) statement. *

- * By default, the {@link ServerConformanceProvider} is used, but this can - * be changed, or set to null if you do not wish to export a - * conformance statement. + * By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to null + * if you do not wish to export a conformance statement. *

*/ public Object getServerConformanceProvider() { @@ -152,9 +147,8 @@ public class RestfulServer extends HttpServlet { } /** - * Gets the server's name, as exported in conformance profiles exported by - * the server. This is informational only, but can be helpful to set with - * something appropriate. + * Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, + * but can be helpful to set with something appropriate. * * @see RestfulServer#setServerName(StringDt) */ @@ -167,19 +161,17 @@ public class RestfulServer extends HttpServlet { } /** - * Gets the server's version, as exported in conformance profiles exported - * by the server. This is informational only, but can be helpful to set with - * something appropriate. + * Gets the server's version, as exported in conformance profiles exported by the server. This is informational + * only, but can be helpful to set with something appropriate. */ public String getServerVersion() { return myServerVersion; } /** - * Initializes the server. Note that this method is final to avoid - * accidentally introducing bugs in implementations, but subclasses may put - * initialization code in {@link #initialize()}, which is called immediately - * before beginning initialization of the restful server's internal init. + * Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations, + * but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning + * initialization of the restful server's internal init. */ @Override public final void init() throws ServletException { @@ -237,8 +229,7 @@ public class RestfulServer extends HttpServlet { } /** - * Sets the non-resource specific providers which implement method calls on - * this server. + * Sets the non-resource specific providers which implement method calls on this server. * * @see #setResourceProviders(Collection) */ @@ -247,8 +238,7 @@ public class RestfulServer extends HttpServlet { } /** - * Sets the non-resource specific providers which implement method calls on - * this server. + * Sets the non-resource specific providers which implement method calls on this server. * * @see #setResourceProviders(Collection) */ @@ -257,8 +247,7 @@ public class RestfulServer extends HttpServlet { } /** - * Sets the non-resource specific providers which implement method calls on - * this server + * Sets the non-resource specific providers which implement method calls on this server * * @see #setResourceProviders(Collection) */ @@ -288,19 +277,16 @@ public class RestfulServer extends HttpServlet { } /** - * Returns the server conformance provider, which is the provider that is - * used to generate the server's conformance (metadata) statement. + * Returns the server conformance provider, which is the provider that is used to generate the server's conformance + * (metadata) statement. *

- * By default, the {@link ServerConformanceProvider} is used, but this can - * be changed, or set to null if you do not wish to export a - * conformance statement. + * By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to null + * if you do not wish to export a conformance statement. *

- * Note that this method can only be called before the server is - * initialized. + * Note that this method can only be called before the server is initialized. * * @throws IllegalStateException - * Note that this method can only be called prior to - * {@link #init() initialization} and will throw an + * Note that this method can only be called prior to {@link #init() initialization} and will throw an * {@link IllegalStateException} if called after that. */ public void setServerConformanceProvider(Object theServerConformanceProvider) { @@ -311,9 +297,8 @@ public class RestfulServer extends HttpServlet { } /** - * Gets the server's name, as exported in conformance profiles exported by - * the server. This is informational only, but can be helpful to set with - * something appropriate. + * Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, + * but can be helpful to set with something appropriate. * * @see RestfulServer#setServerName(StringDt) */ @@ -322,18 +307,16 @@ public class RestfulServer extends HttpServlet { } /** - * Gets the server's version, as exported in conformance profiles exported - * by the server. This is informational only, but can be helpful to set with - * something appropriate. + * Gets the server's version, as exported in conformance profiles exported by the server. This is informational + * only, but can be helpful to set with something appropriate. */ public void setServerVersion(String theServerVersion) { myServerVersion = theServerVersion; } /** - * If set to true (default is false), the server will use - * browser friendly content-types (instead of standard FHIR ones) when it - * detects that the request is coming from a browser instead of a FHIR + * If set to true (default is false), the server will use browser friendly content-types (instead of + * standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR */ public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) { myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes; @@ -361,7 +344,7 @@ public class RestfulServer extends HttpServlet { private void findResourceMethods(Object theProvider, Class clazz) { for (Method m : clazz.getDeclaredMethods()) { if (!Modifier.isPublic(m.getModifiers())) { - ourLog.debug("Ignoring non-public method: {}",m); + ourLog.debug("Ignoring non-public method: {}", m); } else { if (!Modifier.isStatic(m.getModifiers())) { ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName()); @@ -521,13 +504,12 @@ public class RestfulServer extends HttpServlet { Map params = new HashMap(theRequest.getParameterMap()); StringTokenizer tok = new StringTokenizer(requestPath, "/"); - if (!tok.hasMoreTokens()) { - throw new ResourceNotFoundException("No resource name specified"); - } - resourceName = tok.nextToken(); - if (resourceName.startsWith("_")) { - operation = resourceName; - resourceName = null; + if (tok.hasMoreTokens()) { + resourceName = tok.nextToken(); + if (resourceName.startsWith("_")) { + operation = resourceName; + resourceName = null; + } } ResourceBinding resourceBinding = null; @@ -570,16 +552,16 @@ public class RestfulServer extends HttpServlet { } // Secondary is for things like ..../_tags/_delete - String secondaryOperation=null; - + String secondaryOperation = null; + while (tok.hasMoreTokens()) { String nextString = tok.nextToken(); if (operation == null) { operation = nextString; - }else if (secondaryOperation==null) { - secondaryOperation=nextString; - }else { - throw new InvalidRequestException("URL path has unexpected token '"+nextString + "' at the end: " + requestPath); + } else if (secondaryOperation == null) { + secondaryOperation = nextString; + } else { + throw new InvalidRequestException("URL path has unexpected token '" + nextString + "' at the end: " + requestPath); } } @@ -653,8 +635,8 @@ public class RestfulServer extends HttpServlet { } /** - * This method may be overridden by subclasses to do perform initialization - * that needs to be performed prior to the server being used. + * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the + * server being used. */ protected void initialize() { // nothing by default diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/TransactionTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/TransactionTest.java new file mode 100644 index 00000000000..96a24c741a8 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/TransactionTest.java @@ -0,0 +1,113 @@ +package ca.uhn.fhir.rest.server; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPost; +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.Bundle; +import ca.uhn.fhir.model.api.BundleEntry; +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.model.primitive.InstantDt; +import ca.uhn.fhir.rest.annotation.Transaction; +import ca.uhn.fhir.rest.annotation.TransactionParam; +import ca.uhn.fhir.testutil.RandomServerPortProvider; + +/** + * Created by dsotnikov on 2/25/2014. + */ +public class TransactionTest { + + private static CloseableHttpClient ourClient; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TransactionTest.class); + private static int ourPort; + private static Server ourServer; + private static FhirContext ourCtx = new FhirContext(); + + @Test + public void testTransaction() throws Exception { + Bundle b= new Bundle(); + + Patient p1 = new Patient(); + p1.getId().setValue("1"); + b.addEntry().setResource(p1); + + Patient p2 = new Patient(); + p2.getId().setValue("2"); + b.addEntry().setResource(p2); + + BundleEntry deletedEntry = b.addEntry(); + deletedEntry.setId(new IdDt("3")); + deletedEntry.setDeleted(new InstantDt()); + + String bundleString = ourCtx.newXmlParser().encodeBundleToString(b); + + HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/"); + httpPost.setEntity(new StringEntity(bundleString, ContentType.create(Constants.CT_ATOM_XML, "UTF-8"))); + HttpResponse status = ourClient.execute(httpPost); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = new FhirContext().newXmlParser().parseBundle(responseContent); + assertEquals(3, bundle.size()); + + } + + @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(); + servlet.setProviders(patientProvider); + 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(); + + } + + /** + * Created by dsotnikov on 2/25/2014. + */ + public static class DummyProvider { + + @Transaction + public List transaction(@TransactionParam List theResources) { + return theResources; + } + + + } + +}