More work on transaction method

This commit is contained in:
jamesagnew 2014-05-22 10:20:51 -04:00
parent 62b909ff22
commit 30065fdfae
9 changed files with 371 additions and 102 deletions

View File

@ -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 <a href="http://hl7.org/implement/standards/fhir/http.html#transaction">transaction</a> method.
*
* <p>
* This method should have a parameter of type {@link Bundle} annotated with the {@link TransactionParam} annotation.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Transaction {
// nothing
}

View File

@ -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 {
}

View File

@ -56,6 +56,7 @@ import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Search; 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.Update;
import ca.uhn.fhir.rest.annotation.Validate; import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
@ -172,8 +173,9 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
GetTags getTags = theMethod.getAnnotation(GetTags.class); GetTags getTags = theMethod.getAnnotation(GetTags.class);
AddTags addTags = theMethod.getAnnotation(AddTags.class); AddTags addTags = theMethod.getAnnotation(AddTags.class);
DeleteTags deleteTags = theMethod.getAnnotation(DeleteTags.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 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; return null;
} }
@ -283,6 +285,8 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
return new AddTagsMethodBinding(theMethod, theContext, theProvider, addTags); return new AddTagsMethodBinding(theMethod, theContext, theProvider, addTags);
} else if (deleteTags != null) { } else if (deleteTags != null) {
return new DeleteTagsMethodBinding(theMethod, theContext, theProvider, deleteTags); return new DeleteTagsMethodBinding(theMethod, theContext, theProvider, deleteTags);
} else if (transaction != null) {
return new TransactionMethodBinding(theMethod, theContext, theProvider);
} else { } else {
throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName()); throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
} }

View File

@ -209,13 +209,15 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
if (uaHeader != null && uaHeader.contains("Mozilla")) { if (uaHeader != null && uaHeader.contains("Mozilla")) {
requestIsBrowser = true; requestIsBrowser = true;
} }
Object requestObject = parseRequestObject(theRequest);
// Method params // Method params
Object[] params = new Object[getParameters().size()]; Object[] params = new Object[getParameters().size()];
for (int i = 0; i < getParameters().size(); i++) { for (int i = 0; i < getParameters().size(); i++) {
IParameter param = getParameters().get(i); IParameter param = getParameters().get(i);
if (param != null) { if (param != null) {
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null); params[i] = param.translateQueryParametersIntoServerArgument(theRequest, requestObject);
} }
} }
@ -235,6 +237,13 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
} }
} }
/**
* Subclasses may override
*/
protected Object parseRequestObject(@SuppressWarnings("unused") Request theRequest) {
return null;
}
protected static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) { protected static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) {
Object retValObj = theResourceMetadata.get(theKey); Object retValObj = theResourceMetadata.get(theKey);
if (retValObj == null) { if (retValObj == null) {

View File

@ -1,45 +1,45 @@
package ca.uhn.fhir.rest.method; package ca.uhn.fhir.rest.method;
import java.io.IOException; import static org.apache.commons.lang3.StringUtils.*;
import java.io.Reader;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; 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.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; 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.client.BaseClientInvocation;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 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.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class TransactionMethodBinding extends BaseResourceReturningMethodBinding { public class TransactionMethodBinding extends BaseResourceReturningMethodBinding {
private int myResourceParameterIndex;
public TransactionMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) { public TransactionMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) {
super(null, theMethod, theConetxt, theProvider); super(null, theMethod, theConetxt, theProvider);
}
myResourceParameterIndex = -1;
int index=0;
for (IParameter next : getParameters()) {
if (next instanceof TransactionParameter) {
myResourceParameterIndex = index;
}
index++;
}
@Override if (myResourceParameterIndex==-1) {
public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException { throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " does not have a parameter annotated with the @" + TransactionParam.class + " annotation");
// TODO Auto-generated method stub }
return null;
}
@Override
public String getResourceName() {
return null;
}
@Override
public RestfulOperationTypeEnum getResourceOperationType() {
return null;
} }
@Override @Override
@ -47,22 +47,19 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
return RestfulOperationSystemEnum.TRANSACTION; 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 @Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) { public boolean incomingServerRequestMatchesMethod(Request theRequest) {
// TODO Auto-generated method stub if (theRequest.getRequestType() != RequestType.POST) {
return false; return false;
}
if (isNotBlank(theRequest.getOperation())) {
return false;
}
if (isNotBlank(theRequest.getResourceName())) {
return false;
}
return true;
} }
@Override @Override
@ -72,6 +69,26 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
@Override @Override
public List<IResource> invokeServer(Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { public List<IResource> invokeServer(Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
@SuppressWarnings("unchecked")
List<IResource> retVal=(List<IResource>) 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 // TODO Auto-generated method stub
return null; return null;
} }

View File

@ -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.api.annotation.TagListParam;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt; 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.Count;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam; import ca.uhn.fhir.rest.annotation.IncludeParam;
@ -225,6 +226,8 @@ public class ParameterUtil {
param = new CountParameter(); param = new CountParameter();
} else if (nextAnnotation instanceof Sort) { } else if (nextAnnotation instanceof Sort) {
param = new SortParameter(); param = new SortParameter();
} else if (nextAnnotation instanceof TransactionParam) {
param = new TransactionParameter();
} else { } else {
continue; continue;
} }

View File

@ -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<String, List<String>> 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<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> 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()+">");
}
}
}

