Switch from Cobertura to JaCoCo

This commit is contained in:
James Agnew 2015-08-28 17:06:56 -04:00
parent bd13b53099
commit 110abf7cb2
9 changed files with 742 additions and 222 deletions

View File

@ -19,6 +19,6 @@ before_script:
- export MAVEN_SKIP_RC=true
script:
- mvn -B clean install && cd hapi-fhir-cobertura && mvn -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID -P COBERTURA clean cobertura:cobertura coveralls:report
- mvn -B clean install && cd hapi-fhir-cobertura && mvn -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID -P COBERTURA clean test jacoco:report coveralls:report

View File

@ -288,7 +288,7 @@ public class RestfulServer extends HttpServlet {
/**
* Returns the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with the <code>_format</code> URL parameter, or with an <code>Accept</code> header
* in the request. The default is {@link EncodingEnum#XML}.
* in the request. The default is {@link EncodingEnum#XML}. Will not return null.
*/
public EncodingEnum getDefaultResponseEncoding() {
return myDefaultResponseEncoding;

View File

@ -19,8 +19,8 @@ package ca.uhn.fhir.rest.server;
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.OutputStreamWriter;
@ -92,6 +92,46 @@ public class RestfulServerUtils {
}
}
public static void configureResponseParser(RequestDetails theRequestDetails, IParser parser) {
// Pretty print
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequestDetails.getServer(), theRequestDetails);
parser.setPrettyPrint(prettyPrint);
parser.setServerBaseUrl(theRequestDetails.getFhirServerBase());
// Summary mode
Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequestDetails);
// _elements
Set<String> elements = ElementsParameter.getElementsValueOrNull(theRequestDetails);
if (elements != null && summaryMode != null && !summaryMode.equals(Collections.singleton(SummaryEnum.FALSE))) {
throw new InvalidRequestException("Cannot combine the " + Constants.PARAM_SUMMARY + " and " + Constants.PARAM_ELEMENTS + " parameters");
}
Set<String> elementsAppliesTo = null;
if (elements != null && isNotBlank(theRequestDetails.getResourceName())) {
elementsAppliesTo = Collections.singleton(theRequestDetails.getResourceName());
}
if (summaryMode != null) {
if (summaryMode.contains(SummaryEnum.COUNT)) {
parser.setEncodeElements(Collections.singleton("Bundle.total"));
} else if (summaryMode.contains(SummaryEnum.TEXT)) {
parser.setEncodeElements(TEXT_ENCODE_ELEMENTS);
} else {
parser.setSuppressNarratives(summaryMode.contains(SummaryEnum.DATA));
parser.setSummaryMode(summaryMode.contains(SummaryEnum.TRUE));
}
}
if (elements != null && elements.size() > 0) {
Set<String> newElements = new HashSet<String>();
for (String next : elements) {
newElements.add("*." + next);
}
parser.setEncodeElements(newElements);
parser.setEncodeElementsAppliesToResourceTypes(elementsAppliesTo);
}
}
public static String createPagingLink(Set<Include> theIncludes, String theServerBase, String theSearchId, int theOffset, int theCount, EncodingEnum theResponseEncoding, boolean thePrettyPrint) {
try {
StringBuilder b = new StringBuilder();
@ -235,8 +275,7 @@ public class RestfulServerUtils {
}
/**
* Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's
* <code>"_format"</code> parameter and <code>"Accept:"</code> HTTP header.
* Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's <code>"_format"</code> parameter and <code>"Accept:"</code> HTTP header.
*/
public static EncodingEnum determineResponseEncodingWithDefault(RestfulServer theServer, HttpServletRequest theReq) {
EncodingEnum retVal = determineResponseEncodingNoDefault(theReq);
@ -253,8 +292,7 @@ public class RestfulServerUtils {
if (retVal == null) {
/*
* HAPI originally supported a custom parameter called _narrative, but this has been superceded by an official
* parameter called _summary
* HAPI originally supported a custom parameter called _narrative, but this has been superceded by an official parameter called _summary
*/
String[] narrative = requestParams.get(Constants.PARAM_NARRATIVE);
if (narrative != null && narrative.length > 0) {
@ -288,16 +326,12 @@ public class RestfulServerUtils {
}
public static IParser getNewParser(FhirContext theContext, RequestDetails theRequestDetails) {
// Pretty print
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequestDetails.getServer(), theRequestDetails);
// Determine response encoding
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails.getServletRequest());
if (responseEncoding == null) {
responseEncoding = theRequestDetails.getServer().getDefaultResponseEncoding();
}
IParser parser;
switch (responseEncoding) {
case JSON:
@ -308,41 +342,9 @@ public class RestfulServerUtils {
parser = theContext.newXmlParser();
break;
}
parser.setPrettyPrint(prettyPrint);
parser.setServerBaseUrl(theRequestDetails.getFhirServerBase());
// Summary mode
Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequestDetails);
// _elements
Set<String> elements = ElementsParameter.getElementsValueOrNull(theRequestDetails);
if (elements != null && summaryMode != null && !summaryMode.equals(Collections.singleton(SummaryEnum.FALSE))) {
throw new InvalidRequestException("Cannot combine the " + Constants.PARAM_SUMMARY + " and " + Constants.PARAM_ELEMENTS + " parameters");
}
Set<String> elementsAppliesTo = null;
if (elements != null && isNotBlank(theRequestDetails.getResourceName())) {
elementsAppliesTo = Collections.singleton(theRequestDetails.getResourceName());
}
if (summaryMode != null) {
if (summaryMode.contains(SummaryEnum.COUNT)) {
parser.setEncodeElements(Collections.singleton("Bundle.total"));
} else if (summaryMode.contains(SummaryEnum.TEXT)) {
parser.setEncodeElements(TEXT_ENCODE_ELEMENTS);
} else {
parser.setSuppressNarratives(summaryMode.contains(SummaryEnum.DATA));
parser.setSummaryMode(summaryMode.contains(SummaryEnum.TRUE));
}
}
if (elements != null && elements.size() > 0) {
Set<String> newElements = new HashSet<String>();
for (String next : elements) {
newElements.add("*." + next);
}
parser.setEncodeElements(newElements);
parser.setEncodeElementsAppliesToResourceTypes(elementsAppliesTo);
}
configureResponseParser(theRequestDetails, parser);
return parser;
}
@ -357,6 +359,55 @@ public class RestfulServerUtils {
return writer;
}
public static Set<String> parseAcceptHeaderAndReturnHighestRankedOptions(HttpServletRequest theRequest) {
Set<String> retVal = new HashSet<String>();
Enumeration<String> acceptValues = theRequest.getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
float bestQ = -1f;
while (acceptValues.hasMoreElements()) {
String nextAcceptHeaderValue = acceptValues.nextElement();
Matcher m = ACCEPT_HEADER_PATTERN.matcher(nextAcceptHeaderValue);
float q = 1.0f;
while (m.find()) {
String contentTypeGroup = m.group(1);
if (isNotBlank(contentTypeGroup)) {
String name = m.group(3);
String value = m.group(4);
if (name != null && value != null) {
if ("q".equals(name)) {
try {
q = Float.parseFloat(value);
q = Math.max(q, 0.0f);
} catch (NumberFormatException e) {
ourLog.debug("Invalid Accept header q value: {}", value);
}
}
}
if (q > bestQ) {
retVal.clear();
bestQ = q;
}
if (q == bestQ) {
retVal.add(contentTypeGroup.trim());
}
}
if (!",".equals(m.group(5))) {
break;
}
}
}
}
return retVal;
}
public static PreferReturnEnum parsePreferHeader(String theValue) {
if (isBlank(theValue)) {
return null;
@ -414,8 +465,8 @@ public class RestfulServerUtils {
return prettyPrint;
}
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, String theServerBase, Set<SummaryEnum> theSummaryMode, boolean theRespondGzip, boolean theRequestIsBrowser, RequestDetails theRequestDetails)
throws IOException {
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, String theServerBase, Set<SummaryEnum> theSummaryMode, boolean theRespondGzip,
boolean theRequestIsBrowser, RequestDetails theRequestDetails) throws IOException {
assert!theServerBase.endsWith("/");
theHttpResponse.setStatus(200);
@ -446,8 +497,8 @@ public class RestfulServerUtils {
}
}
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IBaseResource theResource, boolean theRequestIsBrowser, Set<SummaryEnum> theSummaryMode, int stausCode, boolean theRespondGzip,
boolean theAddContentLocationHeader, RequestDetails theRequestDetails) throws IOException {
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IBaseResource theResource, boolean theRequestIsBrowser, Set<SummaryEnum> theSummaryMode,
int stausCode, boolean theRespondGzip, boolean theAddContentLocationHeader, RequestDetails theRequestDetails) throws IOException {
theHttpResponse.setStatus(stausCode);
// Determine response encoding
@ -497,12 +548,12 @@ public class RestfulServerUtils {
// Ok, we're not serving a binary resource, so apply default encoding
responseEncoding = responseEncoding != null ? responseEncoding : theServer.getDefaultResponseEncoding();
boolean encodingDomainResourceAsText = theSummaryMode.contains(SummaryEnum.TEXT);
if (encodingDomainResourceAsText) {
/*
* If the user requests "text" for a bundle, only suppress the non text elements in the Element.entry.resource
* parts, we're not streaming just the narrative as HTML (since bundles don't even have one)
* If the user requests "text" for a bundle, only suppress the non text elements in the Element.entry.resource parts, we're not streaming just the narrative as HTML (since bundles don't even
* have one)
*/
if ("Bundle".equals(theServer.getFhirContext().getResourceDefinition(theResource).getName())) {
encodingDomainResourceAsText = false;

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.server.interceptor;
*/
import java.io.IOException;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -166,8 +167,8 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
/*
* It's not a browser...
*/
String accept = theServletRequest.getHeader(Constants.HEADER_ACCEPT);
if (accept == null || !accept.toLowerCase().contains("html")) {
Set<String> highestRankedAcceptValues = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theRequestDetails.getServletRequest());
if (highestRankedAcceptValues.contains(Constants.CT_HTML) == false) {
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
@ -192,11 +193,15 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
}
private void streamResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, IBaseResource resource) {
IParser p = RestfulServerUtils.getNewParser(theRequestDetails.getServer().getFhirContext(), theRequestDetails);
IParser p;
if (theRequestDetails.getParameters().containsKey(Constants.PARAM_FORMAT)) {
p = RestfulServerUtils.getNewParser(theRequestDetails.getServer().getFhirContext(), theRequestDetails);
} else {
EncodingEnum defaultResponseEncoding = theRequestDetails.getServer().getDefaultResponseEncoding();
p = defaultResponseEncoding.newParser(theRequestDetails.getServer().getFhirContext());
}
EncodingEnum encoding = p.getEncoding();
String encoded = p.encodeResourceToString(resource);
theServletResponse.setContentType(Constants.CT_HTML_WITH_UTF8);
@ -245,8 +250,8 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
/*
* It's not a browser...
*/
String accept = theServletRequest.getHeader(Constants.HEADER_ACCEPT);
if (accept == null || !accept.toLowerCase().contains("html")) {
Set<String> accept = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theRequestDetails.getServletRequest());
if (!accept.contains(Constants.CT_HTML)) {
return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse);
}

View File

@ -58,11 +58,7 @@
<version>${thymeleaf-version}</version>
</dependency>
<!--
Use an older version of SLF4j just to make sure we compile correctly
against old SLF4j - Some people can't upgrade and we have no real
need for recent features.
-->
<!-- Use an older version of SLF4j just to make sure we compile correctly against old SLF4j - Some people can't upgrade and we have no real need for recent features. -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
@ -176,36 +172,10 @@
<skipDeploy>true</skipDeploy>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
<formats>
<format>html</format>
<format>xml</format>
</formats>
<maxmem>256m</maxmem>
<instrumentation>
<ignores>
<ignore>ca.uhn.fhir.model.dstu.valueset.*</ignore>
</ignores>
<excludes>
<ignore>**/valueset/*.class</ignore>
<ignore>**/exceptions/*.class</ignore>
</excludes>
<!-- <ignoreMethodAnnotations> <ignoreMethodAnnotation>net.sourceforge.cobertura.CoverageIgnore</ignoreMethodAnnotation> </ignoreMethodAnnotations> -->
</instrumentation>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <configuration> <skip>false</skip> <formats> <format>html</format> <format>xml</format> </formats>
<maxmem>256m</maxmem> <instrumentation> <ignores> <ignore>ca.uhn.fhir.model.dstu.valueset.*</ignore> </ignores> <excludes> <ignore>**/valueset/*.class</ignore> <ignore>**/exceptions/*.class</ignore> </excludes>
</instrumentation> </configuration> <executions> <execution> <phase>verify</phase> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> -->
</plugins>
</pluginManagement>
<plugins>
@ -214,11 +184,9 @@
<artifactId>coveralls-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<coberturaReports>
<coberturaReport>
${basedir}/target/coverage.xml
</coberturaReport>
</coberturaReports>
<jacocoReports>
<jacocoReport></jacocoReport>
</jacocoReports>
<sourceEncoding>UTF-8</sourceEncoding>
<serviceName>travis-ci</serviceName>
<serviceJobId>${env.TRAVIS_JOB_ID}</serviceJobId>
@ -272,7 +240,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<runOrder>alphabetical</runOrder>
<argLine>-Xms512m -Xmx1024m</argLine>
<argLine>-Xms512m -Xmx1024m ${argLine}</argLine>
</configuration>
</plugin>
<plugin>
@ -291,26 +259,25 @@
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<configuration>
<check>
<haltOnFailure>true</haltOnFailure>
</check>
</configuration>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.5.201505241946</version>
<executions>
<execution>
<phase>verify</phase>
<id>prepare-agent</id>
<goals>
<goal>check</goal>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>jacoco-site</id>
<phase>post-integration-test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>${maven_cobertura_plugin_version}</version> <configuration> <check> <branchRate>85</branchRate>
<lineRate>85</lineRate> <haltOnFailure>true</haltOnFailure> <totalBranchRate>85</totalBranchRate> <totalLineRate>85</totalLineRate> <packageLineRate>85</packageLineRate> <packageBranchRate>85</packageBranchRate>
<regexes> <regex> <pattern>com.example.reallyimportant.*</pattern> <branchRate>90</branchRate> <lineRate>80</lineRate> </regex> <regex> <pattern>com.example.boringcode.*</pattern> <branchRate>40</branchRate>
<lineRate>30</lineRate> </regex> </regexes> </check> </configuration> <executions> <execution> <goals> <goal>clean</goal> <goal>check</goal> </goals> </execution> </executions> </plugin> -->
</plugins>
<resources>
</resources>
@ -331,22 +298,6 @@
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<reportSets>
<reportSet>
<reports>
<report>cobertura</report>
</reports>
<configuration>
<check>
<haltOnFailure>true</haltOnFailure>
</check>
</configuration>
</reportSet>
</reportSets>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>

View File

@ -0,0 +1,378 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- The parent of this project is the deployable POM. This project isn't deployable, but this keeps it before the root pom in the reactor order when building the site. I don't know why this works...
Need to investigate this. -->
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>1.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<artifactId>hapi-fhir-cobertura</artifactId>
<packaging>jar</packaging>
<name>HAPI FHIR - Cobertura Test Coverage</name>
<dependencies>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<version>1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu</artifactId>
<version>1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
<version>1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-base</artifactId>
<version>1.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.phloc</groupId>
<artifactId>phloc-schematron</artifactId>
<version>${phloc_schematron_version}</version>
</dependency>
<dependency>
<groupId>com.phloc</groupId>
<artifactId>phloc-commons</artifactId>
<version>${phloc_commons_version}</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>${thymeleaf-version}</version>
</dependency>
<!--
Use an older version of SLF4j just to make sure we compile correctly
against old SLF4j - Some people can't upgrade and we have no real
need for recent features.
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<!-- Test Database -->
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15-sources</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>directory-naming</groupId>
<artifactId>naming-java</artifactId>
<version>0.8</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava_version}</version>
</dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<version>${ebay_cors_filter_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>xmlunit</groupId>
<artifactId>xmlunit</artifactId>
<version>${xmlunit_version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<configuration>
<skipDeploy>true</skipDeploy>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
<formats>
<format>html</format>
<format>xml</format>
</formats>
<maxmem>256m</maxmem>
<instrumentation>
<ignores>
<ignore>ca.uhn.fhir.model.dstu.valueset.*</ignore>
</ignores>
<excludes>
<ignore>**/valueset/*.class</ignore>
<ignore>**/exceptions/*.class</ignore>
</excludes>
<!-- <ignoreMethodAnnotations> <ignoreMethodAnnotation>net.sourceforge.cobertura.CoverageIgnore</ignoreMethodAnnotation> </ignoreMethodAnnotations> -->
</instrumentation>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<coberturaReports>
<coberturaReport>
${basedir}/target/coverage.xml
</coberturaReport>
</coberturaReports>
<sourceEncoding>UTF-8</sourceEncoding>
<serviceName>travis-ci</serviceName>
<serviceJobId>${env.TRAVIS_JOB_ID}</serviceJobId>
<sourceDirectories>
<sourceDirectory>../hapi-fhir-structures-dstu/src/test/java</sourceDirectory>
<sourceDirectory>../hapi-fhir-structures-dstu2/src/test/java</sourceDirectory>
<sourceDirectory>../hapi-fhir-structures-hl7org-dstu2/src/test/java</sourceDirectory>
<sourceDirectory>../hapi-fhir-jpaserver-base/src/test/java</sourceDirectory>
<sourceDirectory>../hapi-fhir-base/src/main/java</sourceDirectory>
<sourceDirectory>../hapi-fhir-jpaserver-base/src/main/java</sourceDirectory>
</sourceDirectories>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>${maven_build_helper_plugin_version}</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>../hapi-fhir-base/src/main/java</source>
<source>../hapi-fhir-jpaserver-base/src/main/java</source>
</sources>
</configuration>
</execution>
<execution>
<id>add-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>../hapi-fhir-structures-dstu/src/test/java</source>
<source>../hapi-fhir-structures-dstu2/src/test/java</source>
<source>../hapi-fhir-structures-hl7org-dstu2/src/test/java</source>
<source>../hapi-fhir-jpaserver-base/src/test/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<runOrder>alphabetical</runOrder>
<argLine>-Xms512m -Xmx1024m</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<configuration>
<check>
<haltOnFailure>true</haltOnFailure>
</check>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>${maven_cobertura_plugin_version}</version> <configuration> <check> <branchRate>85</branchRate>
<lineRate>85</lineRate> <haltOnFailure>true</haltOnFailure> <totalBranchRate>85</totalBranchRate> <totalLineRate>85</totalLineRate> <packageLineRate>85</packageLineRate> <packageBranchRate>85</packageBranchRate>
<regexes> <regex> <pattern>com.example.reallyimportant.*</pattern> <branchRate>90</branchRate> <lineRate>80</lineRate> </regex> <regex> <pattern>com.example.boringcode.*</pattern> <branchRate>40</branchRate>
<lineRate>30</lineRate> </regex> </regexes> </check> </configuration> <executions> <execution> <goals> <goal>clean</goal> <goal>check</goal> </goals> </execution> </executions> </plugin> -->
</plugins>
<resources>
</resources>
<testResources>
<testResource>
<directory>../hapi-fhir-jpaserver-base/src/test/resources</directory>
</testResource>
<testResource>
<directory>../hapi-fhir-structures-dstu/src/test/resources</directory>
</testResource>
<testResource>
<directory>../hapi-fhir-structures-dstu2/src/test/resources</directory>
</testResource>
<testResource>
<directory>../hapi-fhir-structures-hl7org-dstu2/src/test/resources</directory>
</testResource>
</testResources>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<reportSets>
<reportSet>
<reports>
<report>cobertura</report>
</reports>
<configuration>
<check>
<haltOnFailure>true</haltOnFailure>
</check>
</configuration>
</reportSet>
</reportSets>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>${maven_project_info_plugin_version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</reporting>
<profiles>
<profile>
<id>TRAVIS</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- Travis build seems to run out of memory unless we don't reuse JVMs -->
<reuseForks>false</reuseForks>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -1,9 +1,12 @@
package ca.uhn.fhir.rest.client;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.*;
import java.net.URL;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
@ -25,9 +28,13 @@ import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.PortUtil;
import ch.qos.logback.classic.BasicConfigurator;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.classic.util.LogbackMDCAdapter;
import ch.qos.logback.core.Appender;
/**
@ -44,6 +51,7 @@ public class LoggingInterceptorTest {
@SuppressWarnings("unchecked")
@Before
public void before() {
/*
* This is a bit funky, but it's useful for verifying that the headers actually get logged
*/
@ -51,7 +59,7 @@ public class LoggingInterceptorTest {
when(myMockAppender.getName()).thenReturn("MOCK");
org.slf4j.Logger logger = LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
myLoggerRoot = (ch.qos.logback.classic.Logger) logger;
myLoggerRoot.addAppender(myMockAppender);
}
@ -86,6 +94,15 @@ public class LoggingInterceptorTest {
@BeforeClass
public static void beforeClass() throws Exception {
URL conf = LoggingInterceptor.class.getResource("/logback-test-dstuforce.xml");
assertNotNull(conf);
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
context.reset();
configurator.doConfigure(conf);
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);

View File

@ -0,0 +1,30 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] - %msg%n
</pattern>
</encoder>
</appender>
<logger name="org.eclipse" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.apache" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.thymeleaf" additivity="false" level="warn">
<appender-ref ref="STDOUT" />
</logger>
<!--
<logger name="ca.uhn.fhir.rest.client" additivity="false" level="trace">
<appender-ref ref="STDOUT" />
</logger>
-->
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -1,12 +1,16 @@
package ca.uhn.fhir.rest.server.interceptor;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -24,8 +28,13 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import com.phloc.commons.collections.iterate.ArrayEnumeration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
@ -46,6 +55,7 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.BundleInclusionRule;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
@ -53,17 +63,91 @@ import ca.uhn.fhir.util.PortUtil;
public class ResponseHighlightingInterceptorTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResponseHighlightingInterceptorTest.class);
private static CloseableHttpClient ourClient;
private static final FhirContext ourCtx = FhirContext.forDstu2();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResponseHighlightingInterceptorTest.class);
private static int ourPort;
private static Server ourServer;
private static RestfulServer ourServlet;
@Test
public void testGetInvalidResource() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Foobar/123");
httpGet.addHeader("Accept", "text/html");
CloseableHttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Resp: {}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, stringContainsInOrder("<span class='hlTagName'>OperationOutcome</span>", "Unknown resource type 'Foobar' - Server knows how to handle"));
}
@Test
public void testGetRoot() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/");
httpGet.addHeader("Accept", "text/html");
CloseableHttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Resp: {}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, stringContainsInOrder("<span class='hlTagName'>OperationOutcome</span>",
"This is the base URL of FHIR server. Unable to handle this request, as it does not contain a resource type or operation name."));
}
@Test
public void testHighlightException() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@Override
public Enumeration<String> answer(InvocationOnMock theInvocation) throws Throwable {
return new ArrayEnumeration<String>("text/html,application/xhtml+xml,application/xml;q=0.9");
}
});
HttpServletResponse resp = mock(HttpServletResponse.class);
StringWriter sw = new StringWriter();
when(resp.getWriter()).thenReturn(new PrintWriter(sw));
Patient resource = new Patient();
resource.addName().addFamily("FAMILY");
RequestDetails reqDetails = new RequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
reqDetails.setParameters(new HashMap<String, String[]>());
reqDetails.setServer(new RestfulServer());
reqDetails.setServletRequest(req);
ResourceNotFoundException exception = new ResourceNotFoundException("Not found");
exception.setOperationOutcome(new OperationOutcome().addIssue(new Issue().setDiagnostics("Hello")));
assertFalse(ic.handleException(reqDetails, exception, req, resp));
String output = sw.getBuffer().toString();
ourLog.info(output);
assertThat(output, containsString("<span class='hlTagName'>OperationOutcome</span>"));
}
@Test
public void testHighlightNormalResponse() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeader(Constants.HEADER_ACCEPT)).thenReturn("text/html");
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@Override
public Enumeration<String> answer(InvocationOnMock theInvocation) throws Throwable {
return new ArrayEnumeration<String>("text/html,application/xhtml+xml,application/xml;q=0.9");
}
});
HttpServletResponse resp = mock(HttpServletResponse.class);
StringWriter sw = new StringWriter();
@ -84,14 +168,22 @@ public class ResponseHighlightingInterceptorTest {
ourLog.info(output);
assertThat(output, containsString("<span class='hlTagName'>Patient</span>"));
}
/**
* Browsers declare XML but not JSON in their accept header, we should still respond using JSON if that's the default
*/
@Test
public void testHighlightException() throws Exception {
public void testHighlightProducesDefaultJsonWithBrowserRequest() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeader(Constants.HEADER_ACCEPT)).thenReturn("text/html");
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@Override
public Enumeration<String> answer(InvocationOnMock theInvocation) throws Throwable {
return new ArrayEnumeration<String>("text/html,application/xhtml+xml,application/xml;q=0.9");
}
});
HttpServletResponse resp = mock(HttpServletResponse.class);
StringWriter sw = new StringWriter();
@ -103,36 +195,49 @@ public class ResponseHighlightingInterceptorTest {
RequestDetails reqDetails = new RequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
reqDetails.setParameters(new HashMap<String, String[]>());
reqDetails.setServer(new RestfulServer());
RestfulServer server = new RestfulServer();
server.setDefaultResponseEncoding(EncodingEnum.JSON);
reqDetails.setServer(server);
reqDetails.setServletRequest(req);
ResourceNotFoundException exception = new ResourceNotFoundException("Not found");
exception.setOperationOutcome(new OperationOutcome().addIssue(new Issue().setDiagnostics("Hello")));
assertFalse(ic.handleException(reqDetails, exception, req, resp));
assertFalse(ic.outgoingResponse(reqDetails, resource, req, resp));
String output = sw.getBuffer().toString();
ourLog.info(output);
assertThat(output, containsString("<span class='hlTagName'>OperationOutcome</span>"));
assertThat(output, containsString("resourceType"));
}
private static final FhirContext ourCtx = FhirContext.forDstu2();
@Test
public void testGetRoot() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/");
httpGet.addHeader("Accept", "html");
CloseableHttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Resp: {}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, stringContainsInOrder("<span class='hlTagName'>OperationOutcome</span>", "This is the base URL of FHIR server. Unable to handle this request, as it does not contain a resource type or operation name."));
@Test
public void testHighlightProducesDefaultJsonWithBrowserRequest2() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@Override
public Enumeration<String> answer(InvocationOnMock theInvocation) throws Throwable {
return new ArrayEnumeration<String>("text/html;q=0.8,application/xhtml+xml,application/xml;q=0.9");
}
});
HttpServletResponse resp = mock(HttpServletResponse.class);
StringWriter sw = new StringWriter();
when(resp.getWriter()).thenReturn(new PrintWriter(sw));
Patient resource = new Patient();
resource.addName().addFamily("FAMILY");
RequestDetails reqDetails = new RequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
reqDetails.setParameters(new HashMap<String, String[]>());
RestfulServer server = new RestfulServer();
server.setDefaultResponseEncoding(EncodingEnum.JSON);
reqDetails.setServer(server);
reqDetails.setServletRequest(req);
// True here means the interceptor didn't handle the request, because HTML wasn't the top ranked accept header
assertTrue(ic.outgoingResponse(reqDetails, resource, req, resp));
}
@Test
public void testSearchWithSummaryParam() throws Exception {
@ -147,22 +252,6 @@ public class ResponseHighlightingInterceptorTest {
assertThat(responseContent, not(containsString("entry")));
}
@Test
public void testGetInvalidResource() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Foobar/123");
httpGet.addHeader("Accept", "html");
CloseableHttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Resp: {}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(responseContent, stringContainsInOrder("<span class='hlTagName'>OperationOutcome</span>", "Unknown resource type 'Foobar' - Server knows how to handle"));
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
@ -171,11 +260,11 @@ public class ResponseHighlightingInterceptorTest {
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.registerInterceptor(new ResponseHighlighterInterceptor());
servlet.setResourceProviders(patientProvider);
servlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
ServletHolder servletHolder = new ServletHolder(servlet);
ourServlet = new RestfulServer(ourCtx);
ourServlet.registerInterceptor(new ResponseHighlighterInterceptor());
ourServlet.setResourceProviders(patientProvider);
ourServlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
ServletHolder servletHolder = new ServletHolder(ourServlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
@ -192,6 +281,39 @@ public class ResponseHighlightingInterceptorTest {
*/
public static class DummyPatientResourceProvider implements IResourceProvider {
private Patient createPatient1() {
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00001");
patient.addName();
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.getId().setValue("1");
return patient;
}
@Search(queryName = "findPatientsWithAbsoluteIdSpecified")
public List<Patient> findPatientsWithAbsoluteIdSpecified() {
Patient p = new Patient();
p.addIdentifier().setSystem("foo");
p.setId("http://absolute.com/Patient/123/_history/22");
Organization o = new Organization();
o.setId("http://foo.com/Organization/222/_history/333");
p.getManagingOrganization().setResource(o);
return Collections.singletonList(p);
}
@Search(queryName = "findPatientsWithNoIdSpecified")
public List<Patient> findPatientsWithNoIdSpecified() {
Patient p = new Patient();
p.addIdentifier().setSystem("foo");
return Collections.singletonList(p);
}
public Map<String, Patient> getIdToPatient() {
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
{
@ -227,14 +349,6 @@ public class ResponseHighlightingInterceptorTest {
return retVal;
}
@Search(queryName = "searchWithWildcardRetVal")
public List<? extends IResource> searchWithWildcardRetVal() {
Patient p = new Patient();
p.setId("1234");
p.addName().addFamily("searchWithWildcardRetVal");
return Collections.singletonList(p);
}
/**
* Retrieve the resource by its identifier
*
@ -252,45 +366,19 @@ public class ResponseHighlightingInterceptorTest {
}
}
@Search(queryName = "findPatientsWithNoIdSpecified")
public List<Patient> findPatientsWithNoIdSpecified() {
Patient p = new Patient();
p.addIdentifier().setSystem("foo");
return Collections.singletonList(p);
}
@Search(queryName = "findPatientsWithAbsoluteIdSpecified")
public List<Patient> findPatientsWithAbsoluteIdSpecified() {
Patient p = new Patient();
p.addIdentifier().setSystem("foo");
p.setId("http://absolute.com/Patient/123/_history/22");
Organization o = new Organization();
o.setId("http://foo.com/Organization/222/_history/333");
p.getManagingOrganization().setResource(o);
return Collections.singletonList(p);
}
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
private Patient createPatient1() {
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00001");
patient.addName();
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.getId().setValue("1");
return patient;
@Search(queryName = "searchWithWildcardRetVal")
public List<? extends IResource> searchWithWildcardRetVal() {
Patient p = new Patient();
p.setId("1234");
p.addName().addFamily("searchWithWildcardRetVal");
return Collections.singletonList(p);
}
}
}