Merge remote-tracking branch 'remotes/origin/master' into expunge-resource-hook

This commit is contained in:
Ken Stevens 2019-07-18 10:12:15 -04:00
commit 72aa4932d6
101 changed files with 3037 additions and 506 deletions

View File

@ -1,7 +1,9 @@
package example;
import java.io.IOException;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.dstu3.model.Parameters;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
@ -18,11 +20,33 @@ import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ServerOperations {
private static final Logger ourLog = LoggerFactory.getLogger(ServerOperations.class);
//START SNIPPET: searchParamBasic
//START SNIPPET: manualInputAndOutput
@Operation(name="$manualInputAndOutput", manualResponse=true, manualRequest=true)
public void manualInputAndOutput(HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws IOException {
String contentType = theServletRequest.getContentType();
byte[] bytes = IOUtils.toByteArray(theServletRequest.getInputStream());
ourLog.info("Received call with content type {} and {} bytes", contentType, bytes.length);
theServletResponse.setContentType(contentType);
theServletResponse.getOutputStream().write(bytes);
theServletResponse.getOutputStream().close();
}
//END SNIPPET: manualInputAndOutput
//START SNIPPET: searchParamBasic
@Operation(name="$find-matches", idempotent=true)
public Parameters findMatchesBasic(
@OperationParam(name="date") DateParam theDate,

View File

@ -24,6 +24,8 @@ 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 javax.annotation.Nonnull;
import java.util.Collection;
@ -132,4 +134,11 @@ public class HookParams {
}
return this;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SIMPLE_STYLE)
.append("params", myParams)
.toString();
}
}

View File

@ -25,6 +25,7 @@ import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
@ -131,4 +132,10 @@ public abstract class BasePrimitive<T> extends BaseIdentifiableElement implement
public void writeExternal(ObjectOutput theOut) throws IOException {
theOut.writeObject(getValueAsString());
}
@Override
public boolean hasValue() {
return !StringUtils.isBlank(getValueAsString());
}
}

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.model.api;
/*
/*-
* #%L
* HAPI FHIR - Core Library
* %%
@ -20,6 +20,13 @@ package ca.uhn.fhir.model.api;
* #L%
*/
public interface IPrimitiveDatatypeWithPrecision<T, P> extends IPrimitiveDatatype<T> {
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.io.IOException;
import java.io.Writer;
public interface IStreamingDatatype<T> extends IPrimitiveType<T> {
void writeAsText(Writer theWriter) throws IOException;
}

View File

@ -87,4 +87,37 @@ public @interface Operation {
*/
BundleTypeEnum bundleType() default BundleTypeEnum.COLLECTION;
/**
* If this is set to <code>true</code> (default is <code>false</code> and this is almost
* always the right choice), the framework will not attempt to generate a response to
* this method.
* <p>
* This is useful if you want to include an {@link javax.servlet.http.HttpServletResponse}
* in your method parameters and create a response yourself directly from your
* <code>@Operation</code> method.
* </p>
* <p>
* Note that this will mean that interceptor methods will not get fired for the
* response, so there are security implications to using this flag.
* </p>
*/
boolean manualResponse() default false;
/**
* If this is set to <code>true</code> (default is <code>false</code> and this is almost
* always the right choice), the framework will not attempt to parse the request body,
* but will instead delegate it to the <code>@Operation</code> method.
* <p>
* This is useful if you want to include an {@link javax.servlet.http.HttpServletRequest}
* in your method parameters and parse the request yourself.
* </p>
*/
boolean manualRequest() default false;
/**
* If this is set to <code>true</code>, this method will be a <b>global operation</b>
* meaning that it applies to all resource types
*/
boolean global() default false;
}

View File

@ -211,6 +211,8 @@ public class Constants {
public static final String PARAMETER_CASCADE_DELETE = "_cascade";
public static final String HEADER_CASCADE = "X-Cascade";
public static final String CASCADE_DELETE = "delete";
public static final int MAX_RESOURCE_NAME_LENGTH = 100;
public static final String CACHE_CONTROL_PRIVATE = "private";
static {
CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8);

View File

@ -0,0 +1,98 @@
package ca.uhn.fhir.util;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.util.List;
public class AttachmentUtil {
/**
* Fetches the base64Binary value of Attachment.data, creating it if it does not
* already exist.
*/
@SuppressWarnings("unchecked")
public static IPrimitiveType<byte[]> getOrCreateData(FhirContext theContext, ICompositeType theAttachment) {
BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "data");
List<IBase> entries = entryChild.getAccessor().getValues(theAttachment);
return entries
.stream()
.map(t -> (IPrimitiveType<byte[]>) t)
.findFirst()
.orElseGet(() -> {
IPrimitiveType<byte[]> binary = newPrimitive(theContext, "base64Binary", null);
entryChild.getMutator().setValue(theAttachment, binary);
return binary;
});
}
@SuppressWarnings("unchecked")
public static IPrimitiveType<String> getOrCreateContentType(FhirContext theContext, ICompositeType theAttachment) {
BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "contentType");
List<IBase> entries = entryChild.getAccessor().getValues(theAttachment);
return entries
.stream()
.map(t -> (IPrimitiveType<String>) t)
.findFirst()
.orElseGet(() -> {
IPrimitiveType<String> string = newPrimitive(theContext, "string", null);
entryChild.getMutator().setValue(theAttachment, string);
return string;
});
}
public static void setContentType(FhirContext theContext, ICompositeType theAttachment, String theContentType) {
BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "contentType");
entryChild.getMutator().setValue(theAttachment, newPrimitive(theContext, "code", theContentType));
}
public static void setData(FhirContext theContext, ICompositeType theAttachment, byte[] theBytes) {
BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "data");
entryChild.getMutator().setValue(theAttachment, newPrimitive(theContext, "base64Binary", theBytes));
}
public static void setSize(FhirContext theContext, ICompositeType theAttachment, Integer theLength) {
BaseRuntimeChildDefinition entryChild = getChild(theContext, theAttachment, "size");
if (theLength == null) {
entryChild.getMutator().setValue(theAttachment, null);
} else {
entryChild.getMutator().setValue(theAttachment, newPrimitive(theContext, "unsignedInt", theLength));
}
}
@SuppressWarnings("unchecked")
private static <T> IPrimitiveType<T> newPrimitive(FhirContext theContext, String theType, T theValue) {
IPrimitiveType<T> primitive = (IPrimitiveType<T>) theContext.getElementDefinition(theType).newInstance();
primitive.setValue(theValue);
return primitive;
}
private static BaseRuntimeChildDefinition getChild(FhirContext theContext, ICompositeType theAttachment, String theName) {
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theAttachment.getClass());
return def.getChildByName(theName);
}
}

View File

@ -20,253 +20,148 @@ package ca.uhn.fhir.util;
* #L%
*/
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
import java.lang.ref.SoftReference;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.*;
/**
* A utility class for parsing and formatting HTTP dates as used in cookies and
* other headers. This class handles dates as defined by RFC 2616 section
* 3.3.1 as well as some other common non-standard formats.
* <p>
* This class is basically intended to be a high-performance workaround
* for the fact that Java SimpleDateFormat is kind of expensive to
* create and yet isn't thread safe.
* </p>
* <p>
* This class was adapted from the class with the same name from the Jetty
* project, licensed under the terms of the Apache Software License 2.0.
* </p>
*/
public final class DateUtils {
/**
* Date format pattern used to parse HTTP date headers in RFC 1123 format.
*/
public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
/**
* Date format pattern used to parse HTTP date headers in RFC 1123 format.
*/
private static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
/**
* Date format pattern used to parse HTTP date headers in RFC 1036 format.
*/
private static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";
/**
* Date format pattern used to parse HTTP date headers in ANSI C
* {@code asctime()} format.
*/
private static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
private static final String[] DEFAULT_PATTERNS = new String[]{
PATTERN_RFC1123,
PATTERN_RFC1036,
PATTERN_ASCTIME
};
private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
/**
* Date format pattern used to parse HTTP date headers in RFC 1036 format.
*/
public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";
static {
final Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(GMT);
calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
calendar.set(Calendar.MILLISECOND, 0);
DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
}
/**
* Date format pattern used to parse HTTP date headers in ANSI C
* {@code asctime()} format.
*/
public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
/**
* This class should not be instantiated.
*/
private DateUtils() {
}
private static final String[] DEFAULT_PATTERNS = new String[] {
PATTERN_RFC1123,
PATTERN_RFC1036,
PATTERN_ASCTIME
};
/**
* A factory for {@link SimpleDateFormat}s. The instances are stored in a
* threadlocal way because SimpleDateFormat is not thread safe as noted in
* {@link SimpleDateFormat its javadoc}.
*/
final static class DateFormatHolder {
private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>> THREADLOCAL_FORMATS = ThreadLocal.withInitial(() -> new SoftReference<>(new HashMap<>()));
public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
/**
* creates a {@link SimpleDateFormat} for the requested format string.
*
* @param pattern a non-{@code null} format String according to
* {@link SimpleDateFormat}. The format is not checked against
* {@code null} since all paths go through
* {@link DateUtils}.
* @return the requested format. This simple DateFormat should not be used
* to {@link SimpleDateFormat#applyPattern(String) apply} to a
* different pattern.
*/
static SimpleDateFormat formatFor(final String pattern) {
final SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
Map<String, SimpleDateFormat> formats = ref.get();
if (formats == null) {
formats = new HashMap<>();
THREADLOCAL_FORMATS.set(
new SoftReference<>(formats));
}
static {
final Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(GMT);
calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
calendar.set(Calendar.MILLISECOND, 0);
DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
}
SimpleDateFormat format = formats.get(pattern);
if (format == null) {
format = new SimpleDateFormat(pattern, Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
formats.put(pattern, format);
}
/**
* Parses a date value. The formats used for parsing the date value are retrieved from
* the default http params.
*
* @param dateValue the date value to parse
*
* @return the parsed date or null if input could not be parsed
*/
public static Date parseDate(final String dateValue) {
return parseDate(dateValue, null, null);
}
return format;
}
/**
* Parses the date value using the given date formats.
*
* @param dateValue the date value to parse
* @param dateFormats the date formats to use
*
* @return the parsed date or null if input could not be parsed
*/
public static Date parseDate(final String dateValue, final String[] dateFormats) {
return parseDate(dateValue, dateFormats, null);
}
}
/**
* Parses the date value using the given date formats.
*
* @param dateValue the date value to parse
* @param dateFormats the date formats to use
* @param startDate During parsing, two digit years will be placed in the range
* {@code startDate} to {@code startDate + 100 years}. This value may
* be {@code null}. When {@code null} is given as a parameter, year
* {@code 2000} will be used.
*
* @return the parsed date or null if input could not be parsed
*/
public static Date parseDate(
final String dateValue,
final String[] dateFormats,
final Date startDate) {
notNull(dateValue, "Date value");
final String[] localDateFormats = dateFormats != null ? dateFormats : DEFAULT_PATTERNS;
final Date localStartDate = startDate != null ? startDate : DEFAULT_TWO_DIGIT_YEAR_START;
String v = dateValue;
// trim single quotes around date if present
// see issue #5279
if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) {
v = v.substring (1, v.length() - 1);
}
/**
* Parses a date value. The formats used for parsing the date value are retrieved from
* the default http params.
*
* @param theDateValue the date value to parse
* @return the parsed date or null if input could not be parsed
*/
public static Date parseDate(final String theDateValue) {
notNull(theDateValue, "Date value");
String v = theDateValue;
if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) {
v = v.substring(1, v.length() - 1);
}
for (final String dateFormat : localDateFormats) {
final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat);
dateParser.set2DigitYearStart(localStartDate);
final ParsePosition pos = new ParsePosition(0);
final Date result = dateParser.parse(v, pos);
if (pos.getIndex() != 0) {
return result;
}
}
return null;
}
for (final String dateFormat : DEFAULT_PATTERNS) {
final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat);
dateParser.set2DigitYearStart(DEFAULT_TWO_DIGIT_YEAR_START);
final ParsePosition pos = new ParsePosition(0);
final Date result = dateParser.parse(v, pos);
if (pos.getIndex() != 0) {
return result;
}
}
return null;
}
/**
* Formats the given date according to the RFC 1123 pattern.
*
* @param date The date to format.
* @return An RFC 1123 formatted date string.
*
* @see #PATTERN_RFC1123
*/
public static String formatDate(final Date date) {
return formatDate(date, PATTERN_RFC1123);
}
/**
* Formats the given date according to the RFC 1123 pattern.
*
* @param date The date to format.
* @return An RFC 1123 formatted date string.
* @see #PATTERN_RFC1123
*/
public static String formatDate(final Date date) {
notNull(date, "Date");
notNull(PATTERN_RFC1123, "Pattern");
final SimpleDateFormat formatter = DateFormatHolder.formatFor(PATTERN_RFC1123);
return formatter.format(date);
}
/**
* Formats the given date according to the specified pattern. The pattern
* must conform to that used by the {@link SimpleDateFormat simple date
* format} class.
*
* @param date The date to format.
* @param pattern The pattern to use for formatting the date.
* @return A formatted date string.
*
* @throws IllegalArgumentException If the given date pattern is invalid.
*
* @see SimpleDateFormat
*/
public static String formatDate(final Date date, final String pattern) {
notNull(date, "Date");
notNull(pattern, "Pattern");
final SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern);
return formatter.format(date);
}
public static <T> T notNull(final T argument, final String name) {
if (argument == null) {
throw new IllegalArgumentException(name + " may not be null");
}
return argument;
}
/**
* Clears thread-local variable containing {@link java.text.DateFormat} cache.
*
* @since 4.3
*/
public static void clearThreadLocal() {
DateFormatHolder.clearThreadLocal();
}
/** This class should not be instantiated. */
private DateUtils() {
}
/**
* A factory for {@link SimpleDateFormat}s. The instances are stored in a
* threadlocal way because SimpleDateFormat is not threadsafe as noted in
* {@link SimpleDateFormat its javadoc}.
*
*/
final static class DateFormatHolder {
private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>
THREADLOCAL_FORMATS = new ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>>() {
@Override
protected SoftReference<Map<String, SimpleDateFormat>> initialValue() {
return new SoftReference<Map<String, SimpleDateFormat>>(
new HashMap<String, SimpleDateFormat>());
}
};
/**
* creates a {@link SimpleDateFormat} for the requested format string.
*
* @param pattern
* a non-{@code null} format String according to
* {@link SimpleDateFormat}. The format is not checked against
* {@code null} since all paths go through
* {@link DateUtils}.
* @return the requested format. This simple dateformat should not be used
* to {@link SimpleDateFormat#applyPattern(String) apply} to a
* different pattern.
*/
public static SimpleDateFormat formatFor(final String pattern) {
final SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
Map<String, SimpleDateFormat> formats = ref.get();
if (formats == null) {
formats = new HashMap<String, SimpleDateFormat>();
THREADLOCAL_FORMATS.set(
new SoftReference<Map<String, SimpleDateFormat>>(formats));
}
SimpleDateFormat format = formats.get(pattern);
if (format == null) {
format = new SimpleDateFormat(pattern, Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
formats.put(pattern, format);
}
return format;
}
public static void clearThreadLocal() {
THREADLOCAL_FORMATS.remove();
}
}
public static <T> T notNull(final T argument, final String name) {
if (argument == null) {
throw new IllegalArgumentException(name + " may not be null");
}
return argument;
}
}

View File

@ -8,6 +8,7 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import com.google.common.escape.Escaper;
import com.google.common.net.PercentEscaper;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
@ -44,6 +45,44 @@ public class UrlUtil {
private static final String URL_FORM_PARAMETER_OTHER_SAFE_CHARS = "-_.*";
private static final Escaper PARAMETER_ESCAPER = new PercentEscaper(URL_FORM_PARAMETER_OTHER_SAFE_CHARS, false);
public static class UrlParts {
private String myParams;
private String myResourceId;
private String myResourceType;
private String myVersionId;
public String getParams() {
return myParams;
}
public void setParams(String theParams) {
myParams = theParams;
}
public String getResourceId() {
return myResourceId;
}
public void setResourceId(String theResourceId) {
myResourceId = theResourceId;
}
public String getResourceType() {
return myResourceType;
}
public void setResourceType(String theResourceType) {
myResourceType = theResourceType;
}
public String getVersionId() {
return myVersionId;
}
public void setVersionId(String theVersionId) {
myVersionId = theVersionId;
}
}
/**
* Resolve a relative URL - THIS METHOD WILL NOT FAIL but will log a warning and return theEndpoint if the input is invalid.
@ -115,7 +154,6 @@ public class UrlUtil {
return PARAMETER_ESCAPER.escape(theUnescaped);
}
public static boolean isAbsolute(String theValue) {
String value = theValue.toLowerCase();
return value.startsWith("http://") || value.startsWith("https://");
@ -284,7 +322,7 @@ public class UrlUtil {
}
if (nextChar == '?') {
if (url.length() > idx + 1) {
retVal.setParams(url.substring(idx + 1, url.length()));
retVal.setParams(url.substring(idx + 1));
}
break;
}
@ -296,6 +334,18 @@ public class UrlUtil {
}
/**
* This method specifically HTML-encodes the &quot; and
* &lt; characters in order to prevent injection attacks
*/
public static String sanitizeUrlPart(IPrimitiveType<?> theString) {
String retVal = null;
if (theString != null) {
retVal = sanitizeUrlPart(theString.getValueAsString());
}
return retVal;
}
/**
* This method specifically HTML-encodes the &quot; and
* &lt; characters in order to prevent injection attacks
@ -352,6 +402,8 @@ public class UrlUtil {
char nextChar = theString.charAt(i);
if (nextChar == '%' || nextChar == '+') {
try {
// Yes it would be nice to not use a string "UTF-8" but the equivalent
// method that takes Charset is JDK10+ only... sigh....
return URLDecoder.decode(theString, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new Error("UTF-8 not supported, this shouldn't happen", e);
@ -361,43 +413,4 @@ public class UrlUtil {
return theString;
}
public static class UrlParts {
private String myParams;
private String myResourceId;
private String myResourceType;
private String myVersionId;
public String getParams() {
return myParams;
}
public void setParams(String theParams) {
myParams = theParams;
}
public String getResourceId() {
return myResourceId;
}
public void setResourceId(String theResourceId) {
myResourceId = theResourceId;
}
public String getResourceType() {
return myResourceType;
}
public void setResourceType(String theResourceType) {
myResourceType = theResourceType;
}
public String getVersionId() {
return myVersionId;
}
public void setVersionId(String theVersionId) {
myVersionId = theVersionId;
}
}
}

View File

@ -29,6 +29,8 @@ public interface IPrimitiveType<T> extends IBaseDatatype {
String getValueAsString();
T getValue();
boolean hasValue();
IPrimitiveType<T> setValue(T theValue) throws IllegalArgumentException;

View File

@ -114,6 +114,8 @@ ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.successMsg=Cascaded delet
ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor.noParam=Note that cascading deletes are not active for this request. You can enable cascading deletes by using the "_cascade=true" URL parameter.
ca.uhn.fhir.jpa.provider.BaseJpaProvider.cantCombintAtAndSince=Unable to combine _at and _since parameters for history operation
ca.uhn.fhir.jpa.provider.BinaryAccessProvider.noAttachmentDataPresent=The resource with ID {0} has no data at path: {1}
ca.uhn.fhir.jpa.provider.BinaryAccessProvider.unknownBlobId=Can not find the requested binary content. It may have been deleted.
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1}
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1}

View File

@ -0,0 +1,66 @@
package ca.uhn.fhir.jpa.binstore;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.hash.HashingInputStream;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull;
import java.io.InputStream;
import java.security.SecureRandom;
abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
private final SecureRandom myRandom;
private final String CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private final int ID_LENGTH = 100;
private int myMinSize;
BaseBinaryStorageSvcImpl() {
myRandom = new SecureRandom();
}
public void setMinSize(int theMinSize) {
myMinSize = theMinSize;
}
String newRandomId() {
StringBuilder b = new StringBuilder();
for (int i = 0; i < ID_LENGTH; i++) {
int nextInt = Math.abs(myRandom.nextInt());
b.append(CHARS.charAt(nextInt % CHARS.length()));
}
return b.toString();
}
@Override
public boolean shouldStoreBlob(long theSize, IIdType theResourceId, String theContentType) {
return theSize >= myMinSize;
}
@Nonnull
static HashingInputStream createHashingInputStream(InputStream theInputStream) {
HashFunction hash = Hashing.sha256();
return new HashingInputStream(hash, theInputStream);
}
}

View File

@ -0,0 +1,166 @@
package ca.uhn.fhir.jpa.binstore;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.ConfigurationException;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.common.base.Charsets;
import com.google.common.hash.HashingInputStream;
import com.google.common.io.CountingInputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import java.io.*;
import java.util.Date;
public class FilesystemBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl {
private static final Logger ourLog = LoggerFactory.getLogger(FilesystemBinaryStorageSvcImpl.class);
private final File myBasePath;
private final ObjectMapper myJsonSerializer;
public FilesystemBinaryStorageSvcImpl(String theBasePath) {
Validate.notBlank(theBasePath);
myBasePath = new File(theBasePath);
myJsonSerializer = new ObjectMapper();
myJsonSerializer.setSerializationInclusion(JsonInclude.Include.NON_NULL);
myJsonSerializer.enable(SerializationFeature.INDENT_OUTPUT);
}
@PostConstruct
public void start() {
ourLog.info("Starting binary storage service with base path: {}", myBasePath);
mkdir(myBasePath);
}
@Override
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) throws IOException {
String id = newRandomId();
File storagePath = getStoragePath(id, true);
// Write binary file
File storageFilename = getStorageFilename(storagePath, theResourceId, id);
ourLog.info("Writing to file: {}", storageFilename.getAbsolutePath());
CountingInputStream countingInputStream = new CountingInputStream(theInputStream);
HashingInputStream hashingInputStream = createHashingInputStream(countingInputStream);
try (FileOutputStream outputStream = new FileOutputStream(storageFilename)) {
IOUtils.copy(hashingInputStream, outputStream);
}
// Write descriptor file
long count = countingInputStream.getCount();
StoredDetails details = new StoredDetails(id, count, theContentType, hashingInputStream, new Date());
File descriptorFilename = getDescriptorFilename(storagePath, theResourceId, id);
ourLog.info("Writing to file: {}", descriptorFilename.getAbsolutePath());
try (FileWriter writer = new FileWriter(descriptorFilename)) {
myJsonSerializer.writeValue(writer, details);
}
ourLog.info("Stored binary blob with {} bytes and ContentType {} for resource {}", count, theContentType, theResourceId);
return details;
}
@Override
public StoredDetails fetchBlobDetails(IIdType theResourceId, String theBlobId) throws IOException {
StoredDetails retVal = null;
File storagePath = getStoragePath(theBlobId, false);
if (storagePath != null) {
File file = getDescriptorFilename(storagePath, theResourceId, theBlobId);
if (file.exists()) {
try (InputStream inputStream = new FileInputStream(file)) {
try (Reader reader = new InputStreamReader(inputStream, Charsets.UTF_8)) {
retVal = myJsonSerializer.readValue(reader, StoredDetails.class);
}
}
}
}
return retVal;
}
@Override
public void writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException {
File storagePath = getStoragePath(theBlobId, false);
if (storagePath != null) {
File file = getStorageFilename(storagePath, theResourceId, theBlobId);
if (file.exists()) {
try (InputStream inputStream = new FileInputStream(file)) {
IOUtils.copy(inputStream, theOutputStream);
theOutputStream.close();
}
}
}
}
@Nonnull
private File getDescriptorFilename(File theStoragePath, IIdType theResourceId, String theId) {
return getStorageFilename(theStoragePath, theResourceId, theId, ".json");
}
@Nonnull
private File getStorageFilename(File theStoragePath, IIdType theResourceId, String theId) {
return getStorageFilename(theStoragePath, theResourceId, theId, ".bin");
}
private File getStorageFilename(File theStoragePath, IIdType theResourceId, String theId, String theExtension) {
Validate.notBlank(theResourceId.getResourceType());
Validate.notBlank(theResourceId.getIdPart());
String filename = theResourceId.getResourceType() + "_" + theResourceId.getIdPart() + "_" + theId;
return new File(theStoragePath, filename + theExtension);
}
private File getStoragePath(String theId, boolean theCreate) {
File path = myBasePath;
for (int i = 0; i < 10; i++) {
path = new File(path, theId.substring(i, i+1));
if (!path.exists()) {
if (theCreate) {
mkdir(path);
} else {
return null;
}
}
}
return path;
}
private void mkdir(File theBasePath) {
try {
FileUtils.forceMkdir(theBasePath);
} catch (IOException e) {
throw new ConfigurationException("Unable to create path " + myBasePath + ": " + e.toString());
}
}
}

View File

@ -0,0 +1,137 @@
package ca.uhn.fhir.jpa.binstore;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.util.JsonDateDeserializer;
import ca.uhn.fhir.jpa.util.JsonDateSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.hash.HashingInputStream;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
public interface IBinaryStorageSvc {
/**
* Give the storage service the ability to veto items from storage
*
* @param theSize How large is the item
* @param theResourceId What is the resource ID it will be associated with
* @param theContentType What is the content type
* @return <code>true</code> if the storage service should store the item
*/
boolean shouldStoreBlob(long theSize, IIdType theResourceId, String theContentType);
/**
* Store a new binary blob
*
* @param theResourceId The resource ID that owns this blob. Note that it should not be possible to retrieve a blob without both the resource ID and the blob ID being correct.
* @param theContentType The content type to associate with this blob
* @param theInputStream An InputStream to read from. This method should close the stream when it has been fully consumed.
* @return Returns details about the stored data
*/
StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) throws IOException;
StoredDetails fetchBlobDetails(IIdType theResourceId, String theBlobId) throws IOException;
void writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
class StoredDetails {
@JsonProperty("blobId")
private String myBlobId;
@JsonProperty("bytes")
private long myBytes;
@JsonProperty("contentType")
private String myContentType;
@JsonProperty("hash")
private String myHash;
@JsonProperty("published")
@JsonSerialize(using = JsonDateSerializer.class)
@JsonDeserialize(using = JsonDateDeserializer.class)
private Date myPublished;
/**
* Constructor
*/
@SuppressWarnings("unused")
public StoredDetails() {
super();
}
/**
* Constructor
*/
public StoredDetails(@Nonnull String theBlobId, long theBytes, @Nonnull String theContentType, HashingInputStream theIs, Date thePublished) {
myBlobId = theBlobId;
myBytes = theBytes;
myContentType = theContentType;
myHash = theIs.hash().toString();
myPublished = thePublished;
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("blobId", myBlobId)
.append("bytes", myBytes)
.append("contentType", myContentType)
.append("hash", myHash)
.append("published", myPublished)
.toString();
}
public String getHash() {
return myHash;
}
public Date getPublished() {
return myPublished;
}
@Nonnull
public String getContentType() {
return myContentType;
}
@Nonnull
public String getBlobId() {
return myBlobId;
}
public long getBytes() {
return myBytes;
}
}
}

View File

@ -0,0 +1,82 @@
package ca.uhn.fhir.jpa.binstore;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.google.common.hash.HashingInputStream;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IIdType;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
/**
* Purely in-memory implementation of binary storage service. This is really
* only appropriate for testing, since it doesn't persist anywhere and is
* limited by the amount of available RAM.
*/
public class MemoryBinaryStorageSvcImpl extends BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
private ConcurrentHashMap<String, byte[]> myDataMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, StoredDetails> myDetailsMap = new ConcurrentHashMap<>();
/**
* Constructor
*/
public MemoryBinaryStorageSvcImpl() {
super();
}
@Override
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) throws IOException {
String id = newRandomId();
String key = toKey(theResourceId, id);
HashingInputStream is = createHashingInputStream(theInputStream);
byte[] bytes = IOUtils.toByteArray(is);
theInputStream.close();
myDataMap.put(key, bytes);
StoredDetails storedDetails = new StoredDetails(id, bytes.length, theContentType, is, new Date());
myDetailsMap.put(key, storedDetails);
return storedDetails;
}
@Override
public StoredDetails fetchBlobDetails(IIdType theResourceId, String theBlobId) {
String key = toKey(theResourceId, theBlobId);
return myDetailsMap.get(key);
}
@Override
public void writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) throws IOException {
String key = toKey(theResourceId, theBlobId);
byte[] bytes = myDataMap.get(key);
theOutputStream.write(bytes);
}
private String toKey(IIdType theResourceId, String theBlobId) {
return theBlobId + '-' + theResourceId.toUnqualifiedVersionless().getValue();
}
}

View File

@ -0,0 +1,31 @@
package ca.uhn.fhir.jpa.binstore;
import org.hl7.fhir.instance.model.api.IIdType;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
public class NullBinaryStorageSvcImpl implements IBinaryStorageSvc {
@Override
public boolean shouldStoreBlob(long theSize, IIdType theResourceId, String theContentType) {
return false;
}
@Override
public StoredDetails storeBlob(IIdType theResourceId, String theContentType, InputStream theInputStream) {
throw new UnsupportedOperationException();
}
@Override
public StoredDetails fetchBlobDetails(IIdType theResourceId, String theBlobId) {
throw new UnsupportedOperationException();
}
@Override
public void writeBlob(IIdType theResourceId, String theBlobId, OutputStream theOutputStream) {
throw new UnsupportedOperationException();
}
}

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
import ca.uhn.fhir.jpa.provider.BinaryAccessProvider;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
@ -120,6 +121,12 @@ public abstract class BaseConfig implements SchedulingConfigurer {
return new SubscriptionTriggeringProvider();
}
@Bean(name = "myAttachmentBinaryAccessProvider")
@Lazy
public BinaryAccessProvider AttachmentBinaryAccessProvider() {
return new BinaryAccessProvider();
}
@Bean
public TaskScheduler taskScheduler() {
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();

View File

@ -25,7 +25,7 @@ import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.jpa.util.AddRemoveCount;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;

View File

@ -561,7 +561,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return myExpungeService.expunge(getResourceName(), entity.getResourceId(), entity.getVersion(), theExpungeOptions, theRequest);
}
return myExpungeService.expunge(getResourceName(), entity.getResourceId(), null, theExpungeOptions ,theRequest);
return myExpungeService.expunge(getResourceName(), entity.getResourceId(), null, theExpungeOptions, theRequest);
}
@Override
@ -851,6 +851,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
}
}
}
@Override

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.entity;
* #L%
*/
import ca.uhn.fhir.rest.api.Constants;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@ -36,7 +37,7 @@ public class ResourceReindexJobEntity implements Serializable {
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RES_REINDEX_JOB")
@Column(name = "PID")
private Long myId;
@Column(name = "RES_TYPE", nullable = true)
@Column(name = "RES_TYPE", nullable = true, length = Constants.MAX_RESOURCE_NAME_LENGTH)
private String myResourceType;
/**
* Inclusive

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.entity;
*/
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.model.primitive.IdDt;
@ -47,7 +48,7 @@ import java.util.Date;
", h.res_updated as res_updated " +
", h.res_text as res_text " +
", h.res_encoding as res_encoding " +
", f.forced_id as forced_pid " +
", f.forced_id as FORCED_PID " +
"FROM HFJ_RES_VER h "
+ " LEFT OUTER JOIN HFJ_FORCED_ID f ON f.resource_pid = h.res_id "
+ " INNER JOIN HFJ_RESOURCE r ON r.res_id = h.res_id and r.res_ver = h.res_ver")
@ -63,7 +64,7 @@ public class ResourceSearchView implements IBaseResourceEntity, Serializable {
@Column(name = "RES_ID")
private Long myResourceId;
@Column(name = "RES_TYPE")
@Column(name = "RES_TYPE", length = Constants.MAX_RESOURCE_NAME_LENGTH)
private String myResourceType;
@Column(name = "RES_VERSION")
@ -96,7 +97,7 @@ public class ResourceSearchView implements IBaseResourceEntity, Serializable {
@Enumerated(EnumType.STRING)
private ResourceEncodingEnum myEncoding;
@Column(name = "forced_pid")
@Column(name = "FORCED_PID", length= ForcedId.MAX_FORCED_ID_LENGTH)
private String myForcedPid;
public ResourceSearchView() {

View File

@ -44,7 +44,7 @@ import static org.apache.commons.lang3.StringUtils.length;
@Entity
@Indexed(interceptor = DeferConceptIndexingInterceptor.class)
@Table(name = "TRM_CONCEPT", uniqueConstraints = {
@UniqueConstraint(name = "IDX_CONCEPT_CS_CODE", columnNames = {"CODESYSTEM_PID", "CODE"})
@UniqueConstraint(name = "IDX_CONCEPT_CS_CODE", columnNames = {"CODESYSTEM_PID", "CODEVAL"})
}, indexes = {
@Index(name = "IDX_CONCEPT_INDEXSTATUS", columnList = "INDEX_STATUS"),
@Index(name = "IDX_CONCEPT_UPDATED", columnList = "CONCEPT_UPDATED")
@ -60,7 +60,7 @@ public class TermConcept implements Serializable {
@OneToMany(fetch = FetchType.LAZY, mappedBy = "myParent", cascade = {})
private Collection<TermConceptParentChildLink> myChildren;
@Column(name = "CODE", nullable = false, length = MAX_CODE_LENGTH)
@Column(name = "CODEVAL", nullable = false, length = MAX_CODE_LENGTH)
@Fields({@Field(name = "myCode", index = org.hibernate.search.annotations.Index.YES, store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "exactAnalyzer")),})
private String myCode;
@Temporal(TemporalType.TIMESTAMP)

View File

@ -62,7 +62,7 @@ public class TermValueSet implements Serializable {
@Column(name = "RES_ID", insertable = false, updatable = false)
private Long myResourcePid;
@Column(name = "NAME", nullable = true, length = MAX_NAME_LENGTH)
@Column(name = "VSNAME", nullable = true, length = MAX_NAME_LENGTH)
private String myName;
@OneToMany(mappedBy = "myValueSet")

View File

@ -36,7 +36,7 @@ import static org.apache.commons.lang3.StringUtils.left;
import static org.apache.commons.lang3.StringUtils.length;
@Table(name = "TRM_VALUESET_CONCEPT", indexes = {
@Index(name = "IDX_VALUESET_CONCEPT_CS_CD", columnList = "SYSTEM, CODE")
@Index(name = "IDX_VALUESET_CONCEPT_CS_CD", columnList = "SYSTEM, CODEVAL")
})
@Entity()
public class TermValueSetConcept implements Serializable {
@ -58,10 +58,10 @@ public class TermValueSetConcept implements Serializable {
@Transient
private String myValueSetName;
@Column(name = "SYSTEM", nullable = false, length = TermCodeSystem.MAX_URL_LENGTH)
@Column(name = "SYSTEM_URL", nullable = false, length = TermCodeSystem.MAX_URL_LENGTH)
private String mySystem;
@Column(name = "CODE", nullable = false, length = TermConcept.MAX_CODE_LENGTH)
@Column(name = "CODEVAL", nullable = false, length = TermConcept.MAX_CODE_LENGTH)
private String myCode;
@Column(name = "DISPLAY", nullable = true, length = TermConcept.MAX_DESC_LENGTH)

View File

@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.ExpungeOutcome;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;

View File

@ -1,24 +1,12 @@
package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoComposition;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Composition;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Sort;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
/*
* #%L

View File

@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.provider;
*/
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoEncounter;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu2.resource.Encounter;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;

View File

@ -25,7 +25,7 @@ import java.util.List;
*/
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.StringDt;

View File

@ -28,7 +28,7 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.resource.Parameters;

View File

@ -0,0 +1,257 @@
package ca.uhn.fhir.jpa.provider;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc;
import ca.uhn.fhir.jpa.dao.DaoMethodOutcome;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.AttachmentUtil;
import ca.uhn.fhir.util.DateUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nonnull;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;
import static ca.uhn.fhir.util.UrlUtil.sanitizeUrlPart;
import static org.apache.commons.lang3.StringUtils.isBlank;
/**
* This plain provider class can be registered with a JPA RestfulServer
* to provide the <code>$binary-access-read</code> and <code>$binary-access-write</code>
* operations that can be used to access attachment data as a raw binary.
*/
public class BinaryAccessProvider {
@Autowired
private FhirContext myCtx;
@Autowired
private DaoRegistry myDaoRegistry;
@Autowired(required = false)
private IBinaryStorageSvc myBinaryStorageSvc;
/**
* $binary-access-read
*/
@Operation(name = JpaConstants.OPERATION_BINARY_ACCESS_READ, global = true, manualResponse = true, idempotent = true)
public void binaryAccessRead(
@IdParam IIdType theResourceId,
@OperationParam(name = "path", min = 1, max = 1) IPrimitiveType<String> thePath,
ServletRequestDetails theRequestDetails,
HttpServletRequest theServletRequest,
HttpServletResponse theServletResponse) throws IOException {
validateResourceTypeAndPath(theResourceId, thePath);
IFhirResourceDao dao = getDaoForRequest(theResourceId);
IBaseResource resource = dao.read(theResourceId, theRequestDetails, false);
ICompositeType attachment = findAttachmentForRequest(resource, thePath, theRequestDetails);
IBaseHasExtensions attachmentHasExt = (IBaseHasExtensions) attachment;
Optional<? extends IBaseExtension<?, ?>> attachmentId = attachmentHasExt
.getExtension()
.stream()
.filter(t -> JpaConstants.EXT_ATTACHMENT_EXTERNAL_BINARY_ID.equals(t.getUrl()))
.findFirst();
if (attachmentId.isPresent()) {
@SuppressWarnings("unchecked")
IPrimitiveType<String> value = (IPrimitiveType<String>) attachmentId.get().getValue();
String blobId = value.getValueAsString();
IBinaryStorageSvc.StoredDetails blobDetails = myBinaryStorageSvc.fetchBlobDetails(theResourceId, blobId);
if (blobDetails == null) {
String msg = myCtx.getLocalizer().getMessage(BinaryAccessProvider.class, "unknownBlobId");
throw new InvalidRequestException(msg);
}
theServletResponse.setStatus(200);
theServletResponse.setContentType(blobDetails.getContentType());
if (blobDetails.getBytes() <= Integer.MAX_VALUE) {
theServletResponse.setContentLength((int) blobDetails.getBytes());
}
RestfulServer server = theRequestDetails.getServer();
server.addHeadersToResponse(theServletResponse);
theServletResponse.addHeader(Constants.HEADER_CACHE_CONTROL, Constants.CACHE_CONTROL_PRIVATE);
theServletResponse.addHeader(Constants.HEADER_ETAG, '"' + blobDetails.getHash() + '"');
theServletResponse.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(blobDetails.getPublished()));
myBinaryStorageSvc.writeBlob(theResourceId, blobId, theServletResponse.getOutputStream());
theServletResponse.getOutputStream().close();
myBinaryStorageSvc.writeBlob(theResourceId, blobId, theServletResponse.getOutputStream());
} else {
IPrimitiveType<String> contentTypeDt = AttachmentUtil.getOrCreateContentType(theRequestDetails.getFhirContext(), attachment);
String contentType = contentTypeDt.getValueAsString();
contentType = StringUtils.defaultIfBlank(contentType, Constants.CT_OCTET_STREAM);
IPrimitiveType<byte[]> dataDt = AttachmentUtil.getOrCreateData(theRequestDetails.getFhirContext(), attachment);
byte[] data = dataDt.getValue();
if (data == null) {
String msg = myCtx.getLocalizer().getMessage(BinaryAccessProvider.class, "noAttachmentDataPresent", sanitizeUrlPart(theResourceId), sanitizeUrlPart(thePath));
throw new InvalidRequestException(msg);
}
theServletResponse.setStatus(200);
theServletResponse.setContentType(contentType);
theServletResponse.setContentLength(data.length);
RestfulServer server = theRequestDetails.getServer();
server.addHeadersToResponse(theServletResponse);
theServletResponse.getOutputStream().write(data);
theServletResponse.getOutputStream().close();
}
}
/**
* $binary-access-write
*/
@SuppressWarnings("unchecked")
@Operation(name = JpaConstants.OPERATION_BINARY_ACCESS_WRITE, global = true, manualRequest = true, idempotent = false)
public IBaseResource binaryAccessWrite(
@IdParam IIdType theResourceId,
@OperationParam(name = "path", min = 1, max = 1) IPrimitiveType<String> thePath,
ServletRequestDetails theRequestDetails,
HttpServletRequest theServletRequest,
HttpServletResponse theServletResponse) throws IOException {
validateResourceTypeAndPath(theResourceId, thePath);
IFhirResourceDao dao = getDaoForRequest(theResourceId);
IBaseResource resource = dao.read(theResourceId, theRequestDetails, false);
ICompositeType attachment = findAttachmentForRequest(resource, thePath, theRequestDetails);
String requestContentType = theServletRequest.getContentType();
if (isBlank(requestContentType)) {
throw new InvalidRequestException("No content-attachment supplied");
}
if (EncodingEnum.forContentTypeStrict(requestContentType) != null) {
throw new InvalidRequestException("This operation is for binary content, got: " + requestContentType);
}
long size = theServletRequest.getContentLength();
String blobId = null;
if (size > 0) {
if (myBinaryStorageSvc != null) {
if (myBinaryStorageSvc.shouldStoreBlob(size, theResourceId, requestContentType)) {
IBinaryStorageSvc.StoredDetails storedDetails = myBinaryStorageSvc.storeBlob(theResourceId, requestContentType, theRequestDetails.getInputStream());
size = storedDetails.getBytes();
blobId = storedDetails.getBlobId();
Validate.notBlank(blobId, "BinaryStorageSvc returned a null blob ID"); // should not happen
}
}
}
if (blobId == null) {
byte[] bytes = IOUtils.toByteArray(theRequestDetails.getInputStream());
size = bytes.length;
AttachmentUtil.setData(theRequestDetails.getFhirContext(), attachment, bytes);
} else {
IBaseHasExtensions attachmentHasExt = (IBaseHasExtensions) attachment;
attachmentHasExt.getExtension().removeIf(t -> JpaConstants.EXT_ATTACHMENT_EXTERNAL_BINARY_ID.equals(t.getUrl()));
AttachmentUtil.setData(myCtx, attachment, null);
IBaseExtension<?, ?> ext = attachmentHasExt.addExtension();
ext.setUrl(JpaConstants.EXT_ATTACHMENT_EXTERNAL_BINARY_ID);
IPrimitiveType<String> blobIdString = (IPrimitiveType<String>) myCtx.getElementDefinition("string").newInstance();
blobIdString.setValueAsString(blobId);
ext.setValue(blobIdString);
}
AttachmentUtil.setContentType(theRequestDetails.getFhirContext(), attachment, requestContentType);
AttachmentUtil.setSize(theRequestDetails.getFhirContext(), attachment, null);
if (size <= Integer.MAX_VALUE) {
AttachmentUtil.setSize(theRequestDetails.getFhirContext(), attachment, (int) size);
}
DaoMethodOutcome outcome = dao.update(resource, theRequestDetails);
return outcome.getResource();
}
@Nonnull
private ICompositeType findAttachmentForRequest(IBaseResource theResource, @OperationParam(name = "path", min = 1, max = 1) IPrimitiveType<String> thePath, ServletRequestDetails theRequestDetails) {
FhirContext ctx = theRequestDetails.getFhirContext();
String path = thePath.getValueAsString();
Optional<ICompositeType> type = ctx.newFluentPath().evaluateFirst(theResource, path, ICompositeType.class);
if (!type.isPresent()) {
throw new InvalidRequestException("Unable to find Attachment at path: " + sanitizeUrlPart(path));
}
BaseRuntimeElementDefinition<?> def = ctx.getElementDefinition(type.get().getClass());
if (!def.getName().equals("Attachment")) {
throw new InvalidRequestException("Path does not return an Attachment: " + sanitizeUrlPart(path));
}
return type.get();
}
private void validateResourceTypeAndPath(@IdParam IIdType theResourceId, @OperationParam(name = "path", min = 1, max = 1) IPrimitiveType<String> thePath) {
if (isBlank(theResourceId.getResourceType())) {
throw new InvalidRequestException("No resource type specified");
}
if (isBlank(theResourceId.getIdPart())) {
throw new InvalidRequestException("No ID specified");
}
if (thePath == null || isBlank(thePath.getValue())) {
throw new InvalidRequestException("No path specified");
}
}
@Nonnull
private IFhirResourceDao getDaoForRequest(@IdParam IIdType theResourceId) {
String resourceType = theResourceId.getResourceType();
IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceType);
if (dao == null) {
throw new InvalidRequestException("Unknown/unsupported resource type: " + sanitizeUrlPart(resourceType));
}
return dao;
}
}

View File

@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.provider;
*/
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
@ -38,7 +38,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
import javax.servlet.http.HttpServletRequest;
import static ca.uhn.fhir.jpa.util.JpaConstants.*;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.*;
public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResourceProvider<T> {

View File

@ -4,7 +4,7 @@ import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.subscription.ISubscriptionTriggeringSvc;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;

View File

@ -1,7 +1,7 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoComposition;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.IdParam;
@ -13,13 +13,10 @@ import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.print.attribute.standard.Severity;
import java.util.ArrayList;
import java.util.List;
/*

View File

@ -23,7 +23,7 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoConceptMap;
import ca.uhn.fhir.jpa.term.TranslationRequest;
import ca.uhn.fhir.jpa.term.TranslationResult;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import org.hl7.fhir.dstu3.model.*;
/*

View File

@ -1,7 +1,7 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.IdParam;

View File

@ -2,15 +2,13 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.ValidateUtil;
import org.hl7.fhir.dstu3.model.IdType;

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
@ -38,9 +38,9 @@ import org.hl7.fhir.instance.model.api.IIdType;
import javax.servlet.http.HttpServletRequest;
import static ca.uhn.fhir.jpa.util.JpaConstants.OPERATION_META;
import static ca.uhn.fhir.jpa.util.JpaConstants.OPERATION_META_ADD;
import static ca.uhn.fhir.jpa.util.JpaConstants.OPERATION_META_DELETE;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META_ADD;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META_DELETE;
public class JpaResourceProviderDstu3<T extends IAnyResource> extends BaseJpaResourceProvider<T> {

View File

@ -4,7 +4,7 @@ import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.server.RequestDetails;

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem.LookupCodeResult;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;

View File

@ -1,7 +1,7 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoComposition;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.IdParam;

View File

@ -23,7 +23,7 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoConceptMap;
import ca.uhn.fhir.jpa.term.TranslationRequest;
import ca.uhn.fhir.jpa.term.TranslationResult;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import org.hl7.fhir.r4.model.*;
/*

View File

@ -1,17 +1,7 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoMessageHeader;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.MessageHeader;
import javax.servlet.http.HttpServletRequest;
/*
* #%L
* HAPI FHIR JPA Server

View File

@ -1,7 +1,7 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.IdParam;

View File

@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoStructureDefinition;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
@ -35,9 +35,9 @@ import org.hl7.fhir.r4.model.*;
import javax.servlet.http.HttpServletRequest;
import static ca.uhn.fhir.jpa.util.JpaConstants.OPERATION_META;
import static ca.uhn.fhir.jpa.util.JpaConstants.OPERATION_META_ADD;
import static ca.uhn.fhir.jpa.util.JpaConstants.OPERATION_META_DELETE;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META_ADD;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META_DELETE;
public class JpaResourceProviderR4<T extends IAnyResource> extends BaseJpaResourceProvider<T> {

View File

@ -4,7 +4,7 @@ import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.server.RequestDetails;

View File

@ -21,11 +21,14 @@ package ca.uhn.fhir.jpa.term;
*/
import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
import ca.uhn.fhir.model.api.annotation.Block;
import org.hl7.fhir.r4.model.ValueSet;
import java.util.Collection;
@Block()
public class ValueSetExpansionComponentWithCodeAccumulator extends ValueSet.ValueSetExpansionComponent implements IValueSetCodeAccumulator {
@Override
public void includeCode(String theSystem, String theCode, String theDisplay) {
ValueSet.ValueSetExpansionContainsComponent contains = this.addContains();

View File

@ -0,0 +1,44 @@
package ca.uhn.fhir.jpa.util;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.hl7.fhir.dstu3.model.DateTimeType;
import java.io.IOException;
import java.util.Date;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class JsonDateDeserializer extends JsonDeserializer<Date> {
@Override
public Date deserialize(JsonParser theParser, DeserializationContext theDeserializationContext) throws IOException {
String string = theParser.getValueAsString();
if (isNotBlank(string)) {
return new DateTimeType(string).getValue();
}
return null;
}
}

View File

@ -0,0 +1,40 @@
package ca.uhn.fhir.jpa.util;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.hl7.fhir.dstu3.model.InstantType;
import java.io.IOException;
import java.util.Date;
public class JsonDateSerializer extends JsonSerializer<Date> {
@Override
public void serialize(Date theValue, JsonGenerator theGen, SerializerProvider theSerializers) throws IOException {
if (theValue != null) {
theGen.writeString(new InstantType(theValue).getValueAsString());
}
}
}

View File

@ -20,29 +20,39 @@ package ca.uhn.fhir.jpa.util;
* #L%
*/
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.hibernate.validator.constraints.Length;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.InstantType;
import javax.persistence.*;
import javax.validation.constraints.Size;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import static com.google.common.base.Ascii.toUpperCase;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class TestUtil {
public static final int MAX_COL_LENGTH = 2000;
private static final int MAX_LENGTH = 30;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestUtil.class);
private static Set<String> ourReservedWords;
/**
* non instantiable
@ -56,6 +66,16 @@ public class TestUtil {
*/
@SuppressWarnings("UnstableApiUsage")
public static void scanEntities(String packageName) throws IOException, ClassNotFoundException {
try (InputStream is = TestUtil.class.getResourceAsStream("/mysql-reserved-words.txt")) {
String contents = IOUtils.toString(is, Constants.CHARSET_UTF8);
String[] words = contents.split("\\n");
ourReservedWords = Arrays.stream(words)
.filter(t -> isNotBlank(t))
.map(t -> toUpperCase(t))
.collect(Collectors.toSet());
}
ImmutableSet<ClassInfo> classes = ClassPath.from(TestUtil.class.getClassLoader()).getTopLevelClasses(packageName);
Set<String> names = new HashSet<String>();
@ -138,7 +158,10 @@ public class TestUtil {
JoinColumn joinColumn = theAnnotatedElement.getAnnotation(JoinColumn.class);
if (joinColumn != null) {
assertNotADuplicateName(joinColumn.name(), null);
String columnName = joinColumn.name();
validateColumnName(columnName, theAnnotatedElement);
assertNotADuplicateName(columnName, null);
ForeignKey fk = joinColumn.foreignKey();
if (theIsSuperClass) {
Validate.isTrue(isBlank(fk.name()), "Foreign key on " + theAnnotatedElement.toString() + " has a name() and should not as it is a superclass");
@ -152,8 +175,47 @@ public class TestUtil {
Column column = theAnnotatedElement.getAnnotation(Column.class);
if (column != null) {
assertNotADuplicateName(column.name(), null);
String columnName = column.name();
validateColumnName(columnName, theAnnotatedElement);
assertNotADuplicateName(columnName, null);
Validate.isTrue(column.unique() == false, "Should not use unique attribute on column (use named @UniqueConstraint instead) on " + theAnnotatedElement.toString());
boolean hasLob = theAnnotatedElement.getAnnotation(Lob.class) != null;
Field field = (Field) theAnnotatedElement;
/*
* For string columns, we want to make sure that an explicit max
* length is always specified, and that this max is always sensible.
* Unfortunately there is no way to differentiate between "explicitly
* set to 255" and "just using the default of 255" so we have banned
* the exact length of 255.
*/
if (field.getType().equals(String.class)) {
if (!hasLob) {
if (column.length() == 255) {
throw new IllegalStateException("Field does not have an explicit maximum length specified: " + field);
}
if (column.length() > MAX_COL_LENGTH) {
throw new IllegalStateException("Field is too long: " + field);
}
}
Size size = theAnnotatedElement.getAnnotation(Size.class);
if (size != null) {
if (size.max() > MAX_COL_LENGTH) {
throw new IllegalStateException("Field is too long: " + field);
}
}
Length length = theAnnotatedElement.getAnnotation(Length.class);
if (length != null) {
if (length.max() > MAX_COL_LENGTH) {
throw new IllegalStateException("Field is too long: " + field);
}
}
}
}
GeneratedValue gen = theAnnotatedElement.getAnnotation(GeneratedValue.class);
@ -169,6 +231,15 @@ public class TestUtil {
}
private static void validateColumnName(String theColumnName, AnnotatedElement theElement) {
if (!theColumnName.equals(theColumnName.toUpperCase())) {
throw new IllegalArgumentException("Column name must be all upper case: " + theColumnName + " found on " + theElement);
}
if (ourReservedWords.contains(theColumnName)) {
throw new IllegalArgumentException("Column name is a reserved word: " + theColumnName + " found on " + theElement);
}
}
private static void assertEquals(String theGenerator, String theName) {
Validate.isTrue(theGenerator.equals(theName));
}
@ -209,4 +280,6 @@ public class TestUtil {
public static void sleepOneClick() {
sleepAtLeast(1);
}
}

View File

@ -0,0 +1,21 @@
package ca.uhn.fhir.jpa.binstore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.assertThat;
public class BaseBinaryStorageSvcImplTest {
private static final Logger ourLog = LoggerFactory.getLogger(BaseBinaryStorageSvcImplTest.class);
@Test
public void testNewRandomId() {
MemoryBinaryStorageSvcImpl svc = new MemoryBinaryStorageSvcImpl();
String id = svc.newRandomId();
ourLog.info(id);
assertThat(id, matchesPattern("^[a-zA-Z0-9]{100}$"));
}
}

View File

@ -0,0 +1,59 @@
package ca.uhn.fhir.jpa.binstore;
import org.apache.commons.io.FileUtils;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.IdType;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import static org.junit.Assert.*;
public class FilesystemBinaryStorageSvcImplTest {
private static final Logger ourLog = LoggerFactory.getLogger(FilesystemBinaryStorageSvcImplTest.class);
public static final byte[] SOME_BYTES = {2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1};
private File myPath;
private FilesystemBinaryStorageSvcImpl mySvc;
@Before
public void before() {
myPath = new File("./target/fstmp");
mySvc = new FilesystemBinaryStorageSvcImpl(myPath.getAbsolutePath());
}
@After
public void after() throws IOException {
FileUtils.deleteDirectory(myPath);
}
@Test
public void testStoreAndRetrieve() throws IOException {
IIdType id = new IdType("Patient/123");
String contentType = "image/png";
IBinaryStorageSvc.StoredDetails outcome = mySvc.storeBlob(id, contentType, new ByteArrayInputStream(SOME_BYTES));
ourLog.info("Got id: {}", outcome);
IBinaryStorageSvc.StoredDetails details = mySvc.fetchBlobDetails(id, outcome.getBlobId());
assertEquals(16L, details.getBytes());
assertEquals(outcome.getBlobId(), details.getBlobId());
assertEquals("image/png", details.getContentType());
assertEquals("dc7197cfab936698bef7818975c185a9b88b71a0a0a2493deea487706ddf20cb", details.getHash());
assertNotNull(details.getPublished());
ByteArrayOutputStream capture = new ByteArrayOutputStream();
mySvc.writeBlob(id, outcome.getBlobId(), capture);
assertArrayEquals(SOME_BYTES, capture.toByteArray());
}
}

View File

@ -0,0 +1,31 @@
package ca.uhn.fhir.jpa.binstore;
import org.hl7.fhir.r4.model.IdType;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
public class NullBinaryStorageSvcImplTest {
private NullBinaryStorageSvcImpl mySvc = new NullBinaryStorageSvcImpl();
@Test
public void shouldStoreBlob() {
assertFalse(mySvc.shouldStoreBlob(1, new IdType("Patient/2"), "application/json"));
}
@Test(expected = UnsupportedOperationException.class)
public void storeBlob() {
mySvc.storeBlob(null, null, null);
}
@Test(expected = UnsupportedOperationException.class)
public void fetchBlobDetails() {
mySvc.fetchBlobDetails(null, null);
}
@Test(expected = UnsupportedOperationException.class)
public void writeBlob() {
mySvc.writeBlob(null, null, null);
}
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc;
import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
@ -156,6 +158,11 @@ public class TestR4Config extends BaseJavaConfigR4 {
return requestValidator;
}
@Bean
public IBinaryStorageSvc binaryStorage() {
return new MemoryBinaryStorageSvcImpl();
}
public static int getMaxThreads() {
return ourMaxThreads;
}

View File

@ -14,7 +14,7 @@ import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.test.utilities.LoggingRule;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;

View File

@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.interceptor.PerformanceTracingLoggingInterceptor;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.provider.BinaryAccessProvider;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
@ -95,6 +96,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
@Qualifier("myAllergyIntoleranceDaoR4")
protected IFhirResourceDao<AllergyIntolerance> myAllergyIntoleranceDao;
@Autowired
protected BinaryAccessProvider myBinaryAccessProvider;
@Autowired
protected ApplicationContext myAppCtx;
@Autowired
@Qualifier("myAppointmentDaoR4")

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists;
import org.hamcrest.Matchers;

View File

@ -60,7 +60,7 @@ import com.google.common.base.Charsets;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
@ -86,7 +86,6 @@ import ca.uhn.fhir.model.dstu2.resource.Location;
import ca.uhn.fhir.model.dstu2.resource.Medication;
import ca.uhn.fhir.model.dstu2.resource.MedicationAdministration;
import ca.uhn.fhir.model.dstu2.resource.MedicationOrder;
import ca.uhn.fhir.model.dstu2.resource.MessageHeader;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Organization;

View File

@ -1,12 +1,11 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
import org.hl7.fhir.dstu3.model.Composition;
import org.hl7.fhir.dstu3.model.MessageHeader;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.TestUtil;

View File

@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;

View File

@ -1,11 +1,9 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4;
@ -101,6 +99,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
if (ourServer == null) {
ourRestServer = new RestfulServer(myFhirCtx);
ourRestServer.registerProviders(myResourceProviders.createProviders());
ourRestServer.registerProvider(myBinaryAccessProvider);
ourRestServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
ourRestServer.setDefaultResponseEncoding(EncodingEnum.XML);

View File

@ -0,0 +1,317 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.api.Constants;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.DocumentReference;
import org.hl7.fhir.r4.model.StringType;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test {
public static final byte[] SOME_BYTES = {1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1};
public static final byte[] SOME_BYTES_2 = {5, 5, 5, 6};
private static final Logger ourLog = LoggerFactory.getLogger(BinaryAccessProviderR4Test.class);
@Autowired
private MemoryBinaryStorageSvcImpl myStorageSvc;
@Override
@Before
public void before() throws Exception {
super.before();
myStorageSvc.setMinSize(10);
}
@Override
@After
public void after() throws Exception {
super.after();
myStorageSvc.setMinSize(0);
}
@Test
public void testRead() throws IOException {
IIdType id = createDocumentReference(true);
IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class);
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESHOW_RESOURCES, interceptor);
doAnswer(t -> {
Pointcut pointcut = t.getArgument(0, Pointcut.class);
HookParams params = t.getArgument(1, HookParams.class);
ourLog.info("Interceptor invoked with pointcut {} and params {}", pointcut, params);
return null;
}).when(interceptor).invoke(any(), any());
// Read it back using the operation
String path = ourServerBase +
"/DocumentReference/" + id.getIdPart() + "/" +
JpaConstants.OPERATION_BINARY_ACCESS_READ +
"?path=DocumentReference.content.attachment";
HttpGet get = new HttpGet(path);
try (CloseableHttpResponse resp = ourHttpClient.execute(get)) {
assertEquals(200, resp.getStatusLine().getStatusCode());
assertEquals("image/png", resp.getEntity().getContentType().getValue());
assertEquals(SOME_BYTES.length, resp.getEntity().getContentLength());
byte[] actualBytes = IOUtils.toByteArray(resp.getEntity().getContent());
assertArrayEquals(SOME_BYTES, actualBytes);
}
verify(interceptor, times(1)).invoke(eq(Pointcut.STORAGE_PRESHOW_RESOURCES), any());
}
@Test
public void testReadSecondInstance() throws IOException {
IIdType id = createDocumentReference(true);
IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class);
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESHOW_RESOURCES, interceptor);
// Read it back using the operation
String path = ourServerBase +
"/DocumentReference/" + id.getIdPart() + "/" +
JpaConstants.OPERATION_BINARY_ACCESS_READ +
"?path=DocumentReference.content[1].attachment";
HttpGet get = new HttpGet(path);
try (CloseableHttpResponse resp = ourHttpClient.execute(get)) {
assertEquals(200, resp.getStatusLine().getStatusCode());
assertEquals("image/gif", resp.getEntity().getContentType().getValue());
assertEquals(SOME_BYTES_2.length, resp.getEntity().getContentLength());
byte[] actualBytes = IOUtils.toByteArray(resp.getEntity().getContent());
assertArrayEquals(SOME_BYTES_2, actualBytes);
}
verify(interceptor, times(1)).invoke(eq(Pointcut.STORAGE_PRESHOW_RESOURCES), any());
}
@Test
public void testReadNoPath() throws IOException {
IIdType id = createDocumentReference(true);
String path = ourServerBase +
"/DocumentReference/" + id.getIdPart() + "/" +
JpaConstants.OPERATION_BINARY_ACCESS_READ;
HttpGet get = new HttpGet(path);
try (CloseableHttpResponse resp = ourHttpClient.execute(get)) {
assertEquals(400, resp.getStatusLine().getStatusCode());
String response = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8);
assertThat(response, containsString("No path specified"));
}
}
@Test
public void testReadNoData() throws IOException {
IIdType id = createDocumentReference(false);
String path = ourServerBase +
"/DocumentReference/" + id.getIdPart() + "/" +
JpaConstants.OPERATION_BINARY_ACCESS_READ +
"?path=DocumentReference.content.attachment";
HttpGet get = new HttpGet(path);
try (CloseableHttpResponse resp = ourHttpClient.execute(get)) {
assertEquals(400, resp.getStatusLine().getStatusCode());
String response = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8);
assertThat(response, matchesPattern(".*The resource with ID DocumentReference/[0-9]+ has no data at path.*"));
}
}
@Test
public void testReadUnknownBlobId() throws IOException {
IIdType id = createDocumentReference(false);
DocumentReference dr = ourClient.read().resource(DocumentReference.class).withId(id).execute();
dr.getContentFirstRep()
.getAttachment()
.addExtension(JpaConstants.EXT_ATTACHMENT_EXTERNAL_BINARY_ID, new StringType("AAAAA"));
ourClient.update().resource(dr).execute();
String path = ourServerBase +
"/DocumentReference/" + id.getIdPart() + "/" +
JpaConstants.OPERATION_BINARY_ACCESS_READ +
"?path=DocumentReference.content.attachment";
HttpGet get = new HttpGet(path);
try (CloseableHttpResponse resp = ourHttpClient.execute(get)) {
assertEquals(400, resp.getStatusLine().getStatusCode());
String response = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8);
assertThat(response, matchesPattern(".*Can not find the requested binary content. It may have been deleted.*"));
}
}
/**
* Stores a binary large enough that it should live in binary storage
*/
@Test
public void testWriteLarge() throws IOException {
IIdType id = createDocumentReference(false);
IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class);
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESHOW_RESOURCES, interceptor);
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, interceptor);
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, interceptor);
// Read it back using the operation
String path = ourServerBase +
"/DocumentReference/" + id.getIdPart() + "/" +
JpaConstants.OPERATION_BINARY_ACCESS_WRITE +
"?path=DocumentReference.content.attachment";
HttpPost post = new HttpPost(path);
post.setEntity(new ByteArrayEntity(SOME_BYTES, ContentType.IMAGE_JPEG));
post.addHeader("Accept", "application/fhir+json; _pretty=true");
String attachmentId;
try (CloseableHttpResponse resp = ourHttpClient.execute(post)) {
assertEquals(200, resp.getStatusLine().getStatusCode());
assertThat(resp.getEntity().getContentType().getValue(), containsString("application/fhir+json"));
String response = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8);
ourLog.info("Response: {}", response);
DocumentReference ref = myFhirCtx.newJsonParser().parseResource(DocumentReference.class, response);
Attachment attachment = ref.getContentFirstRep().getAttachment();
assertEquals(ContentType.IMAGE_JPEG.getMimeType(), attachment.getContentType());
assertEquals(15, attachment.getSize());
assertEquals(null, attachment.getData());
assertEquals("2", ref.getMeta().getVersionId());
attachmentId = attachment.getExtensionString(JpaConstants.EXT_ATTACHMENT_EXTERNAL_BINARY_ID);
assertThat(attachmentId, matchesPattern("[a-zA-Z0-9]{100}"));
}
verify(interceptor, timeout(5000).times(1)).invoke(eq(Pointcut.STORAGE_PRESHOW_RESOURCES), any());
verify(interceptor, timeout(5000).times(1)).invoke(eq(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED), any());
verifyNoMoreInteractions(interceptor);
// Read it back using the operation
path = ourServerBase +
"/DocumentReference/" + id.getIdPart() + "/" +
JpaConstants.OPERATION_BINARY_ACCESS_READ +
"?path=DocumentReference.content.attachment";
HttpGet get = new HttpGet(path);
try (CloseableHttpResponse resp = ourHttpClient.execute(get)) {
assertEquals(200, resp.getStatusLine().getStatusCode());
assertEquals("image/jpeg", resp.getEntity().getContentType().getValue());
assertEquals(SOME_BYTES.length, resp.getEntity().getContentLength());
byte[] actualBytes = IOUtils.toByteArray(resp.getEntity().getContent());
assertArrayEquals(SOME_BYTES, actualBytes);
}
}
/**
* Stores a binary small enough that it shouldn't live in binary storage
*/
@Test
public void testWriteSmall() throws IOException {
IIdType id = createDocumentReference(false);
IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class);
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESHOW_RESOURCES, interceptor);
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, interceptor);
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, interceptor);
// Read it back using the operation
String path = ourServerBase +
"/DocumentReference/" + id.getIdPart() + "/" +
JpaConstants.OPERATION_BINARY_ACCESS_WRITE +
"?path=DocumentReference.content.attachment";
HttpPost post = new HttpPost(path);
post.setEntity(new ByteArrayEntity(SOME_BYTES_2, ContentType.IMAGE_JPEG));
post.addHeader("Accept", "application/fhir+json; _pretty=true");
String attachmentId;
try (CloseableHttpResponse resp = ourHttpClient.execute(post)) {
assertEquals(200, resp.getStatusLine().getStatusCode());
assertThat(resp.getEntity().getContentType().getValue(), containsString("application/fhir+json"));
String response = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8);
ourLog.info("Response: {}", response);
DocumentReference ref = myFhirCtx.newJsonParser().parseResource(DocumentReference.class, response);
Attachment attachment = ref.getContentFirstRep().getAttachment();
assertEquals(ContentType.IMAGE_JPEG.getMimeType(), attachment.getContentType());
assertEquals(4, attachment.getSize());
assertArrayEquals(SOME_BYTES_2, attachment.getData());
assertEquals("2", ref.getMeta().getVersionId());
attachmentId = attachment.getExtensionString(JpaConstants.EXT_ATTACHMENT_EXTERNAL_BINARY_ID);
assertEquals(null, attachmentId);
}
verify(interceptor, timeout(5000).times(1)).invoke(eq(Pointcut.STORAGE_PRESHOW_RESOURCES), any());
verify(interceptor, timeout(5000).times(1)).invoke(eq(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED), any());
verifyNoMoreInteractions(interceptor);
}
private IIdType createDocumentReference(boolean theSetData) {
DocumentReference documentReference = new DocumentReference();
Attachment attachment = documentReference
.addContent()
.getAttachment()
.setContentType("image/png");
if (theSetData) {
attachment.setData(SOME_BYTES);
}
attachment = documentReference
.addContent()
.getAttachment()
.setContentType("image/gif");
if (theSetData) {
attachment.setData(SOME_BYTES_2);
}
return ourClient.create().resource(documentReference).execute().getId().toUnqualifiedVersionless();
}
}