View File

@ -89,12 +89,10 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* This method is called prior to sending a response to incoming requests. * This method is called prior to sending a response to incoming requests. It is used to add custom headers.
* It is used to add custom headers.
* <p> * <p>
* Use caution if overriding this method: it is recommended to call * Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid
* <code>super.addHeadersToResponse</code> to avoid inadvertantly disabling * inadvertantly disabling functionality.
* functionality.
* </p> * </p>
*/ */
public void addHeadersToResponse(HttpServletResponse theHttpResponse) { public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
@ -102,17 +100,15 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Gets the {@link FhirContext} associated with this server. For efficient * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* processing, resource providers and plain providers should generally use * providers should generally use this context if one is needed, as opposed to creating their own.
* this context if one is needed, as opposed to creating their own.
*/ */
public FhirContext getFhirContext() { public FhirContext getFhirContext() {
return myFhirContext; return myFhirContext;
} }
/** /**
* Provides the non-resource specific providers which implement method calls * Provides the non-resource specific providers which implement method calls on this server
* on this server
* *
* @see #getResourceProviders() * @see #getResourceProviders()
*/ */
@ -139,12 +135,11 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Returns the server conformance provider, which is the provider that is * Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* used to generate the server's conformance (metadata) statement. * (metadata) statement.
* <p> * <p>
* By default, the {@link ServerConformanceProvider} is used, but this can * By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code>
* be changed, or set to <code>null</code> if you do not wish to export a * if you do not wish to export a conformance statement.
* conformance statement.
* </p> * </p>
*/ */
public Object getServerConformanceProvider() { public Object getServerConformanceProvider() {
@ -152,9 +147,8 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Gets the server's name, as exported in conformance profiles exported by * Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* the server. This is informational only, but can be helpful to set with * but can be helpful to set with something appropriate.
* something appropriate.
* *
* @see RestfulServer#setServerName(StringDt) * @see RestfulServer#setServerName(StringDt)
*/ */
@ -167,19 +161,17 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Gets the server's version, as exported in conformance profiles exported * Gets the server's version, as exported in conformance profiles exported by the server. This is informational
* by the server. This is informational only, but can be helpful to set with * only, but can be helpful to set with something appropriate.
* something appropriate.
*/ */
public String getServerVersion() { public String getServerVersion() {
return myServerVersion; return myServerVersion;
} }
/** /**
* Initializes the server. Note that this method is final to avoid * Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
* accidentally introducing bugs in implementations, but subclasses may put * but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning
* initialization code in {@link #initialize()}, which is called immediately * initialization of the restful server's internal init.
* before beginning initialization of the restful server's internal init.
*/ */
@Override @Override
public final void init() throws ServletException { 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 * Sets the non-resource specific providers which implement method calls on this server.
* this server.
* *
* @see #setResourceProviders(Collection) * @see #setResourceProviders(Collection)
*/ */
@ -247,8 +238,7 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Sets the non-resource specific providers which implement method calls on * Sets the non-resource specific providers which implement method calls on this server.
* this server.
* *
* @see #setResourceProviders(Collection) * @see #setResourceProviders(Collection)
*/ */
@ -257,8 +247,7 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Sets the non-resource specific providers which implement method calls on * Sets the non-resource specific providers which implement method calls on this server
* this server
* *
* @see #setResourceProviders(Collection) * @see #setResourceProviders(Collection)
*/ */
@ -288,19 +277,16 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Returns the server conformance provider, which is the provider that is * Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* used to generate the server's conformance (metadata) statement. * (metadata) statement.
* <p> * <p>
* By default, the {@link ServerConformanceProvider} is used, but this can * By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code>
* be changed, or set to <code>null</code> if you do not wish to export a * if you do not wish to export a conformance statement.
* conformance statement.
* </p> * </p>
* Note that this method can only be called before the server is * Note that this method can only be called before the server is initialized.
* initialized.
* *
* @throws IllegalStateException * @throws IllegalStateException
* Note that this method can only be called prior to * Note that this method can only be called prior to {@link #init() initialization} and will throw an
* {@link #init() initialization} and will throw an
* {@link IllegalStateException} if called after that. * {@link IllegalStateException} if called after that.
*/ */
public void setServerConformanceProvider(Object theServerConformanceProvider) { 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 * Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* the server. This is informational only, but can be helpful to set with * but can be helpful to set with something appropriate.
* something appropriate.
* *
* @see RestfulServer#setServerName(StringDt) * @see RestfulServer#setServerName(StringDt)
*/ */
@ -322,18 +307,16 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Gets the server's version, as exported in conformance profiles exported * Gets the server's version, as exported in conformance profiles exported by the server. This is informational
* by the server. This is informational only, but can be helpful to set with * only, but can be helpful to set with something appropriate.
* something appropriate.
*/ */
public void setServerVersion(String theServerVersion) { public void setServerVersion(String theServerVersion) {
myServerVersion = theServerVersion; myServerVersion = theServerVersion;
} }
/** /**
* If set to <code>true</code> (default is false), the server will use * If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of
* browser friendly content-types (instead of standard FHIR ones) when it * standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR
* detects that the request is coming from a browser instead of a FHIR
*/ */
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) { public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes; myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
@ -361,7 +344,7 @@ public class RestfulServer extends HttpServlet {
private void findResourceMethods(Object theProvider, Class<?> clazz) { private void findResourceMethods(Object theProvider, Class<?> clazz) {
for (Method m : clazz.getDeclaredMethods()) { for (Method m : clazz.getDeclaredMethods()) {
if (!Modifier.isPublic(m.getModifiers())) { if (!Modifier.isPublic(m.getModifiers())) {
ourLog.debug("Ignoring non-public method: {}",m); ourLog.debug("Ignoring non-public method: {}", m);
} else { } else {
if (!Modifier.isStatic(m.getModifiers())) { if (!Modifier.isStatic(m.getModifiers())) {
ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName()); ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName());
@ -521,13 +504,12 @@ public class RestfulServer extends HttpServlet {
Map<String, String[]> params = new HashMap<String, String[]>(theRequest.getParameterMap()); Map<String, String[]> params = new HashMap<String, String[]>(theRequest.getParameterMap());
StringTokenizer tok = new StringTokenizer(requestPath, "/"); StringTokenizer tok = new StringTokenizer(requestPath, "/");
if (!tok.hasMoreTokens()) { if (tok.hasMoreTokens()) {
throw new ResourceNotFoundException("No resource name specified"); resourceName = tok.nextToken();
} if (resourceName.startsWith("_")) {
resourceName = tok.nextToken(); operation = resourceName;
if (resourceName.startsWith("_")) { resourceName = null;
operation = resourceName; }
resourceName = null;
} }
ResourceBinding resourceBinding = null; ResourceBinding resourceBinding = null;
@ -570,16 +552,16 @@ public class RestfulServer extends HttpServlet {
} }
// Secondary is for things like ..../_tags/_delete // Secondary is for things like ..../_tags/_delete
String secondaryOperation=null; String secondaryOperation = null;
while (tok.hasMoreTokens()) { while (tok.hasMoreTokens()) {
String nextString = tok.nextToken(); String nextString = tok.nextToken();
if (operation == null) { if (operation == null) {
operation = nextString; operation = nextString;
}else if (secondaryOperation==null) { } else if (secondaryOperation == null) {
secondaryOperation=nextString; secondaryOperation = nextString;
}else { } else {
throw new InvalidRequestException("URL path has unexpected token '"+nextString + "' at the end: " + requestPath); 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 * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
* that needs to be performed prior to the server being used. * server being used.
*/ */
protected void initialize() { protected void initialize() {
// nothing by default // nothing by default

View File

@ -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<IResource> transaction(@TransactionParam List<IResource> theResources) {
return theResources;
}
}
}