Merge branch 'master' of github.com:jamesagnew/hapi-fhir
This commit is contained in:
commit
65598c200e
|
@ -87,4 +87,17 @@ public @interface Search {
|
||||||
*/
|
*/
|
||||||
boolean dynamic() default false;
|
boolean dynamic() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In a REST server, should this method be invoked even if it does not have method parameters
|
||||||
|
* which correspond to all of the URL parameters passed in by the client (default is <code>false</code>).
|
||||||
|
* <p>
|
||||||
|
* Use this method with caution: Methods marked with a value of <code>true</code> will
|
||||||
|
* be greedy, meaning they may handle invocations you had intended to be handled by other
|
||||||
|
* search methods. Such a method may be invoked as long as any method parameters
|
||||||
|
* marked as {@link RequiredParam required} have been satisfied. If there are other methods
|
||||||
|
* which have parameters marked as {@link OptionalParam optional} which would technically be
|
||||||
|
* a better match, either the this method or the other method might be called.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
boolean allowUnknownParams() default false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
|
||||||
import ca.uhn.fhir.model.api.annotation.Description;
|
import ca.uhn.fhir.model.api.annotation.Description;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
|
@ -56,19 +55,18 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class);
|
||||||
|
|
||||||
private String myCompartmentName;
|
private String myCompartmentName;
|
||||||
private Class<? extends IBaseResource> myDeclaredResourceType;
|
|
||||||
private String myDescription;
|
private String myDescription;
|
||||||
private Integer myIdParamIndex;
|
private Integer myIdParamIndex;
|
||||||
private String myQueryName;
|
private String myQueryName;
|
||||||
|
private boolean myAllowUnknownParams;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public SearchMethodBinding(Class<? extends IBaseResource> theReturnResourceType, Method theMethod, FhirContext theContext, Object theProvider) {
|
public SearchMethodBinding(Class<? extends IBaseResource> theReturnResourceType, Method theMethod, FhirContext theContext, Object theProvider) {
|
||||||
super(theReturnResourceType, theMethod, theContext, theProvider);
|
super(theReturnResourceType, theMethod, theContext, theProvider);
|
||||||
Search search = theMethod.getAnnotation(Search.class);
|
Search search = theMethod.getAnnotation(Search.class);
|
||||||
this.myQueryName = StringUtils.defaultIfBlank(search.queryName(), null);
|
this.myQueryName = StringUtils.defaultIfBlank(search.queryName(), null);
|
||||||
this.myCompartmentName = StringUtils.defaultIfBlank(search.compartmentName(), null);
|
this.myCompartmentName = StringUtils.defaultIfBlank(search.compartmentName(), null);
|
||||||
this.myDeclaredResourceType = (Class<? extends IBaseResource>) theMethod.getReturnType();
|
|
||||||
this.myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod);
|
this.myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod);
|
||||||
|
this.myAllowUnknownParams = search.allowUnknownParams();
|
||||||
|
|
||||||
Description desc = theMethod.getAnnotation(Description.class);
|
Description desc = theMethod.getAnnotation(Description.class);
|
||||||
if (desc != null) {
|
if (desc != null) {
|
||||||
|
@ -232,17 +230,13 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Set<String> keySet = theRequest.getParameters().keySet();
|
Set<String> keySet = theRequest.getParameters().keySet();
|
||||||
for (String next : keySet) {
|
if (myAllowUnknownParams == false) {
|
||||||
// if (next.startsWith("_")) {
|
for (String next : keySet) {
|
||||||
// if (!SPECIAL_PARAM_NAMES.contains(next)) {
|
if (!methodParamsTemp.contains(next)) {
|
||||||
// continue;
|
return false;
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
if (!methodParamsTemp.contains(next)) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,10 +297,6 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResourceType(Class<? extends IResource> resourceType) {
|
|
||||||
this.myDeclaredResourceType = resourceType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getMethod().toString();
|
return getMethod().toString();
|
||||||
|
|
|
@ -154,7 +154,7 @@ public class FhirTerser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseRuntimeChildDefinition getDefinition(Class<? extends IResource> theResourceType, String thePath) {
|
public BaseRuntimeChildDefinition getDefinition(Class<? extends IBaseResource> theResourceType, String thePath) {
|
||||||
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType);
|
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType);
|
||||||
|
|
||||||
BaseRuntimeElementCompositeDefinition<?> currentDef = def;
|
BaseRuntimeElementCompositeDefinition<?> currentDef = def;
|
||||||
|
|
|
@ -49,7 +49,6 @@ import javax.xml.stream.events.Characters;
|
||||||
import javax.xml.stream.events.XMLEvent;
|
import javax.xml.stream.events.XMLEvent;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
import org.apache.http.client.utils.URLEncodedUtils;
|
import org.apache.http.client.utils.URLEncodedUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
@ -175,7 +174,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||||
private EntityManager myEntityManager;
|
private EntityManager myEntityManager;
|
||||||
|
|
||||||
private List<IDaoListener> myListeners = new ArrayList<IDaoListener>();
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PlatformTransactionManager myPlatformTransactionManager;
|
private PlatformTransactionManager myPlatformTransactionManager;
|
||||||
|
|
||||||
|
@ -647,12 +645,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void notifyWriteCompleted() {
|
|
||||||
for (IDaoListener next : myListeners) {
|
|
||||||
next.writeCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void populateResourceIntoEntity(IResource theResource, ResourceTable theEntity) {
|
protected void populateResourceIntoEntity(IResource theResource, ResourceTable theEntity) {
|
||||||
theEntity.setResourceType(toResourceName(theResource));
|
theEntity.setResourceType(toResourceName(theResource));
|
||||||
|
|
||||||
|
@ -895,12 +887,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerDaoListener(IDaoListener theListener) {
|
|
||||||
Validate.notNull(theListener, "theListener");
|
|
||||||
myListeners.add(theListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void searchHistoryCurrentVersion(List<HistoryTuple> theTuples, List<BaseHasResource> theRetVal) {
|
private void searchHistoryCurrentVersion(List<HistoryTuple> theTuples, List<BaseHasResource> theRetVal) {
|
||||||
Collection<HistoryTuple> tuples = Collections2.filter(theTuples, new com.google.common.base.Predicate<HistoryTuple>() {
|
Collection<HistoryTuple> tuples = Collections2.filter(theTuples, new com.google.common.base.Predicate<HistoryTuple>() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -95,6 +95,7 @@ import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchResult;
|
import ca.uhn.fhir.jpa.entity.SearchResult;
|
||||||
import ca.uhn.fhir.jpa.entity.TagDefinition;
|
import ca.uhn.fhir.jpa.entity.TagDefinition;
|
||||||
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
|
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
|
||||||
|
import ca.uhn.fhir.jpa.interceptor.IJpaServerInterceptor;
|
||||||
import ca.uhn.fhir.jpa.util.StopWatch;
|
import ca.uhn.fhir.jpa.util.StopWatch;
|
||||||
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
|
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
@ -136,6 +137,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
import ca.uhn.fhir.util.FhirTerser;
|
import ca.uhn.fhir.util.FhirTerser;
|
||||||
import ca.uhn.fhir.util.ObjectUtil;
|
import ca.uhn.fhir.util.ObjectUtil;
|
||||||
|
@ -194,7 +196,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
||||||
|
|
||||||
myEntityManager.persist(newEntity);
|
myEntityManager.persist(newEntity);
|
||||||
myEntityManager.merge(entity);
|
myEntityManager.merge(entity);
|
||||||
notifyWriteCompleted();
|
|
||||||
ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() });
|
ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,7 +260,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
||||||
Date updateTime = new Date();
|
Date updateTime = new Date();
|
||||||
ResourceTable savedEntity = updateEntity(null, entity, true, updateTime, updateTime);
|
ResourceTable savedEntity = updateEntity(null, entity, true, updateTime, updateTime);
|
||||||
|
|
||||||
notifyWriteCompleted();
|
// Notify JPA interceptors
|
||||||
|
for (IServerInterceptor next : getConfig().getInterceptors()) {
|
||||||
|
if (next instanceof IJpaServerInterceptor) {
|
||||||
|
((IJpaServerInterceptor) next).resourceDeleted(requestDetails, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ourLog.info("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart());
|
ourLog.info("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart());
|
||||||
return toMethodOutcome(savedEntity, null);
|
return toMethodOutcome(savedEntity, null);
|
||||||
|
@ -299,7 +306,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
||||||
// Perform delete
|
// Perform delete
|
||||||
Date updateTime = new Date();
|
Date updateTime = new Date();
|
||||||
updateEntity(null, entity, true, updateTime, updateTime);
|
updateEntity(null, entity, true, updateTime, updateTime);
|
||||||
notifyWriteCompleted();
|
|
||||||
|
// Notify JPA interceptors
|
||||||
|
for (IServerInterceptor next : getConfig().getInterceptors()) {
|
||||||
|
if (next instanceof IJpaServerInterceptor) {
|
||||||
|
((IJpaServerInterceptor) next).resourceDeleted(requestDetails, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,12 +363,18 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
||||||
ActionRequestDetails requestDetails = new ActionRequestDetails(theResource.getId(), toResourceName(theResource), theResource);
|
ActionRequestDetails requestDetails = new ActionRequestDetails(theResource.getId(), toResourceName(theResource), theResource);
|
||||||
notifyInterceptors(RestOperationTypeEnum.CREATE, requestDetails);
|
notifyInterceptors(RestOperationTypeEnum.CREATE, requestDetails);
|
||||||
|
|
||||||
|
// Perform actual DB update
|
||||||
updateEntity(theResource, entity, false, null, thePerformIndexing, true, theUpdateTime);
|
updateEntity(theResource, entity, false, null, thePerformIndexing, true, theUpdateTime);
|
||||||
|
|
||||||
|
// Notify JPA interceptors
|
||||||
|
for (IServerInterceptor next : getConfig().getInterceptors()) {
|
||||||
|
if (next instanceof IJpaServerInterceptor) {
|
||||||
|
((IJpaServerInterceptor) next).resourceCreated(requestDetails, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true);
|
DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true);
|
||||||
|
|
||||||
notifyWriteCompleted();
|
|
||||||
|
|
||||||
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulCreate", outcome.getId(), w.getMillisAndRestart());
|
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulCreate", outcome.getId(), w.getMillisAndRestart());
|
||||||
outcome.setOperationOutcome(createInfoOperationOutcome(msg));
|
outcome.setOperationOutcome(createInfoOperationOutcome(msg));
|
||||||
|
|
||||||
|
@ -804,7 +823,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
|
|
||||||
myEntityManager.merge(entity);
|
myEntityManager.merge(entity);
|
||||||
notifyWriteCompleted();
|
|
||||||
ourLog.info("Processed metaAddOperation on {} in {}ms", new Object[] { theResourceId, w.getMillisAndRestart() });
|
ourLog.info("Processed metaAddOperation on {} in {}ms", new Object[] { theResourceId, w.getMillisAndRestart() });
|
||||||
|
|
||||||
return metaGetOperation(theResourceId);
|
return metaGetOperation(theResourceId);
|
||||||
|
@ -1563,7 +1581,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
|
||||||
// Perform update
|
// Perform update
|
||||||
ResourceTable savedEntity = updateEntity(theResource, entity, true, null, thePerformIndexing, true, new Date());
|
ResourceTable savedEntity = updateEntity(theResource, entity, true, null, thePerformIndexing, true, new Date());
|
||||||
|
|
||||||
notifyWriteCompleted();
|
// Notify JPA interceptors
|
||||||
|
for (IServerInterceptor next : getConfig().getInterceptors()) {
|
||||||
|
if (next instanceof IJpaServerInterceptor) {
|
||||||
|
((IJpaServerInterceptor) next).resourceUpdated(requestDetails, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DaoMethodOutcome outcome = toMethodOutcome(savedEntity, theResource).setCreated(false);
|
DaoMethodOutcome outcome = toMethodOutcome(savedEntity, theResource).setCreated(false);
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
@ -65,6 +66,9 @@ public class DaoConfig {
|
||||||
* @see #setInterceptors(List)
|
* @see #setInterceptors(List)
|
||||||
*/
|
*/
|
||||||
public List<IServerInterceptor> getInterceptors() {
|
public List<IServerInterceptor> getInterceptors() {
|
||||||
|
if (myInterceptors == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
return myInterceptors;
|
return myInterceptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ import ca.uhn.fhir.util.FhirTerser;
|
||||||
|
|
||||||
public class FhirResourceDaoDstu1<T extends IResource> extends BaseHapiFhirResourceDao<T> {
|
public class FhirResourceDaoDstu1<T extends IResource> extends BaseHapiFhirResourceDao<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
protected List<Object> getIncludeValues(FhirTerser t, Include next, IBaseResource nextResource, RuntimeResourceDefinition def) {
|
protected List<Object> getIncludeValues(FhirTerser t, Include next, IBaseResource nextResource, RuntimeResourceDefinition def) {
|
||||||
List<Object> values;
|
List<Object> values;
|
||||||
if ("*".equals(next.getValue())) {
|
if ("*".equals(next.getValue())) {
|
||||||
|
|
|
@ -278,8 +278,6 @@ public class FhirSystemDaoDstu1 extends BaseHapiFhirSystemDao<List<IResource>> {
|
||||||
|
|
||||||
oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Transaction completed in " + delay + "ms with " + creations + " creations and " + updates + " updates");
|
oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Transaction completed in " + delay + "ms with " + creations + " creations and " + updates + " updates");
|
||||||
|
|
||||||
notifyWriteCompleted();
|
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -446,8 +446,6 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
|
||||||
long delay = System.currentTimeMillis() - start;
|
long delay = System.currentTimeMillis() - start;
|
||||||
ourLog.info(theActionName + " completed in {}ms", new Object[] { delay });
|
ourLog.info(theActionName + " completed in {}ms", new Object[] { delay });
|
||||||
|
|
||||||
notifyWriteCompleted();
|
|
||||||
|
|
||||||
response.setType(BundleTypeEnum.TRANSACTION_RESPONSE);
|
response.setType(BundleTypeEnum.TRANSACTION_RESPONSE);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package ca.uhn.fhir.jpa.dao;
|
package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||||
|
|
||||||
|
@ -42,8 +40,4 @@ public interface IDao {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void registerDaoListener(IDaoListener theListener);
|
|
||||||
|
|
||||||
// void setResourceDaos(List<IFhirResourceDao<?>> theResourceDaos);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.dao;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2015 University Health Network
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
public interface IDaoListener {
|
|
||||||
|
|
||||||
void writeCompleted();
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package ca.uhn.fhir.jpa.interceptor;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2015 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%
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server interceptor for JPA DAOs which adds methods that will be called at certain points
|
||||||
|
* in the operation lifecycle for JPA operations.
|
||||||
|
*/
|
||||||
|
public interface IJpaServerInterceptor extends IServerInterceptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is invoked by the JPA DAOs when a resource has been newly created in the database.
|
||||||
|
* It will be invoked within the current transaction scope.
|
||||||
|
* <p>
|
||||||
|
* This method is called after the
|
||||||
|
* entity has been persisted and flushed to the database, so it is probably not a good
|
||||||
|
* candidate for security decisions.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param theDetails The request details
|
||||||
|
* @param theResourceTable The actual created entity
|
||||||
|
*/
|
||||||
|
void resourceCreated(ActionRequestDetails theDetails, ResourceTable theResourceTable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is invoked by the JPA DAOs when a resource has been updated in the database.
|
||||||
|
* It will be invoked within the current transaction scope.
|
||||||
|
* <p>
|
||||||
|
* This method is called after the
|
||||||
|
* entity has been persisted and flushed to the database, so it is probably not a good
|
||||||
|
* candidate for security decisions.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param theDetails The request details
|
||||||
|
* @param theResourceTable The actual updated entity
|
||||||
|
*/
|
||||||
|
void resourceUpdated(ActionRequestDetails theDetails, ResourceTable theResourceTable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is invoked by the JPA DAOs when a resource has been updated in the database.
|
||||||
|
* It will be invoked within the current transaction scope.
|
||||||
|
* <p>
|
||||||
|
* This method is called after the
|
||||||
|
* entity has been persisted and flushed to the database, so it is probably not a good
|
||||||
|
* candidate for security decisions.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param theDetails The request details
|
||||||
|
* @param theResourceTable The actual updated entity
|
||||||
|
*/
|
||||||
|
void resourceDeleted(ActionRequestDetails theDetails, ResourceTable theResourceTable);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package ca.uhn.fhir.jpa.interceptor;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||||
|
|
||||||
|
public class JpaServerInterceptorAdapter extends InterceptorAdapter implements IJpaServerInterceptor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resourceCreated(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resourceUpdated(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resourceDeleted(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
|
import ca.uhn.fhir.jpa.interceptor.IJpaServerInterceptor;
|
||||||
|
import ca.uhn.fhir.jpa.interceptor.JpaServerInterceptorAdapter;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
|
|
||||||
|
public class FhirResourceDaoDstu2InterceptorTest extends BaseJpaDstu2Test {
|
||||||
|
|
||||||
|
private IJpaServerInterceptor myJpaInterceptor;
|
||||||
|
private JpaServerInterceptorAdapter myJpaInterceptorAdapter = new JpaServerInterceptorAdapter();
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
myDaoConfig.getInterceptors().remove(myJpaInterceptor);
|
||||||
|
myDaoConfig.getInterceptors().remove(myJpaInterceptorAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
myJpaInterceptor = mock(IJpaServerInterceptor.class);
|
||||||
|
myDaoConfig.getInterceptors().add(myJpaInterceptor);
|
||||||
|
myDaoConfig.getInterceptors().add(myJpaInterceptorAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* *****************************************************
|
||||||
|
* Note that non JPA specific operations get tested in individual
|
||||||
|
* operation test methods too
|
||||||
|
* *****************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJpaCreate() {
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addName().addFamily("PATIENT");
|
||||||
|
Long id = myPatientDao.create(p).getId().getIdPartAsLong();
|
||||||
|
|
||||||
|
ArgumentCaptor<ActionRequestDetails> detailsCapt;
|
||||||
|
ArgumentCaptor<ResourceTable> tableCapt;
|
||||||
|
|
||||||
|
detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
|
||||||
|
tableCapt = ArgumentCaptor.forClass(ResourceTable.class);
|
||||||
|
verify(myJpaInterceptor, times(1)).resourceCreated(detailsCapt.capture(), tableCapt.capture());
|
||||||
|
assertNotNull(tableCapt.getValue().getId());
|
||||||
|
assertEquals(id, tableCapt.getValue().getId());
|
||||||
|
|
||||||
|
detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
|
||||||
|
tableCapt = ArgumentCaptor.forClass(ResourceTable.class);
|
||||||
|
verify(myJpaInterceptor, times(0)).resourceUpdated(detailsCapt.capture(), tableCapt.capture());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Not do a conditional create
|
||||||
|
*/
|
||||||
|
p = new Patient();
|
||||||
|
p.addName().addFamily("PATIENT1");
|
||||||
|
Long id2 = myPatientDao.create(p, "Patient?family=PATIENT").getId().getIdPartAsLong();
|
||||||
|
assertEquals(id, id2);
|
||||||
|
|
||||||
|
detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
|
||||||
|
tableCapt = ArgumentCaptor.forClass(ResourceTable.class);
|
||||||
|
verify(myJpaInterceptor, times(1)).resourceCreated(detailsCapt.capture(), tableCapt.capture());
|
||||||
|
verify(myJpaInterceptor, times(0)).resourceUpdated(detailsCapt.capture(), tableCapt.capture());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJpaDelete() {
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addName().addFamily("PATIENT");
|
||||||
|
Long id = myPatientDao.create(p).getId().getIdPartAsLong();
|
||||||
|
|
||||||
|
myPatientDao.delete(new IdDt("Patient", id));
|
||||||
|
|
||||||
|
ArgumentCaptor<ActionRequestDetails> detailsCapt;
|
||||||
|
ArgumentCaptor<ResourceTable> tableCapt;
|
||||||
|
|
||||||
|
detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
|
||||||
|
tableCapt = ArgumentCaptor.forClass(ResourceTable.class);
|
||||||
|
verify(myJpaInterceptor, times(1)).resourceDeleted(detailsCapt.capture(), tableCapt.capture());
|
||||||
|
assertNotNull(tableCapt.getValue().getId());
|
||||||
|
assertEquals(id, tableCapt.getValue().getId());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJpaUpdate() {
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addName().addFamily("PATIENT");
|
||||||
|
Long id = myPatientDao.create(p).getId().getIdPartAsLong();
|
||||||
|
|
||||||
|
p = new Patient();
|
||||||
|
p.setId(new IdDt(id));
|
||||||
|
p.addName().addFamily("PATIENT1");
|
||||||
|
Long id2 = myPatientDao.update(p).getId().getIdPartAsLong();
|
||||||
|
assertEquals(id, id2);
|
||||||
|
|
||||||
|
ArgumentCaptor<ActionRequestDetails> detailsCapt;
|
||||||
|
ArgumentCaptor<ResourceTable> tableCapt;
|
||||||
|
|
||||||
|
detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
|
||||||
|
tableCapt = ArgumentCaptor.forClass(ResourceTable.class);
|
||||||
|
verify(myJpaInterceptor, times(1)).resourceUpdated(detailsCapt.capture(), tableCapt.capture());
|
||||||
|
assertNotNull(tableCapt.getValue().getId());
|
||||||
|
assertEquals(id, tableCapt.getValue().getId());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now do a conditional update
|
||||||
|
*/
|
||||||
|
|
||||||
|
p = new Patient();
|
||||||
|
p.setId(new IdDt(id));
|
||||||
|
p.addName().addFamily("PATIENT2");
|
||||||
|
id2 = myPatientDao.update(p, "Patient?family=PATIENT1").getId().getIdPartAsLong();
|
||||||
|
assertEquals(id, id2);
|
||||||
|
|
||||||
|
detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
|
||||||
|
tableCapt = ArgumentCaptor.forClass(ResourceTable.class);
|
||||||
|
verify(myJpaInterceptor, times(1)).resourceCreated(detailsCapt.capture(), tableCapt.capture());
|
||||||
|
verify(myJpaInterceptor, times(2)).resourceUpdated(detailsCapt.capture(), tableCapt.capture());
|
||||||
|
assertEquals(id, tableCapt.getAllValues().get(2).getId());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now do a conditional update where none will match (so this is actually a create)
|
||||||
|
*/
|
||||||
|
|
||||||
|
p = new Patient();
|
||||||
|
p.addName().addFamily("PATIENT3");
|
||||||
|
id2 = myPatientDao.update(p, "Patient?family=ZZZ").getId().getIdPartAsLong();
|
||||||
|
assertNotEquals(id, id2);
|
||||||
|
|
||||||
|
detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class);
|
||||||
|
tableCapt = ArgumentCaptor.forClass(ResourceTable.class);
|
||||||
|
verify(myJpaInterceptor, times(2)).resourceUpdated(detailsCapt.capture(), tableCapt.capture());
|
||||||
|
verify(myJpaInterceptor, times(2)).resourceCreated(detailsCapt.capture(), tableCapt.capture());
|
||||||
|
assertEquals(id2, tableCapt.getAllValues().get(3).getId());
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -68,6 +68,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu2 {
|
||||||
|
|
||||||
private Properties jpaProperties() {
|
private Properties jpaProperties() {
|
||||||
Properties extraProperties = new Properties();
|
Properties extraProperties = new Properties();
|
||||||
|
extraProperties.put("hibernate.dialect", org.hibernate.dialect.DerbyTenSevenDialect.class.getName());
|
||||||
extraProperties.put("hibernate.format_sql", "true");
|
extraProperties.put("hibernate.format_sql", "true");
|
||||||
extraProperties.put("hibernate.show_sql", "false");
|
extraProperties.put("hibernate.show_sql", "false");
|
||||||
extraProperties.put("hibernate.hbm2ddl.auto", "update");
|
extraProperties.put("hibernate.hbm2ddl.auto", "update");
|
||||||
|
|
4
pom.xml
4
pom.xml
|
@ -194,6 +194,10 @@
|
||||||
<id>samlanfranchi</id>
|
<id>samlanfranchi</id>
|
||||||
<name>Sam Lanfranchi</name>
|
<name>Sam Lanfranchi</name>
|
||||||
</developer>
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>jkiddo</id>
|
||||||
|
<name>Jens Kristian Villadsen</name>
|
||||||
|
</developer>
|
||||||
</developers>
|
</developers>
|
||||||
|
|
||||||
<licenses>
|
<licenses>
|
||||||
|
|
|
@ -204,9 +204,14 @@
|
||||||
@am202 for reporting!
|
@am202 for reporting!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="245">
|
<action type="fix" issue="245">
|
||||||
FIx issue in testpage-overlay's new Java configuration where only the first
|
Fix issue in testpage-overlay's new Java configuration where only the first
|
||||||
configured server actually gets used.
|
configured server actually gets used.
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
Introduce
|
||||||
|
<![CDATA[<a href="./apidocs-jpaserver/ca/uhn/fhir/jpa/dao/IJpaServerInterceptor.html">IJpaServerInterceptor</a>]]>
|
||||||
|
interceptors for JPA server which can be used for more fine grained operations.
|
||||||
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Parser (XML and JSON) shouldn't encode an ID tag in resources
|
Parser (XML and JSON) shouldn't encode an ID tag in resources
|
||||||
which are part of a bundle when the resource has a UUID/OID
|
which are part of a bundle when the resource has a UUID/OID
|
||||||
|
|
|
@ -275,6 +275,33 @@
|
||||||
</macro>
|
</macro>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section name="JPA Server Interceptors">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The HAPI <a href="./doc_jpa.html">JPA Server</a> is an added layer on top of the HAPI
|
||||||
|
REST server framework. If you are using it, you may wish to also register interceptors
|
||||||
|
against the <a href="./apidocs-jpaserver/ca/uhn/fhir/jpa/dao/DaoConfig.html">DaoConfig</a>
|
||||||
|
bean that you create using Spring configuration.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
By registering an interceptor against the DaoConfig, the server will invoke
|
||||||
|
interceptor methods for operations such as <b>create</b>, <b>update</b>, etc even
|
||||||
|
when these operations are found nested within a transaction. This is useful
|
||||||
|
if you are using interceptors to make access control decisions because
|
||||||
|
it avoids clients using transactions as a means of bypassing these controls.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You may also choose to create interceptors which implement the
|
||||||
|
more specialized
|
||||||
|
<a href="./apidocs-jpaserver/ca/uhn/fhir/jpa/dao/IJpaServerInterceptor.html">IJpaServerInterceptor</a>
|
||||||
|
interface, as this interceptor adds additional methods which are called during the JPA
|
||||||
|
lifecycle.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue