Merge remote-tracking branch 'origin/master' into ng_20201218_survivorship_poc
This commit is contained in:
commit
04db4ed229
36
README.md
36
README.md
|
@ -3,10 +3,17 @@ HAPI FHIR
|
|||
|
||||
HAPI FHIR - Java API for HL7 FHIR Clients and Servers
|
||||
|
||||
[![Build Status](https://dev.azure.com/hapifhir/HAPI%20FHIR/_apis/build/status/jamesagnew.hapi-fhir?branchName=master)](https://dev.azure.com/hapifhir/HAPI%20FHIR/_build/latest?definitionId=1&branchName=master)
|
||||
[![codecov](https://codecov.io/gh/jamesagnew/hapi-fhir/branch/master/graph/badge.svg)](https://codecov.io/gh/jamesagnew/hapi-fhir)
|
||||
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/ca.uhn.hapi.fhir/hapi-fhir-base/badge.svg)](http://search.maven.org/#search|ga|1|ca.uhn.hapi.fhir)
|
||||
[![License](https://img.shields.io/badge/license-apache%202.0-60C060.svg)](https://hapifhir.io/hapi-fhir/license.html)
|
||||
[![License][Badge-License]][Link-License]
|
||||
|
||||
## CI/CD
|
||||
| CI Status (master) | SNAPSHOT Pipeline | Current Release |
|
||||
| :---: | :---: | :---: |
|
||||
| [![Build Status][Badge-AzurePipelineMaster]][Link-AzurePipelinesMaster] | [![Build Status][Badge-AzureReleaseSnapshot]][Link-AzurePipelinesSnapshot] | [![Release Artifacts][Badge-MavenCentral]][Link-MavenCentral] |
|
||||
|
||||
## Test Coverage
|
||||
[![codecov][Badge-CodeCov]][Link-CodeCov]
|
||||
|
||||
## Documentation and wiki
|
||||
|
||||
Complete project documentation is available here:
|
||||
http://hapifhir.io
|
||||
|
@ -16,4 +23,23 @@ http://hapi.fhir.org/
|
|||
|
||||
This project is Open Source, licensed under the Apache Software License 2.0.
|
||||
|
||||
Please see [this wiki page](https://github.com/jamesagnew/hapi-fhir/wiki/Getting-Help) for information on where to get help with HAPI FHIR. Please see [Smile CDR](https://smilecdr.com) for information on commercial support.
|
||||
Please see [this wiki page][Link-wiki] for information on where to get help with HAPI FHIR.
|
||||
|
||||
Please see [Smile CDR][Link-SmileCDR] for information on commercial support.
|
||||
|
||||
[Link-AzurePipelines]: https://dev.azure.com/hapifhir/HAPI%20FHIR/_build
|
||||
[Link-AzurePipelinesMaster]: https://dev.azure.com/hapifhir/HAPI%20FHIR/_build?definitionId=2
|
||||
[Link-AzurePipelinesSnapshot]: https://dev.azure.com/hapifhir/HAPI%20FHIR/_build?definitionId=3
|
||||
[Link-MavenCentral]: http://search.maven.org/#search|ga|1|ca.uhn.hapi.fhir
|
||||
[Link-CodeCov]: https://codecov.io/gh/hapifhir/hapi-fhir
|
||||
[Link-wiki]: https://github.com/jamesagnew/hapi-fhir/wiki/Getting-Help
|
||||
[Link-SmileCDR]: https://smilecdr.com
|
||||
[Link-License]: https://hapifhir.io/hapi-fhir/license.html
|
||||
|
||||
[Badge-AzurePipelineMaster]: https://dev.azure.com/hapifhir/HAPI%20FHIR/_apis/build/status/hapifhir.hapi-fhir?branchName=refs%2Fpull%2F2319%2Fmerge
|
||||
[Badge-AzureReleaseSnapshot]: https://dev.azure.com/hapifhir/HAPI%20FHIR/_apis/build/status/SNAPSHOT%20pipeline?branchName=master
|
||||
[Badge-MavenCentral]: https://maven-badges.herokuapp.com/maven-central/ca.uhn.hapi.fhir/hapi-fhir-base/badge.svg
|
||||
[Badge-CodeCov]: https://codecov.io/gh/hapifhir/hapi-fhir/branch/master/graph/badge.svg?token=zHfnKfQB9X
|
||||
[Badge-License]: https://img.shields.io/badge/license-apache%202.0-60C060.svg
|
||||
|
||||
|
||||
|
|
|
@ -99,14 +99,6 @@
|
|||
<groupId>javax.mail</groupId>
|
||||
<artifactId>javax.mail-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.activation</groupId>
|
||||
<artifactId>javax.activation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.helger</groupId>
|
||||
<artifactId>ph-schematron</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
|
@ -151,6 +143,10 @@
|
|||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.xml.bind</groupId>
|
||||
<artifactId>jaxb-impl</artifactId>
|
||||
|
|
|
@ -103,12 +103,17 @@
|
|||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<configuration>
|
||||
<classpathDependencyExcludes>
|
||||
<dependencyExclude>org.slf4j:slf4j-android</dependencyExclude>
|
||||
</classpathDependencyExcludes>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>it</id>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includes>
|
||||
|
|
|
@ -143,7 +143,7 @@ public class GenericClientDstu3IT {
|
|||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
assertEquals("http://example.com/fhir/Patient?_format=json", capt.getAllValues().get(idx).url().toString());
|
||||
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).url().toString());
|
||||
idx++;
|
||||
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ public class GenericClientDstu3IT {
|
|||
Request request = capt.getAllValues().get(0);
|
||||
ourLog.info(request.headers().toString());
|
||||
|
||||
assertEquals("http://example.com/fhir/Binary?_format=json", request.url().toString());
|
||||
assertEquals("http://example.com/fhir/Binary", request.url().toString());
|
||||
validateUserAgent(capt);
|
||||
|
||||
assertEquals(Constants.CT_FHIR_JSON_NEW + ";charset=utf-8", request.body().contentType().toString().toLowerCase().replace(" ", ""));
|
||||
|
@ -252,7 +252,7 @@ public class GenericClientDstu3IT {
|
|||
assertNotNull(outcome.getResource());
|
||||
|
||||
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">FINAL VALUE</div>", ((Patient) outcome.getResource()).getText().getDivAsString());
|
||||
assertEquals("http://example.com/fhir/Patient?_format=json", capt.getAllValues().get(0).url().toString());
|
||||
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).url().toString());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package ca.uhn.fhir.interceptor.api;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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 IBaseInterceptorBroadcaster<POINTCUT extends IPointcut> {
|
||||
|
||||
/**
|
||||
* Invoke registered interceptor hook methods for the given Pointcut.
|
||||
*
|
||||
* @return Returns <code>false</code> if any of the invoked hook methods returned
|
||||
* <code>false</code>, and returns <code>true</code> otherwise.
|
||||
*/
|
||||
boolean callHooks(POINTCUT thePointcut, HookParams theParams);
|
||||
|
||||
/**
|
||||
* Invoke registered interceptor hook methods for the given Pointcut. This method
|
||||
* should only be called for pointcuts that return a type other than
|
||||
* <code>void</code> or <code>boolean</code>
|
||||
*
|
||||
* @return Returns the object returned by the first hook method that did not return <code>null</code>
|
||||
*/
|
||||
Object callHooksAndReturnObject(POINTCUT thePointcut, HookParams theParams);
|
||||
|
||||
/**
|
||||
* Does this broadcaster have any hooks for the given pointcut?
|
||||
*
|
||||
* @param thePointcut The poointcut
|
||||
* @return Does this broadcaster have any hooks for the given pointcut?
|
||||
* @since 4.0.0
|
||||
*/
|
||||
boolean hasHooks(POINTCUT thePointcut);
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package ca.uhn.fhir.interceptor.api;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public interface IBaseInterceptorService<POINTCUT extends IPointcut> extends IBaseInterceptorBroadcaster<POINTCUT> {
|
||||
|
||||
/**
|
||||
* Register an interceptor that will be used in a {@link ThreadLocal} context.
|
||||
* This means that events will only be broadcast to the given interceptor if
|
||||
* they were fired from the current thread.
|
||||
* <p>
|
||||
* Note that it is almost always desirable to call this method with a
|
||||
* try-finally statement that removes the interceptor afterwards, since
|
||||
* this can lead to memory leakage, poor performance due to ever-increasing
|
||||
* numbers of interceptors, etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that most methods such as {@link #getAllRegisteredInterceptors()} and
|
||||
* {@link #unregisterAllInterceptors()} do not affect thread local interceptors
|
||||
* as they are kept in a separate list.
|
||||
* </p>
|
||||
*
|
||||
* @param theInterceptor The interceptor
|
||||
* @return Returns <code>true</code> if at least one valid hook method was found on this interceptor
|
||||
*/
|
||||
boolean registerThreadLocalInterceptor(Object theInterceptor);
|
||||
|
||||
/**
|
||||
* Unregisters a ThreadLocal interceptor
|
||||
*
|
||||
* @param theInterceptor The interceptor
|
||||
* @see #registerThreadLocalInterceptor(Object)
|
||||
*/
|
||||
void unregisterThreadLocalInterceptor(Object theInterceptor);
|
||||
|
||||
/**
|
||||
* Register an interceptor. This method has no effect if the given interceptor is already registered.
|
||||
*
|
||||
* @param theInterceptor The interceptor to register
|
||||
* @return Returns <code>true</code> if at least one valid hook method was found on this interceptor
|
||||
*/
|
||||
boolean registerInterceptor(Object theInterceptor);
|
||||
|
||||
/**
|
||||
* Unregister an interceptor. This method has no effect if the given interceptor is not already registered.
|
||||
*
|
||||
* @param theInterceptor The interceptor to unregister
|
||||
* @return Returns <code>true</code> if the interceptor was found and removed
|
||||
*/
|
||||
boolean unregisterInterceptor(Object theInterceptor);
|
||||
|
||||
/**
|
||||
* Returns all currently registered interceptors (excluding any thread local interceptors).
|
||||
*/
|
||||
List<Object> getAllRegisteredInterceptors();
|
||||
|
||||
/**
|
||||
* Unregisters all registered interceptors. Note that this method does not unregister
|
||||
* any {@link #registerThreadLocalInterceptor(Object) thread local interceptors}.
|
||||
*/
|
||||
void unregisterAllInterceptors();
|
||||
|
||||
void unregisterInterceptors(@Nullable Collection<?> theInterceptors);
|
||||
|
||||
void registerInterceptors(@Nullable Collection<?> theInterceptors);
|
||||
|
||||
/**
|
||||
* Unregisters all interceptors that are indicated by the given callback function returning <code>true</code>
|
||||
*/
|
||||
void unregisterInterceptorsIf(Predicate<Object> theShouldUnregisterFunction);
|
||||
}
|
|
@ -20,31 +20,6 @@ package ca.uhn.fhir.interceptor.api;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
public interface IInterceptorBroadcaster {
|
||||
public interface IInterceptorBroadcaster extends IBaseInterceptorBroadcaster<Pointcut> {
|
||||
|
||||
/**
|
||||
* Invoke registered interceptor hook methods for the given Pointcut.
|
||||
*
|
||||
* @return Returns <code>false</code> if any of the invoked hook methods returned
|
||||
* <code>false</code>, and returns <code>true</code> otherwise.
|
||||
*/
|
||||
boolean callHooks(Pointcut thePointcut, HookParams theParams);
|
||||
|
||||
/**
|
||||
* Invoke registered interceptor hook methods for the given Pointcut. This method
|
||||
* should only be called for pointcuts that return a type other than
|
||||
* <code>void</code> or <code>boolean</code>
|
||||
*
|
||||
* @return Returns the object returned by the first hook method that did not return <code>null</code>
|
||||
*/
|
||||
Object callHooksAndReturnObject(Pointcut thePointcut, HookParams theParams);
|
||||
|
||||
/**
|
||||
* Does this broadcaster have any hooks for the given pointcut?
|
||||
*
|
||||
* @param thePointcut The poointcut
|
||||
* @return Does this broadcaster have any hooks for the given pointcut?
|
||||
* @since 4.0.0
|
||||
*/
|
||||
boolean hasHooks(Pointcut thePointcut);
|
||||
}
|
||||
|
|
|
@ -20,80 +20,10 @@ package ca.uhn.fhir.interceptor.api;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public interface IInterceptorService extends IInterceptorBroadcaster {
|
||||
|
||||
/**
|
||||
* Register an interceptor that will be used in a {@link ThreadLocal} context.
|
||||
* This means that events will only be broadcast to the given interceptor if
|
||||
* they were fired from the current thread.
|
||||
* <p>
|
||||
* Note that it is almost always desirable to call this method with a
|
||||
* try-finally statement that removes the interceptor afterwards, since
|
||||
* this can lead to memory leakage, poor performance due to ever-increasing
|
||||
* numbers of interceptors, etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that most methods such as {@link #getAllRegisteredInterceptors()} and
|
||||
* {@link #unregisterAllInterceptors()} do not affect thread local interceptors
|
||||
* as they are kept in a separate list.
|
||||
* </p>
|
||||
*
|
||||
* @param theInterceptor The interceptor
|
||||
* @return Returns <code>true</code> if at least one valid hook method was found on this interceptor
|
||||
*/
|
||||
boolean registerThreadLocalInterceptor(Object theInterceptor);
|
||||
|
||||
/**
|
||||
* Unregisters a ThreadLocal interceptor
|
||||
*
|
||||
* @param theInterceptor The interceptor
|
||||
* @see #registerThreadLocalInterceptor(Object)
|
||||
*/
|
||||
void unregisterThreadLocalInterceptor(Object theInterceptor);
|
||||
|
||||
/**
|
||||
* Register an interceptor. This method has no effect if the given interceptor is already registered.
|
||||
*
|
||||
* @param theInterceptor The interceptor to register
|
||||
* @return Returns <code>true</code> if at least one valid hook method was found on this interceptor
|
||||
*/
|
||||
boolean registerInterceptor(Object theInterceptor);
|
||||
|
||||
/**
|
||||
* Unregister an interceptor. This method has no effect if the given interceptor is not already registered.
|
||||
*
|
||||
* @param theInterceptor The interceptor to unregister
|
||||
* @return Returns <code>true</code> if the interceptor was found and removed
|
||||
*/
|
||||
boolean unregisterInterceptor(Object theInterceptor);
|
||||
public interface IInterceptorService extends IBaseInterceptorService<Pointcut>, IInterceptorBroadcaster {
|
||||
|
||||
void registerAnonymousInterceptor(Pointcut thePointcut, IAnonymousInterceptor theInterceptor);
|
||||
|
||||
void registerAnonymousInterceptor(Pointcut thePointcut, int theOrder, IAnonymousInterceptor theInterceptor);
|
||||
|
||||
/**
|
||||
* Returns all currently registered interceptors (excluding any thread local interceptors).
|
||||
*/
|
||||
List<Object> getAllRegisteredInterceptors();
|
||||
|
||||
/**
|
||||
* Unregisters all registered interceptors. Note that this method does not unregister
|
||||
* any {@link #registerThreadLocalInterceptor(Object) thread local interceptors}.
|
||||
*/
|
||||
void unregisterAllInterceptors();
|
||||
|
||||
void unregisterInterceptors(@Nullable Collection<?> theInterceptors);
|
||||
|
||||
void registerInterceptors(@Nullable Collection<?> theInterceptors);
|
||||
|
||||
/**
|
||||
* Unregisters all interceptors that are indicated by the given callback function returning <code>true</code>
|
||||
*/
|
||||
void unregisterInterceptorsIf(Predicate<Object> theShouldUnregisterFunction);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package ca.uhn.fhir.interceptor.api;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
public interface IPointcut {
|
||||
@Nonnull
|
||||
Class<?> getReturnType();
|
||||
|
||||
@Nonnull
|
||||
List<String> getParameterTypes();
|
||||
|
||||
@Nonnull
|
||||
String name();
|
||||
|
||||
boolean isShouldLogAndSwallowException(Throwable theException);
|
||||
}
|
|
@ -48,7 +48,7 @@ import java.util.Set;
|
|||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
public enum Pointcut {
|
||||
public enum Pointcut implements IPointcut {
|
||||
|
||||
/**
|
||||
* <b>Interceptor Framework Hook:</b>
|
||||
|
@ -2178,6 +2178,7 @@ public enum Pointcut {
|
|||
this(theReturnType, new ExceptionHandlingSpec(), theParameterTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShouldLogAndSwallowException(@Nonnull Throwable theException) {
|
||||
for (Class<? extends Throwable> next : myExceptionHandlingSpec.myTypesToLogAndSwallow) {
|
||||
if (next.isAssignableFrom(theException.getClass())) {
|
||||
|
@ -2187,11 +2188,13 @@ public enum Pointcut {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public Class<?> getReturnType() {
|
||||
return myReturnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public List<String> getParameterTypes() {
|
||||
return myParameterTypes;
|
||||
|
|
|
@ -0,0 +1,621 @@
|
|||
package ca.uhn.fhir.interceptor.executor;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.interceptor.api.Hook;
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.interceptor.api.IBaseInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.IBaseInterceptorService;
|
||||
import ca.uhn.fhir.interceptor.api.IPointcut;
|
||||
import ca.uhn.fhir.interceptor.api.Interceptor;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.apache.commons.lang3.reflect.MethodUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class BaseInterceptorService<POINTCUT extends IPointcut> implements IBaseInterceptorService<POINTCUT>, IBaseInterceptorBroadcaster<POINTCUT> {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(BaseInterceptorService.class);
|
||||
private final List<Object> myInterceptors = new ArrayList<>();
|
||||
private final ListMultimap<POINTCUT, BaseInvoker> myGlobalInvokers = ArrayListMultimap.create();
|
||||
private final ListMultimap<POINTCUT, BaseInvoker> myAnonymousInvokers = ArrayListMultimap.create();
|
||||
private final Object myRegistryMutex = new Object();
|
||||
private final ThreadLocal<ListMultimap<POINTCUT, BaseInvoker>> myThreadlocalInvokers = new ThreadLocal<>();
|
||||
private String myName;
|
||||
private boolean myThreadlocalInvokersEnabled = true;
|
||||
|
||||
/**
|
||||
* Constructor which uses a default name of "default"
|
||||
*/
|
||||
public BaseInterceptorService() {
|
||||
this("default");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theName The name for this registry (useful for troubleshooting)
|
||||
*/
|
||||
public BaseInterceptorService(String theName) {
|
||||
super();
|
||||
myName = theName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are threadlocal interceptors enabled on this registry (defaults to true)
|
||||
*/
|
||||
public boolean isThreadlocalInvokersEnabled() {
|
||||
return myThreadlocalInvokersEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are threadlocal interceptors enabled on this registry (defaults to true)
|
||||
*/
|
||||
public void setThreadlocalInvokersEnabled(boolean theThreadlocalInvokersEnabled) {
|
||||
myThreadlocalInvokersEnabled = theThreadlocalInvokersEnabled;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<Object> getGlobalInterceptorsForUnitTest() {
|
||||
return myInterceptors;
|
||||
}
|
||||
|
||||
public void setName(String theName) {
|
||||
myName = theName;
|
||||
}
|
||||
|
||||
protected void registerAnonymousInterceptor(POINTCUT thePointcut, Object theInterceptor, BaseInvoker theInvoker) {
|
||||
Validate.notNull(thePointcut);
|
||||
Validate.notNull(theInterceptor);
|
||||
synchronized (myRegistryMutex) {
|
||||
|
||||
myAnonymousInvokers.put(thePointcut, theInvoker);
|
||||
if (!isInterceptorAlreadyRegistered(theInterceptor)) {
|
||||
myInterceptors.add(theInterceptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> getAllRegisteredInterceptors() {
|
||||
synchronized (myRegistryMutex) {
|
||||
List<Object> retVal = new ArrayList<>();
|
||||
retVal.addAll(myInterceptors);
|
||||
return Collections.unmodifiableList(retVal);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@VisibleForTesting
|
||||
public void unregisterAllInterceptors() {
|
||||
synchronized (myRegistryMutex) {
|
||||
myAnonymousInvokers.clear();
|
||||
myGlobalInvokers.clear();
|
||||
myInterceptors.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterInterceptors(@Nullable Collection<?> theInterceptors) {
|
||||
if (theInterceptors != null) {
|
||||
theInterceptors.forEach(t -> unregisterInterceptor(t));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerInterceptors(@Nullable Collection<?> theInterceptors) {
|
||||
if (theInterceptors != null) {
|
||||
theInterceptors.forEach(t -> registerInterceptor(t));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterInterceptorsIf(Predicate<Object> theShouldUnregisterFunction) {
|
||||
unregisterInterceptorsIf(theShouldUnregisterFunction, myGlobalInvokers);
|
||||
unregisterInterceptorsIf(theShouldUnregisterFunction, myAnonymousInvokers);
|
||||
}
|
||||
|
||||
private void unregisterInterceptorsIf(Predicate<Object> theShouldUnregisterFunction, ListMultimap<POINTCUT, BaseInvoker> theGlobalInvokers) {
|
||||
theGlobalInvokers.entries().removeIf(t -> theShouldUnregisterFunction.test(t.getValue().getInterceptor()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerThreadLocalInterceptor(Object theInterceptor) {
|
||||
if (!myThreadlocalInvokersEnabled) {
|
||||
return false;
|
||||
}
|
||||
ListMultimap<POINTCUT, BaseInvoker> invokers = getThreadLocalInvokerMultimap();
|
||||
scanInterceptorAndAddToInvokerMultimap(theInterceptor, invokers);
|
||||
return !invokers.isEmpty();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterThreadLocalInterceptor(Object theInterceptor) {
|
||||
if (myThreadlocalInvokersEnabled) {
|
||||
ListMultimap<POINTCUT, BaseInvoker> invokers = getThreadLocalInvokerMultimap();
|
||||
invokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
|
||||
if (invokers.isEmpty()) {
|
||||
myThreadlocalInvokers.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ListMultimap<POINTCUT, BaseInvoker> getThreadLocalInvokerMultimap() {
|
||||
ListMultimap<POINTCUT, BaseInvoker> invokers = myThreadlocalInvokers.get();
|
||||
if (invokers == null) {
|
||||
invokers = Multimaps.synchronizedListMultimap(ArrayListMultimap.create());
|
||||
myThreadlocalInvokers.set(invokers);
|
||||
}
|
||||
return invokers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerInterceptor(Object theInterceptor) {
|
||||
synchronized (myRegistryMutex) {
|
||||
|
||||
if (isInterceptorAlreadyRegistered(theInterceptor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<HookInvoker> addedInvokers = scanInterceptorAndAddToInvokerMultimap(theInterceptor, myGlobalInvokers);
|
||||
if (addedInvokers.isEmpty()) {
|
||||
ourLog.warn("Interceptor registered with no valid hooks - Type was: {}", theInterceptor.getClass().getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add to the global list
|
||||
myInterceptors.add(theInterceptor);
|
||||
sortByOrderAnnotation(myInterceptors);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInterceptorAlreadyRegistered(Object theInterceptor) {
|
||||
for (Object next : myInterceptors) {
|
||||
if (next == theInterceptor) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterInterceptor(Object theInterceptor) {
|
||||
synchronized (myRegistryMutex) {
|
||||
boolean removed = myInterceptors.removeIf(t -> t == theInterceptor);
|
||||
removed |= myGlobalInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
|
||||
removed |= myAnonymousInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
private void sortByOrderAnnotation(List<Object> theObjects) {
|
||||
IdentityHashMap<Object, Integer> interceptorToOrder = new IdentityHashMap<>();
|
||||
for (Object next : theObjects) {
|
||||
Interceptor orderAnnotation = next.getClass().getAnnotation(Interceptor.class);
|
||||
int order = orderAnnotation != null ? orderAnnotation.order() : 0;
|
||||
interceptorToOrder.put(next, order);
|
||||
}
|
||||
|
||||
theObjects.sort((a, b) -> {
|
||||
Integer orderA = interceptorToOrder.get(a);
|
||||
Integer orderB = interceptorToOrder.get(b);
|
||||
return orderA - orderB;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object callHooksAndReturnObject(POINTCUT thePointcut, HookParams theParams) {
|
||||
assert haveAppropriateParams(thePointcut, theParams);
|
||||
assert thePointcut.getReturnType() != void.class;
|
||||
|
||||
return doCallHooks(thePointcut, theParams, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasHooks(POINTCUT thePointcut) {
|
||||
return myGlobalInvokers.containsKey(thePointcut)
|
||||
|| myAnonymousInvokers.containsKey(thePointcut)
|
||||
|| hasThreadLocalHooks(thePointcut);
|
||||
}
|
||||
|
||||
private boolean hasThreadLocalHooks(POINTCUT thePointcut) {
|
||||
ListMultimap<POINTCUT, BaseInvoker> hooks = myThreadlocalInvokersEnabled ? myThreadlocalInvokers.get() : null;
|
||||
return hooks != null && hooks.containsKey(thePointcut);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean callHooks(POINTCUT thePointcut, HookParams theParams) {
|
||||
assert haveAppropriateParams(thePointcut, theParams);
|
||||
assert thePointcut.getReturnType() == void.class || thePointcut.getReturnType() == boolean.class;
|
||||
|
||||
Object retValObj = doCallHooks(thePointcut, theParams, true);
|
||||
return (Boolean) retValObj;
|
||||
}
|
||||
|
||||
private Object doCallHooks(POINTCUT thePointcut, HookParams theParams, Object theRetVal) {
|
||||
List<BaseInvoker> invokers = getInvokersForPointcut(thePointcut);
|
||||
|
||||
/*
|
||||
* Call each hook in order
|
||||
*/
|
||||
for (BaseInvoker nextInvoker : invokers) {
|
||||
Object nextOutcome = nextInvoker.invoke(theParams);
|
||||
Class<?> pointcutReturnType = thePointcut.getReturnType();
|
||||
if (pointcutReturnType.equals(boolean.class)) {
|
||||
Boolean nextOutcomeAsBoolean = (Boolean) nextOutcome;
|
||||
if (Boolean.FALSE.equals(nextOutcomeAsBoolean)) {
|
||||
ourLog.trace("callHooks({}) for invoker({}) returned false", thePointcut, nextInvoker);
|
||||
theRetVal = false;
|
||||
break;
|
||||
}
|
||||
} else if (pointcutReturnType.equals(void.class) == false) {
|
||||
if (nextOutcome != null) {
|
||||
theRetVal = nextOutcome;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return theRetVal;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<Object> getInterceptorsWithInvokersForPointcut(POINTCUT thePointcut) {
|
||||
return getInvokersForPointcut(thePointcut)
|
||||
.stream()
|
||||
.map(BaseInvoker::getInterceptor)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ordered list of invokers for the given pointcut. Note that
|
||||
* a new and stable list is returned to.. do whatever you want with it.
|
||||
*/
|
||||
private List<BaseInvoker> getInvokersForPointcut(POINTCUT thePointcut) {
|
||||
List<BaseInvoker> invokers;
|
||||
|
||||
synchronized (myRegistryMutex) {
|
||||
List<BaseInvoker> globalInvokers = myGlobalInvokers.get(thePointcut);
|
||||
List<BaseInvoker> anonymousInvokers = myAnonymousInvokers.get(thePointcut);
|
||||
List<BaseInvoker> threadLocalInvokers = null;
|
||||
if (myThreadlocalInvokersEnabled) {
|
||||
ListMultimap<POINTCUT, BaseInvoker> pointcutToInvokers = myThreadlocalInvokers.get();
|
||||
if (pointcutToInvokers != null) {
|
||||
threadLocalInvokers = pointcutToInvokers.get(thePointcut);
|
||||
}
|
||||
}
|
||||
invokers = union(globalInvokers, anonymousInvokers, threadLocalInvokers);
|
||||
}
|
||||
|
||||
return invokers;
|
||||
}
|
||||
|
||||
/**
|
||||
* First argument must be the global invoker list!!
|
||||
*/
|
||||
@SafeVarargs
|
||||
private final List<BaseInvoker> union(List<BaseInvoker>... theInvokersLists) {
|
||||
List<BaseInvoker> haveOne = null;
|
||||
boolean haveMultiple = false;
|
||||
for (List<BaseInvoker> nextInvokerList : theInvokersLists) {
|
||||
if (nextInvokerList == null || nextInvokerList.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (haveOne == null) {
|
||||
haveOne = nextInvokerList;
|
||||
} else {
|
||||
haveMultiple = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (haveOne == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<BaseInvoker> retVal;
|
||||
|
||||
if (haveMultiple == false) {
|
||||
|
||||
// The global list doesn't need to be sorted every time since it's sorted on
|
||||
// insertion each time. Doing so is a waste of cycles..
|
||||
if (haveOne == theInvokersLists[0]) {
|
||||
retVal = haveOne;
|
||||
} else {
|
||||
retVal = new ArrayList<>(haveOne);
|
||||
retVal.sort(Comparator.naturalOrder());
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
retVal = Arrays
|
||||
.stream(theInvokersLists)
|
||||
.filter(t -> t != null)
|
||||
.flatMap(t -> t.stream())
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only call this when assertions are enabled, it's expensive
|
||||
*/
|
||||
boolean haveAppropriateParams(POINTCUT thePointcut, HookParams theParams) {
|
||||
Validate.isTrue(theParams.getParamsForType().values().size() == thePointcut.getParameterTypes().size(), "Wrong number of params for pointcut %s - Wanted %s but found %s", thePointcut.name(), toErrorString(thePointcut.getParameterTypes()), theParams.getParamsForType().values().stream().map(t -> t != null ? t.getClass().getSimpleName() : "null").sorted().collect(Collectors.toList()));
|
||||
|
||||
List<String> wantedTypes = new ArrayList<>(thePointcut.getParameterTypes());
|
||||
|
||||
ListMultimap<Class<?>, Object> givenTypes = theParams.getParamsForType();
|
||||
for (Class<?> nextTypeClass : givenTypes.keySet()) {
|
||||
String nextTypeName = nextTypeClass.getName();
|
||||
for (Object nextParamValue : givenTypes.get(nextTypeClass)) {
|
||||
Validate.isTrue(nextParamValue == null || nextTypeClass.isAssignableFrom(nextParamValue.getClass()), "Invalid params for pointcut %s - %s is not of type %s", thePointcut.name(), nextParamValue != null ? nextParamValue.getClass() : "null", nextTypeClass);
|
||||
Validate.isTrue(wantedTypes.remove(nextTypeName), "Invalid params for pointcut %s - Wanted %s but found %s", thePointcut.name(), toErrorString(thePointcut.getParameterTypes()), nextTypeName);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<HookInvoker> scanInterceptorAndAddToInvokerMultimap(Object theInterceptor, ListMultimap<POINTCUT, BaseInvoker> theInvokers) {
|
||||
Class<?> interceptorClass = theInterceptor.getClass();
|
||||
int typeOrder = determineOrder(interceptorClass);
|
||||
|
||||
List<HookInvoker> addedInvokers = scanInterceptorForHookMethods(theInterceptor, typeOrder);
|
||||
|
||||
// Invoke the REGISTERED pointcut for any added hooks
|
||||
addedInvokers.stream()
|
||||
.filter(t -> Pointcut.INTERCEPTOR_REGISTERED.equals(t.getPointcut()))
|
||||
.forEach(t -> t.invoke(new HookParams()));
|
||||
|
||||
// Register the interceptor and its various hooks
|
||||
for (HookInvoker nextAddedHook : addedInvokers) {
|
||||
IPointcut nextPointcut = nextAddedHook.getPointcut();
|
||||
if (nextPointcut.equals(Pointcut.INTERCEPTOR_REGISTERED)) {
|
||||
continue;
|
||||
}
|
||||
theInvokers.put((POINTCUT) nextPointcut, nextAddedHook);
|
||||
}
|
||||
|
||||
// Make sure we're always sorted according to the order declared in
|
||||
// @Order
|
||||
for (IPointcut nextPointcut : theInvokers.keys()) {
|
||||
List<BaseInvoker> nextInvokerList = theInvokers.get((POINTCUT) nextPointcut);
|
||||
nextInvokerList.sort(Comparator.naturalOrder());
|
||||
}
|
||||
|
||||
return addedInvokers;
|
||||
}
|
||||
|
||||
protected abstract static class BaseInvoker implements Comparable<BaseInvoker> {
|
||||
|
||||
private final int myOrder;
|
||||
private final Object myInterceptor;
|
||||
|
||||
BaseInvoker(Object theInterceptor, int theOrder) {
|
||||
myInterceptor = theInterceptor;
|
||||
myOrder = theOrder;
|
||||
}
|
||||
|
||||
public Object getInterceptor() {
|
||||
return myInterceptor;
|
||||
}
|
||||
|
||||
abstract Object invoke(HookParams theParams);
|
||||
|
||||
@Override
|
||||
public int compareTo(BaseInvoker theInvoker) {
|
||||
return myOrder - theInvoker.myOrder;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HookInvoker extends BaseInvoker {
|
||||
|
||||
private final Method myMethod;
|
||||
private final Class<?>[] myParameterTypes;
|
||||
private final int[] myParameterIndexes;
|
||||
private final IPointcut myPointcut;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
private HookInvoker(HookDescriptor theHook, @Nonnull Object theInterceptor, @Nonnull Method theHookMethod, int theOrder) {
|
||||
super(theInterceptor, theOrder);
|
||||
myPointcut = theHook.getPointcut();
|
||||
myParameterTypes = theHookMethod.getParameterTypes();
|
||||
myMethod = theHookMethod;
|
||||
|
||||
Class<?> returnType = theHookMethod.getReturnType();
|
||||
if (myPointcut.getReturnType().equals(boolean.class)) {
|
||||
Validate.isTrue(boolean.class.equals(returnType) || void.class.equals(returnType), "Method does not return boolean or void: %s", theHookMethod);
|
||||
} else if (myPointcut.getReturnType().equals(void.class)) {
|
||||
Validate.isTrue(void.class.equals(returnType), "Method does not return void: %s", theHookMethod);
|
||||
} else {
|
||||
Validate.isTrue(myPointcut.getReturnType().isAssignableFrom(returnType) || void.class.equals(returnType), "Method does not return %s or void: %s", myPointcut.getReturnType(), theHookMethod);
|
||||
}
|
||||
|
||||
myParameterIndexes = new int[myParameterTypes.length];
|
||||
Map<Class<?>, AtomicInteger> typeToCount = new HashMap<>();
|
||||
for (int i = 0; i < myParameterTypes.length; i++) {
|
||||
AtomicInteger counter = typeToCount.computeIfAbsent(myParameterTypes[i], t -> new AtomicInteger(0));
|
||||
myParameterIndexes[i] = counter.getAndIncrement();
|
||||
}
|
||||
|
||||
myMethod.setAccessible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||
.append("method", myMethod)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public IPointcut getPointcut() {
|
||||
return myPointcut;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns true/false if the hook method returns a boolean, returns true otherwise
|
||||
*/
|
||||
@Override
|
||||
Object invoke(HookParams theParams) {
|
||||
|
||||
Object[] args = new Object[myParameterTypes.length];
|
||||
for (int i = 0; i < myParameterTypes.length; i++) {
|
||||
Class<?> nextParamType = myParameterTypes[i];
|
||||
if (nextParamType.equals(Pointcut.class)) {
|
||||
args[i] = myPointcut;
|
||||
} else {
|
||||
int nextParamIndex = myParameterIndexes[i];
|
||||
Object nextParamValue = theParams.get(nextParamType, nextParamIndex);
|
||||
args[i] = nextParamValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke the method
|
||||
try {
|
||||
return myMethod.invoke(getInterceptor(), args);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable targetException = e.getTargetException();
|
||||
if (myPointcut.isShouldLogAndSwallowException(targetException)) {
|
||||
ourLog.error("Exception thrown by interceptor: " + targetException.toString(), targetException);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (targetException instanceof RuntimeException) {
|
||||
throw ((RuntimeException) targetException);
|
||||
} else {
|
||||
throw new InternalErrorException("Failure invoking interceptor for pointcut(s) " + getPointcut(), targetException);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns a list of any added invokers
|
||||
*/
|
||||
private List<HookInvoker> scanInterceptorForHookMethods(Object theInterceptor, int theTypeOrder) {
|
||||
ArrayList<HookInvoker> retVal = new ArrayList<>();
|
||||
for (Method nextMethod : ReflectionUtil.getDeclaredMethods(theInterceptor.getClass(), true)) {
|
||||
Optional<HookDescriptor> hook = scanForHook(nextMethod);
|
||||
|
||||
if (hook.isPresent()) {
|
||||
int methodOrder = theTypeOrder;
|
||||
int methodOrderAnnotation = hook.get().getOrder();
|
||||
if (methodOrderAnnotation != Interceptor.DEFAULT_ORDER) {
|
||||
methodOrder = methodOrderAnnotation;
|
||||
}
|
||||
|
||||
retVal.add(new HookInvoker(hook.get(), theInterceptor, nextMethod, methodOrder));
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected abstract Optional<HookDescriptor> scanForHook(Method nextMethod);
|
||||
|
||||
protected static <T extends Annotation> Optional<T> findAnnotation(AnnotatedElement theObject, Class<T> theHookClass) {
|
||||
T annotation;
|
||||
if (theObject instanceof Method) {
|
||||
annotation = MethodUtils.getAnnotation((Method) theObject, theHookClass, true, true);
|
||||
} else {
|
||||
annotation = theObject.getAnnotation(theHookClass);
|
||||
}
|
||||
return Optional.ofNullable(annotation);
|
||||
}
|
||||
|
||||
private static int determineOrder(Class<?> theInterceptorClass) {
|
||||
int typeOrder = Interceptor.DEFAULT_ORDER;
|
||||
Optional<Interceptor> typeOrderAnnotation = findAnnotation(theInterceptorClass, Interceptor.class);
|
||||
if (typeOrderAnnotation.isPresent()) {
|
||||
typeOrder = typeOrderAnnotation.get().order();
|
||||
}
|
||||
return typeOrder;
|
||||
}
|
||||
|
||||
private static String toErrorString(List<String> theParameterTypes) {
|
||||
return theParameterTypes
|
||||
.stream()
|
||||
.sorted()
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
protected static class HookDescriptor {
|
||||
|
||||
private final IPointcut myPointcut;
|
||||
private final int myOrder;
|
||||
|
||||
HookDescriptor(IPointcut thePointcut, int theOrder) {
|
||||
myPointcut = thePointcut;
|
||||
myOrder = theOrder;
|
||||
}
|
||||
|
||||
IPointcut getPointcut() {
|
||||
return myPointcut;
|
||||
}
|
||||
|
||||
int getOrder() {
|
||||
return myOrder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -27,39 +27,13 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
|||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.interceptor.api.Interceptor;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.apache.commons.lang3.reflect.MethodUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Optional;
|
||||
|
||||
public class InterceptorService implements IInterceptorService, IInterceptorBroadcaster {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(InterceptorService.class);
|
||||
private final List<Object> myInterceptors = new ArrayList<>();
|
||||
private final ListMultimap<Pointcut, BaseInvoker> myGlobalInvokers = ArrayListMultimap.create();
|
||||
private final ListMultimap<Pointcut, BaseInvoker> myAnonymousInvokers = ArrayListMultimap.create();
|
||||
private final Object myRegistryMutex = new Object();
|
||||
private final ThreadLocal<ListMultimap<Pointcut, BaseInvoker>> myThreadlocalInvokers = new ThreadLocal<>();
|
||||
private String myName;
|
||||
private boolean myThreadlocalInvokersEnabled = true;
|
||||
public class InterceptorService extends BaseInterceptorService<Pointcut> implements IInterceptorService, IInterceptorBroadcaster {
|
||||
|
||||
/**
|
||||
* Constructor which uses a default name of "default"
|
||||
|
@ -74,28 +48,14 @@ public class InterceptorService implements IInterceptorService, IInterceptorBroa
|
|||
* @param theName The name for this registry (useful for troubleshooting)
|
||||
*/
|
||||
public InterceptorService(String theName) {
|
||||
super();
|
||||
myName = theName;
|
||||
super(theName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Are threadlocal interceptors enabled on this registry (defaults to true)
|
||||
*/
|
||||
public boolean isThreadlocalInvokersEnabled() {
|
||||
return myThreadlocalInvokersEnabled;
|
||||
@Override
|
||||
protected Optional<HookDescriptor> scanForHook(Method nextMethod) {
|
||||
return findAnnotation(nextMethod, Hook.class).map(t -> new HookDescriptor(t.value(), t.order()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Are threadlocal interceptors enabled on this registry (defaults to true)
|
||||
*/
|
||||
public void setThreadlocalInvokersEnabled(boolean theThreadlocalInvokersEnabled) {
|
||||
myThreadlocalInvokersEnabled = theThreadlocalInvokersEnabled;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<Object> getGlobalInterceptorsForUnitTest() {
|
||||
return myInterceptors;
|
||||
}
|
||||
|
||||
@Override
|
||||
@VisibleForTesting
|
||||
|
@ -103,309 +63,14 @@ public class InterceptorService implements IInterceptorService, IInterceptorBroa
|
|||
registerAnonymousInterceptor(thePointcut, Interceptor.DEFAULT_ORDER, theInterceptor);
|
||||
}
|
||||
|
||||
public void setName(String theName) {
|
||||
myName = theName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerAnonymousInterceptor(Pointcut thePointcut, int theOrder, IAnonymousInterceptor theInterceptor) {
|
||||
Validate.notNull(thePointcut);
|
||||
Validate.notNull(theInterceptor);
|
||||
synchronized (myRegistryMutex) {
|
||||
|
||||
myAnonymousInvokers.put(thePointcut, new AnonymousLambdaInvoker(thePointcut, theInterceptor, theOrder));
|
||||
if (!isInterceptorAlreadyRegistered(theInterceptor)) {
|
||||
myInterceptors.add(theInterceptor);
|
||||
}
|
||||
}
|
||||
BaseInvoker invoker = new AnonymousLambdaInvoker(thePointcut, theInterceptor, theOrder);
|
||||
registerAnonymousInterceptor(thePointcut, theInterceptor, invoker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> getAllRegisteredInterceptors() {
|
||||
synchronized (myRegistryMutex) {
|
||||
List<Object> retVal = new ArrayList<>();
|
||||
retVal.addAll(myInterceptors);
|
||||
return Collections.unmodifiableList(retVal);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@VisibleForTesting
|
||||
public void unregisterAllInterceptors() {
|
||||
synchronized (myRegistryMutex) {
|
||||
myAnonymousInvokers.clear();
|
||||
myGlobalInvokers.clear();
|
||||
myInterceptors.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterInterceptors(@Nullable Collection<?> theInterceptors) {
|
||||
if (theInterceptors != null) {
|
||||
theInterceptors.forEach(t -> unregisterInterceptor(t));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerInterceptors(@Nullable Collection<?> theInterceptors) {
|
||||
if (theInterceptors != null) {
|
||||
theInterceptors.forEach(t -> registerInterceptor(t));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterInterceptorsIf(Predicate<Object> theShouldUnregisterFunction) {
|
||||
unregisterInterceptorsIf(theShouldUnregisterFunction, myGlobalInvokers);
|
||||
unregisterInterceptorsIf(theShouldUnregisterFunction, myAnonymousInvokers);
|
||||
}
|
||||
|
||||
private void unregisterInterceptorsIf(Predicate<Object> theShouldUnregisterFunction, ListMultimap<Pointcut, BaseInvoker> theGlobalInvokers) {
|
||||
theGlobalInvokers.entries().removeIf(t->theShouldUnregisterFunction.test(t.getValue().getInterceptor()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerThreadLocalInterceptor(Object theInterceptor) {
|
||||
if (!myThreadlocalInvokersEnabled) {
|
||||
return false;
|
||||
}
|
||||
ListMultimap<Pointcut, BaseInvoker> invokers = getThreadLocalInvokerMultimap();
|
||||
scanInterceptorAndAddToInvokerMultimap(theInterceptor, invokers);
|
||||
return !invokers.isEmpty();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterThreadLocalInterceptor(Object theInterceptor) {
|
||||
if (myThreadlocalInvokersEnabled) {
|
||||
ListMultimap<Pointcut, BaseInvoker> invokers = getThreadLocalInvokerMultimap();
|
||||
invokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
|
||||
if (invokers.isEmpty()) {
|
||||
myThreadlocalInvokers.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ListMultimap<Pointcut, BaseInvoker> getThreadLocalInvokerMultimap() {
|
||||
ListMultimap<Pointcut, BaseInvoker> invokers = myThreadlocalInvokers.get();
|
||||
if (invokers == null) {
|
||||
invokers = Multimaps.synchronizedListMultimap(ArrayListMultimap.create());
|
||||
myThreadlocalInvokers.set(invokers);
|
||||
}
|
||||
return invokers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerInterceptor(Object theInterceptor) {
|
||||
synchronized (myRegistryMutex) {
|
||||
|
||||
if (isInterceptorAlreadyRegistered(theInterceptor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<HookInvoker> addedInvokers = scanInterceptorAndAddToInvokerMultimap(theInterceptor, myGlobalInvokers);
|
||||
if (addedInvokers.isEmpty()) {
|
||||
ourLog.warn("Interceptor registered with no valid hooks - Type was: {}", theInterceptor.getClass().getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add to the global list
|
||||
myInterceptors.add(theInterceptor);
|
||||
sortByOrderAnnotation(myInterceptors);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInterceptorAlreadyRegistered(Object theInterceptor) {
|
||||
for (Object next : myInterceptors) {
|
||||
if (next == theInterceptor) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregisterInterceptor(Object theInterceptor) {
|
||||
synchronized (myRegistryMutex) {
|
||||
boolean removed = myInterceptors.removeIf(t -> t == theInterceptor);
|
||||
removed |= myGlobalInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
|
||||
removed |= myAnonymousInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
private void sortByOrderAnnotation(List<Object> theObjects) {
|
||||
IdentityHashMap<Object, Integer> interceptorToOrder = new IdentityHashMap<>();
|
||||
for (Object next : theObjects) {
|
||||
Interceptor orderAnnotation = next.getClass().getAnnotation(Interceptor.class);
|
||||
int order = orderAnnotation != null ? orderAnnotation.order() : 0;
|
||||
interceptorToOrder.put(next, order);
|
||||
}
|
||||
|
||||
theObjects.sort((a, b) -> {
|
||||
Integer orderA = interceptorToOrder.get(a);
|
||||
Integer orderB = interceptorToOrder.get(b);
|
||||
return orderA - orderB;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object callHooksAndReturnObject(Pointcut thePointcut, HookParams theParams) {
|
||||
assert haveAppropriateParams(thePointcut, theParams);
|
||||
assert thePointcut.getReturnType() != void.class;
|
||||
|
||||
return doCallHooks(thePointcut, theParams, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasHooks(Pointcut thePointcut) {
|
||||
return myGlobalInvokers.containsKey(thePointcut)
|
||||
|| myAnonymousInvokers.containsKey(thePointcut)
|
||||
|| hasThreadLocalHooks(thePointcut);
|
||||
}
|
||||
|
||||
private boolean hasThreadLocalHooks(Pointcut thePointcut) {
|
||||
ListMultimap<Pointcut, BaseInvoker> hooks = myThreadlocalInvokersEnabled ? myThreadlocalInvokers.get() : null;
|
||||
return hooks != null && hooks.containsKey(thePointcut);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean callHooks(Pointcut thePointcut, HookParams theParams) {
|
||||
assert haveAppropriateParams(thePointcut, theParams);
|
||||
assert thePointcut.getReturnType() == void.class || thePointcut.getReturnType() == boolean.class;
|
||||
|
||||
Object retValObj = doCallHooks(thePointcut, theParams, true);
|
||||
return (Boolean) retValObj;
|
||||
}
|
||||
|
||||
private Object doCallHooks(Pointcut thePointcut, HookParams theParams, Object theRetVal) {
|
||||
List<BaseInvoker> invokers = getInvokersForPointcut(thePointcut);
|
||||
|
||||
/*
|
||||
* Call each hook in order
|
||||
*/
|
||||
for (BaseInvoker nextInvoker : invokers) {
|
||||
Object nextOutcome = nextInvoker.invoke(theParams);
|
||||
Class<?> pointcutReturnType = thePointcut.getReturnType();
|
||||
if (pointcutReturnType.equals(boolean.class)) {
|
||||
Boolean nextOutcomeAsBoolean = (Boolean) nextOutcome;
|
||||
if (Boolean.FALSE.equals(nextOutcomeAsBoolean)) {
|
||||
ourLog.trace("callHooks({}) for invoker({}) returned false", thePointcut, nextInvoker);
|
||||
theRetVal = false;
|
||||
break;
|
||||
}
|
||||
} else if (pointcutReturnType.equals(void.class) == false) {
|
||||
if (nextOutcome != null) {
|
||||
theRetVal = nextOutcome;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return theRetVal;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<Object> getInterceptorsWithInvokersForPointcut(Pointcut thePointcut) {
|
||||
return getInvokersForPointcut(thePointcut)
|
||||
.stream()
|
||||
.map(BaseInvoker::getInterceptor)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ordered list of invokers for the given pointcut. Note that
|
||||
* a new and stable list is returned to.. do whatever you want with it.
|
||||
*/
|
||||
private List<BaseInvoker> getInvokersForPointcut(Pointcut thePointcut) {
|
||||
List<BaseInvoker> invokers;
|
||||
|
||||
synchronized (myRegistryMutex) {
|
||||
List<BaseInvoker> globalInvokers = myGlobalInvokers.get(thePointcut);
|
||||
List<BaseInvoker> anonymousInvokers = myAnonymousInvokers.get(thePointcut);
|
||||
List<BaseInvoker> threadLocalInvokers = null;
|
||||
if (myThreadlocalInvokersEnabled) {
|
||||
ListMultimap<Pointcut, BaseInvoker> pointcutToInvokers = myThreadlocalInvokers.get();
|
||||
if (pointcutToInvokers != null) {
|
||||
threadLocalInvokers = pointcutToInvokers.get(thePointcut);
|
||||
}
|
||||
}
|
||||
invokers = union(globalInvokers, anonymousInvokers, threadLocalInvokers);
|
||||
}
|
||||
|
||||
return invokers;
|
||||
}
|
||||
|
||||
/**
|
||||
* First argument must be the global invoker list!!
|
||||
*/
|
||||
@SafeVarargs
|
||||
private final List<BaseInvoker> union(List<BaseInvoker>... theInvokersLists) {
|
||||
List<BaseInvoker> haveOne = null;
|
||||
boolean haveMultiple = false;
|
||||
for (List<BaseInvoker> nextInvokerList : theInvokersLists) {
|
||||
if (nextInvokerList == null || nextInvokerList.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (haveOne == null) {
|
||||
haveOne = nextInvokerList;
|
||||
} else {
|
||||
haveMultiple = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (haveOne == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<BaseInvoker> retVal;
|
||||
|
||||
if (haveMultiple == false) {
|
||||
|
||||
// The global list doesn't need to be sorted every time since it's sorted on
|
||||
// insertion each time. Doing so is a waste of cycles..
|
||||
if (haveOne == theInvokersLists[0]) {
|
||||
retVal = haveOne;
|
||||
} else {
|
||||
retVal = new ArrayList<>(haveOne);
|
||||
retVal.sort(Comparator.naturalOrder());
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
retVal = Arrays
|
||||
.stream(theInvokersLists)
|
||||
.filter(t -> t != null)
|
||||
.flatMap(t -> t.stream())
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only call this when assertions are enabled, it's expensive
|
||||
*/
|
||||
boolean haveAppropriateParams(Pointcut thePointcut, HookParams theParams) {
|
||||
Validate.isTrue(theParams.getParamsForType().values().size() == thePointcut.getParameterTypes().size(), "Wrong number of params for pointcut %s - Wanted %s but found %s", thePointcut.name(), toErrorString(thePointcut.getParameterTypes()), theParams.getParamsForType().values().stream().map(t -> t != null ? t.getClass().getSimpleName() : "null").sorted().collect(Collectors.toList()));
|
||||
|
||||
List<String> wantedTypes = new ArrayList<>(thePointcut.getParameterTypes());
|
||||
|
||||
ListMultimap<Class<?>, Object> givenTypes = theParams.getParamsForType();
|
||||
for (Class<?> nextTypeClass : givenTypes.keySet()) {
|
||||
String nextTypeName = nextTypeClass.getName();
|
||||
for (Object nextParamValue : givenTypes.get(nextTypeClass)) {
|
||||
Validate.isTrue(nextParamValue == null || nextTypeClass.isAssignableFrom(nextParamValue.getClass()), "Invalid params for pointcut %s - %s is not of type %s", thePointcut.name(), nextParamValue != null ? nextParamValue.getClass() : "null", nextTypeClass);
|
||||
Validate.isTrue(wantedTypes.remove(nextTypeName), "Invalid params for pointcut %s - Wanted %s but found %s", thePointcut.name(), toErrorString(thePointcut.getParameterTypes()), nextTypeName);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private class AnonymousLambdaInvoker extends BaseInvoker {
|
||||
private final IAnonymousInterceptor myHook;
|
||||
|
@ -424,191 +89,5 @@ public class InterceptorService implements IInterceptorService, IInterceptorBroa
|
|||
}
|
||||
}
|
||||
|
||||
private abstract static class BaseInvoker implements Comparable<BaseInvoker> {
|
||||
|
||||
private final int myOrder;
|
||||
private final Object myInterceptor;
|
||||
|
||||
BaseInvoker(Object theInterceptor, int theOrder) {
|
||||
myInterceptor = theInterceptor;
|
||||
myOrder = theOrder;
|
||||
}
|
||||
|
||||
public Object getInterceptor() {
|
||||
return myInterceptor;
|
||||
}
|
||||
|
||||
abstract Object invoke(HookParams theParams);
|
||||
|
||||
@Override
|
||||
public int compareTo(BaseInvoker theInvoker) {
|
||||
return myOrder - theInvoker.myOrder;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HookInvoker extends BaseInvoker {
|
||||
|
||||
private final Method myMethod;
|
||||
private final Class<?>[] myParameterTypes;
|
||||
private final int[] myParameterIndexes;
|
||||
private final Pointcut myPointcut;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
private HookInvoker(Hook theHook, @Nonnull Object theInterceptor, @Nonnull Method theHookMethod, int theOrder) {
|
||||
super(theInterceptor, theOrder);
|
||||
myPointcut = theHook.value();
|
||||
myParameterTypes = theHookMethod.getParameterTypes();
|
||||
myMethod = theHookMethod;
|
||||
|
||||
Class<?> returnType = theHookMethod.getReturnType();
|
||||
if (myPointcut.getReturnType().equals(boolean.class)) {
|
||||
Validate.isTrue(boolean.class.equals(returnType) || void.class.equals(returnType), "Method does not return boolean or void: %s", theHookMethod);
|
||||
} else if (myPointcut.getReturnType().equals(void.class)) {
|
||||
Validate.isTrue(void.class.equals(returnType), "Method does not return void: %s", theHookMethod);
|
||||
} else {
|
||||
Validate.isTrue(myPointcut.getReturnType().isAssignableFrom(returnType) || void.class.equals(returnType), "Method does not return %s or void: %s", myPointcut.getReturnType(), theHookMethod);
|
||||
}
|
||||
|
||||
myParameterIndexes = new int[myParameterTypes.length];
|
||||
Map<Class<?>, AtomicInteger> typeToCount = new HashMap<>();
|
||||
for (int i = 0; i < myParameterTypes.length; i++) {
|
||||
AtomicInteger counter = typeToCount.computeIfAbsent(myParameterTypes[i], t -> new AtomicInteger(0));
|
||||
myParameterIndexes[i] = counter.getAndIncrement();
|
||||
}
|
||||
|
||||
myMethod.setAccessible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||
.append("method", myMethod)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public Pointcut getPointcut() {
|
||||
return myPointcut;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns true/false if the hook method returns a boolean, returns true otherwise
|
||||
*/
|
||||
@Override
|
||||
Object invoke(HookParams theParams) {
|
||||
|
||||
Object[] args = new Object[myParameterTypes.length];
|
||||
for (int i = 0; i < myParameterTypes.length; i++) {
|
||||
Class<?> nextParamType = myParameterTypes[i];
|
||||
if (nextParamType.equals(Pointcut.class)) {
|
||||
args[i] = myPointcut;
|
||||
} else {
|
||||
int nextParamIndex = myParameterIndexes[i];
|
||||
Object nextParamValue = theParams.get(nextParamType, nextParamIndex);
|
||||
args[i] = nextParamValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke the method
|
||||
try {
|
||||
return myMethod.invoke(getInterceptor(), args);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable targetException = e.getTargetException();
|
||||
if (myPointcut.isShouldLogAndSwallowException(targetException)) {
|
||||
ourLog.error("Exception thrown by interceptor: " + targetException.toString(), targetException);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (targetException instanceof RuntimeException) {
|
||||
throw ((RuntimeException) targetException);
|
||||
} else {
|
||||
throw new InternalErrorException("Failure invoking interceptor for pointcut(s) " + getPointcut(), targetException);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static List<HookInvoker> scanInterceptorAndAddToInvokerMultimap(Object theInterceptor, ListMultimap<Pointcut, BaseInvoker> theInvokers) {
|
||||
Class<?> interceptorClass = theInterceptor.getClass();
|
||||
int typeOrder = determineOrder(interceptorClass);
|
||||
|
||||
List<HookInvoker> addedInvokers = scanInterceptorForHookMethods(theInterceptor, typeOrder);
|
||||
|
||||
// Invoke the REGISTERED pointcut for any added hooks
|
||||
addedInvokers.stream()
|
||||
.filter(t -> Pointcut.INTERCEPTOR_REGISTERED.equals(t.getPointcut()))
|
||||
.forEach(t -> t.invoke(new HookParams()));
|
||||
|
||||
// Register the interceptor and its various hooks
|
||||
for (HookInvoker nextAddedHook : addedInvokers) {
|
||||
Pointcut nextPointcut = nextAddedHook.getPointcut();
|
||||
if (nextPointcut.equals(Pointcut.INTERCEPTOR_REGISTERED)) {
|
||||
continue;
|
||||
}
|
||||
theInvokers.put(nextPointcut, nextAddedHook);
|
||||
}
|
||||
|
||||
// Make sure we're always sorted according to the order declared in
|
||||
// @Order
|
||||
for (Pointcut nextPointcut : theInvokers.keys()) {
|
||||
List<BaseInvoker> nextInvokerList = theInvokers.get(nextPointcut);
|
||||
nextInvokerList.sort(Comparator.naturalOrder());
|
||||
}
|
||||
|
||||
return addedInvokers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns a list of any added invokers
|
||||
*/
|
||||
private static List<HookInvoker> scanInterceptorForHookMethods(Object theInterceptor, int theTypeOrder) {
|
||||
ArrayList<HookInvoker> retVal = new ArrayList<>();
|
||||
for (Method nextMethod : ReflectionUtil.getDeclaredMethods(theInterceptor.getClass(), true)) {
|
||||
Optional<Hook> hook = findAnnotation(nextMethod, Hook.class);
|
||||
|
||||
if (hook.isPresent()) {
|
||||
int methodOrder = theTypeOrder;
|
||||
int methodOrderAnnotation = hook.get().order();
|
||||
if (methodOrderAnnotation != Interceptor.DEFAULT_ORDER) {
|
||||
methodOrder = methodOrderAnnotation;
|
||||
}
|
||||
|
||||
retVal.add(new HookInvoker(hook.get(), theInterceptor, nextMethod, methodOrder));
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private static <T extends Annotation> Optional<T> findAnnotation(AnnotatedElement theObject, Class<T> theHookClass) {
|
||||
T annotation;
|
||||
if (theObject instanceof Method) {
|
||||
annotation = MethodUtils.getAnnotation((Method) theObject, theHookClass, true, true);
|
||||
} else {
|
||||
annotation = theObject.getAnnotation(theHookClass);
|
||||
}
|
||||
return Optional.ofNullable(annotation);
|
||||
}
|
||||
|
||||
private static int determineOrder(Class<?> theInterceptorClass) {
|
||||
int typeOrder = Interceptor.DEFAULT_ORDER;
|
||||
Optional<Interceptor> typeOrderAnnotation = findAnnotation(theInterceptorClass, Interceptor.class);
|
||||
if (typeOrderAnnotation.isPresent()) {
|
||||
typeOrder = typeOrderAnnotation.get().order();
|
||||
}
|
||||
return typeOrder;
|
||||
}
|
||||
|
||||
private static String toErrorString(List<String> theParameterTypes) {
|
||||
return theParameterTypes
|
||||
.stream()
|
||||
.sorted()
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQuer
|
|||
* Constructor
|
||||
*/
|
||||
public DateParam() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -224,14 +224,6 @@
|
|||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
</dependency>
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-core</artifactId>-->
|
||||
<!--</dependency>-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-impl</artifactId>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: perf
|
||||
issue: 2302
|
||||
title: The JPA server generated SQL for date search parameters has been streamlined to avoid the use of
|
||||
redundant OR expressions that slow down performance in some cases.
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 2309
|
||||
title: "In the JPA server, HumanName.name.text was not being indexed and therefore was not searchable. This has been corrected."
|
|
@ -56,37 +56,15 @@
|
|||
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-jpaserver-model</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-jpaserver-searchparam</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>xml-apis</artifactId>
|
||||
<groupId>xml-apis</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.jboss.spec.javax.transaction</groupId>
|
||||
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>javax.activation</groupId>
|
||||
<artifactId>activation</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>javax.activation</groupId>
|
||||
<artifactId>javax.activation-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-jpaserver-model</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-jpaserver-searchparam</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate.search</groupId>
|
||||
|
@ -137,13 +115,13 @@
|
|||
<artifactId>javax.annotation-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<!-- test dependencies -->
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
|
|
|
@ -373,43 +373,13 @@
|
|||
</dependency>
|
||||
|
||||
<!-- Hibernate -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>xml-apis</artifactId>
|
||||
<groupId>xml-apis</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>javax.activation</groupId>
|
||||
<artifactId>javax.activation-api</artifactId>
|
||||
</exclusion>
|
||||
<!--<exclusion>-->
|
||||
<!--<groupId>org.jboss.spec.javax.transaction</groupId>-->
|
||||
<!--<artifactId>jboss-transaction-api_1.2_spec</artifactId>-->
|
||||
<!--</exclusion>-->
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-entitymanager</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jboss.spec.javax.transaction</groupId>
|
||||
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-java8</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jboss.spec.javax.transaction</groupId>
|
||||
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
|
@ -435,11 +405,6 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.activation</groupId>
|
||||
<artifactId>javax.activation</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>javax.mail-api</artifactId>
|
||||
|
@ -715,16 +680,6 @@
|
|||
<artifactId>jaxb-runtime</artifactId>
|
||||
<version>${jaxb_runtime_version}</version>
|
||||
</dependency>
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-core</artifactId>-->
|
||||
<!--<version>${jaxb_core_version}</version>-->
|
||||
<!--</dependency>-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-impl</artifactId>-->
|
||||
<!--<version>${jaxb_core_version}</version>-->
|
||||
<!--</dependency>-->
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
|
|
@ -1316,11 +1316,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
theParams.setOffset(offset);
|
||||
}
|
||||
|
||||
final Integer count = RestfulServerUtils.extractCountParameter(theRequest);
|
||||
Integer count = RestfulServerUtils.extractCountParameter(theRequest);
|
||||
if (count != null) {
|
||||
Integer maxPageSize = theRequest.getServer().getMaximumPageSize();
|
||||
if (maxPageSize != null) {
|
||||
Validate.inclusiveBetween(1, theRequest.getServer().getMaximumPageSize(), count, "Count must be positive integer and less than " + maxPageSize);
|
||||
if (maxPageSize != null && count > maxPageSize) {
|
||||
ourLog.info("Reducing {} from {} to {} which is the maximum allowable page size.", Constants.PARAM_COUNT, count, maxPageSize);
|
||||
count = maxPageSize;
|
||||
}
|
||||
theParams.setCount(count);
|
||||
} else if (theRequest.getServer().getDefaultPageSize() != null) {
|
||||
|
|
|
@ -126,6 +126,9 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
|
|||
if (theParam instanceof DateParam) {
|
||||
DateParam date = (DateParam) theParam;
|
||||
if (!date.isEmpty()) {
|
||||
if (theOperation == SearchFilterParser.CompareOperation.ne) {
|
||||
date = new DateParam(ParamPrefixEnum.EQUAL, date.getValueAsString());
|
||||
}
|
||||
DateRangeParam range = new DateRangeParam(date);
|
||||
p = createPredicateDateFromRange(theBuilder,
|
||||
theFrom,
|
||||
|
@ -152,6 +155,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
|
|||
return theDateParam == null || theDateParam.getPrecision().ordinal() == TemporalPrecisionEnum.DAY.ordinal();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Predicate createPredicateDateFromRange(CriteriaBuilder theBuilder,
|
||||
From<?, ResourceIndexedSearchParamDate> theFrom,
|
||||
DateRangeParam theRange,
|
||||
|
@ -191,36 +195,60 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi
|
|||
}
|
||||
|
||||
if (operation == SearchFilterParser.CompareOperation.lt) {
|
||||
if (lowerBoundInstant == null) {
|
||||
throw new InvalidRequestException("lowerBound value not correctly specified for compare operation");
|
||||
}
|
||||
//im like 80% sure this should be ub and not lb, as it is an UPPER bound.
|
||||
lb = theBuilder.lessThan(theFrom.get(lowValueField), genericLowerBound);
|
||||
} else if (operation == SearchFilterParser.CompareOperation.le) {
|
||||
if (upperBoundInstant == null) {
|
||||
throw new InvalidRequestException("upperBound value not correctly specified for compare operation");
|
||||
}
|
||||
//im like 80% sure this should be ub and not lb, as it is an UPPER bound.
|
||||
lb = theBuilder.lessThanOrEqualTo(theFrom.get(highValueField), genericUpperBound);
|
||||
} else if (operation == SearchFilterParser.CompareOperation.gt) {
|
||||
if (upperBoundInstant == null) {
|
||||
throw new InvalidRequestException("upperBound value not correctly specified for compare operation");
|
||||
}
|
||||
lb = theBuilder.greaterThan(theFrom.get(highValueField), genericUpperBound);
|
||||
} else if (operation == SearchFilterParser.CompareOperation.ge) {
|
||||
if (lowerBoundInstant == null) {
|
||||
throw new InvalidRequestException("lowerBound value not correctly specified for compare operation");
|
||||
// use lower bound first
|
||||
if (lowerBoundInstant != null) {
|
||||
// the value has been reduced one in this case
|
||||
lb = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound);
|
||||
} else {
|
||||
if (upperBoundInstant != null) {
|
||||
ub = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound);
|
||||
} else {
|
||||
throw new InvalidRequestException("lowerBound and upperBound value not correctly specified for compare theOperation");
|
||||
}
|
||||
lb = theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound);
|
||||
} else if (operation == SearchFilterParser.CompareOperation.ne) {
|
||||
}
|
||||
} else if (operation == SearchFilterParser.CompareOperation.le) {
|
||||
// use lower bound first
|
||||
if (lowerBoundInstant != null) {
|
||||
lb = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound);
|
||||
} else {
|
||||
if (upperBoundInstant != null) {
|
||||
ub = theBuilder.lessThanOrEqualTo(theFrom.get(lowValueField), genericUpperBound);
|
||||
} else {
|
||||
throw new InvalidRequestException("lowerBound and upperBound value not correctly specified for compare theOperation");
|
||||
}
|
||||
}
|
||||
} else if (operation == SearchFilterParser.CompareOperation.gt) {
|
||||
// use upper bound first, e.g value between 6 and 10
|
||||
// gt7 true, 10>7, gt11 false, 10>11 false, gt5 true, 10>5
|
||||
if (upperBoundInstant != null) {
|
||||
ub = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericUpperBound);
|
||||
} else {
|
||||
if (lowerBoundInstant != null) {
|
||||
lb = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericLowerBound);
|
||||
} else {
|
||||
throw new InvalidRequestException("upperBound and lowerBound value not correctly specified for compare theOperation");
|
||||
}
|
||||
}
|
||||
} else if (operation == SearchFilterParser.CompareOperation.ge) {
|
||||
// use upper bound first, e.g value between 6 and 10
|
||||
// gt7 true, 10>7, gt11 false, 10>11 false, gt5 true, 10>5
|
||||
if (upperBoundInstant != null) {
|
||||
ub = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericUpperBound);;
|
||||
} else {
|
||||
if (lowerBoundInstant != null) {
|
||||
lb = theBuilder.greaterThanOrEqualTo(theFrom.get(highValueField), genericLowerBound);
|
||||
} else {
|
||||
throw new InvalidRequestException("upperBound and lowerBound value not correctly specified for compare theOperation");
|
||||
}
|
||||
}
|
||||
} else if (operation == SearchFilterParser.CompareOperation.ne) {
|
||||
if ((lowerBoundInstant == null) ||
|
||||
(upperBoundInstant == null)) {
|
||||
throw new InvalidRequestException("lowerBound and/or upperBound value not correctly specified for compare operation");
|
||||
}
|
||||
lt = theBuilder.lessThan(theFrom.get(lowValueField), genericLowerBound);
|
||||
gt = theBuilder.greaterThan(theFrom.get(highValueField), genericUpperBound);
|
||||
lb = theBuilder.or(lt,
|
||||
gt);
|
||||
lb = theBuilder.or(lt, gt);
|
||||
} else if ((operation == SearchFilterParser.CompareOperation.eq) || (operation == null)) {
|
||||
if (lowerBoundInstant != null) {
|
||||
gt = theBuilder.greaterThanOrEqualTo(theFrom.get(lowValueField), genericLowerBound);
|
||||
|
|
|
@ -102,6 +102,7 @@ import java.util.ListIterator;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.fhir.jpa.search.builder.QueryStack.fromOperation;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.trim;
|
||||
|
@ -267,12 +268,12 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
|||
private Predicate addPredicateReferenceWithChain(String theResourceName, String theParamName, List<? extends IQueryParameterType> theList, From<?, ResourceLink> theJoin, List<Predicate> theCodePredicates, ReferenceParam theReferenceParam, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
/*
|
||||
* Which resource types can the given chained parameter actually link to? This might be a list
|
||||
* where the chain is unqualified, as in: Observation?subject.identifier=(...)
|
||||
* since subject can link to several possible target types.
|
||||
*
|
||||
* If the user has qualified the chain, as in: Observation?subject:Patient.identifier=(...)
|
||||
* this is just a simple 1-entry list.
|
||||
* Which resource types can the given chained parameter actually link to? This might be a list
|
||||
* where the chain is unqualified, as in: Observation?subject.identifier=(...)
|
||||
* since subject can link to several possible target types.
|
||||
*
|
||||
* If the user has qualified the chain, as in: Observation?subject:Patient.identifier=(...)
|
||||
* this is just a simple 1-entry list.
|
||||
*/
|
||||
final List<Class<? extends IBaseResource>> resourceTypes = determineCandidateResourceTypesForChain(theResourceName, theParamName, theReferenceParam);
|
||||
|
||||
|
@ -593,7 +594,14 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
|||
switch (nextParamDef.getParamType()) {
|
||||
case DATE:
|
||||
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||
myPredicateBuilder.addPredicateDate(theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId);
|
||||
// FT: 2021-01-18 use operation 'gt', 'ge', 'le' or 'lt'
|
||||
// to create the predicateDate instead of generic one with operation = null
|
||||
SearchFilterParser.CompareOperation operation = null;
|
||||
if (nextAnd.size() > 0) {
|
||||
DateParam param = (DateParam) nextAnd.get(0);
|
||||
operation = ca.uhn.fhir.jpa.search.builder.QueryStack.toOperation(param.getPrefix());
|
||||
}
|
||||
myPredicateBuilder.addPredicateDate(theResourceName, nextParamDef, nextAnd, operation, theRequestPartitionId);
|
||||
}
|
||||
break;
|
||||
case QUANTITY:
|
||||
|
@ -716,67 +724,58 @@ class PredicateBuilderReference extends BasePredicateBuilder {
|
|||
private Predicate processFilterParameter(SearchFilterParser.FilterParameter theFilter,
|
||||
String theResourceName, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
if (theFilter.getParamPath().getName().equals(Constants.PARAM_SOURCE)) {
|
||||
TokenParam param = new TokenParam();
|
||||
param.setValueAsQueryToken(null, null, null, theFilter.getValue());
|
||||
return addPredicateSource(Collections.singletonList(param), theFilter.getOperation(), theRequest);
|
||||
} else if (theFilter.getParamPath().getName().equals(IAnyResource.SP_RES_ID)) {
|
||||
TokenParam param = new TokenParam();
|
||||
param.setValueAsQueryToken(null,
|
||||
null,
|
||||
null,
|
||||
theFilter.getValue());
|
||||
return myPredicateBuilder.addPredicateResourceId(Collections.singletonList(Collections.singletonList(param)), myResourceName, theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (theFilter.getParamPath().getName().equals(IAnyResource.SP_RES_LANGUAGE)) {
|
||||
return addPredicateLanguage(Collections.singletonList(Collections.singletonList(new StringParam(theFilter.getValue()))),
|
||||
theFilter.getOperation());
|
||||
}
|
||||
|
||||
RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theResourceName, theFilter.getParamPath().getName());
|
||||
|
||||
if (searchParam == null) {
|
||||
throw new InvalidRequestException("Invalid search parameter specified, " + theFilter.getParamPath().getName() + ", for resource type " + theResourceName);
|
||||
} else if (searchParam.getName().equals(IAnyResource.SP_RES_ID)) {
|
||||
if (searchParam.getParamType() == RestSearchParameterTypeEnum.TOKEN) {
|
||||
TokenParam param = new TokenParam();
|
||||
param.setValueAsQueryToken(null,
|
||||
null,
|
||||
null,
|
||||
theFilter.getValue());
|
||||
return myPredicateBuilder.addPredicateResourceId(Collections.singletonList(Collections.singletonList(param)), myResourceName, theFilter.getOperation(), theRequestPartitionId);
|
||||
} else {
|
||||
throw new InvalidRequestException("Unexpected search parameter type encountered, expected token type for _id search");
|
||||
}
|
||||
} else if (searchParam.getName().equals(IAnyResource.SP_RES_LANGUAGE)) {
|
||||
if (searchParam.getParamType() == RestSearchParameterTypeEnum.STRING) {
|
||||
return addPredicateLanguage(Collections.singletonList(Collections.singletonList(new StringParam(theFilter.getValue()))),
|
||||
theFilter.getOperation());
|
||||
} else {
|
||||
throw new InvalidRequestException("Unexpected search parameter type encountered, expected string type for language search");
|
||||
}
|
||||
} else if (searchParam.getName().equals(Constants.PARAM_SOURCE)) {
|
||||
if (searchParam.getParamType() == RestSearchParameterTypeEnum.TOKEN) {
|
||||
TokenParam param = new TokenParam();
|
||||
param.setValueAsQueryToken(null, null, null, theFilter.getValue());
|
||||
return addPredicateSource(Collections.singletonList(param), theFilter.getOperation(), theRequest);
|
||||
} else {
|
||||
throw new InvalidRequestException("Unexpected search parameter type encountered, expected token type for _id search");
|
||||
}
|
||||
} else {
|
||||
RestSearchParameterTypeEnum typeEnum = searchParam.getParamType();
|
||||
if (typeEnum == RestSearchParameterTypeEnum.URI) {
|
||||
return myPredicateBuilder.addPredicateUri(theResourceName, searchParam, Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.STRING) {
|
||||
return myPredicateBuilder.addPredicateString(theResourceName, searchParam, Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.DATE) {
|
||||
return myPredicateBuilder.addPredicateDate(theResourceName, searchParam, Collections.singletonList(new DateParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.NUMBER) {
|
||||
return myPredicateBuilder.addPredicateNumber(theResourceName, searchParam, Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.REFERENCE) {
|
||||
String paramName = theFilter.getParamPath().getName();
|
||||
SearchFilterParser.CompareOperation operation = theFilter.getOperation();
|
||||
String resourceType = null; // The value can either have (Patient/123) or not have (123) a resource type, either way it's not needed here
|
||||
String chain = (theFilter.getParamPath().getNext() != null) ? theFilter.getParamPath().getNext().toString() : null;
|
||||
String value = theFilter.getValue();
|
||||
ReferenceParam referenceParam = new ReferenceParam(resourceType, chain, value);
|
||||
return addPredicate(theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest, theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) {
|
||||
return myPredicateBuilder.addPredicateQuantity(theResourceName, searchParam, Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) {
|
||||
throw new InvalidRequestException("Composite search parameters not currently supported with _filter clauses");
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.TOKEN) {
|
||||
TokenParam param = new TokenParam();
|
||||
param.setValueAsQueryToken(null,
|
||||
null,
|
||||
null,
|
||||
theFilter.getValue());
|
||||
return myPredicateBuilder.addPredicateToken(theResourceName, searchParam, Collections.singletonList(param), theFilter.getOperation(), theRequestPartitionId);
|
||||
}
|
||||
}
|
||||
|
||||
RestSearchParameterTypeEnum typeEnum = searchParam.getParamType();
|
||||
if (typeEnum == RestSearchParameterTypeEnum.URI) {
|
||||
return myPredicateBuilder.addPredicateUri(theResourceName, searchParam, Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.STRING) {
|
||||
return myPredicateBuilder.addPredicateString(theResourceName, searchParam, Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.DATE) {
|
||||
return myPredicateBuilder.addPredicateDate(theResourceName, searchParam, Collections.singletonList(new DateParam(fromOperation(theFilter.getOperation()), theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.NUMBER) {
|
||||
return myPredicateBuilder.addPredicateNumber(theResourceName, searchParam, Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.REFERENCE) {
|
||||
String paramName = theFilter.getParamPath().getName();
|
||||
SearchFilterParser.CompareOperation operation = theFilter.getOperation();
|
||||
String resourceType = null; // The value can either have (Patient/123) or not have (123) a resource type, either way it's not needed here
|
||||
String chain = (theFilter.getParamPath().getNext() != null) ? theFilter.getParamPath().getNext().toString() : null;
|
||||
String value = theFilter.getValue();
|
||||
ReferenceParam referenceParam = new ReferenceParam(resourceType, chain, value);
|
||||
return addPredicate(theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest, theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) {
|
||||
return myPredicateBuilder.addPredicateQuantity(theResourceName, searchParam, Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) {
|
||||
throw new InvalidRequestException("Composite search parameters not currently supported with _filter clauses");
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.TOKEN) {
|
||||
TokenParam param = new TokenParam();
|
||||
param.setValueAsQueryToken(null,
|
||||
null,
|
||||
null,
|
||||
theFilter.getValue());
|
||||
return myPredicateBuilder.addPredicateToken(theResourceName, searchParam, Collections.singletonList(param), theFilter.getOperation(), theRequestPartitionId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.search;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import com.sun.xml.bind.api.impl.NameConverter;
|
||||
import org.apache.lucene.analysis.core.KeywordTokenizerFactory;
|
||||
import org.apache.lucene.analysis.core.LowerCaseFilterFactory;
|
||||
import org.apache.lucene.analysis.core.StopFilterFactory;
|
||||
|
@ -35,7 +34,6 @@ import org.apache.lucene.analysis.standard.StandardTokenizerFactory;
|
|||
import org.hibernate.search.backend.lucene.analysis.LuceneAnalysisConfigurationContext;
|
||||
import org.hibernate.search.backend.lucene.analysis.LuceneAnalysisConfigurer;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Factory for defining the analysers.
|
||||
|
|
|
@ -251,7 +251,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
ourLog.trace("Search entity marked as finished with {} results", search.getNumFound());
|
||||
break;
|
||||
}
|
||||
if (search.getNumFound() >= theTo) {
|
||||
if ((search.getNumFound() - search.getNumBlocked()) >= theTo) {
|
||||
ourLog.trace("Search entity has {} results so far", search.getNumFound());
|
||||
break;
|
||||
}
|
||||
|
@ -1091,7 +1091,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
int minWanted = 0;
|
||||
if (myParams.getCount() != null) {
|
||||
minWanted = myParams.getCount();
|
||||
minWanted = Math.max(minWanted, myPagingProvider.getMaximumPageSize());
|
||||
minWanted = Math.min(minWanted, myPagingProvider.getMaximumPageSize());
|
||||
minWanted += currentlyLoaded;
|
||||
}
|
||||
|
||||
|
|
|
@ -89,6 +89,9 @@ import com.healthmarketscience.sqlbuilder.OrderObject;
|
|||
import com.healthmarketscience.sqlbuilder.SelectQuery;
|
||||
import com.healthmarketscience.sqlbuilder.Subquery;
|
||||
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
|
||||
import org.apache.commons.collections4.BidiMap;
|
||||
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
||||
import org.apache.commons.collections4.bidimap.UnmodifiableBidiMap;
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
@ -112,12 +115,29 @@ import java.util.Set;
|
|||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class QueryStack {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(QueryStack.class);
|
||||
private static final BidiMap<SearchFilterParser.CompareOperation, ParamPrefixEnum> ourCompareOperationToParamPrefix;
|
||||
|
||||
static {
|
||||
DualHashBidiMap<SearchFilterParser.CompareOperation, ParamPrefixEnum> compareOperationToParamPrefix = new DualHashBidiMap<>();
|
||||
compareOperationToParamPrefix.put(SearchFilterParser.CompareOperation.ap, ParamPrefixEnum.APPROXIMATE);
|
||||
compareOperationToParamPrefix.put(SearchFilterParser.CompareOperation.eq, ParamPrefixEnum.EQUAL);
|
||||
compareOperationToParamPrefix.put(SearchFilterParser.CompareOperation.gt, ParamPrefixEnum.GREATERTHAN);
|
||||
compareOperationToParamPrefix.put(SearchFilterParser.CompareOperation.ge, ParamPrefixEnum.GREATERTHAN_OR_EQUALS);
|
||||
compareOperationToParamPrefix.put(SearchFilterParser.CompareOperation.lt, ParamPrefixEnum.LESSTHAN);
|
||||
compareOperationToParamPrefix.put(SearchFilterParser.CompareOperation.le, ParamPrefixEnum.LESSTHAN_OR_EQUALS);
|
||||
compareOperationToParamPrefix.put(SearchFilterParser.CompareOperation.ne, ParamPrefixEnum.NOT_EQUAL);
|
||||
compareOperationToParamPrefix.put(SearchFilterParser.CompareOperation.eb, ParamPrefixEnum.ENDS_BEFORE);
|
||||
compareOperationToParamPrefix.put(SearchFilterParser.CompareOperation.sa, ParamPrefixEnum.STARTS_AFTER);
|
||||
ourCompareOperationToParamPrefix = UnmodifiableBidiMap.unmodifiableBidiMap(compareOperationToParamPrefix);
|
||||
}
|
||||
|
||||
private final ModelConfig myModelConfig;
|
||||
private final FhirContext myFhirContext;
|
||||
private final SearchQueryBuilder mySqlBuilder;
|
||||
|
@ -175,7 +195,6 @@ public class QueryStack {
|
|||
mySqlBuilder.addSortDate(resourceTablePredicateBuilder.getColumnLastUpdated(), theAscending);
|
||||
}
|
||||
|
||||
|
||||
public void addSortOnNumber(String theResourceName, String theParamName, boolean theAscending) {
|
||||
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
|
||||
NumberPredicateBuilder sortPredicateBuilder = mySqlBuilder.addNumberPredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
|
||||
|
@ -217,7 +236,6 @@ public class QueryStack {
|
|||
mySqlBuilder.addSortNumeric(sortPredicateBuilder.getColumnTargetResourceId(), theAscending);
|
||||
}
|
||||
|
||||
|
||||
public void addSortOnString(String theResourceName, String theParamName, boolean theAscending) {
|
||||
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
|
||||
StringPredicateBuilder sortPredicateBuilder = mySqlBuilder.addStringPredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
|
||||
|
@ -246,7 +264,6 @@ public class QueryStack {
|
|||
mySqlBuilder.addSortString(sortPredicateBuilder.getColumnValue(), theAscending);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends BaseJoiningPredicateBuilder> PredicateBuilderCacheLookupResult<T> createOrReusePredicateBuilder(PredicateBuilderTypeEnum theType, DbColumn theSourceJoinColumn, String theParamName, Supplier<T> theFactoryMethod) {
|
||||
boolean cacheHit = false;
|
||||
|
@ -299,7 +316,6 @@ public class QueryStack {
|
|||
return orCondidtion;
|
||||
}
|
||||
|
||||
|
||||
private Condition createPredicateCompositePart(@Nullable DbColumn theSourceJoinColumn, String theResourceName, RuntimeSearchParam theParam, IQueryParameterType theParamValue, RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
switch (theParam.getParamType()) {
|
||||
|
@ -310,7 +326,7 @@ public class QueryStack {
|
|||
return createPredicateToken(theSourceJoinColumn, theResourceName, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
|
||||
}
|
||||
case DATE: {
|
||||
return createPredicateDate(theSourceJoinColumn, theResourceName, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
|
||||
return createPredicateDate(theSourceJoinColumn, theResourceName, theParam, Collections.singletonList(theParamValue), toOperation(((DateParam) theParamValue).getPrefix()), theRequestPartitionId);
|
||||
}
|
||||
case QUANTITY: {
|
||||
return createPredicateQuantity(theSourceJoinColumn, theResourceName, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
|
||||
|
@ -320,7 +336,6 @@ public class QueryStack {
|
|||
throw new InvalidRequestException("Don't know how to handle composite parameter with type of " + theParam.getParamType());
|
||||
}
|
||||
|
||||
|
||||
public Condition createPredicateCoords(@Nullable DbColumn theSourceJoinColumn,
|
||||
String theResourceName,
|
||||
RuntimeSearchParam theSearchParam,
|
||||
|
@ -363,7 +378,7 @@ public class QueryStack {
|
|||
List<Condition> codePredicates = new ArrayList<>();
|
||||
|
||||
for (IQueryParameterType nextOr : theList) {
|
||||
Condition p = predicateBuilder.createPredicateDateWithoutIdentityPredicate(nextOr, theResourceName, paramName, predicateBuilder, theOperation, theRequestPartitionId);
|
||||
Condition p = predicateBuilder.createPredicateDateWithoutIdentityPredicate(nextOr, predicateBuilder, theOperation);
|
||||
codePredicates.add(p);
|
||||
}
|
||||
|
||||
|
@ -429,7 +444,7 @@ public class QueryStack {
|
|||
} else if (typeEnum == RestSearchParameterTypeEnum.STRING) {
|
||||
return theQueryStack3.createPredicateString(null, theResourceName, searchParam, Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.DATE) {
|
||||
return theQueryStack3.createPredicateDate(null, theResourceName, searchParam, Collections.singletonList(new DateParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
return theQueryStack3.createPredicateDate(null, theResourceName, searchParam, Collections.singletonList(new DateParam(fromOperation(theFilter.getOperation()), theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.NUMBER) {
|
||||
return theQueryStack3.createPredicateNumber(null, theResourceName, searchParam, Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.REFERENCE) {
|
||||
|
@ -976,7 +991,15 @@ public class QueryStack {
|
|||
switch (nextParamDef.getParamType()) {
|
||||
case DATE:
|
||||
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||
andPredicates.add(createPredicateDate(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId));
|
||||
// FT: 2021-01-18 use operation 'gt', 'ge', 'le' or 'lt'
|
||||
// to create the predicateDate instead of generic one with operation = null
|
||||
SearchFilterParser.CompareOperation operation = null;
|
||||
if (nextAnd.size() > 0) {
|
||||
DateParam param = (DateParam) nextAnd.get(0);
|
||||
operation = toOperation(param.getPrefix());
|
||||
}
|
||||
andPredicates.add(createPredicateDate(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, operation, theRequestPartitionId));
|
||||
//andPredicates.add(createPredicateDate(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId));
|
||||
}
|
||||
break;
|
||||
case QUANTITY:
|
||||
|
@ -1204,29 +1227,19 @@ public class QueryStack {
|
|||
}
|
||||
|
||||
public static SearchFilterParser.CompareOperation toOperation(ParamPrefixEnum thePrefix) {
|
||||
if (thePrefix != null) {
|
||||
switch (thePrefix) {
|
||||
case APPROXIMATE:
|
||||
return SearchFilterParser.CompareOperation.ap;
|
||||
case EQUAL:
|
||||
return SearchFilterParser.CompareOperation.eq;
|
||||
case GREATERTHAN:
|
||||
return SearchFilterParser.CompareOperation.gt;
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
return SearchFilterParser.CompareOperation.ge;
|
||||
case LESSTHAN:
|
||||
return SearchFilterParser.CompareOperation.lt;
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
return SearchFilterParser.CompareOperation.le;
|
||||
case NOT_EQUAL:
|
||||
return SearchFilterParser.CompareOperation.ne;
|
||||
case ENDS_BEFORE:
|
||||
return SearchFilterParser.CompareOperation.eb;
|
||||
case STARTS_AFTER:
|
||||
return SearchFilterParser.CompareOperation.sa;
|
||||
}
|
||||
SearchFilterParser.CompareOperation retVal = null;
|
||||
if (thePrefix != null && ourCompareOperationToParamPrefix.containsValue(thePrefix)) {
|
||||
retVal = ourCompareOperationToParamPrefix.getKey(thePrefix);
|
||||
}
|
||||
return SearchFilterParser.CompareOperation.eq;
|
||||
return defaultIfNull(retVal, SearchFilterParser.CompareOperation.eq);
|
||||
}
|
||||
|
||||
public static ParamPrefixEnum fromOperation(SearchFilterParser.CompareOperation thePrefix) {
|
||||
ParamPrefixEnum retVal = null;
|
||||
if (thePrefix != null && ourCompareOperationToParamPrefix.containsKey(thePrefix)) {
|
||||
retVal = ourCompareOperationToParamPrefix.get(thePrefix);
|
||||
}
|
||||
return defaultIfNull(retVal, ParamPrefixEnum.EQUAL);
|
||||
}
|
||||
|
||||
private static String getChainedPart(String parameter) {
|
||||
|
|
|
@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.search.builder.predicate;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
||||
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
||||
|
@ -64,16 +63,16 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
|
||||
|
||||
public Condition createPredicateDateWithoutIdentityPredicate(IQueryParameterType theParam,
|
||||
String theResourceName,
|
||||
String theParamName,
|
||||
DatePredicateBuilder theFrom,
|
||||
SearchFilterParser.CompareOperation theOperation,
|
||||
RequestPartitionId theRequestPartitionId) {
|
||||
SearchFilterParser.CompareOperation theOperation) {
|
||||
|
||||
Condition p;
|
||||
if (theParam instanceof DateParam) {
|
||||
DateParam date = (DateParam) theParam;
|
||||
if (!date.isEmpty()) {
|
||||
if (theOperation == SearchFilterParser.CompareOperation.ne) {
|
||||
date = new DateParam(ParamPrefixEnum.EQUAL, date.getValueAsString());
|
||||
}
|
||||
DateRangeParam range = new DateRangeParam(date);
|
||||
p = createPredicateDateFromRange(theFrom, range, theOperation);
|
||||
} else {
|
||||
|
@ -96,6 +95,8 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
private Condition createPredicateDateFromRange(DatePredicateBuilder theFrom,
|
||||
DateRangeParam theRange,
|
||||
SearchFilterParser.CompareOperation theOperation) {
|
||||
|
||||
|
||||
Date lowerBoundInstant = theRange.getLowerBoundAsInstant();
|
||||
Date upperBoundInstant = theRange.getUpperBoundAsInstant();
|
||||
|
||||
|
@ -103,16 +104,17 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
DateParam upperBound = theRange.getUpperBound();
|
||||
Integer lowerBoundAsOrdinal = theRange.getLowerBoundAsDateInteger();
|
||||
Integer upperBoundAsOrdinal = theRange.getUpperBoundAsDateInteger();
|
||||
Comparable genericLowerBound;
|
||||
Comparable genericUpperBound;
|
||||
/**
|
||||
Comparable<?> genericLowerBound;
|
||||
Comparable<?> genericUpperBound;
|
||||
|
||||
/*
|
||||
* If all present search parameters are of DAY precision, and {@link ca.uhn.fhir.jpa.model.entity.ModelConfig#getUseOrdinalDatesForDayPrecisionSearches()} is true,
|
||||
* then we attempt to use the ordinal field for date comparisons instead of the date field.
|
||||
*/
|
||||
boolean isOrdinalComparison = isNullOrDayPrecision(lowerBound) && isNullOrDayPrecision(upperBound) && myDaoConfig.getModelConfig().getUseOrdinalDatesForDayPrecisionSearches();
|
||||
|
||||
Condition lt;
|
||||
Condition gt = null;
|
||||
Condition gt;
|
||||
Condition lb = null;
|
||||
Condition ub = null;
|
||||
DatePredicateBuilder.ColumnEnum lowValueField;
|
||||
|
@ -130,28 +132,24 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
genericUpperBound = upperBoundInstant;
|
||||
}
|
||||
|
||||
if (theOperation == SearchFilterParser.CompareOperation.lt) {
|
||||
if (lowerBoundInstant == null) {
|
||||
throw new InvalidRequestException("lowerBound value not correctly specified for compare theOperation");
|
||||
if (theOperation == SearchFilterParser.CompareOperation.lt || theOperation == SearchFilterParser.CompareOperation.le) {
|
||||
// use lower bound first
|
||||
if (lowerBoundInstant != null) {
|
||||
lb = theFrom.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericLowerBound);
|
||||
} else if (upperBoundInstant != null) {
|
||||
ub = theFrom.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound);
|
||||
} else {
|
||||
throw new InvalidRequestException("lowerBound and upperBound value not correctly specified for comparing " + theOperation);
|
||||
}
|
||||
//im like 80% sure this should be ub and not lb, as it is an UPPER bound.
|
||||
lb = theFrom.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN, genericLowerBound);
|
||||
} else if (theOperation == SearchFilterParser.CompareOperation.le) {
|
||||
if (upperBoundInstant == null) {
|
||||
throw new InvalidRequestException("upperBound value not correctly specified for compare theOperation");
|
||||
} else if (theOperation == SearchFilterParser.CompareOperation.gt || theOperation == SearchFilterParser.CompareOperation.ge) {
|
||||
// use upper bound first, e.g value between 6 and 10
|
||||
if (upperBoundInstant != null) {
|
||||
ub = theFrom.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericUpperBound);
|
||||
} else if (lowerBoundInstant != null) {
|
||||
lb = theFrom.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
|
||||
} else {
|
||||
throw new InvalidRequestException("upperBound and lowerBound value not correctly specified for compare theOperation");
|
||||
}
|
||||
//im like 80% sure this should be ub and not lb, as it is an UPPER bound.
|
||||
lb = theFrom.createPredicate(highValueField, ParamPrefixEnum.LESSTHAN_OR_EQUALS, genericUpperBound);
|
||||
} else if (theOperation == SearchFilterParser.CompareOperation.gt) {
|
||||
if (upperBoundInstant == null) {
|
||||
throw new InvalidRequestException("upperBound value not correctly specified for compare theOperation");
|
||||
}
|
||||
lb = theFrom.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN, genericUpperBound);
|
||||
} else if (theOperation == SearchFilterParser.CompareOperation.ge) {
|
||||
if (lowerBoundInstant == null) {
|
||||
throw new InvalidRequestException("lowerBound value not correctly specified for compare theOperation");
|
||||
}
|
||||
lb = theFrom.createPredicate(lowValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
|
||||
} else if (theOperation == SearchFilterParser.CompareOperation.ne) {
|
||||
if ((lowerBoundInstant == null) ||
|
||||
(upperBoundInstant == null)) {
|
||||
|
@ -160,7 +158,10 @@ public class DatePredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
lt = theFrom.createPredicate(lowValueField, ParamPrefixEnum.LESSTHAN, genericLowerBound);
|
||||
gt = theFrom.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN, genericUpperBound);
|
||||
lb = ComboCondition.or(lt, gt);
|
||||
} else if ((theOperation == SearchFilterParser.CompareOperation.eq) || (theOperation == null)) {
|
||||
} else if ((theOperation == SearchFilterParser.CompareOperation.eq)
|
||||
|| (theOperation == SearchFilterParser.CompareOperation.sa)
|
||||
|| (theOperation == SearchFilterParser.CompareOperation.eb)
|
||||
|| (theOperation == null)) {
|
||||
if (lowerBoundInstant != null) {
|
||||
gt = theFrom.createPredicate(lowValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
|
||||
lt = theFrom.createPredicate(highValueField, ParamPrefixEnum.GREATERTHAN_OR_EQUALS, genericLowerBound);
|
||||
|
|
|
@ -140,7 +140,7 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest {
|
|||
}
|
||||
}
|
||||
assertEquals(myObservationIdsEvenOnly.subList(10, 25), returnedIdValues, "Wrong response from " + outcome.getClass());
|
||||
assertEquals(2, hitCount.get());
|
||||
assertEquals(3, hitCount.get());
|
||||
}
|
||||
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -703,7 +703,9 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
|
|||
map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
map.add(Constants.PARAM_FILTER, new StringParam("birthdate gt 1955-01-01"));
|
||||
myCaptureQueriesListener.clear();
|
||||
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
|
||||
assertThat(found, empty());
|
||||
|
||||
}
|
||||
|
@ -723,7 +725,9 @@ public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
|
|||
map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
map.add(Constants.PARAM_FILTER, new StringParam("birthdate lt 1955-01-02"));
|
||||
myCaptureQueriesListener.clear();
|
||||
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread(0);
|
||||
assertThat(found, containsInAnyOrder(id1));
|
||||
|
||||
map = new SearchParameterMap();
|
||||
|
|
|
@ -4172,7 +4172,7 @@ public class FhirResourceDaoR4LegacySearchBuilderTest extends BaseJpaR4Test {
|
|||
String searchQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
||||
ourLog.info("Search query:\n{}", searchQuery);
|
||||
assertEquals(1, countMatches(searchQuery.toLowerCase(), "join"), searchQuery);
|
||||
assertEquals(1, countMatches(searchQuery.toLowerCase(), "sp_value_low_date_ordinal>='20200605'"), searchQuery);
|
||||
assertEquals(1, countMatches(searchQuery.toLowerCase(), "sp_value_high_date_ordinal>='20200605'"), searchQuery);
|
||||
assertEquals(1, countMatches(searchQuery.toLowerCase(), "sp_value_low_date_ordinal<='20200606'"), searchQuery);
|
||||
}
|
||||
|
||||
|
@ -4192,7 +4192,7 @@ public class FhirResourceDaoR4LegacySearchBuilderTest extends BaseJpaR4Test {
|
|||
assertEquals(0, countMatches(searchQuery.toLowerCase(), "partition"), searchQuery);
|
||||
assertEquals(2, countMatches(searchQuery.toLowerCase(), "join"), searchQuery);
|
||||
assertEquals(2, countMatches(searchQuery.toLowerCase(), "hash_identity"), searchQuery);
|
||||
assertEquals(4, countMatches(searchQuery.toLowerCase(), "sp_value_low"), searchQuery);
|
||||
assertEquals(2, countMatches(searchQuery.toLowerCase(), "sp_value_low"), searchQuery);
|
||||
}
|
||||
|
||||
// Period search
|
||||
|
|
|
@ -4369,7 +4369,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
|||
String searchQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
||||
ourLog.info("Search query:\n{}", searchQuery);
|
||||
assertEquals(1, countMatches(searchQuery.toLowerCase(), "join"), searchQuery);
|
||||
assertEquals(1, countMatches(searchQuery.toLowerCase(), "t0.sp_value_low_date_ordinal >= '20200605'"), searchQuery);
|
||||
assertEquals(1, countMatches(searchQuery.toLowerCase(), "t0.sp_value_high_date_ordinal >= '20200605'"), searchQuery);
|
||||
assertEquals(1, countMatches(searchQuery.toLowerCase(), "t0.sp_value_low_date_ordinal <= '20200606'"), searchQuery);
|
||||
}
|
||||
|
||||
|
@ -4389,7 +4389,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
|||
assertEquals(0, countMatches(searchQuery.toLowerCase(), "partition"), searchQuery);
|
||||
assertEquals(2, countMatches(searchQuery.toLowerCase(), "join"), searchQuery);
|
||||
assertEquals(2, countMatches(searchQuery.toLowerCase(), "hash_identity"), searchQuery);
|
||||
assertEquals(4, countMatches(searchQuery.toLowerCase(), "sp_value_low"), searchQuery);
|
||||
// - query is changed 'or' is removed
|
||||
assertEquals(2, countMatches(searchQuery.toLowerCase(), "sp_value_low"), searchQuery);
|
||||
}
|
||||
|
||||
// Period search
|
||||
|
|
|
@ -1702,7 +1702,9 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
||||
ourLog.info("Search SQL:\n{}", searchSql);
|
||||
assertEquals(0, StringUtils.countMatches(searchSql, "PARTITION_ID"));
|
||||
assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
|
||||
// NOTE: the query is changed, only one SP_VALUE_LOW and SP_VALUE_HIGH
|
||||
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
|
||||
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_HIGH"));
|
||||
|
||||
}
|
||||
|
||||
|
@ -1783,7 +1785,9 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
||||
ourLog.info("Search SQL:\n{}", searchSql);
|
||||
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
|
||||
assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
|
||||
// NOTE: the query is changed, only one SP_VALUE_LOW and SP_VALUE_HIGH
|
||||
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
|
||||
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_HIGH"));
|
||||
|
||||
}
|
||||
|
||||
|
@ -1861,7 +1865,9 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
|
||||
ourLog.info("Search SQL:\n{}", searchSql);
|
||||
assertEquals(1, StringUtils.countMatches(searchSql, "PARTITION_ID"));
|
||||
assertEquals(2, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
|
||||
// NOTE: the query is changed, only one SP_VALUE_LOW and SP_VALUE_HIGH
|
||||
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_LOW"));
|
||||
assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_HIGH"));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.hl7.fhir.r4.model.Coding;
|
|||
import org.hl7.fhir.r4.model.Consent;
|
||||
import org.hl7.fhir.r4.model.Encounter;
|
||||
import org.hl7.fhir.r4.model.Extension;
|
||||
import org.hl7.fhir.r4.model.HumanName;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Quantity;
|
||||
|
@ -50,6 +51,8 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
|
@ -82,6 +85,19 @@ public class SearchParamExtractorR4Test {
|
|||
assertEquals("CODE", token.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testName() {
|
||||
Patient patient = new Patient();
|
||||
HumanName humanName = patient.addName();
|
||||
humanName.addGiven("Jimmy");
|
||||
humanName.setFamily("Jones");
|
||||
humanName.setText("Jimmy Jones");
|
||||
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
|
||||
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamString> stringSearchParams = extractor.extractSearchParamStrings(patient);
|
||||
List<String> nameValues = stringSearchParams.stream().filter(param -> "name".equals(param.getParamName())).map(ResourceIndexedSearchParamString::getValueExact).collect(Collectors.toList());
|
||||
assertThat(nameValues, containsInAnyOrder("Jimmy", "Jones", "Jimmy Jones"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenOnSearchParamContext() {
|
||||
SearchParameter sp = new SearchParameter();
|
||||
|
|
|
@ -65,6 +65,7 @@ import static org.hamcrest.CoreMatchers.containsString;
|
|||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.blankOrNullString;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
@ -126,6 +127,7 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
|
|||
.execute();
|
||||
List<IBaseResource> resources = BundleUtil.toListOfResources(myFhirCtx, result);
|
||||
List<String> returnedIdValues = toUnqualifiedVersionlessIdValues(resources);
|
||||
assertThat(returnedIdValues, hasSize(15));
|
||||
assertEquals(myObservationIdsEvenOnly.subList(0, 15), returnedIdValues);
|
||||
|
||||
// Fetch the next page
|
||||
|
@ -135,6 +137,7 @@ public class ConsentInterceptorResourceProviderR4Test extends BaseResourceProvid
|
|||
.execute();
|
||||
resources = BundleUtil.toListOfResources(myFhirCtx, result);
|
||||
returnedIdValues = toUnqualifiedVersionlessIdValues(resources);
|
||||
assertThat(returnedIdValues, hasSize(10));
|
||||
assertEquals(myObservationIdsEvenOnly.subList(15, 25), returnedIdValues);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ import ca.uhn.fhir.parser.IParser;
|
|||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import com.google.common.collect.Lists;
|
||||
|
@ -52,7 +50,6 @@ import static org.hamcrest.Matchers.startsWith;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
|
@ -428,23 +425,4 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
|
|||
}
|
||||
}
|
||||
|
||||
public static void verifyDaoInterceptor(IServerInterceptor theDaoInterceptor) {
|
||||
ArgumentCaptor<ActionRequestDetails> ardCaptor;
|
||||
ArgumentCaptor<RestOperationTypeEnum> opTypeCaptor;
|
||||
ardCaptor = ArgumentCaptor.forClass(ActionRequestDetails.class);
|
||||
opTypeCaptor = ArgumentCaptor.forClass(RestOperationTypeEnum.class);
|
||||
verify(theDaoInterceptor, atLeast(1)).incomingRequestPreHandled(opTypeCaptor.capture(), ardCaptor.capture());
|
||||
// boolean good = false;
|
||||
// for (int i = 0; i < opTypeCaptor.getAllValues().size(); i++) {
|
||||
// if (RestOperationTypeEnum.CREATE.equals(opTypeCaptor.getAllValues().get(i))) {
|
||||
// if ("Patient".equals(ardCaptor.getValue().getResourceType())) {
|
||||
// if (ardCaptor.getValue().getResource() != null) {
|
||||
// good = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// assertTrue(good);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,16 +12,16 @@
|
|||
<artifactId>hapi-fhir-jpaserver-model</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>HAPI FHIR Model</name>
|
||||
<name>HAPI FHIR JPA Model</name>
|
||||
|
||||
<dependencies>
|
||||
|
||||
|
||||
<!-- FHIR -->
|
||||
<dependency>
|
||||
<groupId>org.fhir</groupId>
|
||||
<artifactId>ucum</artifactId>
|
||||
</dependency>
|
||||
|
||||
<groupId>org.fhir</groupId>
|
||||
<artifactId>ucum</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-base</artifactId>
|
||||
|
@ -71,10 +71,6 @@
|
|||
<artifactId>xml-apis</artifactId>
|
||||
<groupId>xml-apis</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.jboss.spec.javax.transaction</groupId>
|
||||
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>javax.activation</groupId>
|
||||
<artifactId>activation</artifactId>
|
||||
|
@ -83,6 +79,10 @@
|
|||
<groupId>javax.activation</groupId>
|
||||
<artifactId>javax.activation-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -132,6 +132,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
private BaseRuntimeChildDefinition myDurationValueValueChild;
|
||||
private BaseRuntimeChildDefinition myHumanNameFamilyValueChild;
|
||||
private BaseRuntimeChildDefinition myHumanNameGivenValueChild;
|
||||
private BaseRuntimeChildDefinition myHumanNameTextValueChild;
|
||||
private BaseRuntimeChildDefinition myContactPointValueValueChild;
|
||||
private BaseRuntimeChildDefinition myIdentifierSystemValueChild;
|
||||
private BaseRuntimeChildDefinition myIdentifierValueValueChild;
|
||||
|
@ -877,6 +878,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
for (String next : givens) {
|
||||
createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, next);
|
||||
}
|
||||
List<String> texts = extractValuesAsStrings(myHumanNameTextValueChild, theValue);
|
||||
for (String next : texts) {
|
||||
createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, next);
|
||||
}
|
||||
}
|
||||
|
||||
private void addString_Quantity(String theResourceType, Set<ResourceIndexedSearchParamString> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
|
||||
|
@ -1138,6 +1143,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
BaseRuntimeElementCompositeDefinition<?> humanNameDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("HumanName");
|
||||
myHumanNameFamilyValueChild = humanNameDefinition.getChildByName("family");
|
||||
myHumanNameGivenValueChild = humanNameDefinition.getChildByName("given");
|
||||
myHumanNameTextValueChild = humanNameDefinition.getChildByName("text");
|
||||
|
||||
BaseRuntimeElementCompositeDefinition<?> contactPointDefinition = (BaseRuntimeElementCompositeDefinition<?>) getContext().getElementDefinition("ContactPoint");
|
||||
myContactPointValueValueChild = contactPointDefinition.getChildByName("value");
|
||||
|
|
|
@ -675,10 +675,16 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the paging provider to use, or <code>null</code> to use no paging (which is the default)
|
||||
* Sets the paging provider to use, or <code>null</code> to use no paging (which is the default).
|
||||
* This will set defaultPageSize and maximumPageSize from the paging provider.
|
||||
*/
|
||||
public void setPagingProvider(IPagingProvider thePagingProvider) {
|
||||
myPagingProvider = thePagingProvider;
|
||||
if (myPagingProvider != null) {
|
||||
setDefaultPageSize(myPagingProvider.getDefaultPageSize());
|
||||
setMaximumPageSize(myPagingProvider.getMaximumPageSize());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -235,16 +235,6 @@
|
|||
<artifactId>jaxb-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-core</artifactId>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-impl</artifactId>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
|
|
|
@ -125,16 +125,6 @@
|
|||
<artifactId>jaxb-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-core</artifactId>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-impl</artifactId>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
|
|
|
@ -197,23 +197,6 @@
|
|||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
<dependency>
|
||||
<groupId>xpp3</groupId>
|
||||
<artifactId>xpp3_min</artifactId>
|
||||
<version>1.1.4c</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
<!--
|
||||
<dependency>
|
||||
<groupId>net.sf.saxon</groupId>
|
||||
<artifactId>Saxon-HE</artifactId>
|
||||
<version>9.6.0-4</version>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
<!-- Used by the validator -->
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
|
@ -292,27 +275,6 @@
|
|||
<artifactId>javax.activation-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-core</artifactId>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-impl</artifactId>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- UNIT TEST DEPENDENCIES -->
|
||||
<dependency>
|
||||
|
|
|
@ -176,16 +176,6 @@
|
|||
<artifactId>jaxb-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-core</artifactId>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-impl</artifactId>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
|
|
|
@ -245,16 +245,6 @@
|
|||
<artifactId>jaxb-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-core</artifactId>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-impl</artifactId>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
|
|
|
@ -268,16 +268,6 @@
|
|||
<artifactId>jaxb-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-core</artifactId>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<!--<dependency>-->
|
||||
<!--<groupId>com.sun.xml.bind</groupId>-->
|
||||
<!--<artifactId>jaxb-impl</artifactId>-->
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
|
|
10
pom.xml
10
pom.xml
|
@ -746,7 +746,7 @@
|
|||
<jaxb_bundle_version>2.2.11_1</jaxb_bundle_version>
|
||||
<jaxb_api_version>2.3.1</jaxb_api_version>
|
||||
<jaxb_core_version>2.3.0.1</jaxb_core_version>
|
||||
<jaxb_runtime_version>2.3.1</jaxb_runtime_version>
|
||||
<jaxb_runtime_version>3.0.0</jaxb_runtime_version>
|
||||
<jena_version>3.16.0</jena_version>
|
||||
<jersey_version>3.0.0</jersey_version>
|
||||
<!-- 9.4.17 seems to have issues -->
|
||||
|
@ -892,12 +892,6 @@
|
|||
<groupId>com.helger</groupId>
|
||||
<artifactId>ph-schematron</artifactId>
|
||||
<version>${ph_schematron_version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.helger</groupId>
|
||||
|
@ -917,7 +911,7 @@
|
|||
<dependency>
|
||||
<groupId>com.sun.activation</groupId>
|
||||
<artifactId>jakarta.activation</artifactId>
|
||||
<version>1.2.1</version>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
|
|
|
@ -85,5 +85,5 @@ steps:
|
|||
inputs:
|
||||
mavenPomFile: '$(System.DefaultWorkingDirectory)/pom.xml'
|
||||
goals: deploy
|
||||
options: '--settings $(System.DefaultWorkingDirectory)/settings.xml -P DIST,ALLMODULES'
|
||||
publishJUnitResults: false
|
||||
options: '--settings $(System.DefaultWorkingDirectory)/settings.xml -P DIST,ALLMODULES -Dmaven.test.skip'
|
||||
publishJUnitResults: false
|
||||
|
|
Loading…
Reference in New Issue