View File

@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;

View File

@ -1,9 +1,8 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.r4.model.MessageHeader;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleType;

View File

@ -1,7 +1,7 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.TestUtil;

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.TestUtil;

View File

@ -5,8 +5,7 @@ import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.InstantDt;
@ -15,7 +14,6 @@ import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
@ -72,7 +70,6 @@ import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast;
import static ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hamcrest.Matchers.*;

View File

@ -1,11 +1,10 @@
package ca.uhn.fhir.jpa.subscription.email;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import com.google.common.collect.Lists;
import com.icegreen.greenmail.store.FolderException;
import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.ServerSetup;
@ -155,10 +154,10 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
Assert.assertNotNull(subscriptionTemp);
subscriptionTemp.getChannel().addExtension()
.setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
.setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
.setValue(new StringType("mailto:myfrom@from.com"));
subscriptionTemp.getChannel().addExtension()
.setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
.setUrl(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
.setValue(new StringType("This is a subject"));
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless());
@ -201,10 +200,10 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
Subscription subscriptionTemp = ourClient.read(Subscription.class, sub1.getId());
Assert.assertNotNull(subscriptionTemp);
subscriptionTemp.getChannel().addExtension()
.setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
.setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
.setValue(new StringType("myfrom@from.com"));
subscriptionTemp.getChannel().addExtension()
.setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
.setUrl(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
.setValue(new StringType("This is a subject"));
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless());

View File

@ -3,10 +3,10 @@ package ca.uhn.fhir.jpa.subscription.resthook;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
import ca.uhn.fhir.jpa.subscription.NotificationServlet;
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
import ca.uhn.fhir.jpa.subscription.module.interceptor.SubscriptionDebugLogInterceptor;
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy;
import ca.uhn.fhir.rest.annotation.Create;
@ -158,7 +158,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
String databaseCriteria = "Observation?code=17861-6&context.type=IHD";
Subscription subscription = createSubscription(databaseCriteria, null, ourNotificationListenerServer);
List<Coding> tag = subscription.getMeta().getTag();
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.get(0).getSystem());
assertEquals(JpaConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.get(0).getSystem());
assertEquals(SubscriptionMatchingStrategy.DATABASE.toString(), tag.get(0).getCode());
}
@ -168,7 +168,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
Subscription subscription = createSubscription(inMemoryCriteria, null, ourNotificationListenerServer);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(subscription));
List<Coding> tag = subscription.getMeta().getTag();
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.get(0).getSystem());
assertEquals(JpaConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.get(0).getSystem());
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.get(0).getCode());
}
@ -468,7 +468,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
List<Coding> tags = subscriptionOrig.getMeta().getTag();
assertEquals(1, tags.size());
Coding tag = tags.get(0);
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
assertEquals(JpaConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.getCode());
assertEquals("In-memory", tag.getDisplay());
@ -480,7 +480,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
tags = subscriptionActivated.getMeta().getTag();
assertEquals(1, tags.size());
tag = tags.get(0);
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
assertEquals(JpaConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.getCode());
assertEquals("In-memory", tag.getDisplay());
}
@ -495,7 +495,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
List<Coding> tags = subscriptionOrig.getMeta().getTag();
assertEquals(1, tags.size());
Coding tag = tags.get(0);
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
assertEquals(JpaConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
assertEquals(SubscriptionMatchingStrategy.DATABASE.toString(), tag.getCode());
assertEquals("Database", tag.getDisplay());
@ -507,7 +507,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
tags = subscription.getMeta().getTag();
assertEquals(1, tags.size());
tag = tags.get(0);
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
assertEquals(JpaConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
assertEquals(SubscriptionMatchingStrategy.DATABASE.toString(), tag.getCode());
assertEquals("Database", tag.getDisplay());
}

View File

@ -1,13 +1,11 @@
package ca.uhn.fhir.jpa.subscription.resthook;
import ca.uhn.fhir.jpa.config.StoppableSubscriptionDeliveringRestHookSubscriber;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionsR4Test;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.r4.model.*;
@ -302,7 +300,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
subscription1
.getChannel()
.addExtension(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS, new BooleanType("true"));
.addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS, new BooleanType("true"));
ourLog.info("** About to update subscription");
int modCount = myCountingInterceptor.getSentCount();
@ -378,7 +376,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
Subscription subscription = newSubscription(criteria1, payload);
subscription
.getChannel()
.addExtension(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION, new BooleanType("true"));
.addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION, new BooleanType("true"));
ourClient.create().resource(subscription).execute();
waitForActivatedSubscriptionCount(1);

View File

@ -6,7 +6,7 @@ import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.jpa.subscription.SubscriptionTriggeringSvcImpl;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;

View File

@ -33,6 +33,7 @@ public class RenameColumnTask extends BaseTableTask<RenameColumnTask> {
private static final Logger ourLog = LoggerFactory.getLogger(RenameColumnTask.class);
private String myOldName;
private String myNewName;
private boolean myAllowNeitherColumnToExist;
public void setOldName(String theOldName) {
Validate.notBlank(theOldName);
@ -53,6 +54,9 @@ public class RenameColumnTask extends BaseTableTask<RenameColumnTask> {
throw new SQLException("Can not rename " + getTableName() + "." + myOldName + " to " + myNewName + " because both columns exist!");
}
if (!haveOldName && !haveNewName) {
if (isAllowNeitherColumnToExist()) {
return;
}
throw new SQLException("Can not rename " + getTableName() + "." + myOldName + " to " + myNewName + " because neither column exists!");
}
if (haveNewName) {
@ -89,4 +93,12 @@ public class RenameColumnTask extends BaseTableTask<RenameColumnTask> {
executeSql(getTableName(), sql);
}
public void setAllowNeitherColumnToExist(boolean theAllowNeitherColumnToExist) {
myAllowNeitherColumnToExist = theAllowNeitherColumnToExist;
}
public boolean isAllowNeitherColumnToExist() {
return myAllowNeitherColumnToExist;
}
}

View File

@ -80,6 +80,16 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.renameColumn("mySystemVersion", "SYSTEM_VERSION")
.renameColumn("myValueSet", "VALUESET_URL");
version.onTable("TRM_VALUESET")
.renameColumn("NAME", "VSNAME", true);
version.onTable("TRM_VALUESET_CONCEPT")
.renameColumn("CODE", "CODEVAL", true)
.renameColumn("SYSTEM", "SYSTEM_URL", true);
version.onTable("TRM_CONCEPT")
.renameColumn("CODE", "CODEVAL");
// TermValueSet
version.startSectionWithMessage("Processing table: TRM_VALUESET");
version.addIdGenerator("SEQ_VALUESET_PID");

View File

@ -168,10 +168,15 @@ public class BaseMigrationTasks<T extends Enum> {
}
public BuilderWithTableName renameColumn(String theOldName, String theNewName) {
return renameColumn(theOldName, theNewName, false);
}
public BuilderWithTableName renameColumn(String theOldName, String theNewName, boolean theAllowNeitherColumnToExist) {
RenameColumnTask task = new RenameColumnTask();
task.setTableName(myTableName);
task.setOldName(theOldName);
task.setNewName(theNewName);
task.setAllowNeitherColumnToExist(theAllowNeitherColumnToExist);
addTask(task);
return this;
}

View File

@ -45,7 +45,7 @@ public class RenameColumnTaskTest extends BaseTest {
}
@Test
public void testNeitherColumnExists() throws SQLException {
public void testNeitherColumnExists() {
executeSql("create table SOMETABLE (PID bigint not null)");
RenameColumnTask task = new RenameColumnTask();
@ -65,7 +65,21 @@ public class RenameColumnTaskTest extends BaseTest {
}
@Test
public void testBothColumnsExist() throws SQLException {
public void testNeitherColumnExistsButAllowed() {
executeSql("create table SOMETABLE (PID bigint not null)");
RenameColumnTask task = new RenameColumnTask();
task.setTableName("SOMETABLE");
task.setOldName("myTextCol");
task.setNewName("TEXTCOL");
task.setAllowNeitherColumnToExist(true);
getMigrator().addTask(task);
getMigrator().migrate();
}
@Test
public void testBothColumnsExist() {
executeSql("create table SOMETABLE (PID bigint not null, PID2 bigint)");
RenameColumnTask task = new RenameColumnTask();

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.model.entity;
*/
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import com.google.common.hash.HashCode;
@ -64,7 +65,7 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
private Long myResourcePid;
@Field()
@Column(name = "RES_TYPE", nullable = false)
@Column(name = "RES_TYPE", nullable = false, length = Constants.MAX_RESOURCE_NAME_LENGTH)
private String myResourceType;
@Field()

View File

@ -47,12 +47,13 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
/*
* Note that MYSQL chokes on unique indexes for lengths > 255 so be careful here
*/
public static final int MAX_LENGTH = 255;
public static final int MAX_LENGTH = 254;
private static final long serialVersionUID = 1L;
@Column(name = "SP_URI", nullable = true, length = MAX_LENGTH)
@Field()
public String myUri;
@Id
@SequenceGenerator(name = "SEQ_SPIDX_URI", sequenceName = "SEQ_SPIDX_URI")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_SPIDX_URI")

View File

@ -1,8 +1,8 @@
package ca.uhn.fhir.jpa.util;
package ca.uhn.fhir.jpa.model.util;
/*-
* #%L
* HAPI FHIR JPA Server
* HAPI FHIR Model
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
@ -147,4 +147,69 @@ public class JpaConstants {
* Operation name for the "$snapshot" operation
*/
public static final String OPERATION_SNAPSHOT = "$snapshot";
/**
* Operation name for the "$binary-access" operation
*/
public static final String OPERATION_BINARY_ACCESS_READ = "$binary-access-read";
/**
* Operation name for the "$binary-access" operation
*/
public static final String OPERATION_BINARY_ACCESS_WRITE = "$binary-access-write";
/**
* <p>
* This extension should be of type <code>string</code> and should be
* placed on the <code>Subscription.channel</code> element
* </p>
*/
public static final String EXT_SUBSCRIPTION_SUBJECT_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-subject-template";
/**
* This extension URL indicates whether a REST HOOK delivery should
* include the version ID when delivering.
* <p>
* This extension should be of type <code>boolean</code> and should be
* placed on the <code>Subscription.channel</code> element.
* </p>
*/
public static final String EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids";
/**
* This extension URL indicates whether a REST HOOK delivery should
* reload the resource and deliver the latest version always. This
* could be useful for example if a resource which triggers a
* subscription gets updated many times in short succession and there
* is no value in delivering the older versions.
* <p>
* Note that if the resource is now deleted, this may cause
* the delivery to be cancelled altogether.
* </p>
*
* <p>
* This extension should be of type <code>boolean</code> and should be
* placed on the <code>Subscription.channel</code> element.
* </p>
*/
public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version";
/**
* Indicate which strategy will be used to match this subscription
*/
public static final String EXT_SUBSCRIPTION_MATCHING_STRATEGY = "http://hapifhir.io/fhir/StructureDefinition/subscription-matching-strategy";
/**
* <p>
* This extension should be of type <code>string</code> and should be
* placed on the <code>Subscription.channel</code> element
* </p>
*/
public static final String EXT_SUBSCRIPTION_EMAIL_FROM = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-from";
/**
* Extension ID for external binary references
*/
public static final String EXT_ATTACHMENT_EXTERNAL_BINARY_ID = "http://hapifhir.io/fhir/StructureDefinition/attachment-external-binary-id";
}

View File

@ -61,7 +61,11 @@ public class Retrier<T> {
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
super.onError(context, callback, throwable);
ourLog.error("Retry failure {}/{}: {}", context.getRetryCount(), theMaxRetries, throwable.getMessage());
if (throwable instanceof NullPointerException) {
ourLog.error("Retry failure {}/{}: {}", context.getRetryCount(), theMaxRetries, throwable.getMessage(), throwable);
} else {
ourLog.error("Retry failure {}/{}: {}", context.getRetryCount(), theMaxRetries, throwable.getMessage());
}
}
};
myRetryTemplate.registerListener(listener);

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.subscription.module.cache;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType;
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy;
@ -111,8 +112,8 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
String subjectTemplate;
try {
from = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
subjectTemplate = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
} catch (FHIRException theE) {
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
}
@ -125,8 +126,8 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
String stripVersionIds;
String deliverLatestVersion;
try {
stripVersionIds = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
deliverLatestVersion = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
} catch (FHIRException theE) {
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
}
@ -198,8 +199,8 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
String from;
String subjectTemplate;
try {
from = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
subjectTemplate = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
} catch (FHIRException theE) {
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
}
@ -211,8 +212,8 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
String stripVersionIds;
String deliverLatestVersion;
try {
stripVersionIds = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
deliverLatestVersion = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
} catch (FHIRException theE) {
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
}
@ -259,7 +260,7 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
} else {
throw new IllegalStateException("Unknown " + SubscriptionMatchingStrategy.class.getSimpleName() + ": " + theStrategy);
}
meta.addTag().setSystem(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY).setCode(value).setDisplay(display);
meta.addTag().setSystem(JpaConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY).setCode(value).setDisplay(display);
}
public String getSubscriptionStatus(IBaseResource theSubscription) {

View File

@ -24,57 +24,6 @@ import org.hl7.fhir.dstu2.model.Subscription;
public class SubscriptionConstants {
/**
* <p>
* This extension should be of type <code>string</code> and should be
* placed on the <code>Subscription.channel</code> element
* </p>
*/
public static final String EXT_SUBSCRIPTION_EMAIL_FROM = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-from";
/**
* <p>
* This extension should be of type <code>string</code> and should be
* placed on the <code>Subscription.channel</code> element
* </p>
*/
public static final String EXT_SUBSCRIPTION_SUBJECT_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-subject-template";
/**
* This extension URL indicates whether a REST HOOK delivery should
* include the version ID when delivering.
* <p>
* This extension should be of type <code>boolean</code> and should be
* placed on the <code>Subscription.channel</code> element.
* </p>
*/
public static final String EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids";
/**
* This extension URL indicates whether a REST HOOK delivery should
* reload the resource and deliver the latest version always. This
* could be useful for example if a resource which triggers a
* subscription gets updated many times in short succession and there
* is no value in delivering the older versions.
* <p>
* Note that if the resource is now deleted, this may cause
* the delivery to be cancelled altogether.
* </p>
*
* <p>
* This extension should be of type <code>boolean</code> and should be
* placed on the <code>Subscription.channel</code> element.
* </p>
*/
public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version";
/**
* Indicate which strategy will be used to match this subscription
*/
public static final String EXT_SUBSCRIPTION_MATCHING_STRATEGY = "http://hapifhir.io/fhir/StructureDefinition/subscription-matching-strategy";
/**
* The number of threads used in subscription channel processing

View File

@ -2,7 +2,7 @@ package ca.uhn.fhirtest.interceptor;
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProvider;
import ca.uhn.fhir.jpa.provider.BaseTerminologyUploaderProvider;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;

View File

@ -1048,7 +1048,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
/*
* If it's a 410 Gone, we want to include a location header inthe response
* If it's a 410 Gone, we want to include a location header in the response
* if we can, since that can include the resource version which is nice
* for the user.
*/

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.server.interceptor;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
@ -43,7 +44,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* interceptor may be configured to run any validator modules, and will then add headers to the response or fail the
* request with an {@link UnprocessableEntityException HTTP 422 Unprocessable Entity}.
*/
abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
@Interceptor
abstract class BaseValidatingInterceptor<T> {
/**
* Default value:<br/>

View File

@ -27,6 +27,8 @@ import java.nio.charset.Charset;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -67,7 +69,7 @@ public class RequestValidatingInterceptor extends BaseValidatingInterceptor<Stri
return theValidator.validateWithResult(theRequest);
}
@Override
@Hook(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED)
public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequestDetails);
if (encoding == null) {
@ -102,7 +104,7 @@ public class RequestValidatingInterceptor extends BaseValidatingInterceptor<Stri
return myAddValidationResultsToResponseOperationOutcome;
}
@Override
@Hook(Pointcut.SERVER_OUTGOING_RESPONSE)
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
if (myAddValidationResultsToResponseOperationOutcome) {
if (theResponseObject instanceof IBaseOperationOutcome) {

View File

@ -23,6 +23,8 @@ package ca.uhn.fhir.rest.server.interceptor;
import java.util.HashSet;
import java.util.Set;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -54,7 +56,7 @@ public class ResponseValidatingInterceptor extends BaseValidatingInterceptor<IBa
public void addExcludeOperationType(RestOperationTypeEnum theOperationType) {
Validate.notNull(theOperationType, "theOperationType must not be null");
if (myExcludeOperationTypes == null) {
myExcludeOperationTypes = new HashSet<RestOperationTypeEnum>();
myExcludeOperationTypes = new HashSet<>();
}
myExcludeOperationTypes.add(theOperationType);
}
@ -64,7 +66,7 @@ public class ResponseValidatingInterceptor extends BaseValidatingInterceptor<IBa
return theValidator.validateWithResult(theRequest);
}
@Override
@Hook(Pointcut.SERVER_OUTGOING_RESPONSE)
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
RestOperationTypeEnum operationType = theRequestDetails.getRestOperationType();
if (operationType != null && myExcludeOperationTypes != null && myExcludeOperationTypes.contains(operationType)) {

View File

@ -300,8 +300,12 @@ public class ConsentInterceptor {
}
private boolean isRequestAuthorized(RequestDetails theRequestDetails) {
Object authorizedObj = theRequestDetails.getUserData().get(myRequestAuthorizedKey);
return Boolean.TRUE.equals(authorizedObj);
boolean retVal = false;
if (theRequestDetails != null) {
Object authorizedObj = theRequestDetails.getUserData().get(myRequestAuthorizedKey);
retVal = Boolean.TRUE.equals(authorizedObj);
}
return retVal;
}
@SuppressWarnings("unchecked")

View File

@ -89,6 +89,8 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
myMethodReturnType = MethodReturnTypeEnum.BUNDLE_PROVIDER;
} else if (MethodOutcome.class.isAssignableFrom(methodReturnType)) {
myMethodReturnType = MethodReturnTypeEnum.METHOD_OUTCOME;
} else if (void.class.equals(methodReturnType)) {
myMethodReturnType = MethodReturnTypeEnum.VOID;
} else {
throw new ConfigurationException(
"Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
@ -238,9 +240,10 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
public IBaseResource doInvokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) {
Object[] params = createMethodParams(theRequest);
Object resultObj = invokeServer(theServer, theRequest, params);
if (resultObj == null) {
return null;
}
Integer count = RestfulServerUtils.extractCountParameter(theRequest);
@ -375,6 +378,9 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
IBaseResource response = doInvokeServer(theServer, theRequest);
if (response == null) {
return null;
}
Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequest);
@ -407,6 +413,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
BUNDLE_RESOURCE,
LIST_OF_RESOURCES,
METHOD_OUTCOME,
VOID,
RESOURCE
}

View File

@ -61,16 +61,19 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
private final String myName;
private final RestOperationTypeEnum myOtherOperationType;
private final ReturnTypeEnum myReturnType;
private boolean myGlobal;
private BundleTypeEnum myBundleType;
private boolean myCanOperateAtInstanceLevel;
private boolean myCanOperateAtServerLevel;
private boolean myCanOperateAtTypeLevel;
private String myDescription;
private List<ReturnType> myReturnParams;
private boolean myManualRequestMode;
private boolean myManualResponseMode;
protected OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType,
OperationParam[] theReturnParams, BundleTypeEnum theBundleType) {
boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType,
OperationParam[] theReturnParams, BundleTypeEnum theBundleType) {
super(theReturnResourceType, theMethod, theContext, theProvider);
myBundleType = theBundleType;
@ -89,7 +92,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
if (isBlank(theOperationName)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' on type " + theMethod.getDeclaringClass().getName() + " is annotated with @" + Operation.class.getSimpleName()
+ " but this annotation has no name defined");
+ " but this annotation has no name defined");
}
if (theOperationName.startsWith("$") == false) {
theOperationName = "$" + theOperationName;
@ -99,10 +102,10 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
if (theReturnTypeFromRp != null) {
setResourceName(theContext.getResourceDefinition(theReturnTypeFromRp).getName());
} else if (Modifier.isAbstract(theOperationType.getModifiers()) == false) {
setResourceName(theContext.getResourceDefinition(theOperationType).getName());
} else {
setResourceName(null);
}
setResourceName(theContext.getResourceDefinition(theOperationType).getName());
} else {
setResourceName(null);
}
if (theMethod.getReturnType().equals(IBundleProvider.class)) {
myReturnType = ReturnTypeEnum.BUNDLE;
@ -110,24 +113,24 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
myReturnType = ReturnTypeEnum.RESOURCE;
}
myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
if (getResourceName() == null) {
myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER;
myCanOperateAtServerLevel = true;
if (myIdParamIndex != null) {
myCanOperateAtInstanceLevel = true;
}
myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER;
myCanOperateAtServerLevel = true;
if (myIdParamIndex != null) {
myCanOperateAtInstanceLevel = true;
}
} else if (myIdParamIndex == null) {
myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_TYPE;
myCanOperateAtTypeLevel = true;
myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_TYPE;
myCanOperateAtTypeLevel = true;
} else {
myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
myCanOperateAtInstanceLevel = true;
for (Annotation next : theMethod.getParameterAnnotations()[myIdParamIndex]) {
if (next instanceof IdParam) {
myCanOperateAtTypeLevel = ((IdParam) next).optional() == true;
}
}
myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
myCanOperateAtInstanceLevel = true;
for (Annotation next : theMethod.getParameterAnnotations()[myIdParamIndex]) {
if (next instanceof IdParam) {
myCanOperateAtTypeLevel = ((IdParam) next).optional() == true;
}
}
}
myReturnParams = new ArrayList<>();
@ -151,10 +154,23 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
}
}
/**
* Constructor - This is the constructor that is called when binding a
* standard @Operation method.
*/
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
Operation theAnnotation) {
Operation theAnnotation) {
this(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, theAnnotation.idempotent(), theAnnotation.name(), theAnnotation.type(), theAnnotation.returnParameters(),
theAnnotation.bundleType());
theAnnotation.bundleType());
myManualRequestMode = theAnnotation.manualRequest();
myManualResponseMode = theAnnotation.manualResponse();
myGlobal = theAnnotation.global();
}
@Override
public boolean isGlobalMethod() {
return myGlobal;
}
public String getDescription() {
@ -206,7 +222,9 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
if (getResourceName() == null) {
if (isNotBlank(theRequest.getResourceName())) {
return false;
if (!isGlobalMethod()) {
return false;
}
}
}
@ -222,12 +240,12 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
boolean requestHasId = theRequest.getId() != null;
if (requestHasId) {
return myCanOperateAtInstanceLevel;
}
if (isNotBlank(theRequest.getResourceName())) {
return myCanOperateAtTypeLevel;
}
return myCanOperateAtServerLevel;
return myCanOperateAtInstanceLevel;
}
if (isNotBlank(theRequest.getResourceName())) {
return myCanOperateAtTypeLevel;
}
return myCanOperateAtServerLevel;
}
@Override
@ -240,6 +258,12 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
}
}
if (myGlobal && theRequestDetails.getId() != null && theRequestDetails.getId().hasIdPart()) {
retVal = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
} else if (myGlobal && isNotBlank(theRequestDetails.getResourceName())) {
retVal = RestOperationTypeEnum.EXTENDED_OPERATION_TYPE;
}
return retVal;
}
@ -256,7 +280,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
@Override
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
if (theRequest.getRequestType() == RequestTypeEnum.POST) {
if (theRequest.getRequestType() == RequestTypeEnum.POST && !myManualRequestMode) {
IBaseResource requestContents = ResourceParameter.loadResourceFromRequest(theRequest, this, null);
theRequest.getUserData().put(OperationParameter.REQUEST_CONTENTS_USERDATA_KEY, requestContents);
}
@ -286,6 +310,10 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
}
Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
if (myManualResponseMode) {
return null;
}
IBundleProvider retVal = toResourceList(response);
return retVal;
}
@ -316,6 +344,10 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
}
}
public boolean isManualRequestMode() {
return myManualRequestMode;
}
public static class ReturnType {
private int myMax;
private int myMin;

View File

@ -210,7 +210,9 @@ public class OperationParameter implements IParameter {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
List<Object> matchingParamValues = new ArrayList<Object>();
if (theRequest.getRequestType() == RequestTypeEnum.GET) {
OperationMethodBinding method = (OperationMethodBinding) theMethodBinding;
if (theRequest.getRequestType() == RequestTypeEnum.GET || method.isManualRequestMode()) {
translateQueryParametersIntoServerArgumentForGet(theRequest, matchingParamValues);
} else {
translateQueryParametersIntoServerArgumentForPost(theRequest, matchingParamValues);

View File

@ -7,9 +7,14 @@ import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
@ -17,6 +22,7 @@ import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
@ -26,6 +32,8 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.OperationDefinition.OperationParameterUse;
import org.junit.AfterClass;
@ -33,16 +41,18 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import ca.uhn.fhir.test.utilities.JettyUtil;
public class OperationServerR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationServerR4Test.class);
private static final String TEXT_HTML = "text/html";
@ -57,6 +67,7 @@ public class OperationServerR4Test {
private static UnsignedIntType ourLastParamUnsignedInt1;
private static int ourPort;
private static Server ourServer;
private static IBaseResource ourNextResponse;
private IGenericClient myFhirClient;
@Before
@ -69,11 +80,11 @@ public class OperationServerR4Test {
ourLastId = null;
ourLastMethod = "";
ourNextResponse = null;
ourLastRestOperation = null;
myFhirClient = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
}
@Test
public void testConformance() {
LoggingInterceptor loggingInterceptor = new LoggingInterceptor();
@ -150,7 +161,7 @@ public class OperationServerR4Test {
bundle.addEntry().setResource(patient);
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient/$OP_TYPE_RETURNING_BUNDLE"
+ "?_pretty=true&_elements=identifier");
+ "?_pretty=true&_elements=identifier");
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
assertEquals(200, status.getStatusLine().getStatusCode());
@ -165,6 +176,24 @@ public class OperationServerR4Test {
}
@Test
public void testManualResponseWithPrimitiveParam() throws Exception {
// Try with a GET
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$manualResponseWithPrimitiveParam?path=THIS_IS_A_PATH");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
}
assertEquals("$manualResponseWithPrimitiveParam", ourLastMethod);
assertEquals("Patient/123", ourLastId.toUnqualifiedVersionless().getValue());
assertEquals("THIS_IS_A_PATH", ourLastParam1.getValue());
}
@Test
public void testInstanceEverythingGet() throws Exception {
@ -181,6 +210,23 @@ public class OperationServerR4Test {
}
@Test
public void testInstanceOnPlainProvider() throws Exception {
// Try with a GET
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$OP_PLAIN_PROVIDER_ON_INSTANCE");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
assertThat(response, startsWith("<Bundle"));
}
assertEquals("$OP_PLAIN_PROVIDER_ON_INSTANCE", ourLastMethod);
assertEquals("Patient/123", ourLastId.toUnqualifiedVersionless().getValue());
assertEquals(RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE, ourLastRestOperation);
}
@Test
public void testInstanceEverythingHapiClient() {
ourCtx.newRestfulGenericClient("http://localhost:" + ourPort).operation().onInstance(new IdType("Patient/123")).named("$everything").withParameters(new Parameters()).execute();
@ -226,6 +272,46 @@ public class OperationServerR4Test {
}
@Test
public void testManualInputAndOutput() throws Exception {
byte[] bytes = new byte[]{1,2,3,4,5,6,7,8,7,6,5,4,3,2,1};
ContentType contentType = ContentType.IMAGE_PNG;
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$manualInputAndOutput");
httpPost.setEntity(new ByteArrayEntity(bytes, contentType));
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
String receivedContentType = status.getEntity().getContentType().getValue();
byte[] receivedBytes = IOUtils.toByteArray(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(contentType.getMimeType(), receivedContentType);
assertArrayEquals(bytes, receivedBytes);
}
}
@Test
public void testManualInputAndOutputWithUrlParam() throws Exception {
byte[] bytes = new byte[]{1,2,3,4,5,6,7,8,7,6,5,4,3,2,1};
ContentType contentType = ContentType.IMAGE_PNG;
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$manualInputAndOutputWithParam?param1=value");
httpPost.setEntity(new ByteArrayEntity(bytes, contentType));
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
String receivedContentType = status.getEntity().getContentType().getValue();
byte[] receivedBytes = IOUtils.toByteArray(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(contentType.getMimeType(), receivedContentType);
assertArrayEquals(bytes, receivedBytes);
assertEquals("value", ourLastParam1.getValue());
}
}
@Test
public void testOperationCantUseGetIfItIsntIdempotent() throws Exception {
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE");
@ -439,7 +525,7 @@ public class OperationServerR4Test {
@Test
public void testOperationWithBundleProviderResponse() throws Exception {
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/$OP_INSTANCE_BUNDLE_PROVIDER?_pretty=true");
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/$OP_SERVER_BUNDLE_PROVIDER?_pretty=true");
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -480,7 +566,6 @@ public class OperationServerR4Test {
assertThat(response, containsString("Can not invoke operation $OP_TYPE using HTTP GET because parameter PARAM2 is not a primitive datatype"));
}
@Test
public void testOperationWithListParam() throws Exception {
Parameters p = new Parameters();
@ -618,7 +703,6 @@ public class OperationServerR4Test {
}
}
private static IBaseResource ourNextResponse;
public static class PatientProvider implements IResourceProvider {
@ -750,6 +834,36 @@ public class OperationServerR4Test {
return new Bundle();
}
@Operation(name="$manualInputAndOutput", manualResponse=true, manualRequest=true)
public void manualInputAndOutput(HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws IOException {
String contentType = theServletRequest.getContentType();
byte[] bytes = IOUtils.toByteArray(theServletRequest.getInputStream());
ourLog.info("Received call with content type {} and {} bytes", contentType, bytes.length);
theServletResponse.setContentType(contentType);
theServletResponse.getOutputStream().write(bytes);
theServletResponse.getOutputStream().close();
}
@Operation(name="$manualInputAndOutputWithParam", manualResponse=true, manualRequest=true)
public void manualInputAndOutputWithParam(
@OperationParam(name="param1") StringType theParam1,
HttpServletRequest theServletRequest,
HttpServletResponse theServletResponse
) throws IOException {
ourLastParam1 = theParam1;
String contentType = theServletRequest.getContentType();
byte[] bytes = IOUtils.toByteArray(theServletRequest.getInputStream());
ourLog.info("Received call with content type {} and {} bytes", contentType, bytes.length);
theServletResponse.setContentType(contentType);
theServletResponse.getOutputStream().write(bytes);
theServletResponse.getOutputStream().close();
}
/**
* Just to make sure this method doesn't "steal" calls
*/
@ -762,12 +876,16 @@ public class OperationServerR4Test {
}
}
private static RestOperationTypeEnum ourLastRestOperation;
public static class PlainProvider {
@Operation(name = "$OP_INSTANCE_BUNDLE_PROVIDER", idempotent = true)
public IBundleProvider opInstanceReturnsBundleProvider() {
ourLastMethod = "$OP_INSTANCE_BUNDLE_PROVIDER";
@Operation(name = "$OP_PLAIN_PROVIDER_ON_INSTANCE", idempotent = true, global = true)
public IBundleProvider opPlainProviderOnInstance(@IdParam IdType theId, RequestDetails theRequestDetails) {
ourLastMethod = "$OP_PLAIN_PROVIDER_ON_INSTANCE";
ourLastId = theId;
ourLastRestOperation = theRequestDetails.getRestOperationType();
List<IBaseResource> resources = new ArrayList<IBaseResource>();
for (int i = 0; i < 100; i++) {
@ -780,7 +898,38 @@ public class OperationServerR4Test {
return new SimpleBundleProvider(resources);
}
@Operation(name = "$OP_SERVER")
@Operation(name = "$OP_SERVER_BUNDLE_PROVIDER", idempotent = true)
public IBundleProvider opInstanceReturnsBundleProvider() {
ourLastMethod = "$OP_SERVER_BUNDLE_PROVIDER";
List<IBaseResource> resources = new ArrayList<IBaseResource>();
for (int i = 0; i < 100; i++) {
Patient p = new Patient();
p.setId("Patient/" + i);
p.addName().setFamily("Patient " + i);
resources.add(p);
}
return new SimpleBundleProvider(resources);
}
@Operation(name= "$manualResponseWithPrimitiveParam", idempotent = true, global = true, manualResponse = true)
public void binaryAccess(
@IdParam IIdType theResourceId,
@OperationParam(name="path", min = 1, max = 1) IPrimitiveType<String> thePath,
ServletRequestDetails theRequestDetails,
HttpServletRequest theServletRequest,
HttpServletResponse theServletResponse) {
ourLastMethod = "$manualResponseWithPrimitiveParam";
ourLastId = (IdType) theResourceId;
ourLastParam1 = (StringType) thePath;
theServletResponse.setStatus(200);
}
@Operation(name = "$OP_SERVER")
public Parameters opServer(
@OperationParam(name = "PARAM1") StringType theParam1,
@OperationParam(name = "PARAM2") Patient theParam2
@ -859,13 +1008,14 @@ public class OperationServerR4Test {
servlet.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(2));
servlet.setFhirContext(ourCtx);
servlet.setResourceProviders(new PatientProvider());
servlet.setPlainProviders(new PlainProvider());
PlainProvider plainProvider = new PlainProvider();
PatientProvider patientProvider = new PatientProvider();
servlet.registerProviders(patientProvider, plainProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
JettyUtil.startServer(ourServer);
ourPort = JettyUtil.getPortForStartedServer(ourServer);
ourPort = JettyUtil.getPortForStartedServer(ourServer);
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();

View File

@ -0,0 +1,818 @@
ACCESSIBLE
ACCOUNT
ACTION
ACTIVE
ADD
ADMIN
AFTER
AGAINST
AGGREGATE
ALGORITHM
ALL
ALTER
ALWAYS
ANALYSE
ANALYZE
AND
ANY
ARRAY
AS
ASC
ASCII
ASENSITIVE
AT
AUTOEXTEND_SIZE
AUTO_INCREMENT
AVG
AVG_ROW_LENGTH
B
BACKUP
BEFORE
BEGIN
BETWEEN
BIGINT
BINARY
BINLOG
BIT
BLOB
BLOCK
BOOL
BOOLEAN
BOTH
BTREE
BUCKETS
BY
BYTE
C
CACHE
CALL
CASCADE
CASCADED
CASE
CATALOG_NAME
CHAIN
CHANGE
CHANGED
CHANNEL
CHAR
CHARACTER
CHARSET
CHECK
CHECKSUM
CIPHER
CLASS_ORIGIN
CLIENT
CLONE
CLOSE
COALESCE
CODE
COLLATE
COLLATION
COLUMN
COLUMNS
COLUMN_FORMAT
COLUMN_NAME
COMMENT
COMMIT
COMMITTED
COMPACT
COMPLETION
COMPONENT
COMPRESSED
COMPRESSION
CONCURRENT
CONDITION
CONNECTION
CONSISTENT
CONSTRAINT
CONSTRAINT_CATALOG
CONSTRAINT_NAME
CONSTRAINT_SCHEMA
CONTAINS
CONTEXT
CONTINUE
CONVERT
CPU
CREATE
CROSS
CUBE
CUME_DIST
CURRENT
CURRENT_DATE
CURRENT_TIME
CURRENT_TIMESTAMP
CURRENT_USER
CURSOR
CURSOR_NAME
D
DATA
DATABASE
DATABASES
DATAFILE
DATE
DATETIME
DAY
DAY_HOUR
DAY_MICROSECOND
DAY_MINUTE
DAY_SECOND
DEALLOCATE
DEC
DECIMAL
DECLARE
DEFAULT
DEFAULT_AUTH
DEFINER
DEFINITION
DELAYED
DELAY_KEY_WRITE
DELETE
DENSE_RANK
DESC
DESCRIBE
DESCRIPTION
DES_KEY_FILE
DETERMINISTIC
DIAGNOSTICS
DIRECTORY
DISABLE
DISCARD
DISK
DISTINCT
DISTINCTROW
DIV
DO
DOUBLE
DROP
DUAL
DUMPFILE
DUPLICATE
DYNAMIC
E
EACH
ELSE
ELSEIF
EMPTY
ENABLE
ENCLOSED
ENCRYPTION
END
ENDS
ENFORCED
ENGINE
ENGINES
ENUM
ERROR
ERRORS
ESCAPE
ESCAPED
EVENT
EVENTS
EVERY
EXCEPT
EXCHANGE
EXCLUDE
EXECUTE
EXISTS
EXIT
EXPANSION
EXPIRE
EXPLAIN
EXPORT
EXTENDED
EXTENT_SIZE
F
FALSE
FAST
FAULTS
FETCH
FIELDS
FILE
FILE_BLOCK_SIZE
FILTER
FIRST
FIRST_VALUE
FIXED
FLOAT
FLUSH
FOLLOWING
FOLLOWS
FOR
FORCE
FOREIGN
FORMAT
FOUND
FROM
FULL
FULLTEXT
FUNCTION
G
GENERAL
GENERATED
GEOMCOLLECTION
GEOMETRY
GEOMETRYCOLLECTION
GET
GET_FORMAT
GET_MASTER_PUBLIC_KEY
GLOBAL
GRANT
GRANTS
GROUP
GROUPING
GROUPS
GROUP_REPLICATION
H
HANDLER
HASH
HAVING
HELP
HIGH_PRIORITY
HISTOGRAM
HISTORY
HOST
HOSTS
HOUR
HOUR_MICROSECOND
HOUR_MINUTE
HOUR_SECOND
I
IDENTIFIED
IF
IGNORE
IGNORE_SERVER_IDS
IMPORT
IN
INACTIVE
INDEX
INDEXES
INFILE
INITIAL_SIZE
INNER
INOUT
INSENSITIVE
INSERT
INSERT_METHOD
INSTALL
INSTANCE
INT
INTEGER
INTERVAL
INTO
INVISIBLE
INVOKER
IO
IO_AFTER_GTIDS
IO_BEFORE_GTIDS
IO_THREAD
IPC
IS
ISOLATION
ISSUER
ITERATE
J
JOIN
JSON
JSON_TABLE
K
KEY
KEYS
KEY_BLOCK_SIZE
KILL
L
LAG
LANGUAGE
LAST
LAST_VALUE
LATERAL
LEAD
LEADING
LEAVE
LEAVES
LEFT
LESS
LEVEL
LIKE
LIMIT
LINEAR
LINES
LINESTRING
LIST
LOAD
LOCAL
LOCALTIME
LOCALTIMESTAMP
LOCK
LOCKED
LOCKS
LOGFILE
LOGS
LONG
LONGBLOB
LONGTEXT
LOOP
LOW_PRIORITY
M
MASTER
MASTER_AUTO_POSITION
MASTER_BIND
MASTER_COMPRESSION_ALGORITHMS
MASTER_CONNECT_RETRY
MASTER_DELAY
MASTER_HEARTBEAT_PERIOD
MASTER_HOST
MASTER_LOG_FILE
MASTER_LOG_POS
MASTER_PASSWORD
MASTER_PORT
MASTER_PUBLIC_KEY_PATH
MASTER_RETRY_COUNT
MASTER_SERVER_ID
MASTER_SSL
MASTER_SSL_CA
MASTER_SSL_CAPATH
MASTER_SSL_CERT
MASTER_SSL_CIPHER
MASTER_SSL_CRL
MASTER_SSL_CRLPATH
MASTER_SSL_KEY
MASTER_SSL_VERIFY_SERVER_CERT
MASTER_TLS_VERSION
MASTER_USER
MASTER_ZSTD_COMPRESSION_LEVEL
MATCH
MAXVALUE
MAX_CONNECTIONS_PER_HOUR
MAX_QUERIES_PER_HOUR
MAX_ROWS
MAX_SIZE
MAX_UPDATES_PER_HOUR
MAX_USER_CONNECTIONS
MEDIUM
MEDIUMBLOB
MEDIUMINT
MEDIUMTEXT
MEMBER
MEMORY
MERGE
MESSAGE_TEXT
MICROSECOND
MIDDLEINT
MIGRATE
MINUTE
MINUTE_MICROSECOND
MINUTE_SECOND
MIN_ROWS
MOD
MODE
MODIFIES
MODIFY
MONTH
MULTILINESTRING
MULTIPOINT
MULTIPOLYGON
MUTEX
MYSQL_ERRNO
N
NAME
NAMES
NATIONAL
NATURAL
NCHAR
NDB
NDBCLUSTER
NESTED
NETWORK_NAMESPACE
NEVER
NEW
NEXT
NO
NODEGROUP
NONE
NOT
NOWAIT
NO_WAIT
NO_WRITE_TO_BINLOG
NTH_VALUE
NTILE
NULL
NULLS
NUMBER
NUMERIC
NVARCHAR
O
OF
OFFSET
OJ
OLD
ON
ONE
ONLY
OPEN
OPTIMIZE
OPTIMIZER_COSTS
OPTION
OPTIONAL
OPTIONALLY
OPTIONS
OR
ORDER
ORDINALITY
ORGANIZATION
OTHERS
OUT
OUTER
OUTFILE
OVER
OWNER
P
PACK_KEYS
PAGE
PARSER
PARTIAL
PARTITION
PARTITIONING
PARTITIONS
PASSWORD
PATH
PERCENT_RANK
PERSIST
PERSIST_ONLY
PHASE
PLUGIN
PLUGINS
PLUGIN_DIR
POINT
POLYGON
PORT
PRECEDES
PRECEDING
PRECISION
PREPARE
PRESERVE
PREV
PRIMARY
PRIVILEGES
PROCEDURE
PROCESS
PROCESSLIST
PROFILE
PROFILES
PROXY
PURGE
Q
QUARTER
QUERY
QUICK
R
RANDOM
RANGE
RANK
READ
READS
READ_ONLY
READ_WRITE
REAL
REBUILD
RECOVER
RECURSIVE
REDOFILE
REDO_BUFFER_SIZE
REDUNDANT
REFERENCE
REFERENCES
REGEXP
RELAY
RELAYLOG
RELAY_LOG_FILE
RELAY_LOG_POS
RELAY_THREAD
RELEASE
RELOAD
REMOTE
REMOVE
RENAME
REORGANIZE
REPAIR
REPEAT
REPEATABLE
REPLACE
REPLICATE_DO_DB
REPLICATE_DO_TABLE
REPLICATE_IGNORE_DB
REPLICATE_IGNORE_TABLE
REPLICATE_REWRITE_DB
REPLICATE_WILD_DO_TABLE
REPLICATE_WILD_IGNORE_TABLE
REPLICATION
REQUIRE
RESET
RESIGNAL
RESOURCE
RESPECT
RESTART
RESTORE
RESTRICT
RESUME
RETAIN
RETURN
RETURNED_SQLSTATE
RETURNS
REUSE
REVERSE
REVOKE
RIGHT
RLIKE
ROLE
ROLLBACK
ROLLUP
ROTATE
ROUTINE
ROW
ROWS
ROW_COUNT
ROW_FORMAT
ROW_NUMBER
RTREE
S
SAVEPOINT
SCHEDULE
SCHEMA
SCHEMAS
SCHEMA_NAME
SECOND
SECONDARY
SECONDARY_ENGINE
SECONDARY_LOAD
SECONDARY_UNLOAD
SECOND_MICROSECOND
SECURITY
SELECT
SENSITIVE
SEPARATOR
SERIAL
SERIALIZABLE
SERVER
SESSION
SET
SHARE
SHOW
SHUTDOWN
SIGNAL
SIGNED
SIMPLE
SKIP
SLAVE
SLOW
SMALLINT
SNAPSHOT
SOCKET
SOME
SONAME
SOUNDS
SOURCE
SPATIAL
SPECIFIC
SQL
SQLEXCEPTION
SQLSTATE
SQLWARNING
SQL_AFTER_GTIDS
SQL_AFTER_MTS_GAPS
SQL_BEFORE_GTIDS
SQL_BIG_RESULT
SQL_BUFFER_RESULT
SQL_CACHE
SQL_CALC_FOUND_ROWS
SQL_NO_CACHE
SQL_SMALL_RESULT
SQL_THREAD
SQL_TSI_DAY
SQL_TSI_HOUR
SQL_TSI_MINUTE
SQL_TSI_MONTH
SQL_TSI_QUARTER
SQL_TSI_SECOND
SQL_TSI_WEEK
SQL_TSI_YEAR
SRID
SSL
STACKED
START
STARTING
STARTS
STATS_AUTO_RECALC
STATS_PERSISTENT
STATS_SAMPLE_PAGES
STATUS
STOP
STORAGE
STORED
STRAIGHT_JOIN
STRING
SUBCLASS_ORIGIN
SUBJECT
SUBPARTITION
SUBPARTITIONS
SUPER
SUSPEND
SWAPS
SWITCHES
SYSTEM
T
TABLE
TABLES
TABLESPACE
TABLE_CHECKSUM
TABLE_NAME
TEMPORARY
TEMPTABLE
TERMINATED
TEXT
THAN
THEN
THREAD_PRIORITY
TIES
TIME
TIMESTAMP
TIMESTAMPADD
TIMESTAMPDIFF
TINYBLOB
TINYINT
TINYTEXT
TO
TRAILING
TRANSACTION
TRIGGER
TRIGGERS
TRUE
TRUNCATE
TYPE
TYPES
U
UNBOUNDED
UNCOMMITTED
UNDEFINED
UNDO
UNDOFILE
UNDO_BUFFER_SIZE
UNICODE
UNINSTALL
UNION
UNIQUE
UNKNOWN
UNLOCK
UNSIGNED
UNTIL
UPDATE
UPGRADE
USAGE
USE
USER
USER_RESOURCES
USE_FRM
USING
UTC_DATE
UTC_TIME
UTC_TIMESTAMP
V
VALIDATION
VALUE
VALUES
VARBINARY
VARCHAR
VARCHARACTER
VARIABLES
VARYING
VCPU
VIEW
VIRTUAL
VISIBLE
W
WAIT
WARNINGS
WEEK
WEIGHT_STRING
WHEN
WHERE
WHILE
WINDOW
WITH
WITHOUT
WORK
WRAPPER
WRITE
X
XA
XID
XML
XOR
Y
YEAR
YEAR_MONTH
Z
ZEROFILL
A
ACTIVE
ADMIN
ARRAY
B
BUCKETS
C
CLONE
COMPONENT
CUME_DIST
D
DEFINITION
DENSE_RANK
DESCRIPTION
E
EMPTY
ENFORCED
EXCEPT
EXCLUDE
F
FIRST_VALUE
FOLLOWING
G
GEOMCOLLECTION
GET_MASTER_PUBLIC_KEY
GROUPING
GROUPS
H
HISTOGRAM
HISTORY
I
INACTIVE
INVISIBLE
J
JSON_TABLE
L
LAG
LAST_VALUE
LATERAL
LEAD
LOCKED
M
MASTER_COMPRESSION_ALGORITHMS
MASTER_PUBLIC_KEY_PATH
MASTER_ZSTD_COMPRESSION_LEVEL
MEMBER
N
NESTED
NETWORK_NAMESPACE
NOWAIT
NTH_VALUE
NTILE
NULLS
O
OF
OJ
OLD
OPTIONAL
ORDINALITY
ORGANIZATION
OTHERS
OVER
P
PATH
PERCENT_RANK
PERSIST
PERSIST_ONLY
PRECEDING
PROCESS
R
RANDOM
RANK
RECURSIVE
REFERENCE
RESOURCE
RESPECT
RESTART
RETAIN
REUSE
ROLE
ROW_NUMBER
S
SECONDARY
SECONDARY_ENGINE
SECONDARY_LOAD
SECONDARY_UNLOAD
SKIP
SRID
SYSTEM
T
THREAD_PRIORITY
TIES
U
UNBOUNDED
V
VCPU
VISIBLE
W
WINDOW
ANALYSE
DES_KEY_FILE
PARSE_GCOL_EXPR
REDOFILE
SQL_CACHE

View File

@ -296,6 +296,32 @@
<action type="add">
Expanded ValueSets now populate <![CDATA[<code>ValueSet.expansion.contains.designation.language</code>]]>.
</action>
<action type="add">
@Operation methods can now declare that they will manually process the request
body and/or manually generate a response instead of letting the HAPI FHIR
framework take care of these things. This is useful for situations where
direct access to the low-level servlet streaming API is needed.
</action>
<action type="add">
@Operation methods can now declare that they are global, meaning that they will
apply to all resource types (or instances of all resource types) if they
are found on a plain provider.
</action>
<action type="add">
A new resource provider for JPA servers called
<![CDATA[<code>BinaryAccessProvider</code>]]>
has been added. This provider serves two custom operations called
<![CDATA[<code>$binary-access-read</code>]]> and
<![CDATA[<code>$binary-access-write</code>]]> that can be used to
request binary data in Attachments as raw binary content instead of
as base 64 encoded content.
</action>
<action type="change">
A few columns named 'CODE' in the JPA terminology services tables have been
renamed to 'CODEVAL' to avoid any possibility of conflicting with reserved
words in MySQL. The database migrator tool has been updated to handle this
change.
</action>
</release>
<release version="3.8.0" date="2019-05-30" description="Hippo">
<action type="fix">

Some files were not shown because too many files have changed in this diff Show More